diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index daae84d0..dc73b14b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,3 +34,26 @@ jobs: # Ensure that "keg-only" Homebrew versions are used. PKG_CONFIG_PATH: "/usr/local/opt/ncurses/lib/pkgconfig:/usr/local/opt/expat/lib/pkgconfig:/usr/local/opt/curl/lib/pkgconfig:/usr/local/opt/openssl/lib/pkgconfig:/usr/local/opt/libffi/lib/pkgconfig:/usr/local/opt/sqlite/lib/pkgconfig:$PKG_CONFIG_PATH" run: ./ci-build.sh + + code-style: + runs-on: ubuntu-20.04 + name: Check coding style + continue-on-error: true + steps: + - uses: actions/checkout@v2 + - name: install dependencies + run: | + sudo apt update + sudo apt install -y --no-install-recommends autoconf autoconf-archive automake expect gcc git libcmocka-dev libcurl3-dev libgcrypt-dev libglib2.0-dev libgpgme11-dev libgtk2.0-dev libmicrohttpd-dev libncursesw5-dev libnotify-dev libotr5-dev libreadline-dev libsignal-protocol-c-dev libssl-dev libtool libxss-dev make pkg-config python3-dev python-dev-is-python3 libsqlite3-dev + - name: Install libstrophe + run: | + git clone https://github.com/strophe/libstrophe ../libstrophe + cd ../libstrophe && ./bootstrap.sh && ./configure && make -j$(nproc) && sudo make install + - name: Configure + run: | + ./bootstrap.sh + ./configure + - name: Check style + run: | + make format + git diff --exit-code diff --git a/.gitignore b/.gitignore index e06f5136..446a3868 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ profanity.workspace compile_commands.json .tern-port .tern-project +.cproject +.project +.settings/ # autotools .libs/ @@ -25,6 +28,7 @@ build-aux/ config.log config.status configure +configure*~ libprofanity.la libtool m4/ @@ -82,6 +86,7 @@ python3/ .DS_Store *.bak +*.orig breaks *.tar.* diff --git a/Makefile.am b/Makefile.am index cfeb4f7d..0e35a518 100644 --- a/Makefile.am +++ b/Makefile.am @@ -221,7 +221,7 @@ icons_sources = $(top_srcdir)/icons/* script_sources = bootstrap.sh -man_sources = $(wildcard docs/profanity*.1) +man1_sources = $(wildcard $(top_srcdir)/docs/profanity*.1) if BUILD_PGP core_sources += $(pgp_sources) @@ -289,9 +289,9 @@ tests_unittests_unittests_LDADD = -lcmocka #endif #endif -man_MANS = $(man_sources) +man1_MANS = $(man1_sources) -EXTRA_DIST = $(man_sources) $(icons_sources) $(themes_sources) $(script_sources) profrc.example theme_template LICENSE.txt README.md CHANGELOG +EXTRA_DIST = $(man1_sources) $(icons_sources) $(themes_sources) $(script_sources) profrc.example theme_template LICENSE.txt README.md CHANGELOG # Ship API documentation with `make dist` EXTRA_DIST += \ diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 685369c4..43ec3a0c 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -124,6 +124,31 @@ static void _who_roster(ProfWin* window, const char* const command, gchar** args static gboolean _cmd_execute(ProfWin* window, const char* const command, const char* const inp); static gboolean _cmd_execute_default(ProfWin* window, const char* inp); static gboolean _cmd_execute_alias(ProfWin* window, const char* const inp, gboolean* ran); +static gboolean +_string_matches_one_of(const char* is, bool is_can_be_null, const char* first, ...) __attribute__((sentinel)); + +static gboolean +_string_matches_one_of(const char* is, bool is_can_be_null, const char* first, ...) +{ + gboolean ret = FALSE; + va_list ap; + const char* cur = first; + if (!is) + return is_can_be_null; + + log_debug("%s vs.", is); + va_start(ap, first); + while (cur != NULL) { + log_debug("%s", cur); + if (g_strcmp0(is, cur) == 0) { + ret = TRUE; + break; + } + cur = va_arg(ap, const char*); + } + va_end(ap); + return ret; +} /* * Take a line of input and process it, return TRUE if profanity is to @@ -339,7 +364,7 @@ cmd_connect(ProfWin* window, const char* const command, gchar** args) char* altdomain = g_hash_table_lookup(options, "server"); char* tls_policy = g_hash_table_lookup(options, "tls"); - if (tls_policy && (g_strcmp0(tls_policy, "force") != 0) && (g_strcmp0(tls_policy, "allow") != 0) && (g_strcmp0(tls_policy, "trust") != 0) && (g_strcmp0(tls_policy, "disable") != 0) && (g_strcmp0(tls_policy, "legacy") != 0)) { + if (!_string_matches_one_of(tls_policy, TRUE, "force", "allow", "trust", "disable", "legacy", NULL)) { cons_bad_cmd_usage(command); cons_show(""); options_destroy(options); @@ -347,7 +372,7 @@ cmd_connect(ProfWin* window, const char* const command, gchar** args) } char* auth_policy = g_hash_table_lookup(options, "auth"); - if (auth_policy && (g_strcmp0(auth_policy, "default") != 0) && (g_strcmp0(auth_policy, "legacy") != 0)) { + if (!_string_matches_one_of(auth_policy, TRUE, "default", "legacy", NULL)) { cons_bad_cmd_usage(command); cons_show(""); options_destroy(options); @@ -732,9 +757,7 @@ _account_set_nick(char* account_name, char* nick) gboolean _account_set_otr(char* account_name, char* policy) { - if ((g_strcmp0(policy, "manual") != 0) - && (g_strcmp0(policy, "opportunistic") != 0) - && (g_strcmp0(policy, "always") != 0)) { + if (!_string_matches_one_of(policy, FALSE, "manual", "opportunistic", "always", NULL)) { cons_show("OTR policy must be one of: manual, opportunistic or always."); } else { accounts_set_otr_policy(account_name, policy); @@ -821,11 +844,7 @@ _account_set_theme(char* account_name, char* theme) gboolean _account_set_tls(char* account_name, char* policy) { - if ((g_strcmp0(policy, "force") != 0) - && (g_strcmp0(policy, "allow") != 0) - && (g_strcmp0(policy, "trust") != 0) - && (g_strcmp0(policy, "disable") != 0) - && (g_strcmp0(policy, "legacy") != 0)) { + if (!_string_matches_one_of(policy, FALSE, "force", "allow", "trust", "disable", "legacy", NULL)) { cons_show("TLS policy must be one of: force, allow, legacy or disable."); } else { accounts_set_tls_policy(account_name, policy); @@ -838,8 +857,7 @@ _account_set_tls(char* account_name, char* policy) gboolean _account_set_auth(char* account_name, char* policy) { - if ((g_strcmp0(policy, "default") != 0) - && (g_strcmp0(policy, "legacy") != 0)) { + if (!_string_matches_one_of(policy, FALSE, "default", "legacy", NULL)) { cons_show("Auth policy must be either default or legacy."); } else { accounts_set_auth_policy(account_name, policy); @@ -3773,7 +3791,7 @@ cmd_form_field(ProfWin* window, char* tag, gchar** args) if (cmd) { value = args[1]; } - if ((g_strcmp0(cmd, "add") != 0) && (g_strcmp0(cmd, "remove"))) { + if (!_string_matches_one_of(cmd, FALSE, "add", "remove", NULL)) { win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:"); confwin_field_help(confwin, tag); win_println(window, THEME_DEFAULT, "-", ""); @@ -3827,7 +3845,7 @@ cmd_form_field(ProfWin* window, char* tag, gchar** args) if (cmd) { value = args[1]; } - if ((g_strcmp0(cmd, "add") != 0) && (g_strcmp0(cmd, "remove"))) { + if (!_string_matches_one_of(cmd, FALSE, "add", "remove", NULL)) { win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:"); confwin_field_help(confwin, tag); win_println(window, THEME_DEFAULT, "-", ""); @@ -3878,7 +3896,7 @@ cmd_form_field(ProfWin* window, char* tag, gchar** args) if (cmd) { value = args[1]; } - if ((g_strcmp0(cmd, "add") != 0) && (g_strcmp0(cmd, "remove"))) { + if (!_string_matches_one_of(cmd, FALSE, "add", "remove", NULL)) { win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:"); confwin_field_help(confwin, tag); win_println(window, THEME_DEFAULT, "-", ""); @@ -4183,7 +4201,7 @@ cmd_affiliation(ProfWin* window, const char* const command, gchar** args) } char* affiliation = args[1]; - if (affiliation && (g_strcmp0(affiliation, "owner") != 0) && (g_strcmp0(affiliation, "admin") != 0) && (g_strcmp0(affiliation, "member") != 0) && (g_strcmp0(affiliation, "none") != 0) && (g_strcmp0(affiliation, "outcast") != 0)) { + if (!_string_matches_one_of(affiliation, TRUE, "owner", "admin", "member", "none", "outcast", NULL)) { cons_bad_cmd_usage(command); return TRUE; } @@ -4258,7 +4276,7 @@ cmd_role(ProfWin* window, const char* const command, gchar** args) } char* role = args[1]; - if (role && (g_strcmp0(role, "visitor") != 0) && (g_strcmp0(role, "participant") != 0) && (g_strcmp0(role, "moderator") != 0) && (g_strcmp0(role, "none") != 0)) { + if (!_string_matches_one_of(role, TRUE, "visitor", "participant", "moderator", "none", NULL)) { cons_bad_cmd_usage(command); return TRUE; } @@ -5248,7 +5266,7 @@ cmd_console(ProfWin* window, const char* const command, gchar** args) } gchar* setting = args[1]; - if ((g_strcmp0(setting, "all") != 0) && (g_strcmp0(setting, "first") != 0) && (g_strcmp0(setting, "none") != 0)) { + if (!_string_matches_one_of(setting, FALSE, "all", "first", "none", NULL)) { if (!(isMuc && (g_strcmp0(setting, "mention") == 0))) { cons_bad_cmd_usage(command); return TRUE; @@ -7752,7 +7770,7 @@ cmd_otr_policy(ProfWin* window, const char* const command, gchar** args) } char* choice = args[1]; - if ((g_strcmp0(choice, "manual") != 0) && (g_strcmp0(choice, "opportunistic") != 0) && (g_strcmp0(choice, "always") != 0)) { + if (!_string_matches_one_of(choice, FALSE, "manual", "opportunistic", "always", NULL)) { cons_show("OTR policy can be set to: manual, opportunistic or always."); return TRUE; } @@ -8942,7 +8960,7 @@ cmd_omemo_policy(ProfWin* window, const char* const command, gchar** args) } char* choice = args[1]; - if ((g_strcmp0(choice, "manual") != 0) && (g_strcmp0(choice, "automatic") != 0) && (g_strcmp0(choice, "always") != 0)) { + if (!_string_matches_one_of(choice, FALSE, "manual", "automatic", "always", NULL)) { cons_show("OMEMO policy can be set to: manual, automatic or always."); return TRUE; } @@ -9556,7 +9574,7 @@ cmd_register(ProfWin* window, const char* const command, gchar** args) } char* tls_policy = g_hash_table_lookup(options, "tls"); - if (tls_policy && (g_strcmp0(tls_policy, "force") != 0) && (g_strcmp0(tls_policy, "allow") != 0) && (g_strcmp0(tls_policy, "trust") != 0) && (g_strcmp0(tls_policy, "disable") != 0) && (g_strcmp0(tls_policy, "legacy") != 0)) { + if (!_string_matches_one_of(tls_policy, TRUE, "force", "allow", "trust", "disable", "legacy", NULL)) { cons_bad_cmd_usage(command); cons_show(""); options_destroy(options); diff --git a/src/config/preferences.c b/src/config/preferences.c index 358c5fbf..43c91b54 100644 --- a/src/config/preferences.c +++ b/src/config/preferences.c @@ -2277,7 +2277,8 @@ _get_default_string(preference_t pref) return "xdg-open"; case PREF_URL_OPEN_CMD: return "xdg-open %u"; - case PREF_COMPOSE_EDITOR: { + case PREF_COMPOSE_EDITOR: + { gchar* editor = getenv("EDITOR"); return editor ? editor : "vim"; } diff --git a/src/ui/window.c b/src/ui/window.c index 7952497c..353b72ff 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -2033,8 +2033,8 @@ win_quote_autocomplete(ProfWin* window, const char* const input, gboolean previo return NULL; } - gchar **parts = g_strsplit(result, "\n", -1); - gchar *quoted_result = g_strjoinv("\n> ", parts); + gchar** parts = g_strsplit(result, "\n", -1); + gchar* quoted_result = g_strjoinv("\n> ", parts); GString* replace_with = g_string_new("> "); g_string_append(replace_with, quoted_result); diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c index 54bb1449..9505622a 100644 --- a/src/xmpp/connection.c +++ b/src/xmpp/connection.c @@ -58,7 +58,6 @@ typedef struct prof_conn_t { - xmpp_log_t* xmpp_log; xmpp_ctx_t* xmpp_ctx; xmpp_conn_t* xmpp_conn; gboolean xmpp_in_event_loop; @@ -82,25 +81,51 @@ static ProfConnection conn; static gchar* profanity_instance_id = NULL; static gchar* prof_identifier = NULL; -static xmpp_log_t* _xmpp_get_file_logger(void); static void _xmpp_file_logger(void* const userdata, const xmpp_log_level_t level, const char* const area, const char* const msg); static void _connection_handler(xmpp_conn_t* const xmpp_conn, const xmpp_conn_event_t status, const int error, xmpp_stream_error_t* const stream_error, void* const userdata); -TLSCertificate* _xmppcert_to_profcert(const xmpp_tlscert_t* xmpptlscert); +static TLSCertificate* _xmppcert_to_profcert(const xmpp_tlscert_t* xmpptlscert); static int _connection_certfail_cb(const xmpp_tlscert_t* xmpptlscert, const char* errormsg); static void _random_bytes_init(void); static void _random_bytes_close(void); static void _compute_identifier(const char* barejid); +static void* +_xmalloc(size_t size, void* userdata) +{ + void* ret = malloc(size); + assert(ret != NULL); + return ret; +} + +static void +_xfree(void* p, void* userdata) +{ + free(p); +} + +static void* +_xrealloc(void* p, size_t size, void* userdata) +{ + void* ret = realloc(p, size); + assert(ret != NULL); + return ret; +} + +static xmpp_mem_t prof_mem = { + _xmalloc, _xfree, _xrealloc, NULL +}; +static xmpp_log_t prof_log = { + _xmpp_file_logger, NULL +}; + void connection_init(void) { xmpp_initialize(); - conn.xmpp_conn = NULL; - conn.xmpp_ctx = NULL; conn.xmpp_in_event_loop = FALSE; conn.conn_status = JABBER_DISCONNECTED; conn.conn_last_event = XMPP_CONN_DISCONNECT; @@ -110,6 +135,9 @@ connection_init(void) conn.available_resources = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)resource_destroy); conn.requested_features = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL); + conn.xmpp_ctx = xmpp_ctx_new(&prof_mem, &prof_log); + conn.xmpp_conn = xmpp_conn_new(conn.xmpp_ctx); + _random_bytes_init(); } @@ -125,61 +153,38 @@ void connection_shutdown(void) { connection_clear_data(); + if (conn.xmpp_conn) { + xmpp_conn_release(conn.xmpp_conn); + conn.xmpp_conn = NULL; + } + if (conn.xmpp_ctx) { + xmpp_ctx_free(conn.xmpp_ctx); + conn.xmpp_ctx = NULL; + } xmpp_shutdown(); - free(conn.xmpp_log); - conn.xmpp_log = NULL; - _random_bytes_close(); } -jabber_conn_status_t -connection_connect(const char* const jid, const char* const passwd, const char* const altdomain, int port, - const char* const tls_policy, const char* const auth_policy) +static gboolean +_conn_apply_settings(const char* const jid, const char* const passwd, const char* const tls_policy, const char* const auth_policy) { - long flags; - - assert(jid != NULL); - assert(passwd != NULL); - Jid* jidp = jid_create(jid); if (jidp == NULL) { log_error("Malformed JID not able to connect: %s", jid); conn.conn_status = JABBER_DISCONNECTED; - return conn.conn_status; + return FALSE; } _compute_identifier(jidp->barejid); jid_destroy(jidp); - log_info("Connecting as %s", jid); - - if (conn.xmpp_log) { - free(conn.xmpp_log); - } - conn.xmpp_log = _xmpp_get_file_logger(); - - if (conn.xmpp_conn) { - xmpp_conn_release(conn.xmpp_conn); - } - if (conn.xmpp_ctx) { - xmpp_ctx_free(conn.xmpp_ctx); - } - conn.xmpp_ctx = xmpp_ctx_new(NULL, conn.xmpp_log); - if (conn.xmpp_ctx == NULL) { - log_warning("Failed to get libstrophe ctx during connect"); - return JABBER_DISCONNECTED; - } xmpp_ctx_set_verbosity(conn.xmpp_ctx, 0); - conn.xmpp_conn = xmpp_conn_new(conn.xmpp_ctx); - if (conn.xmpp_conn == NULL) { - log_warning("Failed to get libstrophe conn during connect"); - return JABBER_DISCONNECTED; - } xmpp_conn_set_jid(conn.xmpp_conn, jid); - xmpp_conn_set_pass(conn.xmpp_conn, passwd); + if (passwd) + xmpp_conn_set_pass(conn.xmpp_conn, passwd); - flags = xmpp_conn_get_flags(conn.xmpp_conn); + long flags = xmpp_conn_get_flags(conn.xmpp_conn); if (!tls_policy || (g_strcmp0(tls_policy, "force") == 0)) { flags |= XMPP_CONN_FLAG_MANDATORY_TLS; @@ -221,6 +226,19 @@ connection_connect(const char* const jid, const char* const passwd, const char* xmpp_conn_set_certfail_handler(conn.xmpp_conn, _connection_certfail_cb); + return TRUE; +} + +jabber_conn_status_t +connection_connect(const char* const jid, const char* const passwd, const char* const altdomain, int port, + const char* const tls_policy, const char* const auth_policy) +{ + assert(jid != NULL); + assert(passwd != NULL); + log_info("Connecting as %s", jid); + + _conn_apply_settings(jid, passwd, tls_policy, auth_policy); + int connect_status = xmpp_connect_client( conn.xmpp_conn, altdomain, @@ -465,69 +483,7 @@ jabber_conn_status_t connection_register(const char* const altdomain, int port, const char* const tls_policy, const char* const username, const char* const password) { - long flags; - - Jid* jidp = jid_create(altdomain); - if (jidp == NULL) { - log_error("Malformed JID not able to connect: %s", altdomain); - conn.conn_status = JABBER_DISCONNECTED; - return conn.conn_status; - } - - _compute_identifier(jidp->barejid); - jid_destroy(jidp); - - if (conn.xmpp_log) { - free(conn.xmpp_log); - } - conn.xmpp_log = _xmpp_get_file_logger(); - - if (conn.xmpp_conn) { - xmpp_conn_release(conn.xmpp_conn); - } - if (conn.xmpp_ctx) { - xmpp_ctx_free(conn.xmpp_ctx); - } - conn.xmpp_ctx = xmpp_ctx_new(NULL, conn.xmpp_log); - if (conn.xmpp_ctx == NULL) { - log_warning("Failed to get libstrophe ctx during connect"); - return JABBER_DISCONNECTED; - } - conn.xmpp_conn = xmpp_conn_new(conn.xmpp_ctx); - if (conn.xmpp_conn == NULL) { - log_warning("Failed to get libstrophe conn during connect"); - return JABBER_DISCONNECTED; - } - xmpp_conn_set_jid(conn.xmpp_conn, altdomain); - - flags = xmpp_conn_get_flags(conn.xmpp_conn); - - if (!tls_policy || (g_strcmp0(tls_policy, "force") == 0)) { - flags |= XMPP_CONN_FLAG_MANDATORY_TLS; - } else if (g_strcmp0(tls_policy, "trust") == 0) { - flags |= XMPP_CONN_FLAG_MANDATORY_TLS; - flags |= XMPP_CONN_FLAG_TRUST_TLS; - } else if (g_strcmp0(tls_policy, "disable") == 0) { - flags |= XMPP_CONN_FLAG_DISABLE_TLS; - } else if (g_strcmp0(tls_policy, "legacy") == 0) { - flags |= XMPP_CONN_FLAG_LEGACY_SSL; - } - - xmpp_conn_set_flags(conn.xmpp_conn, flags); - - /* Print debug logs that can help when users share the logs */ - if (flags != 0) { - log_debug("Connecting with flags (0x%lx):", flags); -#define LOG_FLAG_IF_SET(name) \ - if (flags & name) { \ - log_debug(" " #name); \ - } - LOG_FLAG_IF_SET(XMPP_CONN_FLAG_MANDATORY_TLS); - LOG_FLAG_IF_SET(XMPP_CONN_FLAG_TRUST_TLS); - LOG_FLAG_IF_SET(XMPP_CONN_FLAG_DISABLE_TLS); - LOG_FLAG_IF_SET(XMPP_CONN_FLAG_LEGACY_SSL); -#undef LOG_FLAG_IF_SET - } + _conn_apply_settings(altdomain, NULL, tls_policy, NULL); prof_reg_t* reg; @@ -540,14 +496,6 @@ connection_register(const char* const altdomain, int port, const char* const tls reg->username = strdup(username); reg->password = strdup(password); - char* cert_path = prefs_get_tls_certpath(); - if (cert_path) { - xmpp_conn_set_capath(conn.xmpp_conn, cert_path); - free(cert_path); - } - - xmpp_conn_set_certfail_handler(conn.xmpp_conn, _connection_certfail_cb); - int connect_status = xmpp_connect_raw( conn.xmpp_conn, altdomain, @@ -584,12 +532,7 @@ connection_disconnect(void) if (!conn.xmpp_in_event_loop) { if (conn.xmpp_conn) { xmpp_conn_release(conn.xmpp_conn); - conn.xmpp_conn = NULL; - } - - if (conn.xmpp_ctx) { - xmpp_ctx_free(conn.xmpp_ctx); - conn.xmpp_ctx = NULL; + conn.xmpp_conn = xmpp_conn_new(conn.xmpp_ctx); } } @@ -909,11 +852,7 @@ _split_url(const char* alturi, gchar** host, gint* port) * requires this to be there. */ const char* xmpp = "xmpp://"; - char* xmpp_uri = malloc(strlen(xmpp) + strlen(alturi) + 1); - if (!xmpp_uri) { - log_debug("_get_other_host: malloc failed \"%s\"", alturi); - return false; - } + char* xmpp_uri = _xmalloc(strlen(xmpp) + strlen(alturi) + 1, NULL); memcpy(xmpp_uri, xmpp, strlen(xmpp)); memcpy(xmpp_uri + strlen(xmpp), alturi, strlen(alturi) + 1); gboolean ret = g_uri_split_network(xmpp_uri, 0, NULL, host, port, NULL); @@ -1104,34 +1043,6 @@ _xmppcert_to_profcert(const xmpp_tlscert_t* xmpptlscert) xmpp_tlscert_get_pem(xmpptlscert)); } -static xmpp_log_t* -_xmpp_get_file_logger(void) -{ - log_level_t prof_level = log_get_filter(); - xmpp_log_level_t xmpp_level = XMPP_LEVEL_ERROR; - - switch (prof_level) { - case PROF_LEVEL_DEBUG: - xmpp_level = XMPP_LEVEL_DEBUG; - break; - case PROF_LEVEL_INFO: - xmpp_level = XMPP_LEVEL_INFO; - break; - case PROF_LEVEL_WARN: - xmpp_level = XMPP_LEVEL_WARN; - break; - default: - xmpp_level = XMPP_LEVEL_ERROR; - break; - } - - xmpp_log_t* file_log = malloc(sizeof(xmpp_log_t)); - file_log->handler = _xmpp_file_logger; - file_log->userdata = &xmpp_level; - - return file_log; -} - static void _xmpp_file_logger(void* const userdata, const xmpp_log_level_t xmpp_level, const char* const area, const char* const msg) {