From 3e7113b0bde23fcb7361137a163d16ee7eb1d7d2 Mon Sep 17 00:00:00 2001 From: Norm Brandinger Date: Thu, 7 Mar 2024 16:51:38 -0500 Subject: [PATCH 01/79] Update rtp_relay_ctx.c to avoid segfault rtp_relay_release_tmp(ctmp, 0) is called before the variable ctmp has been allocated. --- modules/rtp_relay/rtp_relay_ctx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/rtp_relay/rtp_relay_ctx.c b/modules/rtp_relay/rtp_relay_ctx.c index 2a78be35836..c2da555c144 100644 --- a/modules/rtp_relay/rtp_relay_ctx.c +++ b/modules/rtp_relay/rtp_relay_ctx.c @@ -2582,7 +2582,7 @@ mi_response_t *mi_rtp_relay_update_callid(const mi_params_t *params, if (rtp_relay_ctx_pending(ctx)) { RTP_RELAY_CTX_UNLOCK(ctx); lock_stop_read(rtp_relay_contexts_lock); - goto error; + return 0; } ctmp = rtp_relay_new_tmp(ctx, set, node); From 975f5d13e9444dcb4ebab8f0dec50637ca3809da Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Fri, 8 Mar 2024 15:59:58 +0200 Subject: [PATCH 02/79] rest_client: Fix RHEL 7.9 build regression in commit 7e85fddb6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RHEL 7.9 is using libcurl 7.29 (from 11 years ago), so the CURLINFO_CONNECT_TIME_T easyinfo option is not available. So let's use the CURLINFO_CONNECT_TIME info instead, which returns the exact same data (i.e. the `data->progress.t_connect` handle info), but divided as (double)seconds instead of being returned as (long)useconds. Credits to Răzvan Crainea for reporting this issue! --- modules/rest_client/rest_methods.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/rest_client/rest_methods.c b/modules/rest_client/rest_methods.c index 09e98dd9d82..8b3d20872f5 100644 --- a/modules/rest_client/rest_methods.c +++ b/modules/rest_client/rest_methods.c @@ -898,7 +898,7 @@ int start_async_http_req(struct sip_msg *msg, enum rest_client_method method, /* obtain a read fd in "connection_timeout" seconds at worst */ for (timeout = connect_timeout; timeout > 0; timeout -= busy_wait) { - curl_off_t connect = -1; + double connect = -1; long req_sz = -1; mrc = curl_multi_perform(multi_handle, &running_handles); @@ -907,11 +907,11 @@ int start_async_http_req(struct sip_msg *msg, enum rest_client_method method, goto error; } - curl_easy_getinfo(handle, CURLINFO_CONNECT_TIME_T, &connect); + curl_easy_getinfo(handle, CURLINFO_CONNECT_TIME, &connect); curl_easy_getinfo(handle, CURLINFO_REQUEST_SIZE, &req_sz); - LM_DBG("perform code: %d, handles: %d, connect: %ldµs, reqsz: %ldB\n", - mrc, running_handles, (long)connect, req_sz); + LM_DBG("perform code: %d, handles: %d, connect: %.3lfs, reqsz: %ldB\n", + mrc, running_handles, connect, req_sz); /* transfer completed! But how well? */ if (running_handles == 0) { From 75810c5549b856dc22b09e024c1859188694b83c Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Fri, 8 Mar 2024 16:52:57 +0200 Subject: [PATCH 03/79] dialog: call DLGCB_PROCESS_VARS under dlg lock Avoid taking the var logs, as this might lead to a deadlock if one of the callbacks are setting the variable. Credits go to Norman Brandinger (@NormB on GitHub) for reporting it! --- modules/dialog/dlg_replication.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/dialog/dlg_replication.c b/modules/dialog/dlg_replication.c index 9c78221cb42..d8eb248ace2 100644 --- a/modules/dialog/dlg_replication.c +++ b/modules/dialog/dlg_replication.c @@ -741,7 +741,7 @@ int dlg_replicated_value(bin_packet_t *packet) str call_id, name; struct dlg_cell *dlg; unsigned int h_id; - int h_entry, type; + int h_entry, type, ret; struct dlg_entry *d_entry; int_str val; @@ -774,12 +774,13 @@ int dlg_replicated_value(bin_packet_t *packet) return -1; } lock_start_write(dlg->vals_lock); - if (store_dlg_value_unsafe(dlg, &name, &val, type) < 0) + ret = store_dlg_value_unsafe(dlg, &name, &val, type); + lock_stop_write(dlg->vals_lock); + if (ret < 0) LM_ERR("cannot store dlg value\n"); else run_dlg_callbacks(DLGCB_PROCESS_VARS, dlg, NULL, DLG_DIR_NONE, -1, &name, 1, 0); - lock_stop_write(dlg->vals_lock); dlg_unlock(d_table, d_entry); From 831cdd5ac46412ba8c82e36d4a61648717d9b5b2 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Fri, 8 Mar 2024 17:27:11 +0200 Subject: [PATCH 04/79] rtpengine: proper count of the exported processes This should fix a memory corruption generated by reloading a process that was not initially counted. Moreover, an overflow is no longer possible, as the commit checks on it and triggers a BUG in case it happens --- modules/rtpengine/rtpengine.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/rtpengine/rtpengine.c b/modules/rtpengine/rtpengine.c index 0890b9404d1..f3f8665f84c 100644 --- a/modules/rtpengine/rtpengine.c +++ b/modules/rtpengine/rtpengine.c @@ -1503,12 +1503,13 @@ mod_init(void) pv_spec_t avp_spec; unsigned short avp_flags; str s; + int count = count_child_processes(); rtpe_ctx_idx = context_register_ptr(CONTEXT_GLOBAL, rtpe_ctx_free); rtpe_no = (unsigned int*)shm_malloc(sizeof(unsigned int)); list_version = (unsigned int*)shm_malloc(sizeof(unsigned int)); - child_versions=(unsigned int*)shm_malloc((1/*non-SIP at head*/ + udp_workers_no + tcp_workers_no) * sizeof(unsigned int)); + child_versions=(unsigned int*)shm_malloc(count * sizeof(unsigned int)); child_versions_no = (unsigned int*)shm_malloc(sizeof(unsigned int)); rtpe_versions = (struct rtpe_version_head **)shm_malloc(sizeof(struct rtpe_version_head *)); @@ -1519,7 +1520,7 @@ mod_init(void) *rtpe_no = 0; *list_version = 0; - *child_versions_no = 1/*non-SIP at head*/ + udp_workers_no + tcp_workers_no; + *child_versions_no = count; *rtpe_versions = 0; my_version = 0; @@ -1853,7 +1854,10 @@ static int update_rtpengines(int force_test) my_version = crt_version->version; } - child_versions[myrank] = my_version; + if (myrank < *child_versions_no) + child_versions[myrank] = my_version; + else + LM_BUG("rank overflow %d vs %d\n", myrank, *child_versions_no); /* close all sockets if any versions required From 64fd8c7e97ed40c91323d671076e732c5c9dff82 Mon Sep 17 00:00:00 2001 From: OpenSIPS Date: Sun, 10 Mar 2024 00:49:13 +0200 Subject: [PATCH 05/79] Rebuild documentation --- modules/dialog/README | 4 ++-- modules/dialog/doc/contributors.xml | 10 +++++----- modules/rest_client/README | 4 ++-- modules/rest_client/doc/contributors.xml | 10 +++++----- modules/rtp_relay/README | 12 +++++++----- modules/rtp_relay/doc/contributors.xml | 19 ++++++++++++++++--- modules/rtpengine/README | 4 ++-- modules/rtpengine/doc/contributors.xml | 10 +++++----- 8 files changed, 44 insertions(+), 29 deletions(-) diff --git a/modules/dialog/README b/modules/dialog/README index 4119679cb26..d939e966829 100644 --- a/modules/dialog/README +++ b/modules/dialog/README @@ -2560,7 +2560,7 @@ Chapter 4. Contributors commits^(2) and lines added/removed^(3) Name DevScore Commits Lines ++ Lines -- 1. Bogdan-Andrei Iancu (@bogdan-iancu) 453 286 13279 3527 - 2. Razvan Crainea (@razvancrainea) 283 212 5281 1656 + 2. Razvan Crainea (@razvancrainea) 285 213 5285 1659 3. Vlad Paiu (@vladpaiu) 254 145 7206 3027 4. Vlad Patrascu (@rvlad-patrascu) 198 104 4330 3514 5. Liviu Chircu (@liviuchircu) 178 129 3118 1367 @@ -2604,7 +2604,7 @@ Chapter 4. Contributors Table 4.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Razvan Crainea (@razvancrainea) Aug 2010 - Feb 2024 + 1. Razvan Crainea (@razvancrainea) Aug 2010 - Mar 2024 2. Stefan-Cristian Mititelu Jan 2024 - Jan 2024 3. Bogdan-Andrei Iancu (@bogdan-iancu) Apr 2006 - Dec 2023 4. Maksym Sobolyev (@sobomax) Nov 2020 - Nov 2023 diff --git a/modules/dialog/doc/contributors.xml b/modules/dialog/doc/contributors.xml index 316fd416701..de2984f8520 100644 --- a/modules/dialog/doc/contributors.xml +++ b/modules/dialog/doc/contributors.xml @@ -29,10 +29,10 @@ 2. Razvan Crainea (@razvancrainea) - 283 - 212 - 5281 - 1656 + 285 + 213 + 5285 + 1659 3. @@ -129,7 +129,7 @@ 1. Razvan Crainea (@razvancrainea) - Aug 2010 - Feb 2024 + Aug 2010 - Mar 2024 2. diff --git a/modules/rest_client/README b/modules/rest_client/README index 6161f52a684..1d0186787eb 100644 --- a/modules/rest_client/README +++ b/modules/rest_client/README @@ -720,7 +720,7 @@ Chapter 2. Contributors Table 2.1. Top contributors by DevScore^(1), authored commits^(2) and lines added/removed^(3) Name DevScore Commits Lines ++ Lines -- - 1. Liviu Chircu (@liviuchircu) 146 84 4025 1776 + 1. Liviu Chircu (@liviuchircu) 148 85 4029 1780 2. Ionut Ionita (@ionutrazvanionita) 23 12 663 262 3. Vlad Patrascu (@rvlad-patrascu) 17 8 336 345 4. Razvan Crainea (@razvancrainea) 15 13 41 17 @@ -754,7 +754,7 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Liviu Chircu (@liviuchircu) Mar 2013 - Feb 2024 + 1. Liviu Chircu (@liviuchircu) Mar 2013 - Mar 2024 2. Maksym Sobolyev (@sobomax) Oct 2020 - Feb 2023 3. Vlad Patrascu (@rvlad-patrascu) May 2017 - May 2021 4. John Burke (@john08burke) Apr 2021 - Apr 2021 diff --git a/modules/rest_client/doc/contributors.xml b/modules/rest_client/doc/contributors.xml index f8fe9ee2c5f..a1f23a3ff33 100644 --- a/modules/rest_client/doc/contributors.xml +++ b/modules/rest_client/doc/contributors.xml @@ -21,10 +21,10 @@ 1. Liviu Chircu (@liviuchircu) - 146 - 84 - 4025 - 1776 + 148 + 85 + 4029 + 1780 2. @@ -129,7 +129,7 @@ 1. Liviu Chircu (@liviuchircu) - Mar 2013 - Feb 2024 + Mar 2013 - Mar 2024 2. diff --git a/modules/rtp_relay/README b/modules/rtp_relay/README index 0fce026413a..8e47aea6ef2 100644 --- a/modules/rtp_relay/README +++ b/modules/rtp_relay/README @@ -335,7 +335,8 @@ Chapter 2. Contributors 1. Razvan Crainea (@razvancrainea) 146 80 5771 1134 2. Maksym Sobolyev (@sobomax) 6 4 10 11 3. Vlad Patrascu (@rvlad-patrascu) 3 1 11 7 - 4. Vlad Paiu (@vladpaiu) 2 1 5 0 + 4. Norman Brandinger (@NormB) 3 1 1 1 + 5. Vlad Paiu (@vladpaiu) 2 1 5 0 (1) DevScore = author_commits + author_lines_added / (project_lines_added / project_commits) + author_lines_deleted @@ -357,10 +358,11 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Razvan Crainea (@razvancrainea) Apr 2021 - Feb 2024 - 2. Maksym Sobolyev (@sobomax) Feb 2023 - Nov 2023 - 3. Vlad Patrascu (@rvlad-patrascu) Mar 2023 - Mar 2023 - 4. Vlad Paiu (@vladpaiu) Oct 2022 - Oct 2022 + 1. Norman Brandinger (@NormB) Mar 2024 - Mar 2024 + 2. Razvan Crainea (@razvancrainea) Apr 2021 - Feb 2024 + 3. Maksym Sobolyev (@sobomax) Feb 2023 - Nov 2023 + 4. Vlad Patrascu (@rvlad-patrascu) Mar 2023 - Mar 2023 + 5. Vlad Paiu (@vladpaiu) Oct 2022 - Oct 2022 (1) including any documentation-related commits, excluding merge commits diff --git a/modules/rtp_relay/doc/contributors.xml b/modules/rtp_relay/doc/contributors.xml index a3ef8fd9964..ea9e3f00dd7 100644 --- a/modules/rtp_relay/doc/contributors.xml +++ b/modules/rtp_relay/doc/contributors.xml @@ -44,6 +44,14 @@ 4. + Norman Brandinger (@NormB) + 3 + 1 + 1 + 1 + + + 5. Vlad Paiu (@vladpaiu) 2 1 @@ -80,21 +88,26 @@ 1. + Norman Brandinger (@NormB) + Mar 2024 - Mar 2024 + + + 2. Razvan Crainea (@razvancrainea) Apr 2021 - Feb 2024 - 2. + 3. Maksym Sobolyev (@sobomax) Feb 2023 - Nov 2023 - 3. + 4. Vlad Patrascu (@rvlad-patrascu) Mar 2023 - Mar 2023 - 4. + 5. Vlad Paiu (@vladpaiu) Oct 2022 - Oct 2022 diff --git a/modules/rtpengine/README b/modules/rtpengine/README index e4a1a2df059..b450be6ac7c 100644 --- a/modules/rtpengine/README +++ b/modules/rtpengine/README @@ -1266,7 +1266,7 @@ Chapter 3. Contributors Table 3.1. Top contributors by DevScore^(1), authored commits^(2) and lines added/removed^(3) Name DevScore Commits Lines ++ Lines -- - 1. Razvan Crainea (@razvancrainea) 216 116 5959 3074 + 1. Razvan Crainea (@razvancrainea) 217 117 5965 3076 2. Bogdan-Andrei Iancu (@bogdan-iancu) 31 17 423 595 3. John Burke (@john08burke) 25 17 647 102 4. Liviu Chircu (@liviuchircu) 20 16 91 173 @@ -1301,7 +1301,7 @@ Chapter 3. Contributors Table 3.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Razvan Crainea (@razvancrainea) Jun 2014 - Feb 2024 + 1. Razvan Crainea (@razvancrainea) Jun 2014 - Mar 2024 2. Maksym Sobolyev (@sobomax) Jan 2021 - Nov 2023 3. Liviu Chircu (@liviuchircu) Jul 2014 - May 2023 4. Peter Lemenkov (@lemenkov) Jun 2018 - Apr 2022 diff --git a/modules/rtpengine/doc/contributors.xml b/modules/rtpengine/doc/contributors.xml index 5e73ac46975..9057deebc5d 100644 --- a/modules/rtpengine/doc/contributors.xml +++ b/modules/rtpengine/doc/contributors.xml @@ -21,10 +21,10 @@ 1. Razvan Crainea (@razvancrainea) - 216 - 116 - 5959 - 3074 + 217 + 117 + 5965 + 3076 2. @@ -129,7 +129,7 @@ 1. Razvan Crainea (@razvancrainea) - Jun 2014 - Feb 2024 + Jun 2014 - Mar 2024 2. From 4befbb436d27011c8bd05c48ece4a1961d4f0ef4 Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Tue, 12 Mar 2024 15:30:57 +0200 Subject: [PATCH 06/79] [dbops] remove obsolete flags from docs --- modules/dbops/doc/dbops_admin.xml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/modules/dbops/doc/dbops_admin.xml b/modules/dbops/doc/dbops_admin.xml index 040767dc4ec..888bfded759 100644 --- a/modules/dbops/doc/dbops_admin.xml +++ b/modules/dbops/doc/dbops_admin.xml @@ -314,16 +314,6 @@ modparam("dbops","db_scheme", name = avp_spec['/'(table_name|'$'db_scheme)] - - avp_spec = matching_flags|$avp(avp_name)|$avp(avp_alias) - - - matching_flags = 'a' | 'A' | 'i' | 'I' | 's' | 'S' - [script_flags] - 'a' or 'A' means matching any of - AVP name types ('i' and 's'), the rest have the - meaning descriped in 'AVP naming format' chapter. - From 80c2ee12dfc7f754e48af51cec47dc26b8cf0d3a Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Tue, 12 Mar 2024 17:37:28 +0200 Subject: [PATCH 07/79] [dbops] Add SQL structured query functions These are functions performing structured (not raw) queries via the internal DB SQL API. They provide full standard INSERT/UPDATE/SELECT/REPLACE/DELETE support. --- modules/dbops/dbops.c | 157 +++++++++++++++ modules/dbops/dbops_db.c | 383 +++++++++++++++++++++++++++++++++++++ modules/dbops/dbops_db.h | 17 ++ modules/dbops/dbops_impl.c | 182 ++++++++++++++++++ modules/dbops/dbops_impl.h | 15 ++ 5 files changed, 754 insertions(+) diff --git a/modules/dbops/dbops.c b/modules/dbops/dbops.c index 9fc4b91374d..dc7bcc57a80 100644 --- a/modules/dbops/dbops.c +++ b/modules/dbops/dbops.c @@ -96,6 +96,20 @@ static int w_async_db_query(struct sip_msg* msg, async_ctx *ctx, static int w_async_db_query_one(struct sip_msg* msg, async_ctx *ctx, str* query, void* dest, void* url); +static int w_db_select(struct sip_msg* msg, str* cols, str *table, + str *filter, str *order, void* dest, void *url); +static int w_db_select_one(struct sip_msg* msg, str* cols, str *table, + str *filter, str *order, void* dest, void *url); +static int w_db_update(struct sip_msg* msg, str* cols, str *table, + str *filter, void *url); +static int w_db_insert(struct sip_msg* msg, str* cols, str *table, + void *url); +static int w_db_delete(struct sip_msg* msg, str *table, str *filter, + void *url); +static int w_db_replace(struct sip_msg* msg, str* cols, str *table, + void *url); + + static const acmd_export_t acmds[] = { {"db_query", (acmd_function)w_async_db_query, { {CMD_PARAM_STR, 0, 0}, @@ -166,6 +180,63 @@ static const cmd_export_t cmds[] = { {0, 0, 0}}, ALL_ROUTES}, + {"db_select", (cmd_function)w_db_select, { + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* columns */ + {CMD_PARAM_STR, 0, 0}, /* table */ + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* filter */ + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* order */ + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_NO_EXPAND, + fixup_avpname_list, fixup_free_pvname_list}, + {CMD_PARAM_INT|CMD_PARAM_OPT, + fixup_db_id_sync, fixup_free_pkg}, + {0, 0, 0}}, + ALL_ROUTES}, + + {"db_selec_onet", (cmd_function)w_db_select_one, { + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* columns */ + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* table */ + {CMD_PARAM_STR, 0, 0}, /* filter */ + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* order */ + {CMD_PARAM_STR|CMD_PARAM_NO_EXPAND, + fixup_pvname_list, fixup_free_pvname_list}, + {CMD_PARAM_INT|CMD_PARAM_OPT, + fixup_db_id_sync, fixup_free_pkg}, + {0, 0, 0}}, + ALL_ROUTES}, + + {"db_update", (cmd_function)w_db_update, { + {CMD_PARAM_STR, 0, 0}, /* columns */ + {CMD_PARAM_STR, 0, 0}, /* table */ + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* filter */ + {CMD_PARAM_INT|CMD_PARAM_OPT, + fixup_db_id_sync, fixup_free_pkg}, + {0, 0, 0}}, + ALL_ROUTES}, + + {"db_insert", (cmd_function)w_db_insert, { + {CMD_PARAM_STR, 0, 0}, /* columns */ + {CMD_PARAM_STR, 0, 0}, /* table */ + {CMD_PARAM_INT|CMD_PARAM_OPT, + fixup_db_id_sync, fixup_free_pkg}, + {0, 0, 0}}, + ALL_ROUTES}, + + {"db_delete", (cmd_function)w_db_delete, { + {CMD_PARAM_STR, 0, 0}, /* table */ + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* filter */ + {CMD_PARAM_INT|CMD_PARAM_OPT, + fixup_db_id_sync, fixup_free_pkg}, + {0, 0, 0}}, + ALL_ROUTES}, + + {"db_replace", (cmd_function)w_db_replace, { + {CMD_PARAM_STR, 0, 0}, /* columns */ + {CMD_PARAM_STR, 0, 0}, /* table */ + {CMD_PARAM_INT|CMD_PARAM_OPT, + fixup_db_id_sync, fixup_free_pkg}, + {0, 0, 0}}, + ALL_ROUTES}, + {0, 0, {{0, 0, 0}}, 0} }; @@ -637,6 +708,92 @@ static int w_db_query_one(struct sip_msg* msg, str* query, } +static int w_db_select(struct sip_msg* msg, str* cols, str *table, + str *filter, str *order, void* dest, void *url) +{ + struct db_url *parsed_url; + + if (url) + parsed_url = ((struct db_url_container *)url)->u.url; + else + parsed_url = default_db_url; + + return ops_db_api_select(parsed_url, msg, cols, table, filter, order, + (pvname_list_t*)dest, 0); +} + + +static int w_db_select_one(struct sip_msg* msg, str* cols, str *table, + str *filter, str *order, void* dest, void *url) +{ + struct db_url *parsed_url; + + if (url) + parsed_url = ((struct db_url_container *)url)->u.url; + else + parsed_url = default_db_url; + + return ops_db_api_select(parsed_url, msg, cols, table, filter, order, + (pvname_list_t*)dest, 1); +} + + +static int w_db_update(struct sip_msg* msg, str* cols, str *table, + str *filter, void *url) +{ + struct db_url *parsed_url; + + if (url) + parsed_url = ((struct db_url_container *)url)->u.url; + else + parsed_url = default_db_url; + + return ops_db_api_update(parsed_url, msg, cols, table, filter); +} + + +static int w_db_insert(struct sip_msg* msg, str* cols, str *table, + void *url) +{ + struct db_url *parsed_url; + + if (url) + parsed_url = ((struct db_url_container *)url)->u.url; + else + parsed_url = default_db_url; + + return ops_db_api_insert(parsed_url, msg, cols, table); +} + + +static int w_db_delete(struct sip_msg* msg, str *table, str *filter, + void *url) +{ + struct db_url *parsed_url; + + if (url) + parsed_url = ((struct db_url_container *)url)->u.url; + else + parsed_url = default_db_url; + + return ops_db_api_delete(parsed_url, msg, table, filter); +} + + +static int w_db_replace(struct sip_msg* msg, str* cols, str *table, + void *url) +{ + struct db_url *parsed_url; + + if (url) + parsed_url = ((struct db_url_container *)url)->u.url; + else + parsed_url = default_db_url; + + return ops_db_api_replace(parsed_url, msg, cols, table); +} + + static int w_async_db_query(struct sip_msg* msg, async_ctx *ctx, str* query, void* dest, void* url) { diff --git a/modules/dbops/dbops_db.c b/modules/dbops/dbops_db.c index b3a0abe034b..a713fd5868a 100644 --- a/modules/dbops/dbops_db.c +++ b/modules/dbops/dbops_db.c @@ -466,6 +466,389 @@ int db_query(struct db_url *url, struct sip_msg *msg, str *query, } +static inline int _json_to_cols(cJSON *Jcols, db_key_t** _c) +{ + static db_key_t *cols; + static unsigned int cols_size = 0; + cJSON *col; + str *str_cols; + int nc, i; + + if (Jcols->type != cJSON_Array) { + LM_ERR("bad JSON format, 'cols' must be an array\n"); + goto error; + } + + /* iterate the columns to check and validate */ + for( col=Jcols->child,nc=0 ; col ; col=col->next,nc++ ) { + if (col->type!=cJSON_String) { + LM_ERR("bad JSON format, 'cols' elements must be strings\n"); + goto error; + } + } + + /* resize the array of cols if we need more */ + if (nc>cols_size) { + /* need a larger set of cols/keys */ + cols = (db_key_t*)pkg_realloc( cols, nc*(sizeof(db_key_t)+sizeof(str)) ); + if (cols==NULL) { + cols_size = 0; + LM_ERR("failed to allocate the needed %d keys/cols\n",nc); + goto error; + } + str_cols = (str*)( cols+nc ); + /* link db_keys to strs */ + for ( i=0 ; ichild,i=0 ; col ; col=col->next,i++ ) { + str_cols[i].s = col->valuestring; + str_cols[i].len = strlen(col->valuestring); + } + + *_c = cols; + + return nc; +error: + *_c = NULL; + return -1; +} + + +static inline int _json_to_filters(cJSON *Jfilter, + db_key_t** _k, db_op_t** _o, db_val_t** _v, int only_equal) +{ + static db_key_t *keys; + static db_op_t *ops; + static db_val_t *vals; + static unsigned int keys_size = 0; + cJSON *filter, *node; + str *str_keys; + int nk, i; + + if (Jfilter->type != cJSON_Array) { + LM_ERR("bad JSON format, 'filter' must be an array\n"); + goto error; + } + + /* iterate the filters to check and validate */ + for( filter=Jfilter->child,nk=0 ; filter ; filter=filter->next,nk++ ) { + if (filter->type!=cJSON_Object) { + LM_ERR("bad JSON format, 'cols' elements must be strings\n"); + goto error; + } + if (filter->child->string==NULL) { + LM_ERR("invalid filter node type %d , name %s\n", + filter->child->type, filter->child->string); + goto error; + } + } + + /* resize the array of cols if we need more */ + if (nk>keys_size) { + /* need a larger set of keys */ + keys = (db_key_t*)pkg_realloc( keys, + nk*(sizeof(db_key_t)+sizeof(str)+sizeof(db_op_t)+sizeof(db_val_t)) + ); + if (keys==NULL) { + keys_size = 0; + LM_ERR("failed to allocate the needed %d keys/cols\n",nk); + goto error; + } + str_keys = (str*)( keys+nk ); + ops = (db_op_t*)(str_keys+nk); + vals = (db_val_t*)(ops+nk); + /* link db_keys to strs */ + for ( i=0 ; ichild,i=0 ; filter ; filter=filter->next,i++ ) { + /* name of the key/col */ + node = filter->child; + keys[i]->s = node->string; + keys[i]->len = strlen(node->string); + /* operator */ + if (node->type==cJSON_Object) { + node = node->child; + ops[i] = node->string; + if (only_equal && memcmp(ops[i],OP_EQ,sizeof(OP_EQ)+1)) { + LM_ERR("only equal allowed between keys and values at " + "pos %d\n",i); + goto error; + } + } else { + ops[i] = OP_EQ; + } + /* value */ + switch (node->type) { + case cJSON_NULL: + vals[i].type = 0; + vals[i].nul = 1; + vals[i].free = 0; + vals[i].val.bigint_val = 0; + break; + case cJSON_String: + vals[i].type = DB_STRING; + vals[i].nul = 0; + vals[i].free = 0; + vals[i].val.string_val = node->valuestring; + break; + case cJSON_Number: + vals[i].type = DB_INT; + vals[i].nul = 0; + vals[i].free = 0; + vals[i].val.int_val = node->valueint; + break; + default: + LM_ERR("unsupported value node %d\n",node->type); + goto error; + } + } + + *_k = keys; + *_o = ops; + *_v = vals; + + return nk; +error: + *_k = NULL; + *_o = NULL; + *_v = NULL; + return -1; +} + + +int db_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, + str *table, cJSON *Jfilter, str * order, pvname_list_t* dest, int one_row) +{ + db_res_t* db_res = NULL; + db_key_t *cols, *keys; + db_op_t *ops; + db_val_t *vals; + int nk, nc; + + /* convert JSON to COLs */ + if (Jcols) { + nc = _json_to_cols( Jcols, &cols); + if (nc<0) { + LM_ERR("failed to extract cols from JSON\n"); + return -1; + } + } else { + cols = NULL; + nc = 0; + } + + /* convert JSON to keys */ + if (Jfilter) { + nk = _json_to_filters( Jfilter, &keys, &ops, &vals, 0); + if (nk<0) { + LM_ERR("failed to extract filter from JSON\n"); + return -1; + } + } else { + keys = NULL; + ops = NULL; + vals = NULL; + nk = 0; + } + + /* set table */ + if (set_table( url, table ,"API select")!=0) + return -1; + + /* do the DB query */ + if ( url->dbf.query( url->hdl, keys, ops, vals, cols, nk, nc, + order, &db_res) < 0 ) { + const str *t = url->hdl&&url->hdl->table&&url->hdl->table->s + ? url->hdl->table : 0; + LM_ERR("select API query failed: db%d (%.*s)\n", + url->idx, t?t->len:0, t?t->s:""); + return -1; + } + + if(db_res==NULL || RES_ROW_N(db_res)<=0 || RES_COL_N(db_res)<=0) { + LM_DBG("no result after query\n"); + db_close_query( url, db_res ); + return 1; + } + + if (one_row) { + if (db_query_print_one_result(msg, db_res, dest) != 0) { + LM_ERR("failed to print ONE result\n"); + db_close_query( url, db_res ); + return -1; + } + } else { + if (db_query_print_results(msg, db_res, dest) != 0) { + LM_ERR("failed to print results\n"); + db_close_query( url, db_res ); + return -1; + } + } + + db_close_query( url, db_res ); + return 0; +} + + +int db_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, + str *table, cJSON *Jfilter) +{ + db_key_t *ukeys, *keys; + db_op_t *uops, *ops; + db_val_t *uvals, *vals; + int nk, unk; + + /* convert JSON to COLs */ + unk = _json_to_filters( Jcols, &ukeys, &uops, &uvals, 1); + if (unk<0) { + LM_ERR("failed to extract cols from JSON\n"); + return -1; + } + + /* convert JSON to keys */ + if (Jfilter) { + nk = _json_to_filters( Jfilter, &keys, &ops, &vals, 0); + if (nk<0) { + LM_ERR("failed to extract filter from JSON\n"); + return -1; + } + } else { + keys = NULL; + ops = NULL; + vals = NULL; + nk = 0; + } + + /* set table */ + if (set_table( url, table ,"API update")!=0) + return -1; + + /* do the DB query */ + if (url->dbf.update( url->hdl, keys, ops, vals, ukeys, uvals, nk, unk)<0){ + const str *t = url->hdl&&url->hdl->table&&url->hdl->table->s + ? url->hdl->table : 0; + LM_ERR("update API query failed: db%d (%.*s)\n", + url->idx, t?t->len:0, t?t->s:""); + return -1; + } + + return 0; +} + + +int db_api_insert(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, + str *table) +{ + db_key_t *ukeys; + db_op_t *uops; + db_val_t *uvals; + int unk; + + /* convert JSON to COLs */ + unk = _json_to_filters( Jcols, &ukeys, &uops, &uvals, 1); + if (unk<0) { + LM_ERR("failed to extract cols from JSON\n"); + return -1; + } + + /* set table */ + if (set_table( url, table ,"API insert")!=0) + return -1; + + /* do the DB query */ + if (url->dbf.insert( url->hdl, ukeys, uvals, unk)<0){ + const str *t = url->hdl&&url->hdl->table&&url->hdl->table->s + ? url->hdl->table : 0; + LM_ERR("update API query failed: db%d (%.*s)\n", + url->idx, t?t->len:0, t?t->s:""); + return -1; + } + + return 0; +} + + +int db_api_delete(struct db_url *url, struct sip_msg* msg, + str *table, cJSON *Jfilter) +{ + db_key_t *keys; + db_op_t *ops; + db_val_t *vals; + int nk; + + /* convert JSON to keys */ + if (Jfilter) { + nk = _json_to_filters( Jfilter, &keys, &ops, &vals, 0); + if (nk<0) { + LM_ERR("failed to extract filter from JSON\n"); + return -1; + } + } else { + keys = NULL; + ops = NULL; + vals = NULL; + nk = 0; + } + + /* set table */ + if (set_table( url, table ,"API delete")!=0) + return -1; + + /* do the DB query */ + if (url->dbf.delete( url->hdl, keys, ops, vals, nk)<0){ + const str *t = url->hdl&&url->hdl->table&&url->hdl->table->s + ? url->hdl->table : 0; + LM_ERR("update API query failed: db%d (%.*s)\n", + url->idx, t?t->len:0, t?t->s:""); + return -1; + } + + return 0; +} + + +int db_api_replace(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, + str *table) +{ + db_key_t *ukeys; + db_op_t *uops; + db_val_t *uvals; + int unk; + + /* convert JSON to COLs */ + unk = _json_to_filters( Jcols, &ukeys, &uops, &uvals, 1); + if (unk<0) { + LM_ERR("failed to extract cols from JSON\n"); + return -1; + } + + /* set table */ + if (set_table( url, table ,"API replace")!=0) + return -1; + + /* do the DB query */ + if (url->dbf.replace( url->hdl, ukeys, uvals, unk)<0){ + const str *t = url->hdl&&url->hdl->table&&url->hdl->table->s + ? url->hdl->table : 0; + LM_ERR("update API query failed: db%d (%.*s)\n", + url->idx, t?t->len:0, t?t->s:""); + return -1; + } + + return 0; +} + + int db_query_print_one_result(struct sip_msg *msg, const db_res_t *db_res, pvname_list_t *dest) { diff --git a/modules/dbops/dbops_db.h b/modules/dbops/dbops_db.h index 1bb566e4750..0f18fb52bb9 100644 --- a/modules/dbops/dbops_db.h +++ b/modules/dbops/dbops_db.h @@ -25,6 +25,7 @@ #ifndef _DB_OPS_DB_H_ #define _DB_OPS_DB_H_ +#include "../../lib/cJSON.h" #include "../../db/db.h" #include "../../parser/msg_parser.h" #include "../../str.h" @@ -89,4 +90,20 @@ int db_query_print_one_result(struct sip_msg *msg, const db_res_t *db_res, int db_query_print_results(struct sip_msg *msg, const db_res_t *db_res, pvname_list_t *dest); +int db_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, + str *table, cJSON *Jfilter, str * order, + pvname_list_t* dest, int one_row); + +int db_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, + str *table, cJSON *Jfilter); + +int db_api_insert(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, + str *table); + +int db_api_delete(struct db_url *url, struct sip_msg* msg, + str *table, cJSON *Jfilter); + +int db_api_replace(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, + str *table); + #endif diff --git a/modules/dbops/dbops_impl.c b/modules/dbops/dbops_impl.c index 462295ebb6c..c47d8b95945 100644 --- a/modules/dbops/dbops_impl.c +++ b/modules/dbops/dbops_impl.c @@ -779,6 +779,188 @@ int ops_db_query(struct sip_msg* msg, str* query, struct db_url *url, return 1; } + +static inline int _parse_json_col_and_filter( str *cols, str *filter, + cJSON **Jcols, cJSON**Jfilter) +{ + char *j, *p, *cols_nt, *filter_nt; + + if (cols==NULL && filter==NULL) { + *Jcols = NULL; + *Jfilter = NULL; + return 0; + } + + /* make cols and filter NULL terminated and parse them as JSON objs */ + j = (char*)pkg_malloc( (cols?cols->len+1:0) + (filter?filter->len+1:0) ); + if (j==NULL) { + LM_ERR("failed to alloc and null-terminate JSON params\n"); + return -1; + } + + /* parse jsons */ + p = j; + if (cols) { + cols_nt = p; + memcpy( p, cols->s, cols->len); + p += cols->len; + *(p++) = 0; + /* parse as json */ + *Jcols = cJSON_Parse( cols_nt ); + if (!*Jcols) { + LM_ERR("failed to parse input cols JSON <%.*s>\n", + 128, cols_nt); + goto err1; + } + } else + *Jcols = NULL; + + if (filter) { + filter_nt = p; + memcpy( p, filter->s, filter->len); + p += filter->len; + *(p++) = 0; + *Jfilter = cJSON_Parse( filter_nt ); + if (!*Jfilter) { + LM_ERR("failed to parse input filter JSON <%.*s>\n", + 128, filter_nt); + goto err2; + } + } else + *Jfilter = NULL; + + pkg_free(j); + return 0; +err2: + if (*Jcols) cJSON_Delete(*Jcols); + *Jcols = NULL; +err1: + *Jfilter = NULL; + return -1; +} + + +int ops_db_api_select(struct db_url *url, struct sip_msg* msg, str *cols, + str *table, str *filter, str * order, pvname_list_t* dest, int one_col) +{ + cJSON *Jcols, *Jfilter; + int ret; + + ret = _parse_json_col_and_filter( cols, filter, &Jcols, &Jfilter); + if (ret<0) { + LM_ERR("failed to JSON parse cols and filter\n"); + } else { + ret = db_api_select( url, msg, Jcols, table, Jfilter, + order, dest, one_col); + if (ret<0) { + LM_ERR("failed to perform DB select query\n"); + } else { + ret =1; + } + } + + if (Jcols) cJSON_Delete(Jcols); + if (Jfilter) cJSON_Delete(Jfilter); + return ret; +} + + +int ops_db_api_update(struct db_url *url, struct sip_msg* msg, str *cols, + str *table, str *filter) +{ + cJSON *Jcols, *Jfilter; + int ret; + + ret = _parse_json_col_and_filter( cols, filter, &Jcols, &Jfilter); + if (ret<0) { + LM_ERR("failed to JSON parse cols and filter\n"); + } else { + ret = db_api_update( url, msg, Jcols, table, Jfilter); + if (ret<0) { + LM_ERR("failed to perform DB update query\n"); + } else { + ret =1; + } + } + + if (Jcols) cJSON_Delete(Jcols); + if (Jfilter) cJSON_Delete(Jfilter); + return ret; +} + + +int ops_db_api_insert(struct db_url *url, struct sip_msg* msg, str *cols, + str *table) +{ + cJSON *Jcols, *Jfilter; + int ret; + + ret = _parse_json_col_and_filter( cols, NULL, &Jcols, &Jfilter); + if (ret<0) { + LM_ERR("failed to JSON parse cols and filter\n"); + } else { + ret = db_api_insert( url, msg, Jcols, table); + if (ret<0) { + LM_ERR("failed to perform DB insert query\n"); + } else { + ret =1; + } + } + + if (Jcols) cJSON_Delete(Jcols); + if (Jfilter) cJSON_Delete(Jfilter); + return ret; +} + + +int ops_db_api_delete(struct db_url *url, struct sip_msg* msg, + str *table, str *filter) +{ + cJSON *Jcols, *Jfilter; + int ret; + + ret = _parse_json_col_and_filter( NULL, filter, &Jcols, &Jfilter); + if (ret<0) { + LM_ERR("failed to JSON parse cols and filter\n"); + } else { + ret = db_api_delete( url, msg, table, Jfilter); + if (ret<0) { + LM_ERR("failed to perform DB insert query\n"); + } else { + ret =1; + } + } + + if (Jcols) cJSON_Delete(Jcols); + if (Jfilter) cJSON_Delete(Jfilter); + return ret; +} + + +int ops_db_api_replace(struct db_url *url, struct sip_msg* msg, str *cols, + str *table) +{ + cJSON *Jcols, *Jfilter; + int ret; + + ret = _parse_json_col_and_filter( cols, NULL, &Jcols, &Jfilter); + if (ret<0) { + LM_ERR("failed to JSON parse cols and filter\n"); + } else { + ret = db_api_replace( url, msg, Jcols, table); + if (ret<0) { + LM_ERR("failed to perform DB replace query\n"); + } else { + ret =1; + } + } + + if (Jcols) cJSON_Delete(Jcols); + if (Jfilter) cJSON_Delete(Jfilter); + return ret; +} + + int ops_async_db_query(struct sip_msg* msg, async_ctx *ctx, str *query, struct db_url *url, pvname_list_t *dest, int one_row) { diff --git a/modules/dbops/dbops_impl.h b/modules/dbops/dbops_impl.h index d63873a1fd8..db87ba72dd0 100644 --- a/modules/dbops/dbops_impl.h +++ b/modules/dbops/dbops_impl.h @@ -99,6 +99,21 @@ int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, int ops_db_query(struct sip_msg* msg, str* query, struct db_url *url, pvname_list_t* dest, int one_row); +int ops_db_api_select(struct db_url *url, struct sip_msg* msg, str *cols, + str *table, str *filter, str *order, pvname_list_t* dest, int one_row); + +int ops_db_api_update(struct db_url *url, struct sip_msg* msg, str *cols, + str *table, str *filter); + +int ops_db_api_insert(struct db_url *url, struct sip_msg* msg, str *cols, + str *table); + +int ops_db_api_delete(struct db_url *url, struct sip_msg* msg, + str *table, str *filter); + +int ops_db_api_replace(struct db_url *url, struct sip_msg* msg, str *cols, + str *table); + int ops_async_db_query(struct sip_msg* msg, async_ctx *ctx, str *query, struct db_url *url, pvname_list_t *dest, int one_row); From 518c4f94d82b589f5645dfb8e92ad095f772730e Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Tue, 12 Mar 2024 17:39:26 +0200 Subject: [PATCH 08/79] [dbops] docs update with the latest functions --- modules/dbops/doc/dbops.xml | 2 +- modules/dbops/doc/dbops_admin.xml | 580 +++++++++++++++++++++++------- 2 files changed, 459 insertions(+), 123 deletions(-) diff --git a/modules/dbops/doc/dbops.xml b/modules/dbops/doc/dbops.xml index a7c77d890ab..c2614fb736a 100644 --- a/modules/dbops/doc/dbops.xml +++ b/modules/dbops/doc/dbops.xml @@ -25,6 +25,6 @@ &contrib; &docCopyrights; - ©right; 2009-2012 &osipssol; + ©right; 2009-2024 &osipssol; ©right; 2004-2008 &voicesystem; diff --git a/modules/dbops/doc/dbops_admin.xml b/modules/dbops/doc/dbops_admin.xml index 888bfded759..4579d49b233 100644 --- a/modules/dbops/doc/dbops_admin.xml +++ b/modules/dbops/doc/dbops_admin.xml @@ -9,8 +9,9 @@ Overview DBops (DB-operations) modules implements a set of script - functions which allow DB manipulation (loading/storing/removing) - of user AVPs (preferences). + functions for generic SQL standard queries (raw or structure queries). + It also provides a dedicated set of functions for DB manipulation + (loading/storing/removing) of user AVPs (preferences).
@@ -273,6 +274,461 @@ modparam("dbops","db_scheme",
Exported Functions + +
+ + <function moreinfo="none">db_query(query, [res_col_avps], [db_id])</function> + + + Make a database query and store the result in AVPs. + + + The meaning and usage of the parameters: + + + + query (string) - must be a valid SQL + query. The parameter can contain pseudo-variables. + You must escape any pseudo-variables manually to prevent + SQL injection attacks. You can use the existing transformations + escape.common and + unescape.common + to escape and unescape the content of any pseudo-variable. + Failing to escape the variables used in the query makes you + vulnerable to SQL injection, e.g. make it possible for an + outside attacker to alter your database content. + The function returns true if the query was successful, -2 in + case the query returned an empty result set, and -1 for all + other types of errors. + + + + res_col_avps (string, optional, no expand) - a list with AVP names where + to store the result. The format is + $avp(name1);$avp(name2);.... If this parameter + is omitted, the result is stored in + $avp(1);$avp(2);.... If the result consists of + multiple rows, then multiple AVPs with corresponding names will + be added. The value type of the AVP (string or integer) will + be derived from the type of the columns. If the value in the + database is NULL, the returned avp will + be a string with the <null> value. + + + + db_id (int, optional) - reference to a defined + DB URL (a numerical id) - see the db_url + module parameter. It can be either a constant, or a + string/int variable. + + + + + This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, + BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. + + + <function>db_query</function> usage + +... +db_query("SELECT password, ha1 FROM subscriber WHERE username='$tu'", + "$avp(pass);$avp(hash)"); +db_query("DELETE FROM subscriber"); +db_query("DELETE FROM subscriber", , 2); + +$avp(id) = 2; +db_query("DELETE FROM subscriber", , $avp(id)); +... + + +
+ +
+ + <function moreinfo="none">db_query_one(query, [res_col_vars], [db_id])</function> + + + Similar to , it makes a generic raw + database query and returns the results, but with the following + differences: + + + + returns only one row - even if + the query results in a multi row result, only the first row + will be returned to script. + + + + return variables are not limited to AVPs - + the variables for returning the query result may any kind + of variable, of course, as time as it is writeable. NOTE that + the number of return vairable MUST match (as number) the number + of returned columns. If less variables are provided, the query + will fail. + + + + NULL is returned - any a DB NULL + value resulting from the query will be pushed as NULL indicator + (and NOT as <null> string) to the + script variables. + + + + + This function can be used from any type of route. + + + <function>db_query_one</function> usage + +... +db_query_one("SELECT password, ha1 FROM subscriber WHERE username='$tU'", + "$var(pass);$var(hash)"); +# $var(pass) or $var(hash) may be NULL if the corresponding columns +# are not populated +... +db_query_one("SELECT value, type FROM usr_preferences WHERE username='$fU' and attribute='cfna'", + "$var(cf_uri);$var(type)"); +# the above query will return only one row, even if there are multiple `cfna` +# attributes for the user +... + + +
+ +
+ + <function moreinfo="none">db_select([columns],table,[filter],[order],[res_col_avps], [db_id]) + </function> + + + Function to perform a structured (not raw) SQL SELECT operation. + The query is performed via OpenSIPS internal SQL interface, taking + advantages of the prepared-statements support (if the db backend + provides something like that). The selected columns are returned + into a set of AVPs (one to one matching the selected columns). + + + If using varibales in constructing the query, you must + manually escape their values in order to prevent SQL injection + attacks. You can use the existing transformations + escape.common and + unescape.common + to escape and unescape the content of any pseudo-variable. + Failing to escape the variables used in the query makes you + vulnerable to SQL injection, e.g. make it possible for an + outside attacker to alter your database content. + + + + The function returns true if the query was successful, -2 in + case the query returned an empty result set, and -1 for all + other types of errors. + + + The meaning and usage of the parameters: + + + + columns (string,optional) - JSON + formated string holding an array of columns to be returned by + the select. Ex: ["col1","col2"]. + If missing, a * (all columns) select will be + performed. + + + + table (string, mandatory) - the + name of the table to be queried. + + + + filter (string, optional) - JSON + formated string holding the "where" filter of the query. This + must be an array of (column, operator,value) pairs. The + exact JSON syntax of such a pair is + {"column":{"operator":"value"}}.; operators + may be `>`, `<`, `=`, `!=` or custom string; The values + may be string, integer or `null`. To simplify the usage with + the `=` operator, you can use {"column":"value"} + If missing, all rows will be selected. + + + + order (string, optional) - the + name of the column to oder by (only ascending). + + + + res_col_avps (string, optional, no expand) - a list with AVP names where + to store the result. The format is + $avp(name1);$avp(name2);.... If this parameter + is omitted, the result is stored in + $avp(1);$avp(2);.... If the result consists of + multiple rows, then multiple AVPs with corresponding names will + be added. The value type of the AVP (string or integer) will + be derived from the type of the columns. If the value in the + database is NULL, the returned avp will + be a string with the <null> value. + + + + db_id (int, optional) - reference + to a defined DB URL (a numerical id) - see the + module parameter. It can + be either a constant, or a string/int variable. + + + + + This function can be used from any type of route. + + + <function>db_select</function> usage + +... +db_select('["password","ha1"]', 'subscriber', + '[ {"username", "$tu"}, {"domain",{"!=",null}}]', , + '$avp(pass);$avp(hash)'); +... + + +
+ +
+ + <function moreinfo="none">db_select_one([columns],table,[filter],[order],[res_col_vars], [db_id])</function> + + + Similar to , it makes a SELECT SQL + query and returns the results, but with the following + differences: + + + + returns only one row - even if + the query results in a multi row result, only the first row + will be returned to script. + + + + return variables are not limited to AVPs - + the variables for returning the query result may any kind + of variable, of course, as time as it is writeable. NOTE that + the number of return vairable MUST match (as number) the number + of returned columns. If less variables are provided, the query + will fail. + + + + NULL is returned - any a DB NULL + value resulting from the query will be pushed as NULL indicator + (and NOT as <null> string) to the + script variables. + + + + + This function can be used from any type of route. + + + <function>db_query_one</function> usage + +... +db_select_one('["value","type"]', 'usr_preferences', + '[ {"username", "$tu"}, {"attribute","cfna"}]', , + '$var(cf_uri);$var(type)'); +# the above query will return only one row, even if there are multiple `cfna` +# attributes for the user +... + + +
+ +
+ + <function moreinfo="none">db_update(columns,table,[filter],[db_id]) + </function> + + + Function to perform a structured (not raw) SQL UPDATE operation. + IMPORTANT: please see all the general notes from the + function. + + + The function returns true if the query was successful. + + + The meaning and usage of the parameters: + + + + columns (string,mandatory) - JSON + formated string holding an array of (column,value) pairs to + be updated by the query. + Ex: [{"col1":"val1"},{"col2":"val1"}]. + + + + table (string, mandatory) - the + name of the table to be queried. + + + + filter (string, optional) - JSON + formated string holding the "where" filter of the query. This + must be an array of (column, operator,value) pairs. The + exact JSON syntax of such a pair is + {"column":{"operator":"value"}}.; operators + may be `>`, `<`, `=`, `!=` or custom string; The values + may be string, integer or `null`. To simplify the usage with + the `=` operator, you can use {"column":"value"} + If missing, all rows will be updated. + + + + db_id (int, optional) - reference + to a defined DB URL (a numerical id) - see the + module parameter. It can + be either a constant, or a string/int variable. + + + + + This function can be used from any type of route. + + + <function>db_update</function> usage + +... +db_update( '[{"password":"my_secret"}]', 'subscriber', + '[{"username", "$tu"}]'); +... + + +
+ +
+ + <function moreinfo="none">db_insert(columns,table,[db_id]) + </function> + + + Function to perform a structured (not raw) SQL INSERT operation. + IMPORTANT: please see all the general notes from the + function. + + + The function returns true if the query was successful. + + + The meaning and usage of the parameters: + + + + columns (string,mandatory) - JSON + formated string holding an array of (column,value) pairs to + be inserted. + Ex: [{"col1":"val1"},{"col2":"val1"}]. + + + + table (string, mandatory) - the + name of the table to be queried. + + + + db_id (int, optional) - reference + to a defined DB URL (a numerical id) - see the + module parameter. It can + be either a constant, or a string/int variable. + + + + + This function can be used from any type of route. + + + <function>db_insert</function> usage + +... +db_insert( '[{"agentid":"agentX"},{"skills":"info"},{"location":null},{"msrp_location":"sip:agentX@opensips.com"},{"msrp_max_sessions":2}]','cc_agents'); +... + + +
+ +
+ + <function moreinfo="none">db_delete(table,[filter],[db_id]) + </function> + + + Function to perform a structured (not raw) SQL DELETE operation. + IMPORTANT: please see all the general notes from the + function. + + + The function returns true if the query was successful. + + + The meaning and usage of the parameters: + + + + table (string, mandatory) - the + name of the table to delete from. + + + + filter (string, optional) - JSON + formated string holding the "where" filter of the query. This + must be an array of (column, operator,value) pairs. The + exact JSON syntax of such a pair is + {"column":{"operator":"value"}}.; operators + may be `>`, `<`, `=`, `!=` or custom string; The values + may be string, integer or `null`. To simplify the usage with + the `=` operator, you can use {"column":"value"} + If missing, all rows will be updated. + + + + db_id (int, optional) - reference + to a defined DB URL (a numerical id) - see the + module parameter. It can + be either a constant, or a string/int variable. + + + + + This function can be used from any type of route. + + + <function>db_update</function> usage + +... +db_delete( 'subscriber', '[{"username", "$tu"}]'); +... + + +
+ +
+ + <function moreinfo="none">db_insert(columns,table,[db_id]) + </function> + + + Function very similar to function, + but performing an SQL REPLACE operation instead. Note that not all + SQL backend in OpenSIPS may support a REPLACE operation. + + + The function returns true if the query was successful. + +
+
<function moreinfo="none">db_avp_load(source, name, [db_id], [prefix]]) @@ -408,131 +864,11 @@ db_avp_delete("$ru/username", "$avp(email)"); db_avp_delete("$avp(uuid)", "$avp(404fwd)/fwd_table"); # use DB URL id 3 db_avp_delete("$ru", "$avp(1)", 3); -... - </programlisting> - </example> - </section> - <section id="func_db_query" xreflabel="db_query()"> - <title> - <function moreinfo="none">db_query(query, [res_col_avps], [db_id])</function> - - - Make a database query and store the result in AVPs. - - - The meaning and usage of the parameters: - - - - query (string) - must be a valid SQL - query. The parameter can contain pseudo-variables. - You must escape any pseudo-variables manually to prevent - SQL injection attacks. You can use the existing transformations - escape.common and - unescape.common - to escape and unescape the content of any pseudo-variable. - Failing to escape the variables used in the query makes you - vulnerable to SQL injection, e.g. make it possible for an - outside attacker to alter your database content. - The function returns true if the query was successful, -2 in case - the query returned an empty result set, and -1 for all other types - of errors - - - - res_col_avps (string, optional, no expand) - a list with AVP names where - to store the result. The format is - $avp(name1);$avp(name2);.... If this parameter - is omitted, the result is stored in - $avp(1);$avp(2);.... If the result consists of - multiple rows, then multiple AVPs with corresponding names will - be added. The value type of the AVP (string or integer) will - be derived from the type of the columns. If the value in the - database is NULL, the returned avp will - be a string with the <null> value. - - - - db_id (int, optional) - reference to a defined - DB URL (a numerical id) - see the db_url - module parameter. It can be either a constant, or a - string/int variable. - - - - - This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, - BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - - - <function>db_query</function> usage - -... -db_query("SELECT password, ha1 FROM subscriber WHERE username='$tu'", - "$avp(pass);$avp(hash)"); -db_query("DELETE FROM subscriber"); -db_query("DELETE FROM subscriber", , 2); - -$avp(id) = 2; -db_query("DELETE FROM subscriber", , $avp(id)); ...
-
- - <function moreinfo="none">db_query_one(query, [res_col_vars], [db_id])</function> - - - Similar to , it makes a generic raw - database query and returns the results, but with the following - differences: - - - - returns only one row - even if - the query results in a multi row result, only the first row - will be returned to script. - - - - return variables are not limited to AVPs - - the variables for returning the query result may any kind - of variable, of course, as time as it is writeable. NOTE that - the number of return vairable MUST match (as number) the number - of returned columns. If less variables are provided, the query - will fail. - - - - NULL is returned - any a DB NULL - value resulting from the query will be pushed as NULL indicator - (and NOT as <null> string) to the - script variables. - - - - - This function can be used from any type of route. - - - <function>db_query_one</function> usage - -... -db_query_one("SELECT password, ha1 FROM subscriber WHERE username='$tU'", - "$var(pass);$var(hash)"); -# $var(pass) or $var(hash) may be NULL if the corresponding columns -# are not populated -... -db_query_one("SELECT value, type FROM usr_preferences WHERE username='$fU' and attribute='cfna'", - "$var(cf_uri);$var(type)"); -# the above query will return only one row, even if there are multiple `cfna` -# attributes for the user -... - - -
From 12b9ba4a8af0c9191d7567ebdce298fad9a8723c Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Tue, 12 Mar 2024 18:12:45 +0200 Subject: [PATCH 09/79] [dbops] swap cols and table as args for INSERT and REPLACE.. ...just to follow order int the SQL syntax :) --- modules/dbops/dbops.c | 18 +++++++++--------- modules/dbops/dbops_db.c | 8 ++++---- modules/dbops/dbops_db.h | 8 ++++---- modules/dbops/dbops_impl.c | 12 ++++++------ modules/dbops/dbops_impl.h | 8 ++++---- modules/dbops/doc/dbops_admin.xml | 20 ++++++++++---------- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/modules/dbops/dbops.c b/modules/dbops/dbops.c index dc7bcc57a80..c5e80e32c29 100644 --- a/modules/dbops/dbops.c +++ b/modules/dbops/dbops.c @@ -102,11 +102,11 @@ static int w_db_select_one(struct sip_msg* msg, str* cols, str *table, str *filter, str *order, void* dest, void *url); static int w_db_update(struct sip_msg* msg, str* cols, str *table, str *filter, void *url); -static int w_db_insert(struct sip_msg* msg, str* cols, str *table, +static int w_db_insert(struct sip_msg* msg, str* table, str *cols, void *url); static int w_db_delete(struct sip_msg* msg, str *table, str *filter, void *url); -static int w_db_replace(struct sip_msg* msg, str* cols, str *table, +static int w_db_replace(struct sip_msg* msg, str* table, str *cols, void *url); @@ -192,7 +192,7 @@ static const cmd_export_t cmds[] = { {0, 0, 0}}, ALL_ROUTES}, - {"db_selec_onet", (cmd_function)w_db_select_one, { + {"db_selec_one", (cmd_function)w_db_select_one, { {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* columns */ {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* table */ {CMD_PARAM_STR, 0, 0}, /* filter */ @@ -214,8 +214,8 @@ static const cmd_export_t cmds[] = { ALL_ROUTES}, {"db_insert", (cmd_function)w_db_insert, { - {CMD_PARAM_STR, 0, 0}, /* columns */ {CMD_PARAM_STR, 0, 0}, /* table */ + {CMD_PARAM_STR, 0, 0}, /* columns */ {CMD_PARAM_INT|CMD_PARAM_OPT, fixup_db_id_sync, fixup_free_pkg}, {0, 0, 0}}, @@ -230,8 +230,8 @@ static const cmd_export_t cmds[] = { ALL_ROUTES}, {"db_replace", (cmd_function)w_db_replace, { - {CMD_PARAM_STR, 0, 0}, /* columns */ {CMD_PARAM_STR, 0, 0}, /* table */ + {CMD_PARAM_STR, 0, 0}, /* columns */ {CMD_PARAM_INT|CMD_PARAM_OPT, fixup_db_id_sync, fixup_free_pkg}, {0, 0, 0}}, @@ -752,7 +752,7 @@ static int w_db_update(struct sip_msg* msg, str* cols, str *table, } -static int w_db_insert(struct sip_msg* msg, str* cols, str *table, +static int w_db_insert(struct sip_msg* msg, str* table, str *cols, void *url) { struct db_url *parsed_url; @@ -762,7 +762,7 @@ static int w_db_insert(struct sip_msg* msg, str* cols, str *table, else parsed_url = default_db_url; - return ops_db_api_insert(parsed_url, msg, cols, table); + return ops_db_api_insert(parsed_url, msg, table, cols); } @@ -780,7 +780,7 @@ static int w_db_delete(struct sip_msg* msg, str *table, str *filter, } -static int w_db_replace(struct sip_msg* msg, str* cols, str *table, +static int w_db_replace(struct sip_msg* msg, str* table, str *cols, void *url) { struct db_url *parsed_url; @@ -790,7 +790,7 @@ static int w_db_replace(struct sip_msg* msg, str* cols, str *table, else parsed_url = default_db_url; - return ops_db_api_replace(parsed_url, msg, cols, table); + return ops_db_api_replace(parsed_url, msg, table, cols); } diff --git a/modules/dbops/dbops_db.c b/modules/dbops/dbops_db.c index a713fd5868a..2770e6a9898 100644 --- a/modules/dbops/dbops_db.c +++ b/modules/dbops/dbops_db.c @@ -746,8 +746,8 @@ int db_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, } -int db_api_insert(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, - str *table) +int db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, + cJSON *Jcols) { db_key_t *ukeys; db_op_t *uops; @@ -817,8 +817,8 @@ int db_api_delete(struct db_url *url, struct sip_msg* msg, } -int db_api_replace(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, - str *table) +int db_api_replace(struct db_url *url, struct sip_msg* msg, str *table, + cJSON *Jcols) { db_key_t *ukeys; db_op_t *uops; diff --git a/modules/dbops/dbops_db.h b/modules/dbops/dbops_db.h index 0f18fb52bb9..a6ca2228607 100644 --- a/modules/dbops/dbops_db.h +++ b/modules/dbops/dbops_db.h @@ -97,13 +97,13 @@ int db_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, int db_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, str *table, cJSON *Jfilter); -int db_api_insert(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, - str *table); +int db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, + cJSON *Jcols); int db_api_delete(struct db_url *url, struct sip_msg* msg, str *table, cJSON *Jfilter); -int db_api_replace(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, - str *table); +int db_api_replace(struct db_url *url, struct sip_msg* msg, str *table, + cJSON *Jcols); #endif diff --git a/modules/dbops/dbops_impl.c b/modules/dbops/dbops_impl.c index c47d8b95945..45663746c3c 100644 --- a/modules/dbops/dbops_impl.c +++ b/modules/dbops/dbops_impl.c @@ -889,8 +889,8 @@ int ops_db_api_update(struct db_url *url, struct sip_msg* msg, str *cols, } -int ops_db_api_insert(struct db_url *url, struct sip_msg* msg, str *cols, - str *table) +int ops_db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, + str *cols) { cJSON *Jcols, *Jfilter; int ret; @@ -899,7 +899,7 @@ int ops_db_api_insert(struct db_url *url, struct sip_msg* msg, str *cols, if (ret<0) { LM_ERR("failed to JSON parse cols and filter\n"); } else { - ret = db_api_insert( url, msg, Jcols, table); + ret = db_api_insert( url, msg, table, Jcols); if (ret<0) { LM_ERR("failed to perform DB insert query\n"); } else { @@ -937,8 +937,8 @@ int ops_db_api_delete(struct db_url *url, struct sip_msg* msg, } -int ops_db_api_replace(struct db_url *url, struct sip_msg* msg, str *cols, - str *table) +int ops_db_api_replace(struct db_url *url, struct sip_msg* msg, str *table, + str *cols) { cJSON *Jcols, *Jfilter; int ret; @@ -947,7 +947,7 @@ int ops_db_api_replace(struct db_url *url, struct sip_msg* msg, str *cols, if (ret<0) { LM_ERR("failed to JSON parse cols and filter\n"); } else { - ret = db_api_replace( url, msg, Jcols, table); + ret = db_api_replace( url, msg, table, Jcols); if (ret<0) { LM_ERR("failed to perform DB replace query\n"); } else { diff --git a/modules/dbops/dbops_impl.h b/modules/dbops/dbops_impl.h index db87ba72dd0..d6423385ba5 100644 --- a/modules/dbops/dbops_impl.h +++ b/modules/dbops/dbops_impl.h @@ -105,14 +105,14 @@ int ops_db_api_select(struct db_url *url, struct sip_msg* msg, str *cols, int ops_db_api_update(struct db_url *url, struct sip_msg* msg, str *cols, str *table, str *filter); -int ops_db_api_insert(struct db_url *url, struct sip_msg* msg, str *cols, - str *table); +int ops_db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, + str *cols); int ops_db_api_delete(struct db_url *url, struct sip_msg* msg, str *table, str *filter); -int ops_db_api_replace(struct db_url *url, struct sip_msg* msg, str *cols, - str *table); +int ops_db_api_replace(struct db_url *url, struct sip_msg* msg, str *table, + str *cols); int ops_async_db_query(struct sip_msg* msg, async_ctx *ctx, str *query, struct db_url *url, pvname_list_t *dest, int one_row); diff --git a/modules/dbops/doc/dbops_admin.xml b/modules/dbops/doc/dbops_admin.xml index 4579d49b233..b9a66d11330 100644 --- a/modules/dbops/doc/dbops_admin.xml +++ b/modules/dbops/doc/dbops_admin.xml @@ -533,7 +533,7 @@ db_select('["password","ha1"]', 'subscriber', This function can be used from any type of route. - <function>db_query_one</function> usage + <function>db_select_one</function> usage ... db_select_one('["value","type"]', 'usr_preferences', @@ -611,7 +611,7 @@ db_update( '[{"password":"my_secret"}]', 'subscriber',
- <function moreinfo="none">db_insert(columns,table,[db_id]) + <function moreinfo="none">db_insert(table,columns,[db_id]) </function> @@ -626,6 +626,11 @@ db_update( '[{"password":"my_secret"}]', 'subscriber', The meaning and usage of the parameters: + + table (string, mandatory) - the + name of the table to be queried. + + columns (string,mandatory) - JSON formated string holding an array of (column,value) pairs to @@ -633,11 +638,6 @@ db_update( '[{"password":"my_secret"}]', 'subscriber', Ex: [{"col1":"val1"},{"col2":"val1"}]. - - table (string, mandatory) - the - name of the table to be queried. - - db_id (int, optional) - reference to a defined DB URL (a numerical id) - see the @@ -653,7 +653,7 @@ db_update( '[{"password":"my_secret"}]', 'subscriber', <function>db_insert</function> usage ... -db_insert( '[{"agentid":"agentX"},{"skills":"info"},{"location":null},{"msrp_location":"sip:agentX@opensips.com"},{"msrp_max_sessions":2}]','cc_agents'); +db_insert( 'cc_agents', '[{"agentid":"agentX"},{"skills":"info"},{"location":null},{"msrp_location":"sip:agentX@opensips.com"},{"msrp_max_sessions":2}]' ); ... @@ -705,7 +705,7 @@ db_insert( '[{"agentid":"agentX"},{"skills":"info"},{"location":null},{"msrp_loc This function can be used from any type of route. - <function>db_update</function> usage + <function>db_delete</function> usage ... db_delete( 'subscriber', '[{"username", "$tu"}]'); @@ -716,7 +716,7 @@ db_delete( 'subscriber', '[{"username", "$tu"}]');
- <function moreinfo="none">db_insert(columns,table,[db_id]) + <function moreinfo="none">db_replace(table,columns,[db_id]) </function> From 5b7d83d4399b718601ba01f6a95be44f6b9a2f03 Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Tue, 12 Mar 2024 22:09:50 +0200 Subject: [PATCH 10/79] [dbops] fix bad usage of non-static variable Use the cols pointer (which is static) and not the str_cols which are valid only during a mem realloc --- modules/dbops/dbops_db.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/dbops/dbops_db.c b/modules/dbops/dbops_db.c index 2770e6a9898..a9fda4c3a7f 100644 --- a/modules/dbops/dbops_db.c +++ b/modules/dbops/dbops_db.c @@ -506,8 +506,8 @@ static inline int _json_to_cols(cJSON *Jcols, db_key_t** _c) /* iterate again to fill in the cols */ for( col=Jcols->child,i=0 ; col ; col=col->next,i++ ) { - str_cols[i].s = col->valuestring; - str_cols[i].len = strlen(col->valuestring); + cols[i]->s = col->valuestring; + cols[i]->len = strlen(col->valuestring); } *_c = cols; From 5efa82fcdee49c20377460b0ebfd94d1a73337ea Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Tue, 12 Mar 2024 22:53:56 +0200 Subject: [PATCH 11/79] [dbops] added prepared statements support for the newly added db_select|update|insert|replace|delete() functions --- modules/dbops/dbops.c | 1 + modules/dbops/dbops_db.c | 147 ++++++++++++++++++++++++++++++ modules/dbops/dbops_db.h | 2 + modules/dbops/doc/dbops_admin.xml | 121 +++++++++++++++--------- 4 files changed, 226 insertions(+), 45 deletions(-) diff --git a/modules/dbops/dbops.c b/modules/dbops/dbops.c index c5e80e32c29..ce9e418b8f0 100644 --- a/modules/dbops/dbops.c +++ b/modules/dbops/dbops.c @@ -255,6 +255,7 @@ static const param_export_t params[] = { {"username_column", STR_PARAM, &username_col.s }, {"domain_column", STR_PARAM, &domain_col.s }, {"db_scheme", STR_PARAM|USE_FUNC_PARAM, (void*)add_avp_db_scheme }, + {"ps_id_max_buf_len", INT_PARAM, &query_id_max_len}, {0, 0, 0} }; diff --git a/modules/dbops/dbops_db.c b/modules/dbops/dbops_db.c index a9fda4c3a7f..5b9bdd9c5bd 100644 --- a/modules/dbops/dbops_db.c +++ b/modules/dbops/dbops_db.c @@ -30,6 +30,7 @@ #include "../../db/db_insertq.h" #include "../../dprint.h" #include "../../route.h" +#include "../../map.h" #include "dbops_parse.h" #include "dbops_db.h" @@ -45,6 +46,8 @@ static struct db_scheme *db_scheme_list=0; struct db_url *default_db_url = NULL; +int query_id_max_len = 1024; + /* array of db urls */ static struct db_url *db_urls = NULL; /* array of database urls */ static unsigned int no_db_urls = 0; @@ -625,15 +628,77 @@ static inline int _json_to_filters(cJSON *Jfilter, return -1; } +static str _query_id = {NULL,0}; +static inline str* _query_id_start( str *table) +{ + if (_query_id.s==NULL) { + if ( !query_id_max_len || + (_query_id.s=pkg_malloc(query_id_max_len ))==NULL ) + return NULL; + } + _query_id.len = 0; + + memcpy( _query_id.s + _query_id.len, table->s, table->len); + _query_id.len += table->len; + *(_query_id.s + _query_id.len++) = '^'; + + return &_query_id; +} + +static inline str* _query_id_add_cols( db_key_t *_c, int _nc) +{ + int i; + + for ( i=0 ; i<_nc ; i++) { + if (query_id_max_len-_query_id.len < _c[i]->len+2 /* both |^ */) { + _query_id.len = 0; // reset + return NULL; + } + memcpy( _query_id.s + _query_id.len, _c[i]->s, _c[i]->len); + _query_id.len += _c[i]->len; + *(_query_id.s + _query_id.len++) = '|'; + } + *(_query_id.s + _query_id.len++) = '^'; + + return &_query_id; +} + +static inline str* _query_id_add_filter(db_key_t* _k, db_op_t* _o, int _nk) +{ + int i,l; + + for ( i=0 ; i<_nk ; i++) { + l = strlen( _o[i] ); + if (query_id_max_len-_query_id.len < _k[i]->len+l+3 /* all %|^ */) { + _query_id.len = 0; // reset + return NULL; + } + memcpy( _query_id.s + _query_id.len, _k[i]->s, _k[i]->len); + _query_id.len += _k[i]->len; + *(_query_id.s + _query_id.len++) = '%'; + + memcpy( _query_id.s + _query_id.len, _o[i], l); + _query_id.len += l; + *(_query_id.s + _query_id.len++) = '|'; + } + *(_query_id.s + _query_id.len++) = '^'; + + return &_query_id; +} + + int db_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, str *table, cJSON *Jfilter, str * order, pvname_list_t* dest, int one_row) { + static map_t ps_map = NULL; db_res_t* db_res = NULL; db_key_t *cols, *keys; db_op_t *ops; db_val_t *vals; int nk, nc; + str *id; + db_ps_t *my_ps; /* convert JSON to COLs */ if (Jcols) { @@ -665,6 +730,20 @@ int db_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, if (set_table( url, table ,"API select")!=0) return -1; + if ( _query_id_start(table)==NULL || + (cols && (id=_query_id_add_cols(cols,nc))==NULL) || + (keys && (id=_query_id_add_filter(keys,ops,nk))==NULL) ) { + LM_ERR("failed to build PS id\n"); + } else { + LM_DBG("PS id is <%.*s>\n",id->len,id->s); + if (ps_map!=NULL || (ps_map=map_create(0))!=NULL) { + if ( (my_ps=map_get( ps_map, *id ))!=NULL) { + LM_DBG("using PS %p\n",*my_ps); + CON_SET_CURR_PS( url->hdl, my_ps); + } + } + } + /* do the DB query */ if ( url->dbf.query( url->hdl, keys, ops, vals, cols, nk, nc, order, &db_res) < 0 ) { @@ -703,10 +782,13 @@ int db_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, int db_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, str *table, cJSON *Jfilter) { + static map_t ps_map = NULL; db_key_t *ukeys, *keys; db_op_t *uops, *ops; db_val_t *uvals, *vals; int nk, unk; + str *id; + db_ps_t *my_ps; /* convert JSON to COLs */ unk = _json_to_filters( Jcols, &ukeys, &uops, &uvals, 1); @@ -733,6 +815,20 @@ int db_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, if (set_table( url, table ,"API update")!=0) return -1; + if ( _query_id_start(table)==NULL || + (id=_query_id_add_filter(ukeys,uops,unk))==NULL || + (keys && (id=_query_id_add_filter(keys,ops,nk))==NULL) ) { + LM_ERR("failed to build PS id\n"); + } else { + LM_DBG("PS id is <%.*s>\n",id->len,id->s); + if (ps_map!=NULL || (ps_map=map_create(0))!=NULL) { + if ( (my_ps=map_get( ps_map, *id ))!=NULL) { + LM_DBG("using PS %p\n",*my_ps); + CON_SET_CURR_PS( url->hdl, my_ps); + } + } + } + /* do the DB query */ if (url->dbf.update( url->hdl, keys, ops, vals, ukeys, uvals, nk, unk)<0){ const str *t = url->hdl&&url->hdl->table&&url->hdl->table->s @@ -749,10 +845,13 @@ int db_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, int db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, cJSON *Jcols) { + static map_t ps_map = NULL; db_key_t *ukeys; db_op_t *uops; db_val_t *uvals; int unk; + str *id; + db_ps_t *my_ps; /* convert JSON to COLs */ unk = _json_to_filters( Jcols, &ukeys, &uops, &uvals, 1); @@ -765,6 +864,20 @@ int db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, if (set_table( url, table ,"API insert")!=0) return -1; + /* set the PS to be used */ + if ( _query_id_start(table)==NULL || + (id=_query_id_add_filter(ukeys,uops,unk))==NULL ) { + LM_ERR("failed to build PS id\n"); + } else { + LM_DBG("PS id is <%.*s>\n",id->len,id->s); + if (ps_map!=NULL || (ps_map=map_create(0))!=NULL) { + if ( (my_ps=map_get( ps_map, *id ))!=NULL) { + LM_DBG("using PS %p\n",*my_ps); + CON_SET_CURR_PS( url->hdl, my_ps); + } + } + } + /* do the DB query */ if (url->dbf.insert( url->hdl, ukeys, uvals, unk)<0){ const str *t = url->hdl&&url->hdl->table&&url->hdl->table->s @@ -781,10 +894,13 @@ int db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, int db_api_delete(struct db_url *url, struct sip_msg* msg, str *table, cJSON *Jfilter) { + static map_t ps_map = NULL; db_key_t *keys; db_op_t *ops; db_val_t *vals; int nk; + str *id; + db_ps_t *my_ps; /* convert JSON to keys */ if (Jfilter) { @@ -804,6 +920,20 @@ int db_api_delete(struct db_url *url, struct sip_msg* msg, if (set_table( url, table ,"API delete")!=0) return -1; + /* set the PS to be used */ + if ( _query_id_start(table)==NULL || + (keys && (id=_query_id_add_filter(keys,ops,nk))==NULL) ) { + LM_ERR("failed to build PS id\n"); + } else { + LM_DBG("PS id is <%.*s>\n",id->len,id->s); + if (ps_map!=NULL || (ps_map=map_create(0))!=NULL) { + if ( (my_ps=map_get( ps_map, *id ))!=NULL) { + LM_DBG("using PS %p\n",*my_ps); + CON_SET_CURR_PS( url->hdl, my_ps); + } + } + } + /* do the DB query */ if (url->dbf.delete( url->hdl, keys, ops, vals, nk)<0){ const str *t = url->hdl&&url->hdl->table&&url->hdl->table->s @@ -820,10 +950,13 @@ int db_api_delete(struct db_url *url, struct sip_msg* msg, int db_api_replace(struct db_url *url, struct sip_msg* msg, str *table, cJSON *Jcols) { + static map_t ps_map = NULL; db_key_t *ukeys; db_op_t *uops; db_val_t *uvals; int unk; + str *id; + db_ps_t *my_ps; /* convert JSON to COLs */ unk = _json_to_filters( Jcols, &ukeys, &uops, &uvals, 1); @@ -836,6 +969,20 @@ int db_api_replace(struct db_url *url, struct sip_msg* msg, str *table, if (set_table( url, table ,"API replace")!=0) return -1; + /* set the PS to be used */ + if ( _query_id_start(table)==NULL || + (id=_query_id_add_filter(ukeys,uops,unk))==NULL ) { + LM_ERR("failed to build PS id\n"); + } else { + LM_DBG("PS id is <%.*s>\n",id->len,id->s); + if (ps_map!=NULL || (ps_map=map_create(0))!=NULL) { + if ( (my_ps=map_get( ps_map, *id ))!=NULL) { + LM_DBG("using PS %p\n",*my_ps); + CON_SET_CURR_PS( url->hdl, my_ps); + } + } + } + /* do the DB query */ if (url->dbf.replace( url->hdl, ukeys, uvals, unk)<0){ const str *t = url->hdl&&url->hdl->table&&url->hdl->table->s diff --git a/modules/dbops/dbops_db.h b/modules/dbops/dbops_db.h index a6ca2228607..7f58b112319 100644 --- a/modules/dbops/dbops_db.h +++ b/modules/dbops/dbops_db.h @@ -34,6 +34,8 @@ extern struct db_url *default_db_url; +extern int query_id_max_len; + struct db_url { str url; diff --git a/modules/dbops/doc/dbops_admin.xml b/modules/dbops/doc/dbops_admin.xml index b9a66d11330..99b7c4b9387 100644 --- a/modules/dbops/doc/dbops_admin.xml +++ b/modules/dbops/doc/dbops_admin.xml @@ -94,6 +94,53 @@ modparam("dbops","usr_table","avptable")
+ +
+ <varname>db_scheme</varname> (string) + + Definition of a DB scheme to be used for accessing + a non-standard User Preference -like table. + + + Definition of a DB scheme. Scheme syntax is: + + + db_scheme = name':'element[';'element]* + + element = + + 'uuid_col='string + + 'username_col='string + + 'domain_col='string + + 'value_col='string + + 'value_type='('integer'|'string') + + 'table='string + + + + + + + Default value is NULL. + + + + Set <varname>db_scheme</varname> parameter + + +... +modparam("dbops","db_scheme", +"scheme1:table=subscriber;uuid_col=uuid;value_col=first_name") +... + + +
+
<varname>use_domain</varname> (integer) @@ -114,6 +161,34 @@ modparam("dbops","use_domain",1)
+ +
+ <varname>ps_id_max_buf_len</varname> (integer) + + The maximum size of the buffer used to build the query IDs which + are used for managing the Prepare Statements when comes to the + "db_select|update|insert|replace|delete()" functions + + + If the size is exceeded (when trying to build the PS query ID), + the PS support will be dropped for the query. If set to 0, the PS + support will be completly disabled. + + + Default value is 1024. + + + + Set <varname>ps_id_max_buf_len</varname> parameter + + +... +modparam("dbops","ps_id_max_buf_len", 2048) +... + + +
+
<varname>uuid_column</varname> (string) @@ -225,51 +300,7 @@ modparam("dbops","type_column","type")
-
- <varname>db_scheme</varname> (string) - - Definition of a DB scheme to be used for accessing - a non-standard User Preference -like table. - - - Definition of a DB scheme. Scheme syntax is: - - - db_scheme = name':'element[';'element]* - - element = - - 'uuid_col='string - - 'username_col='string - - 'domain_col='string - - 'value_col='string - - 'value_type='('integer'|'string') - - 'table='string - - - - - - - Default value is NULL. - - - - Set <varname>db_scheme</varname> parameter - - -... -modparam("dbops","db_scheme", -"scheme1:table=subscriber;uuid_col=uuid;value_col=first_name") -... - - -
+
From 254fa4b967e2a72fbc6be833f19bf02a2ffd9004 Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Tue, 12 Mar 2024 23:06:57 +0200 Subject: [PATCH 12/79] [dbops] fix +1 overreading during memcmp Reported by CI --- modules/dbops/dbops_db.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dbops/dbops_db.c b/modules/dbops/dbops_db.c index 5b9bdd9c5bd..8fc0d9307ae 100644 --- a/modules/dbops/dbops_db.c +++ b/modules/dbops/dbops_db.c @@ -582,7 +582,7 @@ static inline int _json_to_filters(cJSON *Jfilter, if (node->type==cJSON_Object) { node = node->child; ops[i] = node->string; - if (only_equal && memcmp(ops[i],OP_EQ,sizeof(OP_EQ)+1)) { + if (only_equal && memcmp(ops[i],OP_EQ,sizeof(OP_EQ))) { LM_ERR("only equal allowed between keys and values at " "pos %d\n",i); goto error; From d15a7d8a8ec673d460324f434b951db7ececf0b4 Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Tue, 12 Mar 2024 23:34:17 +0200 Subject: [PATCH 13/79] [dbops] fixed bogus condition on JSON handling Reported by CI --- modules/dbops/README | 534 +++++++++++++++++++++++++++------------ modules/dbops/dbops_db.c | 6 +- 2 files changed, 382 insertions(+), 158 deletions(-) diff --git a/modules/dbops/README b/modules/dbops/README index 45737a6cacc..1653cb835a5 100644 --- a/modules/dbops/README +++ b/modules/dbops/README @@ -15,23 +15,36 @@ DBops Module 1.3.1. db_url (string) 1.3.2. usr_table (string) - 1.3.3. use_domain (integer) - 1.3.4. uuid_column (string) - 1.3.5. username_column (string) - 1.3.6. domain_column (string) - 1.3.7. attribute_column (string) - 1.3.8. value_column (string) - 1.3.9. type_column (string) - 1.3.10. db_scheme (string) + 1.3.3. db_scheme (string) + 1.3.4. use_domain (integer) + 1.3.5. ps_id_max_buf_len (integer) + 1.3.6. uuid_column (string) + 1.3.7. username_column (string) + 1.3.8. domain_column (string) + 1.3.9. attribute_column (string) + 1.3.10. value_column (string) + 1.3.11. type_column (string) 1.4. Exported Functions - 1.4.1. db_avp_load(source, name, [db_id], [prefix]]) + 1.4.1. db_query(query, [res_col_avps], [db_id]) + 1.4.2. db_query_one(query, [res_col_vars], [db_id]) + 1.4.3. + db_select([columns],table,[filter],[order],[re + s_col_avps], [db_id]) - 1.4.2. db_avp_store(source, name, [db_id]) - 1.4.3. db_avp_delete(source, name, [db_id]) - 1.4.4. db_query(query, [res_col_avps], [db_id]) - 1.4.5. db_query_one(query, [res_col_vars], [db_id]) + 1.4.4. + db_select_one([columns],table,[filter],[order] + ,[res_col_vars], [db_id]) + + 1.4.5. db_update(columns,table,[filter],[db_id]) + 1.4.6. db_insert(table,columns,[db_id]) + 1.4.7. db_delete(table,[filter],[db_id]) + 1.4.8. db_replace(table,columns,[db_id]) + 1.4.9. db_avp_load(source, name, [db_id], [prefix]]) + + 1.4.10. db_avp_store(source, name, [db_id]) + 1.4.11. db_avp_delete(source, name, [db_id]) 1.5. Exported Asynchronous Functions @@ -58,29 +71,37 @@ DBops Module 1.1. Set db_url parameter 1.2. Set usr_table parameter - 1.3. Set use_domain parameter - 1.4. Set uuid_column parameter - 1.5. Set username_column parameter - 1.6. Set domain_column parameter - 1.7. Set attribute_column parameter - 1.8. Set value_column parameter - 1.9. Set type_column parameter - 1.10. Set db_scheme parameter - 1.11. db_avp_load usage - 1.12. db_avp_store usage - 1.13. db_avp_delete usage - 1.14. db_query usage - 1.15. db_query_one usage - 1.16. async db_query usage - 1.17. async db_query_one usage + 1.3. Set db_scheme parameter + 1.4. Set use_domain parameter + 1.5. Set ps_id_max_buf_len parameter + 1.6. Set uuid_column parameter + 1.7. Set username_column parameter + 1.8. Set domain_column parameter + 1.9. Set attribute_column parameter + 1.10. Set value_column parameter + 1.11. Set type_column parameter + 1.12. db_query usage + 1.13. db_query_one usage + 1.14. db_select usage + 1.15. db_select_one usage + 1.16. db_update usage + 1.17. db_insert usage + 1.18. db_delete usage + 1.19. db_avp_load usage + 1.20. db_avp_store usage + 1.21. db_avp_delete usage + 1.22. async db_query usage + 1.23. async db_query_one usage Chapter 1. Admin Guide 1.1. Overview DBops (DB-operations) modules implements a set of script - functions which allow DB manipulation - (loading/storing/removing) of user AVPs (preferences). + functions for generic SQL standard queries (raw or structure + queries). It also provides a dedicated set of functions for DB + manipulation (loading/storing/removing) of user AVPs + (preferences). 1.2. Dependencies @@ -128,110 +149,398 @@ modparam("dbops","db_url","1 postgres://user:passwd@host2/opensips") modparam("dbops","usr_table","avptable") ... -1.3.3. use_domain (integer) +1.3.3. db_scheme (string) + + Definition of a DB scheme to be used for accessing a + non-standard User Preference -like table. + + Definition of a DB scheme. Scheme syntax is: + * db_scheme = name':'element[';'element]* + * element = + + 'uuid_col='string + + 'username_col='string + + 'domain_col='string + + 'value_col='string + + 'value_type='('integer'|'string') + + 'table='string + + Default value is “NULL”. + + Example 1.3. Set db_scheme parameter +... +modparam("dbops","db_scheme", +"scheme1:table=subscriber;uuid_col=uuid;value_col=first_name") +... + +1.3.4. use_domain (integer) If the domain part of the a SIP URI should be used for identifying an AVP in DB operations. Default value is 0 (no). - Example 1.3. Set use_domain parameter + Example 1.4. Set use_domain parameter ... modparam("dbops","use_domain",1) ... -1.3.4. uuid_column (string) +1.3.5. ps_id_max_buf_len (integer) + + The maximum size of the buffer used to build the query IDs + which are used for managing the Prepare Statements when comes + to the "db_select|update|insert|replace|delete()" functions + + If the size is exceeded (when trying to build the PS query ID), + the PS support will be dropped for the query. If set to 0, the + PS support will be completly disabled. + + Default value is 1024. + + Example 1.5. Set ps_id_max_buf_len parameter +... +modparam("dbops","ps_id_max_buf_len", 2048) +... + +1.3.6. uuid_column (string) Name of column containing the uuid (unique user id). Default value is “uuid”. - Example 1.4. Set uuid_column parameter + Example 1.6. Set uuid_column parameter ... modparam("dbops","uuid_column","uuid") ... -1.3.5. username_column (string) +1.3.7. username_column (string) Name of column containing the username. Default value is “username”. - Example 1.5. Set username_column parameter + Example 1.7. Set username_column parameter ... modparam("dbops","username_column","username") ... -1.3.6. domain_column (string) +1.3.8. domain_column (string) Name of column containing the domain name. Default value is “domain”. - Example 1.6. Set domain_column parameter + Example 1.8. Set domain_column parameter ... modparam("dbops","domain_column","domain") ... -1.3.7. attribute_column (string) +1.3.9. attribute_column (string) Name of column containing the attribute name (AVP name). Default value is “attribute”. - Example 1.7. Set attribute_column parameter + Example 1.9. Set attribute_column parameter ... modparam("dbops","attribute_column","attribute") ... -1.3.8. value_column (string) +1.3.10. value_column (string) Name of column containing the AVP value. Default value is “value”. - Example 1.8. Set value_column parameter + Example 1.10. Set value_column parameter ... modparam("dbops","value_column","value") ... -1.3.9. type_column (string) +1.3.11. type_column (string) Name of column containing the AVP type. Default value is “type”. - Example 1.9. Set type_column parameter + Example 1.11. Set type_column parameter ... modparam("dbops","type_column","type") ... -1.3.10. db_scheme (string) +1.4. Exported Functions - Definition of a DB scheme to be used for accessing a - non-standard User Preference -like table. +1.4.1. db_query(query, [res_col_avps], [db_id]) - Definition of a DB scheme. Scheme syntax is: - * db_scheme = name':'element[';'element]* - * element = - + 'uuid_col='string - + 'username_col='string - + 'domain_col='string - + 'value_col='string - + 'value_type='('integer'|'string') - + 'table='string + Make a database query and store the result in AVPs. - Default value is “NULL”. + The meaning and usage of the parameters: + * query (string) - must be a valid SQL query. The parameter + can contain pseudo-variables. + You must escape any pseudo-variables manually to prevent + SQL injection attacks. You can use the existing + transformations escape.common and unescape.common to escape + and unescape the content of any pseudo-variable. Failing to + escape the variables used in the query makes you vulnerable + to SQL injection, e.g. make it possible for an outside + attacker to alter your database content. The function + returns true if the query was successful, -2 in case the + query returned an empty result set, and -1 for all other + types of errors. + * res_col_avps (string, optional, no expand) - a list with + AVP names where to store the result. The format is + “$avp(name1);$avp(name2);...”. If this parameter is + omitted, the result is stored in “$avp(1);$avp(2);...”. If + the result consists of multiple rows, then multiple AVPs + with corresponding names will be added. The value type of + the AVP (string or integer) will be derived from the type + of the columns. If the value in the database is NULL, the + returned avp will be a string with the value. + * db_id (int, optional) - reference to a defined DB URL (a + numerical id) - see the “db_url” module parameter. It can + be either a constant, or a string/int variable. + + This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, + BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - Example 1.10. Set db_scheme parameter + Example 1.12. db_query usage ... -modparam("dbops","db_scheme", -"scheme1:table=subscriber;uuid_col=uuid;value_col=first_name") +db_query("SELECT password, ha1 FROM subscriber WHERE username='$tu'", + "$avp(pass);$avp(hash)"); +db_query("DELETE FROM subscriber"); +db_query("DELETE FROM subscriber", , 2); + +$avp(id) = 2; +db_query("DELETE FROM subscriber", , $avp(id)); ... -1.4. Exported Functions +1.4.2. db_query_one(query, [res_col_vars], [db_id]) + + Similar to db_query(), it makes a generic raw database query + and returns the results, but with the following differences: + * returns only one row - even if the query results in a multi + row result, only the first row will be returned to script. + * return variables are not limited to AVPs - the variables + for returning the query result may any kind of variable, of + course, as time as it is writeable. NOTE that the number of + return vairable MUST match (as number) the number of + returned columns. If less variables are provided, the query + will fail. + * NULL is returned - any a DB NULL value resulting from the + query will be pushed as NULL indicator (and NOT as + string) to the script variables. + + This function can be used from any type of route. + + Example 1.13. db_query_one usage +... +db_query_one("SELECT password, ha1 FROM subscriber WHERE username='$tU'" +, + "$var(pass);$var(hash)"); +# $var(pass) or $var(hash) may be NULL if the corresponding columns +# are not populated +... +db_query_one("SELECT value, type FROM usr_preferences WHERE username='$f +U' and attribute='cfna'", + "$var(cf_uri);$var(type)"); +# the above query will return only one row, even if there are multiple ` +cfna` +# attributes for the user +... + +1.4.3. db_select([columns],table,[filter],[order],[res_col_avps], +[db_id]) + + Function to perform a structured (not raw) SQL SELECT + operation. The query is performed via OpenSIPS internal SQL + interface, taking advantages of the prepared-statements support + (if the db backend provides something like that). The selected + columns are returned into a set of AVPs (one to one matching + the selected columns). + +Warning + + If using varibales in constructing the query, you must manually + escape their values in order to prevent SQL injection attacks. + You can use the existing transformations escape.common and + unescape.common to escape and unescape the content of any + pseudo-variable. Failing to escape the variables used in the + query makes you vulnerable to SQL injection, e.g. make it + possible for an outside attacker to alter your database + content. + + The function returns true if the query was successful, -2 in + case the query returned an empty result set, and -1 for all + other types of errors. + + The meaning and usage of the parameters: + * columns (string,optional) - JSON formated string holding an + array of columns to be returned by the select. Ex: + “["col1","col2"]”. If missing, a “*” (all columns) select + will be performed. + * table (string, mandatory) - the name of the table to be + queried. + * filter (string, optional) - JSON formated string holding + the "where" filter of the query. This must be an array of + (column, operator,value) pairs. The exact JSON syntax of + such a pair is “{"column":{"operator":"value"}}”.; + operators may be `>`, `<`, `=`, `!=` or custom string; The + values may be string, integer or `null`. To simplify the + usage with the `=` operator, you can use + “{"column":"value"}” If missing, all rows will be selected. + * order (string, optional) - the name of the column to oder + by (only ascending). + * res_col_avps (string, optional, no expand) - a list with + AVP names where to store the result. The format is + “$avp(name1);$avp(name2);...”. If this parameter is + omitted, the result is stored in “$avp(1);$avp(2);...”. If + the result consists of multiple rows, then multiple AVPs + with corresponding names will be added. The value type of + the AVP (string or integer) will be derived from the type + of the columns. If the value in the database is NULL, the + returned avp will be a string with the value. + * db_id (int, optional) - reference to a defined DB URL (a + numerical id) - see the db_url module parameter. It can be + either a constant, or a string/int variable. + + This function can be used from any type of route. + + Example 1.14. db_select usage +... +db_select('["password","ha1"]', 'subscriber', + '[ {"username", "$tu"}, {"domain",{"!=",null}}]', , + '$avp(pass);$avp(hash)'); +... + +1.4.4. +db_select_one([columns],table,[filter],[order],[res_col_vars], +[db_id]) + + Similar to db_select(), it makes a SELECT SQL query and returns + the results, but with the following differences: + * returns only one row - even if the query results in a multi + row result, only the first row will be returned to script. + * return variables are not limited to AVPs - the variables + for returning the query result may any kind of variable, of + course, as time as it is writeable. NOTE that the number of + return vairable MUST match (as number) the number of + returned columns. If less variables are provided, the query + will fail. + * NULL is returned - any a DB NULL value resulting from the + query will be pushed as NULL indicator (and NOT as + string) to the script variables. + + This function can be used from any type of route. + + Example 1.15. db_select_one usage +... +db_select_one('["value","type"]', 'usr_preferences', + '[ {"username", "$tu"}, {"attribute","cfna"}]', , + '$var(cf_uri);$var(type)'); +# the above query will return only one row, even if there are multiple ` +cfna` +# attributes for the user +... + +1.4.5. db_update(columns,table,[filter],[db_id]) + + Function to perform a structured (not raw) SQL UPDATE + operation. IMPORTANT: please see all the general notes from the + db_select() function. + + The function returns true if the query was successful. + + The meaning and usage of the parameters: + * columns (string,mandatory) - JSON formated string holding + an array of (column,value) pairs to be updated by the + query. Ex: “[{"col1":"val1"},{"col2":"val1"}]”. + * table (string, mandatory) - the name of the table to be + queried. + * filter (string, optional) - JSON formated string holding + the "where" filter of the query. This must be an array of + (column, operator,value) pairs. The exact JSON syntax of + such a pair is “{"column":{"operator":"value"}}”.; + operators may be `>`, `<`, `=`, `!=` or custom string; The + values may be string, integer or `null`. To simplify the + usage with the `=` operator, you can use + “{"column":"value"}” If missing, all rows will be updated. + * db_id (int, optional) - reference to a defined DB URL (a + numerical id) - see the db_url module parameter. It can be + either a constant, or a string/int variable. + + This function can be used from any type of route. + + Example 1.16. db_update usage +... +db_update( '[{"password":"my_secret"}]', 'subscriber', + '[{"username", "$tu"}]'); +... + +1.4.6. db_insert(table,columns,[db_id]) + + Function to perform a structured (not raw) SQL INSERT + operation. IMPORTANT: please see all the general notes from the + db_select() function. + + The function returns true if the query was successful. + + The meaning and usage of the parameters: + * table (string, mandatory) - the name of the table to be + queried. + * columns (string,mandatory) - JSON formated string holding + an array of (column,value) pairs to be inserted. Ex: + “[{"col1":"val1"},{"col2":"val1"}]”. + * db_id (int, optional) - reference to a defined DB URL (a + numerical id) - see the db_url module parameter. It can be + either a constant, or a string/int variable. + + This function can be used from any type of route. -1.4.1. db_avp_load(source, name, [db_id], [prefix]]) + Example 1.17. db_insert usage +... +db_insert( 'cc_agents', '[{"agentid":"agentX"},{"skills":"info"},{"locat +ion":null},{"msrp_location":"sip:agentX@opensips.com"},{"msrp_max_sessio +ns":2}]' ); +... + +1.4.7. db_delete(table,[filter],[db_id]) + + Function to perform a structured (not raw) SQL DELETE + operation. IMPORTANT: please see all the general notes from the + db_select() function. + + The function returns true if the query was successful. + + The meaning and usage of the parameters: + * table (string, mandatory) - the name of the table to delete + from. + * filter (string, optional) - JSON formated string holding + the "where" filter of the query. This must be an array of + (column, operator,value) pairs. The exact JSON syntax of + such a pair is “{"column":{"operator":"value"}}”.; + operators may be `>`, `<`, `=`, `!=` or custom string; The + values may be string, integer or `null`. To simplify the + usage with the `=` operator, you can use + “{"column":"value"}” If missing, all rows will be updated. + * db_id (int, optional) - reference to a defined DB URL (a + numerical id) - see the db_url module parameter. It can be + either a constant, or a string/int variable. + + This function can be used from any type of route. + + Example 1.18. db_delete usage +... +db_delete( 'subscriber', '[{"username", "$tu"}]'); +... + +1.4.8. db_replace(table,columns,[db_id]) + + Function very similar to db_insert() function, but performing + an SQL REPLACE operation instead. Note that not all SQL backend + in OpenSIPS may support a REPLACE operation. + + The function returns true if the query was successful. + +1.4.9. db_avp_load(source, name, [db_id], [prefix]]) Loads from DB into memory the AVPs corresponding to the given source. If given, it sets the script flags for loaded AVPs. It @@ -253,13 +562,6 @@ modparam("dbops","db_scheme", * name (string, no expand) - which AVPs will be loaded from DB into memory. Parameter syntax is: + name = avp_spec['/'(table_name|'$'db_scheme)] - + avp_spec = - matching_flags|$avp(avp_name)|$avp(avp_alias) - + matching_flags = 'a' | 'A' | 'i' | 'I' | 's' | 'S' - [script_flags] - 'a' or 'A' means matching any of AVP name types ('i' - and 's'), the rest have the meaning descriped in 'AVP - naming format' chapter. * db_id (int, optional) - reference to a defined DB URL (a numerical id) - see the “db_url” module parameter. * prefix (string, optional) - static string which will @@ -268,7 +570,7 @@ modparam("dbops","db_scheme", This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - Example 1.11. db_avp_load usage + Example 1.19. db_avp_load usage ... db_avp_load("$fu", "$avp(678)"); db_avp_load("$ru/domain", "i/domain_preferences"); @@ -284,7 +586,7 @@ xlog("Loaded: $avp(caller_100)\n"); ... -1.4.2. db_avp_store(source, name, [db_id]) +1.4.10. db_avp_store(source, name, [db_id]) Stores to DB the AVPs corresponding to the given source. @@ -295,7 +597,7 @@ xlog("Loaded: $avp(caller_100)\n"); This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - Example 1.12. db_avp_store usage + Example 1.20. db_avp_store usage ... db_avp_store("$tu", "$avp(678)"); db_avp_store("$ru/username", "$avp(email)"); @@ -303,7 +605,7 @@ db_avp_store("$ru/username", "$avp(email)"); db_avp_store("$ru", "$avp(1)", 3); ... -1.4.3. db_avp_delete(source, name, [db_id]) +1.4.11. db_avp_delete(source, name, [db_id]) Deletes from DB the AVPs corresponding to the given source. @@ -314,7 +616,7 @@ db_avp_store("$ru", "$avp(1)", 3); This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - Example 1.13. db_avp_delete usage + Example 1.21. db_avp_delete usage ... db_avp_delete("$tu", "$avp(678)"); db_avp_delete("$ru/username", "$avp(email)"); @@ -323,84 +625,6 @@ db_avp_delete("$avp(uuid)", "$avp(404fwd)/fwd_table"); db_avp_delete("$ru", "$avp(1)", 3); ... -1.4.4. db_query(query, [res_col_avps], [db_id]) - - Make a database query and store the result in AVPs. - - The meaning and usage of the parameters: - * query (string) - must be a valid SQL query. The parameter - can contain pseudo-variables. - You must escape any pseudo-variables manually to prevent - SQL injection attacks. You can use the existing - transformations escape.common and unescape.common to escape - and unescape the content of any pseudo-variable. Failing to - escape the variables used in the query makes you vulnerable - to SQL injection, e.g. make it possible for an outside - attacker to alter your database content. The function - returns true if the query was successful, -2 in case the - query returned an empty result set, and -1 for all other - types of errors - * res_col_avps (string, optional, no expand) - a list with - AVP names where to store the result. The format is - “$avp(name1);$avp(name2);...”. If this parameter is - omitted, the result is stored in “$avp(1);$avp(2);...”. If - the result consists of multiple rows, then multiple AVPs - with corresponding names will be added. The value type of - the AVP (string or integer) will be derived from the type - of the columns. If the value in the database is NULL, the - returned avp will be a string with the value. - * db_id (int, optional) - reference to a defined DB URL (a - numerical id) - see the “db_url” module parameter. It can - be either a constant, or a string/int variable. - - This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, - BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - - Example 1.14. db_query usage -... -db_query("SELECT password, ha1 FROM subscriber WHERE username='$tu'", - "$avp(pass);$avp(hash)"); -db_query("DELETE FROM subscriber"); -db_query("DELETE FROM subscriber", , 2); - -$avp(id) = 2; -db_query("DELETE FROM subscriber", , $avp(id)); -... - -1.4.5. db_query_one(query, [res_col_vars], [db_id]) - - Similar to db_query(), it makes a generic raw database query - and returns the results, but with the following differences: - * returns only one row - even if the query results in a multi - row result, only the first row will be returned to script. - * return variables are not limited to AVPs - the variables - for returning the query result may any kind of variable, of - course, as time as it is writeable. NOTE that the number of - return vairable MUST match (as number) the number of - returned columns. If less variables are provided, the query - will fail. - * NULL is returned - any a DB NULL value resulting from the - query will be pushed as NULL indicator (and NOT as - string) to the script variables. - - This function can be used from any type of route. - - Example 1.15. db_query_one usage -... -db_query_one("SELECT password, ha1 FROM subscriber WHERE username='$tU'" -, - "$var(pass);$var(hash)"); -# $var(pass) or $var(hash) may be NULL if the corresponding columns -# are not populated -... -db_query_one("SELECT value, type FROM usr_preferences WHERE username='$f -U' and attribute='cfna'", - "$var(cf_uri);$var(type)"); -# the above query will return only one row, even if there are multiple ` -cfna` -# attributes for the user -... - 1.5. Exported Asynchronous Functions 1.5.1. db_query(query, [dest], [db_id]) @@ -414,7 +638,7 @@ cfna` This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - Example 1.16. async db_query usage + Example 1.22. async db_query usage ... { ... @@ -448,7 +672,7 @@ route [my_resume_route] This function can be used from any route. - Example 1.17. async db_query_one usage + Example 1.23. async db_query_one usage ... { ... @@ -512,6 +736,6 @@ Chapter 3. Documentation Documentation Copyrights: - Copyright © 2009-2012 www.opensips-solutions.com + Copyright © 2009-2024 www.opensips-solutions.com Copyright © 2004-2008 Voice Sistem SRL diff --git a/modules/dbops/dbops_db.c b/modules/dbops/dbops_db.c index 8fc0d9307ae..e3057aed246 100644 --- a/modules/dbops/dbops_db.c +++ b/modules/dbops/dbops_db.c @@ -541,12 +541,12 @@ static inline int _json_to_filters(cJSON *Jfilter, /* iterate the filters to check and validate */ for( filter=Jfilter->child,nk=0 ; filter ; filter=filter->next,nk++ ) { if (filter->type!=cJSON_Object) { - LM_ERR("bad JSON format, 'cols' elements must be strings\n"); + LM_ERR("bad JSON format, 'cols' elements must be objects\n"); goto error; } if (filter->child->string==NULL) { - LM_ERR("invalid filter node type %d , name %s\n", - filter->child->type, filter->child->string); + LM_ERR("invalid filter node %d type %d , without name\n", + nk, filter->child->type); goto error; } } From 3a7cc6a12677eecffc19c651affe0c4851ef2a01 Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Wed, 13 Mar 2024 10:06:24 +0200 Subject: [PATCH 14/79] [dbops] renamed to sqlops --- modules/{dbops => sqlops}/Makefile | 0 modules/{dbops => sqlops}/README | 0 modules/{dbops => sqlops}/doc/contributors.xml | 0 modules/{dbops/doc/dbops.xml => sqlops/doc/sqlops.xml} | 0 .../{dbops/doc/dbops_admin.xml => sqlops/doc/sqlops_admin.xml} | 0 modules/{dbops/dbops.c => sqlops/sqlops.c} | 0 modules/{dbops/dbops_db.c => sqlops/sqlops_db.c} | 0 modules/{dbops/dbops_db.h => sqlops/sqlops_db.h} | 0 modules/{dbops/dbops_impl.c => sqlops/sqlops_impl.c} | 0 modules/{dbops/dbops_impl.h => sqlops/sqlops_impl.h} | 0 modules/{dbops/dbops_parse.c => sqlops/sqlops_parse.c} | 0 modules/{dbops/dbops_parse.h => sqlops/sqlops_parse.h} | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename modules/{dbops => sqlops}/Makefile (100%) rename modules/{dbops => sqlops}/README (100%) rename modules/{dbops => sqlops}/doc/contributors.xml (100%) rename modules/{dbops/doc/dbops.xml => sqlops/doc/sqlops.xml} (100%) rename modules/{dbops/doc/dbops_admin.xml => sqlops/doc/sqlops_admin.xml} (100%) rename modules/{dbops/dbops.c => sqlops/sqlops.c} (100%) rename modules/{dbops/dbops_db.c => sqlops/sqlops_db.c} (100%) rename modules/{dbops/dbops_db.h => sqlops/sqlops_db.h} (100%) rename modules/{dbops/dbops_impl.c => sqlops/sqlops_impl.c} (100%) rename modules/{dbops/dbops_impl.h => sqlops/sqlops_impl.h} (100%) rename modules/{dbops/dbops_parse.c => sqlops/sqlops_parse.c} (100%) rename modules/{dbops/dbops_parse.h => sqlops/sqlops_parse.h} (100%) diff --git a/modules/dbops/Makefile b/modules/sqlops/Makefile similarity index 100% rename from modules/dbops/Makefile rename to modules/sqlops/Makefile diff --git a/modules/dbops/README b/modules/sqlops/README similarity index 100% rename from modules/dbops/README rename to modules/sqlops/README diff --git a/modules/dbops/doc/contributors.xml b/modules/sqlops/doc/contributors.xml similarity index 100% rename from modules/dbops/doc/contributors.xml rename to modules/sqlops/doc/contributors.xml diff --git a/modules/dbops/doc/dbops.xml b/modules/sqlops/doc/sqlops.xml similarity index 100% rename from modules/dbops/doc/dbops.xml rename to modules/sqlops/doc/sqlops.xml diff --git a/modules/dbops/doc/dbops_admin.xml b/modules/sqlops/doc/sqlops_admin.xml similarity index 100% rename from modules/dbops/doc/dbops_admin.xml rename to modules/sqlops/doc/sqlops_admin.xml diff --git a/modules/dbops/dbops.c b/modules/sqlops/sqlops.c similarity index 100% rename from modules/dbops/dbops.c rename to modules/sqlops/sqlops.c diff --git a/modules/dbops/dbops_db.c b/modules/sqlops/sqlops_db.c similarity index 100% rename from modules/dbops/dbops_db.c rename to modules/sqlops/sqlops_db.c diff --git a/modules/dbops/dbops_db.h b/modules/sqlops/sqlops_db.h similarity index 100% rename from modules/dbops/dbops_db.h rename to modules/sqlops/sqlops_db.h diff --git a/modules/dbops/dbops_impl.c b/modules/sqlops/sqlops_impl.c similarity index 100% rename from modules/dbops/dbops_impl.c rename to modules/sqlops/sqlops_impl.c diff --git a/modules/dbops/dbops_impl.h b/modules/sqlops/sqlops_impl.h similarity index 100% rename from modules/dbops/dbops_impl.h rename to modules/sqlops/sqlops_impl.h diff --git a/modules/dbops/dbops_parse.c b/modules/sqlops/sqlops_parse.c similarity index 100% rename from modules/dbops/dbops_parse.c rename to modules/sqlops/sqlops_parse.c diff --git a/modules/dbops/dbops_parse.h b/modules/sqlops/sqlops_parse.h similarity index 100% rename from modules/dbops/dbops_parse.h rename to modules/sqlops/sqlops_parse.h From 5d01414970814b353a29c376312fb2e024dfd198 Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Wed, 13 Mar 2024 13:05:52 +0200 Subject: [PATCH 15/79] [sqlops] renaming from DBops to SQLops After all the module does only SQL ops, no noSQL ones --- modules/sqlops/Makefile | 4 +- modules/sqlops/README | 218 ++++++++++++++-------------- modules/sqlops/doc/sqlops.xml | 4 +- modules/sqlops/doc/sqlops_admin.xml | 176 +++++++++++----------- modules/sqlops/sqlops.c | 178 +++++++++++------------ modules/sqlops/sqlops_db.c | 30 ++-- modules/sqlops/sqlops_db.h | 22 +-- modules/sqlops/sqlops_impl.c | 148 +++++++++---------- modules/sqlops/sqlops_impl.h | 46 +++--- modules/sqlops/sqlops_parse.c | 18 +-- modules/sqlops/sqlops_parse.h | 8 +- 11 files changed, 427 insertions(+), 425 deletions(-) diff --git a/modules/sqlops/Makefile b/modules/sqlops/Makefile index 2d61b22130f..15a524a469a 100644 --- a/modules/sqlops/Makefile +++ b/modules/sqlops/Makefile @@ -1,9 +1,9 @@ -# dbops module makefile +# sqlops module makefile # # WARNING: do not run this directly, it should be run by the master Makefile include ../../Makefile.defs auto_gen= -NAME=dbops.so +NAME=sqlops.so include ../../Makefile.modules diff --git a/modules/sqlops/README b/modules/sqlops/README index 1653cb835a5..2c79be88d16 100644 --- a/modules/sqlops/README +++ b/modules/sqlops/README @@ -1,4 +1,4 @@ -DBops Module +SQLops Module __________________________________________________________ Table of Contents @@ -27,29 +27,31 @@ DBops Module 1.4. Exported Functions - 1.4.1. db_query(query, [res_col_avps], [db_id]) - 1.4.2. db_query_one(query, [res_col_vars], [db_id]) + 1.4.1. sql_query(query, [res_col_avps], [db_id]) + 1.4.2. sql_query_one(query, [res_col_vars], [db_id]) + 1.4.3. - db_select([columns],table,[filter],[order],[re - s_col_avps], [db_id]) + sql_select([columns],table,[filter],[order],[r + es_col_avps], [db_id]) 1.4.4. - db_select_one([columns],table,[filter],[order] - ,[res_col_vars], [db_id]) + sql_select_one([columns],table,[filter],[order + ],[res_col_vars], [db_id]) - 1.4.5. db_update(columns,table,[filter],[db_id]) - 1.4.6. db_insert(table,columns,[db_id]) - 1.4.7. db_delete(table,[filter],[db_id]) - 1.4.8. db_replace(table,columns,[db_id]) - 1.4.9. db_avp_load(source, name, [db_id], [prefix]]) + 1.4.5. sql_update(columns,table,[filter],[db_id]) + 1.4.6. sql_insert(table,columns,[db_id]) + 1.4.7. sql_delete(table,[filter],[db_id]) + 1.4.8. sql_replace(table,columns,[db_id]) + 1.4.9. sql_avp_load(source, name, [db_id], + [prefix]]) - 1.4.10. db_avp_store(source, name, [db_id]) - 1.4.11. db_avp_delete(source, name, [db_id]) + 1.4.10. sql_avp_store(source, name, [db_id]) + 1.4.11. sql_avp_delete(source, name, [db_id]) 1.5. Exported Asynchronous Functions - 1.5.1. db_query(query, [dest], [db_id]) - 1.5.2. db_query_one(query, [dest], [db_id]) + 1.5.1. sql_query(query, [dest], [db_id]) + 1.5.2. sql_query_one(query, [dest], [db_id]) 2. Contributors @@ -80,24 +82,24 @@ DBops Module 1.9. Set attribute_column parameter 1.10. Set value_column parameter 1.11. Set type_column parameter - 1.12. db_query usage - 1.13. db_query_one usage - 1.14. db_select usage - 1.15. db_select_one usage - 1.16. db_update usage - 1.17. db_insert usage - 1.18. db_delete usage - 1.19. db_avp_load usage - 1.20. db_avp_store usage - 1.21. db_avp_delete usage - 1.22. async db_query usage - 1.23. async db_query_one usage + 1.12. sql_query usage + 1.13. sql_query_one usage + 1.14. sql_select usage + 1.15. sql_select_one usage + 1.16. sql_update usage + 1.17. sql_insert usage + 1.18. sql_delete usage + 1.19. sql_avp_load usage + 1.20. sql_avp_store usage + 1.21. sql_avp_delete usage + 1.22. async sql_query usage + 1.23. async sql_query_one usage Chapter 1. Admin Guide 1.1. Overview - DBops (DB-operations) modules implements a set of script + SQLops (SQL-operations) modules implements a set of script functions for generic SQL standard queries (raw or structure queries). It also provides a dedicated set of functions for DB manipulation (loading/storing/removing) of user AVPs @@ -132,9 +134,9 @@ Chapter 1. Admin Guide Example 1.1. Set db_url parameter ... # default URL -modparam("dbops","db_url","mysql://user:passwd@host/database") +modparam("sqlops","db_url","mysql://user:passwd@host/database") # an additional DB URL -modparam("dbops","db_url","1 postgres://user:passwd@host2/opensips") +modparam("sqlops","db_url","1 postgres://user:passwd@host2/opensips") ... 1.3.2. usr_table (string) @@ -146,7 +148,7 @@ modparam("dbops","db_url","1 postgres://user:passwd@host2/opensips") Example 1.2. Set usr_table parameter ... -modparam("dbops","usr_table","avptable") +modparam("sqlops","usr_table","avptable") ... 1.3.3. db_scheme (string) @@ -168,7 +170,7 @@ modparam("dbops","usr_table","avptable") Example 1.3. Set db_scheme parameter ... -modparam("dbops","db_scheme", +modparam("sqlops","db_scheme", "scheme1:table=subscriber;uuid_col=uuid;value_col=first_name") ... @@ -181,14 +183,14 @@ modparam("dbops","db_scheme", Example 1.4. Set use_domain parameter ... -modparam("dbops","use_domain",1) +modparam("sqlops","use_domain",1) ... 1.3.5. ps_id_max_buf_len (integer) The maximum size of the buffer used to build the query IDs which are used for managing the Prepare Statements when comes - to the "db_select|update|insert|replace|delete()" functions + to the "sql_select|update|insert|replace|delete()" functions If the size is exceeded (when trying to build the PS query ID), the PS support will be dropped for the query. If set to 0, the @@ -198,7 +200,7 @@ modparam("dbops","use_domain",1) Example 1.5. Set ps_id_max_buf_len parameter ... -modparam("dbops","ps_id_max_buf_len", 2048) +modparam("sqlops","ps_id_max_buf_len", 2048) ... 1.3.6. uuid_column (string) @@ -209,7 +211,7 @@ modparam("dbops","ps_id_max_buf_len", 2048) Example 1.6. Set uuid_column parameter ... -modparam("dbops","uuid_column","uuid") +modparam("sqlops","uuid_column","uuid") ... 1.3.7. username_column (string) @@ -220,7 +222,7 @@ modparam("dbops","uuid_column","uuid") Example 1.7. Set username_column parameter ... -modparam("dbops","username_column","username") +modparam("sqlops","username_column","username") ... 1.3.8. domain_column (string) @@ -231,7 +233,7 @@ modparam("dbops","username_column","username") Example 1.8. Set domain_column parameter ... -modparam("dbops","domain_column","domain") +modparam("sqlops","domain_column","domain") ... 1.3.9. attribute_column (string) @@ -242,7 +244,7 @@ modparam("dbops","domain_column","domain") Example 1.9. Set attribute_column parameter ... -modparam("dbops","attribute_column","attribute") +modparam("sqlops","attribute_column","attribute") ... 1.3.10. value_column (string) @@ -253,7 +255,7 @@ modparam("dbops","attribute_column","attribute") Example 1.10. Set value_column parameter ... -modparam("dbops","value_column","value") +modparam("sqlops","value_column","value") ... 1.3.11. type_column (string) @@ -264,12 +266,12 @@ modparam("dbops","value_column","value") Example 1.11. Set type_column parameter ... -modparam("dbops","type_column","type") +modparam("sqlops","type_column","type") ... 1.4. Exported Functions -1.4.1. db_query(query, [res_col_avps], [db_id]) +1.4.1. sql_query(query, [res_col_avps], [db_id]) Make a database query and store the result in AVPs. @@ -302,20 +304,20 @@ modparam("dbops","type_column","type") This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - Example 1.12. db_query usage + Example 1.12. sql_query usage ... -db_query("SELECT password, ha1 FROM subscriber WHERE username='$tu'", +sql_query("SELECT password, ha1 FROM subscriber WHERE username='$tu'", "$avp(pass);$avp(hash)"); -db_query("DELETE FROM subscriber"); -db_query("DELETE FROM subscriber", , 2); +sql_query("DELETE FROM subscriber"); +sql_query("DELETE FROM subscriber", , 2); $avp(id) = 2; -db_query("DELETE FROM subscriber", , $avp(id)); +sql_query("DELETE FROM subscriber", , $avp(id)); ... -1.4.2. db_query_one(query, [res_col_vars], [db_id]) +1.4.2. sql_query_one(query, [res_col_vars], [db_id]) - Similar to db_query(), it makes a generic raw database query + Similar to sql_query(), it makes a generic raw database query and returns the results, but with the following differences: * returns only one row - even if the query results in a multi row result, only the first row will be returned to script. @@ -331,23 +333,23 @@ db_query("DELETE FROM subscriber", , $avp(id)); This function can be used from any type of route. - Example 1.13. db_query_one usage + Example 1.13. sql_query_one usage ... -db_query_one("SELECT password, ha1 FROM subscriber WHERE username='$tU'" -, +sql_query_one("SELECT password, ha1 FROM subscriber WHERE username='$tU' +", "$var(pass);$var(hash)"); # $var(pass) or $var(hash) may be NULL if the corresponding columns # are not populated ... -db_query_one("SELECT value, type FROM usr_preferences WHERE username='$f -U' and attribute='cfna'", +sql_query_one("SELECT value, type FROM usr_preferences WHERE username='$ +fU' and attribute='cfna'", "$var(cf_uri);$var(type)"); # the above query will return only one row, even if there are multiple ` cfna` # attributes for the user ... -1.4.3. db_select([columns],table,[filter],[order],[res_col_avps], +1.4.3. sql_select([columns],table,[filter],[order],[res_col_avps], [db_id]) Function to perform a structured (not raw) SQL SELECT @@ -404,19 +406,19 @@ Warning This function can be used from any type of route. - Example 1.14. db_select usage + Example 1.14. sql_select usage ... -db_select('["password","ha1"]', 'subscriber', +sql_select('["password","ha1"]', 'subscriber', '[ {"username", "$tu"}, {"domain",{"!=",null}}]', , '$avp(pass);$avp(hash)'); ... 1.4.4. -db_select_one([columns],table,[filter],[order],[res_col_vars], +sql_select_one([columns],table,[filter],[order],[res_col_vars], [db_id]) - Similar to db_select(), it makes a SELECT SQL query and returns - the results, but with the following differences: + Similar to sql_select(), it makes a SELECT SQL query and + returns the results, but with the following differences: * returns only one row - even if the query results in a multi row result, only the first row will be returned to script. * return variables are not limited to AVPs - the variables @@ -431,9 +433,9 @@ db_select_one([columns],table,[filter],[order],[res_col_vars], This function can be used from any type of route. - Example 1.15. db_select_one usage + Example 1.15. sql_select_one usage ... -db_select_one('["value","type"]', 'usr_preferences', +sql_select_one('["value","type"]', 'usr_preferences', '[ {"username", "$tu"}, {"attribute","cfna"}]', , '$var(cf_uri);$var(type)'); # the above query will return only one row, even if there are multiple ` @@ -441,11 +443,11 @@ cfna` # attributes for the user ... -1.4.5. db_update(columns,table,[filter],[db_id]) +1.4.5. sql_update(columns,table,[filter],[db_id]) Function to perform a structured (not raw) SQL UPDATE operation. IMPORTANT: please see all the general notes from the - db_select() function. + sql_select() function. The function returns true if the query was successful. @@ -469,17 +471,17 @@ cfna` This function can be used from any type of route. - Example 1.16. db_update usage + Example 1.16. sql_update usage ... -db_update( '[{"password":"my_secret"}]', 'subscriber', +sql_update( '[{"password":"my_secret"}]', 'subscriber', '[{"username", "$tu"}]'); ... -1.4.6. db_insert(table,columns,[db_id]) +1.4.6. sql_insert(table,columns,[db_id]) Function to perform a structured (not raw) SQL INSERT operation. IMPORTANT: please see all the general notes from the - db_select() function. + sql_select() function. The function returns true if the query was successful. @@ -495,18 +497,18 @@ db_update( '[{"password":"my_secret"}]', 'subscriber', This function can be used from any type of route. - Example 1.17. db_insert usage + Example 1.17. sql_insert usage ... -db_insert( 'cc_agents', '[{"agentid":"agentX"},{"skills":"info"},{"locat -ion":null},{"msrp_location":"sip:agentX@opensips.com"},{"msrp_max_sessio -ns":2}]' ); +sql_insert( 'cc_agents', '[{"agentid":"agentX"},{"skills":"info"},{"loca +tion":null},{"msrp_location":"sip:agentX@opensips.com"},{"msrp_max_sessi +ons":2}]' ); ... -1.4.7. db_delete(table,[filter],[db_id]) +1.4.7. sql_delete(table,[filter],[db_id]) Function to perform a structured (not raw) SQL DELETE operation. IMPORTANT: please see all the general notes from the - db_select() function. + sql_select() function. The function returns true if the query was successful. @@ -527,20 +529,20 @@ ns":2}]' ); This function can be used from any type of route. - Example 1.18. db_delete usage + Example 1.18. sql_delete usage ... -db_delete( 'subscriber', '[{"username", "$tu"}]'); +sql_delete( 'subscriber', '[{"username", "$tu"}]'); ... -1.4.8. db_replace(table,columns,[db_id]) +1.4.8. sql_replace(table,columns,[db_id]) - Function very similar to db_insert() function, but performing + Function very similar to sql_insert() function, but performing an SQL REPLACE operation instead. Note that not all SQL backend in OpenSIPS may support a REPLACE operation. The function returns true if the query was successful. -1.4.9. db_avp_load(source, name, [db_id], [prefix]]) +1.4.9. sql_avp_load(source, name, [db_id], [prefix]]) Loads from DB into memory the AVPs corresponding to the given source. If given, it sets the script flags for loaded AVPs. It @@ -570,67 +572,67 @@ db_delete( 'subscriber', '[{"username", "$tu"}]'); This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - Example 1.19. db_avp_load usage + Example 1.19. sql_avp_load usage ... -db_avp_load("$fu", "$avp(678)"); -db_avp_load("$ru/domain", "i/domain_preferences"); -db_avp_load("$avp(uuid)", "$avp(404fwd)/fwd_table"); -db_avp_load("$ru", "$avp(123)/$some_scheme"); +sql_avp_load("$fu", "$avp(678)"); +sql_avp_load("$ru/domain", "i/domain_preferences"); +sql_avp_load("$avp(uuid)", "$avp(404fwd)/fwd_table"); +sql_avp_load("$ru", "$avp(123)/$some_scheme"); # use DB URL id 3 -db_avp_load("$ru", "$avp(1)", 3); +sql_avp_load("$ru", "$avp(1)", 3); # precede all loaded AVPs by the "caller_" prefix -db_avp_load("$ru", "$avp(100)", , "caller_"); +sql_avp_load("$ru", "$avp(100)", , "caller_"); xlog("Loaded: $avp(caller_100)\n"); ... -1.4.10. db_avp_store(source, name, [db_id]) +1.4.10. sql_avp_store(source, name, [db_id]) Stores to DB the AVPs corresponding to the given source. The meaning and usage of the parameters are identical as for - db_avp_load(source, name) function. Please refer to its + sql_avp_load(source, name) function. Please refer to its description. This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - Example 1.20. db_avp_store usage + Example 1.20. sql_avp_store usage ... -db_avp_store("$tu", "$avp(678)"); -db_avp_store("$ru/username", "$avp(email)"); +sql_avp_store("$tu", "$avp(678)"); +sql_avp_store("$ru/username", "$avp(email)"); # use DB URL id 3 -db_avp_store("$ru", "$avp(1)", 3); +sql_avp_store("$ru", "$avp(1)", 3); ... -1.4.11. db_avp_delete(source, name, [db_id]) +1.4.11. sql_avp_delete(source, name, [db_id]) Deletes from DB the AVPs corresponding to the given source. The meaning and usage of the parameters are identical as for - db_avp_load(source, name) function. Please refer to its + sql_avp_load(source, name) function. Please refer to its description. This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - Example 1.21. db_avp_delete usage + Example 1.21. sql_avp_delete usage ... -db_avp_delete("$tu", "$avp(678)"); -db_avp_delete("$ru/username", "$avp(email)"); -db_avp_delete("$avp(uuid)", "$avp(404fwd)/fwd_table"); +sql_avp_delete("$tu", "$avp(678)"); +sql_avp_delete("$ru/username", "$avp(email)"); +sql_avp_delete("$avp(uuid)", "$avp(404fwd)/fwd_table"); # use DB URL id 3 -db_avp_delete("$ru", "$avp(1)", 3); +sql_avp_delete("$ru", "$avp(1)", 3); ... 1.5. Exported Asynchronous Functions -1.5.1. db_query(query, [dest], [db_id]) +1.5.1. sql_query(query, [dest], [db_id]) This function takes the same parameters and behaves identically - to db_query(), but asynchronously (after launching the query, + to sql_query(), but asynchronously (after launching the query, the current SIP worker pauses the execution of the current SIP message until the result is available and attempts to process more SIP traffic). @@ -638,13 +640,13 @@ db_avp_delete("$ru", "$avp(1)", 3); This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - Example 1.22. async db_query usage + Example 1.22. async sql_query usage ... { ... /* Example of a slow MySQL query - it should take around 5 seconds */ async( - db_query( + sql_query( "SELECT table_name, table_version, SLEEP(0.1) from versi on", "$avp(tb_name); $avp(tb_ver); $avp(retcode)"), @@ -662,23 +664,23 @@ route [my_resume_route] } ... -1.5.2. db_query_one(query, [dest], [db_id]) +1.5.2. sql_query_one(query, [dest], [db_id]) This function takes the same parameters and behaves identically - to db_query_one(), but asynchronously (after launching the + to sql_query_one(), but asynchronously (after launching the query, the current SIP worker pauses the execution of the current SIP message until the result is available and attempts to process more SIP traffic). This function can be used from any route. - Example 1.23. async db_query_one usage + Example 1.23. async sql_query_one usage ... { ... /* Example of a slow MySQL query - it should take around 5 seconds */ async( - db_query_one( + sql_query_one( "SELECT table_name, table_version, SLEEP(0.1) from versi on", "$var(tb_name); $var(tb_ver); $var(retcode)"), diff --git a/modules/sqlops/doc/sqlops.xml b/modules/sqlops/doc/sqlops.xml index c2614fb736a..cbb06fc442b 100644 --- a/modules/sqlops/doc/sqlops.xml +++ b/modules/sqlops/doc/sqlops.xml @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [ - + @@ -15,7 +15,7 @@ - DBops Module + SQLops Module &osipsname; diff --git a/modules/sqlops/doc/sqlops_admin.xml b/modules/sqlops/doc/sqlops_admin.xml index 99b7c4b9387..49031b5607b 100644 --- a/modules/sqlops/doc/sqlops_admin.xml +++ b/modules/sqlops/doc/sqlops_admin.xml @@ -8,7 +8,7 @@
Overview - DBops (DB-operations) modules implements a set of script + SQLops (SQL-operations) modules implements a set of script functions for generic SQL standard queries (raw or structure queries). It also provides a dedicated set of functions for DB manipulation (loading/storing/removing) of user AVPs (preferences). @@ -67,9 +67,9 @@ ... # default URL -modparam("dbops","db_url","mysql://user:passwd@host/database") +modparam("sqlops","db_url","mysql://user:passwd@host/database") # an additional DB URL -modparam("dbops","db_url","1 postgres://user:passwd@host2/opensips") +modparam("sqlops","db_url","1 postgres://user:passwd@host2/opensips") ... @@ -89,7 +89,7 @@ modparam("dbops","db_url","1 postgres://user:passwd@host2/opensips") Set <varname>usr_table</varname> parameter ... -modparam("dbops","usr_table","avptable") +modparam("sqlops","usr_table","avptable") ... @@ -134,7 +134,7 @@ modparam("dbops","usr_table","avptable") ... -modparam("dbops","db_scheme", +modparam("sqlops","db_scheme", "scheme1:table=subscriber;uuid_col=uuid;value_col=first_name") ... @@ -156,7 +156,7 @@ modparam("dbops","db_scheme", ... -modparam("dbops","use_domain",1) +modparam("sqlops","use_domain",1) ... @@ -167,7 +167,7 @@ modparam("dbops","use_domain",1) The maximum size of the buffer used to build the query IDs which are used for managing the Prepare Statements when comes to the - "db_select|update|insert|replace|delete()" functions + "sql_select|update|insert|replace|delete()" functions If the size is exceeded (when trying to build the PS query ID), @@ -183,7 +183,7 @@ modparam("dbops","use_domain",1) ... -modparam("dbops","ps_id_max_buf_len", 2048) +modparam("sqlops","ps_id_max_buf_len", 2048) ... @@ -202,7 +202,7 @@ modparam("dbops","ps_id_max_buf_len", 2048) Set <varname>uuid_column</varname> parameter ... -modparam("dbops","uuid_column","uuid") +modparam("sqlops","uuid_column","uuid") ... @@ -220,7 +220,7 @@ modparam("dbops","uuid_column","uuid") Set <varname>username_column</varname> parameter ... -modparam("dbops","username_column","username") +modparam("sqlops","username_column","username") ... @@ -238,7 +238,7 @@ modparam("dbops","username_column","username") Set <varname>domain_column</varname> parameter ... -modparam("dbops","domain_column","domain") +modparam("sqlops","domain_column","domain") ... @@ -257,7 +257,7 @@ modparam("dbops","domain_column","domain") ... -modparam("dbops","attribute_column","attribute") +modparam("sqlops","attribute_column","attribute") ... @@ -276,7 +276,7 @@ modparam("dbops","attribute_column","attribute") ... -modparam("dbops","value_column","value") +modparam("sqlops","value_column","value") ... @@ -295,7 +295,7 @@ modparam("dbops","value_column","value") ... -modparam("dbops","type_column","type") +modparam("sqlops","type_column","type") ... @@ -306,9 +306,9 @@ modparam("dbops","type_column","type")
Exported Functions -
+
- <function moreinfo="none">db_query(query, [res_col_avps], [db_id])</function> + <function moreinfo="none">sql_query(query, [res_col_avps], [db_id])</function> Make a database query and store the result in AVPs. @@ -359,27 +359,27 @@ modparam("dbops","type_column","type") BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - <function>db_query</function> usage + <function>sql_query</function> usage ... -db_query("SELECT password, ha1 FROM subscriber WHERE username='$tu'", +sql_query("SELECT password, ha1 FROM subscriber WHERE username='$tu'", "$avp(pass);$avp(hash)"); -db_query("DELETE FROM subscriber"); -db_query("DELETE FROM subscriber", , 2); +sql_query("DELETE FROM subscriber"); +sql_query("DELETE FROM subscriber", , 2); $avp(id) = 2; -db_query("DELETE FROM subscriber", , $avp(id)); +sql_query("DELETE FROM subscriber", , $avp(id)); ...
-
+
- <function moreinfo="none">db_query_one(query, [res_col_vars], [db_id])</function> + <function moreinfo="none">sql_query_one(query, [res_col_vars], [db_id])</function> - Similar to , it makes a generic raw + Similar to , it makes a generic raw database query and returns the results, but with the following differences: @@ -411,15 +411,15 @@ db_query("DELETE FROM subscriber", , $avp(id)); This function can be used from any type of route. - <function>db_query_one</function> usage + <function>sql_query_one</function> usage ... -db_query_one("SELECT password, ha1 FROM subscriber WHERE username='$tU'", +sql_query_one("SELECT password, ha1 FROM subscriber WHERE username='$tU'", "$var(pass);$var(hash)"); # $var(pass) or $var(hash) may be NULL if the corresponding columns # are not populated ... -db_query_one("SELECT value, type FROM usr_preferences WHERE username='$fU' and attribute='cfna'", +sql_query_one("SELECT value, type FROM usr_preferences WHERE username='$fU' and attribute='cfna'", "$var(cf_uri);$var(type)"); # the above query will return only one row, even if there are multiple `cfna` # attributes for the user @@ -428,9 +428,9 @@ db_query_one("SELECT value, type FROM usr_preferences WHERE username='$fU' and a
-
+
- <function moreinfo="none">db_select([columns],table,[filter],[order],[res_col_avps], [db_id]) + <function moreinfo="none">sql_select([columns],table,[filter],[order],[res_col_avps], [db_id]) </function> @@ -516,10 +516,10 @@ db_query_one("SELECT value, type FROM usr_preferences WHERE username='$fU' and a This function can be used from any type of route. - <function>db_select</function> usage + <function>sql_select</function> usage ... -db_select('["password","ha1"]', 'subscriber', +sql_select('["password","ha1"]', 'subscriber', '[ {"username", "$tu"}, {"domain",{"!=",null}}]', , '$avp(pass);$avp(hash)'); ... @@ -527,12 +527,12 @@ db_select('["password","ha1"]', 'subscriber',
-
+
- <function moreinfo="none">db_select_one([columns],table,[filter],[order],[res_col_vars], [db_id])</function> + <function moreinfo="none">sql_select_one([columns],table,[filter],[order],[res_col_vars], [db_id])</function> - Similar to , it makes a SELECT SQL + Similar to , it makes a SELECT SQL query and returns the results, but with the following differences: @@ -564,10 +564,10 @@ db_select('["password","ha1"]', 'subscriber', This function can be used from any type of route. - <function>db_select_one</function> usage + <function>sql_select_one</function> usage ... -db_select_one('["value","type"]', 'usr_preferences', +sql_select_one('["value","type"]', 'usr_preferences', '[ {"username", "$tu"}, {"attribute","cfna"}]', , '$var(cf_uri);$var(type)'); # the above query will return only one row, even if there are multiple `cfna` @@ -577,15 +577,15 @@ db_select_one('["value","type"]', 'usr_preferences',
-
+
- <function moreinfo="none">db_update(columns,table,[filter],[db_id]) + <function moreinfo="none">sql_update(columns,table,[filter],[db_id]) </function> Function to perform a structured (not raw) SQL UPDATE operation. IMPORTANT: please see all the general notes from the - function. + function. The function returns true if the query was successful. @@ -630,25 +630,25 @@ db_select_one('["value","type"]', 'usr_preferences', This function can be used from any type of route. - <function>db_update</function> usage + <function>sql_update</function> usage ... -db_update( '[{"password":"my_secret"}]', 'subscriber', +sql_update( '[{"password":"my_secret"}]', 'subscriber', '[{"username", "$tu"}]'); ...
-
+
- <function moreinfo="none">db_insert(table,columns,[db_id]) + <function moreinfo="none">sql_insert(table,columns,[db_id]) </function> Function to perform a structured (not raw) SQL INSERT operation. IMPORTANT: please see all the general notes from the - function. + function. The function returns true if the query was successful. @@ -681,24 +681,24 @@ db_update( '[{"password":"my_secret"}]', 'subscriber', This function can be used from any type of route. - <function>db_insert</function> usage + <function>sql_insert</function> usage ... -db_insert( 'cc_agents', '[{"agentid":"agentX"},{"skills":"info"},{"location":null},{"msrp_location":"sip:agentX@opensips.com"},{"msrp_max_sessions":2}]' ); +sql_insert( 'cc_agents', '[{"agentid":"agentX"},{"skills":"info"},{"location":null},{"msrp_location":"sip:agentX@opensips.com"},{"msrp_max_sessions":2}]' ); ...
-
+
- <function moreinfo="none">db_delete(table,[filter],[db_id]) + <function moreinfo="none">sql_delete(table,[filter],[db_id]) </function> Function to perform a structured (not raw) SQL DELETE operation. IMPORTANT: please see all the general notes from the - function. + function. The function returns true if the query was successful. @@ -736,22 +736,22 @@ db_insert( 'cc_agents', '[{"agentid":"agentX"},{"skills":"info"},{"location":nul This function can be used from any type of route. - <function>db_delete</function> usage + <function>sql_delete</function> usage ... -db_delete( 'subscriber', '[{"username", "$tu"}]'); +sql_delete( 'subscriber', '[{"username", "$tu"}]'); ...
-
+
- <function moreinfo="none">db_replace(table,columns,[db_id]) + <function moreinfo="none">sql_replace(table,columns,[db_id]) </function> - Function very similar to function, + Function very similar to function, but performing an SQL REPLACE operation instead. Note that not all SQL backend in OpenSIPS may support a REPLACE operation. @@ -760,9 +760,9 @@ db_delete( 'subscriber', '[{"username", "$tu"}]');
-
+
- <function moreinfo="none">db_avp_load(source, name, [db_id], [prefix]]) + <function moreinfo="none">sql_avp_load(source, name, [db_id], [prefix]]) </function> @@ -822,35 +822,35 @@ db_delete( 'subscriber', '[{"username", "$tu"}]'); - <function>db_avp_load</function> usage + <function>sql_avp_load</function> usage ... -db_avp_load("$fu", "$avp(678)"); -db_avp_load("$ru/domain", "i/domain_preferences"); -db_avp_load("$avp(uuid)", "$avp(404fwd)/fwd_table"); -db_avp_load("$ru", "$avp(123)/$some_scheme"); +sql_avp_load("$fu", "$avp(678)"); +sql_avp_load("$ru/domain", "i/domain_preferences"); +sql_avp_load("$avp(uuid)", "$avp(404fwd)/fwd_table"); +sql_avp_load("$ru", "$avp(123)/$some_scheme"); # use DB URL id 3 -db_avp_load("$ru", "$avp(1)", 3); +sql_avp_load("$ru", "$avp(1)", 3); # precede all loaded AVPs by the "caller_" prefix -db_avp_load("$ru", "$avp(100)", , "caller_"); +sql_avp_load("$ru", "$avp(100)", , "caller_"); xlog("Loaded: $avp(caller_100)\n"); ...
-
+
- <function moreinfo="none">db_avp_store(source, name, [db_id])</function> + <function moreinfo="none">sql_avp_store(source, name, [db_id])</function> Stores to DB the AVPs corresponding to the given source. The meaning and usage of the parameters are identical as for - db_avp_load(source, name) + sql_avp_load(source, name) function. Please refer to its description. @@ -859,27 +859,27 @@ xlog("Loaded: $avp(caller_100)\n"); - <function>db_avp_store</function> usage + <function>sql_avp_store</function> usage ... -db_avp_store("$tu", "$avp(678)"); -db_avp_store("$ru/username", "$avp(email)"); +sql_avp_store("$tu", "$avp(678)"); +sql_avp_store("$ru/username", "$avp(email)"); # use DB URL id 3 -db_avp_store("$ru", "$avp(1)", 3); +sql_avp_store("$ru", "$avp(1)", 3); ...
-
+
- <function moreinfo="none">db_avp_delete(source, name, [db_id])</function> + <function moreinfo="none">sql_avp_delete(source, name, [db_id])</function> Deletes from DB the AVPs corresponding to the given source. The meaning and usage of the parameters are identical as for - db_avp_load(source, name) + sql_avp_load(source, name) function. Please refer to its description. @@ -887,14 +887,14 @@ db_avp_store("$ru", "$avp(1)", 3); BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - <function>db_avp_delete</function> usage + <function>sql_avp_delete</function> usage ... -db_avp_delete("$tu", "$avp(678)"); -db_avp_delete("$ru/username", "$avp(email)"); -db_avp_delete("$avp(uuid)", "$avp(404fwd)/fwd_table"); +sql_avp_delete("$tu", "$avp(678)"); +sql_avp_delete("$ru/username", "$avp(email)"); +sql_avp_delete("$avp(uuid)", "$avp(404fwd)/fwd_table"); # use DB URL id 3 -db_avp_delete("$ru", "$avp(1)", 3); +sql_avp_delete("$ru", "$avp(1)", 3); ... @@ -905,13 +905,13 @@ db_avp_delete("$ru", "$avp(1)", 3);
Exported Asynchronous Functions -
+
- <function moreinfo="none">db_query(query, [dest], [db_id])</function> + <function moreinfo="none">sql_query(query, [dest], [db_id])</function> This function takes the same parameters and behaves identically - to , but asynchronously + to , but asynchronously (after launching the query, the current SIP worker pauses the execution of the current SIP message until the result is available and attempts to process more SIP traffic). @@ -921,14 +921,14 @@ db_avp_delete("$ru", "$avp(1)", 3); BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - <function>async db_query</function> usage + <function>async sql_query</function> usage ... { ... /* Example of a slow MySQL query - it should take around 5 seconds */ async( - db_query( + sql_query( "SELECT table_name, table_version, SLEEP(0.1) from version", "$avp(tb_name); $avp(tb_ver); $avp(retcode)"), my_resume_route); @@ -947,13 +947,13 @@ route [my_resume_route]
-
+
- <function moreinfo="none">db_query_one(query, [dest], [db_id])</function> + <function moreinfo="none">sql_query_one(query, [dest], [db_id])</function> This function takes the same parameters and behaves identically - to , but asynchronously + to , but asynchronously (after launching the query, the current SIP worker pauses the execution of the current SIP message until the result is available and attempts to process more SIP traffic). @@ -962,14 +962,14 @@ route [my_resume_route] This function can be used from any route. - <function>async db_query_one</function> usage + <function>async sql_query_one</function> usage ... { ... /* Example of a slow MySQL query - it should take around 5 seconds */ async( - db_query_one( + sql_query_one( "SELECT table_name, table_version, SLEEP(0.1) from version", "$var(tb_name); $var(tb_ver); $var(retcode)"), my_resume_route); diff --git a/modules/sqlops/sqlops.c b/modules/sqlops/sqlops.c index ce9e418b8f0..ffc0c29c07b 100644 --- a/modules/sqlops/sqlops.c +++ b/modules/sqlops/sqlops.c @@ -35,9 +35,9 @@ #include "../../error.h" #include "../../ut.h" #include "../../mod_fix.h" -#include "dbops_parse.h" -#include "dbops_impl.h" -#include "dbops_db.h" +#include "sqlops_parse.h" +#include "sqlops_impl.h" +#include "sqlops_db.h" typedef enum {GPARAM=0, URL} db_id_type; @@ -64,12 +64,12 @@ static str domain_col = str_init("domain"); static str* db_columns[6] = {&uuid_col, &attribute_col, &value_col, &type_col, &username_col, &domain_col}; -static int dbops_init(void); -static int dbops_child_init(int rank); +static int sqlops_init(void); +static int sqlops_child_init(int rank); -static int fixup_db_avp_source(void** param); -static int fixup_db_avp_dbparam_scheme(void** param); -static int fixup_db_avp_dbparam(void** param); +static int fixup_sql_avp_source(void** param); +static int fixup_sql_avp_dbparam_scheme(void** param); +static int fixup_sql_avp_dbparam(void** param); static int fixup_db_url(void ** param); static int fixup_avp_prefix(void **param); @@ -81,44 +81,44 @@ static int fixup_avpname_list(void** param); static int fixup_free_pvname_list(void** param); static int fixup_free_avp_dbparam(void** param); -static int w_db_avp_load(struct sip_msg* msg, void* source, +static int w_sql_avp_load(struct sip_msg* msg, void* source, void* param, void *url, str *prefix); -static int w_db_avp_delete(struct sip_msg* msg, void* source, +static int w_sql_avp_delete(struct sip_msg* msg, void* source, void* param, void *url); -static int w_db_avp_store(struct sip_msg* msg, void* source, +static int w_sql_avp_store(struct sip_msg* msg, void* source, void* param, void *url); -static int w_db_query(struct sip_msg* msg, str* query, +static int w_sql_query(struct sip_msg* msg, str* query, void* dest, void *url); -static int w_db_query_one(struct sip_msg* msg, str* query, +static int w_sql_query_one(struct sip_msg* msg, str* query, void* dest, void *url); -static int w_async_db_query(struct sip_msg* msg, async_ctx *ctx, +static int w_async_sql_query(struct sip_msg* msg, async_ctx *ctx, str* query, void* dest, void* url); -static int w_async_db_query_one(struct sip_msg* msg, async_ctx *ctx, +static int w_async_sql_query_one(struct sip_msg* msg, async_ctx *ctx, str* query, void* dest, void* url); -static int w_db_select(struct sip_msg* msg, str* cols, str *table, +static int w_sql_select(struct sip_msg* msg, str* cols, str *table, str *filter, str *order, void* dest, void *url); -static int w_db_select_one(struct sip_msg* msg, str* cols, str *table, +static int w_sql_select_one(struct sip_msg* msg, str* cols, str *table, str *filter, str *order, void* dest, void *url); -static int w_db_update(struct sip_msg* msg, str* cols, str *table, +static int w_sql_update(struct sip_msg* msg, str* cols, str *table, str *filter, void *url); -static int w_db_insert(struct sip_msg* msg, str* table, str *cols, +static int w_sql_insert(struct sip_msg* msg, str* table, str *cols, void *url); -static int w_db_delete(struct sip_msg* msg, str *table, str *filter, +static int w_sql_delete(struct sip_msg* msg, str *table, str *filter, void *url); -static int w_db_replace(struct sip_msg* msg, str* table, str *cols, +static int w_sql_replace(struct sip_msg* msg, str* table, str *cols, void *url); static const acmd_export_t acmds[] = { - {"db_query", (acmd_function)w_async_db_query, { + {"sql_query", (acmd_function)w_async_sql_query, { {CMD_PARAM_STR, 0, 0}, {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_NO_EXPAND, fixup_avpname_list, fixup_free_pvname_list}, {CMD_PARAM_INT|CMD_PARAM_OPT, fixup_db_id_async, fixup_free_pkg}, {0, 0, 0}}}, - {"db_query_one", (acmd_function)w_async_db_query_one, { + {"sql_query_one", (acmd_function)w_async_sql_query_one, { {CMD_PARAM_STR, 0, 0}, {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_NO_EXPAND, fixup_pvname_list, fixup_free_pvname_list}, @@ -134,35 +134,35 @@ static const acmd_export_t acmds[] = { */ static const cmd_export_t cmds[] = { - {"db_avp_load", (cmd_function)w_db_avp_load, { + {"sql_avp_load", (cmd_function)w_sql_avp_load, { {CMD_PARAM_STR|CMD_PARAM_NO_EXPAND, - fixup_db_avp_source, fixup_free_pkg}, + fixup_sql_avp_source, fixup_free_pkg}, {CMD_PARAM_STR|CMD_PARAM_NO_EXPAND, - fixup_db_avp_dbparam_scheme, fixup_free_avp_dbparam}, + fixup_sql_avp_dbparam_scheme, fixup_free_avp_dbparam}, {CMD_PARAM_INT|CMD_PARAM_OPT, fixup_db_url, 0}, {CMD_PARAM_STR|CMD_PARAM_OPT, fixup_avp_prefix, fixup_free_pkg}, {0, 0, 0}}, ALL_ROUTES}, - {"db_avp_delete", (cmd_function)w_db_avp_delete, { + {"sql_avp_delete", (cmd_function)w_sql_avp_delete, { {CMD_PARAM_STR|CMD_PARAM_NO_EXPAND, - fixup_db_avp_source, fixup_free_pkg}, + fixup_sql_avp_source, fixup_free_pkg}, {CMD_PARAM_STR|CMD_PARAM_NO_EXPAND, - fixup_db_avp_dbparam, fixup_free_avp_dbparam}, + fixup_sql_avp_dbparam, fixup_free_avp_dbparam}, {CMD_PARAM_INT|CMD_PARAM_OPT, fixup_db_url, 0}, {0, 0, 0}}, ALL_ROUTES}, - {"db_avp_store", (cmd_function)w_db_avp_store, { + {"sql_avp_store", (cmd_function)w_sql_avp_store, { {CMD_PARAM_STR|CMD_PARAM_NO_EXPAND, - fixup_db_avp_source, fixup_free_pkg}, + fixup_sql_avp_source, fixup_free_pkg}, {CMD_PARAM_STR|CMD_PARAM_NO_EXPAND, - fixup_db_avp_dbparam, fixup_free_avp_dbparam}, + fixup_sql_avp_dbparam, fixup_free_avp_dbparam}, {CMD_PARAM_INT|CMD_PARAM_OPT, fixup_db_url, 0}, {0, 0, 0}}, ALL_ROUTES}, - {"db_query", (cmd_function)w_db_query, { + {"sql_query", (cmd_function)w_sql_query, { {CMD_PARAM_STR, 0, 0}, {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_NO_EXPAND, fixup_avpname_list, fixup_free_pvname_list}, @@ -171,7 +171,7 @@ static const cmd_export_t cmds[] = { {0, 0, 0}}, ALL_ROUTES}, - {"db_query_one", (cmd_function)w_db_query_one, { + {"sql_query_one", (cmd_function)w_sql_query_one, { {CMD_PARAM_STR, 0, 0}, {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_NO_EXPAND, fixup_pvname_list, fixup_free_pvname_list}, @@ -180,7 +180,7 @@ static const cmd_export_t cmds[] = { {0, 0, 0}}, ALL_ROUTES}, - {"db_select", (cmd_function)w_db_select, { + {"sql_select", (cmd_function)w_sql_select, { {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* columns */ {CMD_PARAM_STR, 0, 0}, /* table */ {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* filter */ @@ -192,7 +192,7 @@ static const cmd_export_t cmds[] = { {0, 0, 0}}, ALL_ROUTES}, - {"db_selec_one", (cmd_function)w_db_select_one, { + {"sql_selec_one", (cmd_function)w_sql_select_one, { {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* columns */ {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* table */ {CMD_PARAM_STR, 0, 0}, /* filter */ @@ -204,7 +204,7 @@ static const cmd_export_t cmds[] = { {0, 0, 0}}, ALL_ROUTES}, - {"db_update", (cmd_function)w_db_update, { + {"sql_update", (cmd_function)w_sql_update, { {CMD_PARAM_STR, 0, 0}, /* columns */ {CMD_PARAM_STR, 0, 0}, /* table */ {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* filter */ @@ -213,7 +213,7 @@ static const cmd_export_t cmds[] = { {0, 0, 0}}, ALL_ROUTES}, - {"db_insert", (cmd_function)w_db_insert, { + {"sql_insert", (cmd_function)w_sql_insert, { {CMD_PARAM_STR, 0, 0}, /* table */ {CMD_PARAM_STR, 0, 0}, /* columns */ {CMD_PARAM_INT|CMD_PARAM_OPT, @@ -221,7 +221,7 @@ static const cmd_export_t cmds[] = { {0, 0, 0}}, ALL_ROUTES}, - {"db_delete", (cmd_function)w_db_delete, { + {"sql_delete", (cmd_function)w_sql_delete, { {CMD_PARAM_STR, 0, 0}, /* table */ {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* filter */ {CMD_PARAM_INT|CMD_PARAM_OPT, @@ -229,7 +229,7 @@ static const cmd_export_t cmds[] = { {0, 0, 0}}, ALL_ROUTES}, - {"db_replace", (cmd_function)w_db_replace, { + {"sql_replace", (cmd_function)w_sql_replace, { {CMD_PARAM_STR, 0, 0}, /* table */ {CMD_PARAM_STR, 0, 0}, /* columns */ {CMD_PARAM_INT|CMD_PARAM_OPT, @@ -270,7 +270,7 @@ static const dep_export_t deps = { }; struct module_exports exports = { - "dbops", + "sqlops", MOD_TYPE_DEFAULT,/* class of this module */ MODULE_VERSION, /* module version */ DEFAULT_DLFLAGS, /* dlopen flags */ @@ -285,18 +285,18 @@ struct module_exports exports = { 0, /* exported transformations */ 0, /* extra processes */ 0, /* Module pre-initialization function */ - dbops_init,/* Module initialization function */ + sqlops_init,/* Module initialization function */ (response_function) 0, (destroy_function) 0, - (child_init_function) dbops_child_init, /* per-child init function */ + (child_init_function) sqlops_child_init, /* per-child init function */ NULL /* reload confirm function */ }; -static int dbops_init(void) +static int sqlops_init(void) { - LM_INFO("initializing...\n"); + LM_DBG("initializing...\n"); db_table.len = strlen(db_table.s); uuid_col.len = strlen(uuid_col.s); @@ -326,7 +326,7 @@ static int dbops_init(void) } /* bind to the DB module */ - if (dbops_db_bind()<0) + if (sqlops_db_bind()<0) goto error; init_store_avps(db_columns); @@ -337,10 +337,10 @@ static int dbops_init(void) } -static int dbops_child_init(int rank) +static int sqlops_child_init(int rank) { /* init DB connection */ - return dbops_db_init(&db_table, db_columns); + return sqlops_db_init(&db_table, db_columns); } @@ -397,7 +397,7 @@ static int fixup_avp_prefix(void **param) name = get_avp_name_id(dbp_fixup->a.u.sval.pvp.pvn.u.isname.name.n); - if (name && dbp_fixup->a.type == AVPOPS_VAL_PVAR) { + if (name && dbp_fixup->a.type == SQLOPS_VAL_PVAR) { p = pkg_malloc(name->len + prefix->len + 7); if (!p) { @@ -420,7 +420,7 @@ static int fixup_avp_prefix(void **param) return 0; } -static int fixup_db_avp(void** param, int param_no, int allow_scheme) +static int fixup_sql_avp(void** param, int param_no, int allow_scheme) { struct fis_param *sp = NULL; struct db_param *dbp; @@ -456,13 +456,13 @@ static int fixup_db_avp(void** param, int param_no, int allow_scheme) *(p++) = 0; /* check for extra flags/params */ if (!strcasecmp("domain",p)) { - flags|=AVPOPS_FLAG_DOMAIN0; + flags|=SQLOPS_FLAG_DOMAIN0; } else if (!strcasecmp("username",p)) { - flags|=AVPOPS_FLAG_USER0; + flags|=SQLOPS_FLAG_USER0; } else if (!strcasecmp("uri",p)) { - flags|=AVPOPS_FLAG_URI0; + flags|=SQLOPS_FLAG_URI0; } else if (!strcasecmp("uuid",p)) { - flags|=AVPOPS_FLAG_UUID0; + flags|=SQLOPS_FLAG_UUID0; } else { LM_ERR("unknown flag " "<%s>\n",p); @@ -472,7 +472,7 @@ static int fixup_db_avp(void** param, int param_no, int allow_scheme) if (*s.s!='$') { /* is a constant string -> use it as uuid*/ - sp->opd = ((flags==0)?AVPOPS_FLAG_UUID0:flags)|AVPOPS_VAL_STR; + sp->opd = ((flags==0)?SQLOPS_FLAG_UUID0:flags)|SQLOPS_VAL_STR; sp->u.s.s = (char*)pkg_malloc(s.len + 1); if (sp->u.s.s==0) { LM_ERR("no more pkg mem!!\n"); @@ -493,9 +493,9 @@ static int fixup_db_avp(void** param, int param_no, int allow_scheme) if(sp->u.sval.type==PVT_RURI || sp->u.sval.type==PVT_FROM || sp->u.sval.type==PVT_TO || sp->u.sval.type==PVT_OURI) { - sp->opd = ((flags==0)?AVPOPS_FLAG_URI0:flags)|AVPOPS_VAL_PVAR; + sp->opd = ((flags==0)?SQLOPS_FLAG_URI0:flags)|SQLOPS_VAL_PVAR; } else { - sp->opd = ((flags==0)?AVPOPS_FLAG_UUID0:flags)|AVPOPS_VAL_PVAR; + sp->opd = ((flags==0)?SQLOPS_FLAG_UUID0:flags)|SQLOPS_VAL_PVAR; } } *param=(void*)sp; @@ -528,19 +528,19 @@ static int fixup_db_avp(void** param, int param_no, int allow_scheme) return E_UNSPEC; } -static int fixup_db_avp_source(void** param) +static int fixup_sql_avp_source(void** param) { - return fixup_db_avp(param, 1, 0); + return fixup_sql_avp(param, 1, 0); } -static int fixup_db_avp_dbparam_scheme(void** param) +static int fixup_sql_avp_dbparam_scheme(void** param) { - return fixup_db_avp(param, 2, 1); + return fixup_sql_avp(param, 2, 1); } -static int fixup_db_avp_dbparam(void** param) +static int fixup_sql_avp_dbparam(void** param) { - return fixup_db_avp(param, 2, 0); + return fixup_sql_avp(param, 2, 0); } static int fixup_free_avp_dbparam(void** param) @@ -654,34 +654,34 @@ static int fixup_db_id_async(void** param) } -static int w_db_avp_load(struct sip_msg* msg, void* source, +static int w_sql_avp_load(struct sip_msg* msg, void* source, void* param, void *url, str *prefix) { - return ops_db_avp_load ( msg, (struct fis_param*)source, + return ops_sql_avp_load ( msg, (struct fis_param*)source, (struct db_param*)param, url?(struct db_url*)url:default_db_url, use_domain, prefix); } -static int w_db_avp_delete(struct sip_msg* msg, void* source, +static int w_sql_avp_delete(struct sip_msg* msg, void* source, void* param, void *url) { - return ops_db_avp_delete ( msg, (struct fis_param*)source, + return ops_sql_avp_delete ( msg, (struct fis_param*)source, (struct db_param*)param, url?(struct db_url*)url:default_db_url, use_domain); } -static int w_db_avp_store(struct sip_msg* msg, void* source, +static int w_sql_avp_store(struct sip_msg* msg, void* source, void* param, void *url) { - return ops_db_avp_store ( msg, (struct fis_param*)source, + return ops_sql_avp_store ( msg, (struct fis_param*)source, (struct db_param*)param, url?(struct db_url*)url:default_db_url, use_domain); } -static int w_db_query(struct sip_msg* msg, str* query, +static int w_sql_query(struct sip_msg* msg, str* query, void* dest, void *url) { struct db_url *parsed_url; @@ -691,11 +691,11 @@ static int w_db_query(struct sip_msg* msg, str* query, else parsed_url = default_db_url; - return ops_db_query(msg, query, parsed_url, (pvname_list_t*)dest, 0); + return ops_sql_query(msg, query, parsed_url, (pvname_list_t*)dest, 0); } -static int w_db_query_one(struct sip_msg* msg, str* query, +static int w_sql_query_one(struct sip_msg* msg, str* query, void* dest, void *url) { struct db_url *parsed_url; @@ -705,11 +705,11 @@ static int w_db_query_one(struct sip_msg* msg, str* query, else parsed_url = default_db_url; - return ops_db_query(msg, query, parsed_url, (pvname_list_t*)dest, 1); + return ops_sql_query(msg, query, parsed_url, (pvname_list_t*)dest, 1); } -static int w_db_select(struct sip_msg* msg, str* cols, str *table, +static int w_sql_select(struct sip_msg* msg, str* cols, str *table, str *filter, str *order, void* dest, void *url) { struct db_url *parsed_url; @@ -719,12 +719,12 @@ static int w_db_select(struct sip_msg* msg, str* cols, str *table, else parsed_url = default_db_url; - return ops_db_api_select(parsed_url, msg, cols, table, filter, order, + return ops_sql_api_select(parsed_url, msg, cols, table, filter, order, (pvname_list_t*)dest, 0); } -static int w_db_select_one(struct sip_msg* msg, str* cols, str *table, +static int w_sql_select_one(struct sip_msg* msg, str* cols, str *table, str *filter, str *order, void* dest, void *url) { struct db_url *parsed_url; @@ -734,12 +734,12 @@ static int w_db_select_one(struct sip_msg* msg, str* cols, str *table, else parsed_url = default_db_url; - return ops_db_api_select(parsed_url, msg, cols, table, filter, order, + return ops_sql_api_select(parsed_url, msg, cols, table, filter, order, (pvname_list_t*)dest, 1); } -static int w_db_update(struct sip_msg* msg, str* cols, str *table, +static int w_sql_update(struct sip_msg* msg, str* cols, str *table, str *filter, void *url) { struct db_url *parsed_url; @@ -749,11 +749,11 @@ static int w_db_update(struct sip_msg* msg, str* cols, str *table, else parsed_url = default_db_url; - return ops_db_api_update(parsed_url, msg, cols, table, filter); + return ops_sql_api_update(parsed_url, msg, cols, table, filter); } -static int w_db_insert(struct sip_msg* msg, str* table, str *cols, +static int w_sql_insert(struct sip_msg* msg, str* table, str *cols, void *url) { struct db_url *parsed_url; @@ -763,11 +763,11 @@ static int w_db_insert(struct sip_msg* msg, str* table, str *cols, else parsed_url = default_db_url; - return ops_db_api_insert(parsed_url, msg, table, cols); + return ops_sql_api_insert(parsed_url, msg, table, cols); } -static int w_db_delete(struct sip_msg* msg, str *table, str *filter, +static int w_sql_delete(struct sip_msg* msg, str *table, str *filter, void *url) { struct db_url *parsed_url; @@ -777,11 +777,11 @@ static int w_db_delete(struct sip_msg* msg, str *table, str *filter, else parsed_url = default_db_url; - return ops_db_api_delete(parsed_url, msg, table, filter); + return ops_sql_api_delete(parsed_url, msg, table, filter); } -static int w_db_replace(struct sip_msg* msg, str* table, str *cols, +static int w_sql_replace(struct sip_msg* msg, str* table, str *cols, void *url) { struct db_url *parsed_url; @@ -791,11 +791,11 @@ static int w_db_replace(struct sip_msg* msg, str* table, str *cols, else parsed_url = default_db_url; - return ops_db_api_replace(parsed_url, msg, table, cols); + return ops_sql_api_replace(parsed_url, msg, table, cols); } -static int w_async_db_query(struct sip_msg* msg, async_ctx *ctx, +static int w_async_sql_query(struct sip_msg* msg, async_ctx *ctx, str* query, void* dest, void* url) { struct db_url *parsed_url; @@ -805,11 +805,11 @@ static int w_async_db_query(struct sip_msg* msg, async_ctx *ctx, else parsed_url = default_db_url; - return ops_async_db_query(msg, ctx, query, parsed_url, + return ops_async_sql_query(msg, ctx, query, parsed_url, (pvname_list_t *)dest, 0); } -static int w_async_db_query_one(struct sip_msg* msg, async_ctx *ctx, +static int w_async_sql_query_one(struct sip_msg* msg, async_ctx *ctx, str* query, void* dest, void* url) { struct db_url *parsed_url; @@ -819,6 +819,6 @@ static int w_async_db_query_one(struct sip_msg* msg, async_ctx *ctx, else parsed_url = default_db_url; - return ops_async_db_query(msg, ctx, query, parsed_url, + return ops_async_sql_query(msg, ctx, query, parsed_url, (pvname_list_t *)dest, 1); } diff --git a/modules/sqlops/sqlops_db.c b/modules/sqlops/sqlops_db.c index e3057aed246..3633358531a 100644 --- a/modules/sqlops/sqlops_db.c +++ b/modules/sqlops/sqlops_db.c @@ -31,8 +31,8 @@ #include "../../dprint.h" #include "../../route.h" #include "../../map.h" -#include "dbops_parse.h" -#include "dbops_db.h" +#include "sqlops_parse.h" +#include "sqlops_db.h" static str def_table; /* default DB table */ @@ -127,7 +127,7 @@ int add_db_url(modparam_t type, void *val) -int dbops_db_bind(void) +int sqlops_db_bind(void) { unsigned int i; @@ -141,7 +141,7 @@ int dbops_db_bind(void) if (!DB_CAPABILITY(db_urls[i].dbf, DB_CAP_ALL)) { LM_CRIT("database modules (%.*s) does not " - "provide all functions needed by dbops module\n", + "provide all functions needed by sqlops module\n", db_urls[i].url.len,db_urls[i].url.s); return -1; } @@ -149,7 +149,7 @@ int dbops_db_bind(void) /* * we cannot catch the default DB url usage at fixup time - * as we do with the other bunch of extra dbops DB URLs + * as we do with the other bunch of extra sqlops DB URLs * * so just dig through the whole script tree */ @@ -173,7 +173,7 @@ int dbops_db_bind(void) } -int dbops_db_init(const str* db_table, str** db_cols) +int sqlops_db_init(const str* db_table, str** db_cols) { int i; @@ -336,7 +336,7 @@ static inline int prepare_selection( str *uuid, str *username, str *domain, } -db_res_t *db_avp_load(struct db_url *url, str *uuid, str *username,str *domain, +db_res_t *sql_avp_load(struct db_url *url, str *uuid, str *username,str *domain, char *attr, const str *table, struct db_scheme *scheme) { static db_key_t keys_ret[3]; @@ -380,7 +380,7 @@ void db_close_query(struct db_url *url, db_res_t *res ) } -int db_avp_store(struct db_url *url, db_key_t *keys, db_val_t *vals, +int sql_avp_store(struct db_url *url, db_key_t *keys, db_val_t *vals, int n, const str *table) { int r; @@ -402,7 +402,7 @@ int db_avp_store(struct db_url *url, db_key_t *keys, db_val_t *vals, -int db_avp_delete(struct db_url *url, str *uuid, str *username, str *domain, +int sql_avp_delete(struct db_url *url, str *uuid, str *username, str *domain, char *attr, const str *table) { unsigned int nr_keys_cmp; @@ -422,7 +422,7 @@ int db_avp_delete(struct db_url *url, str *uuid, str *username, str *domain, } -int db_query(struct db_url *url, struct sip_msg *msg, str *query, +int sql_query(struct db_url *url, struct sip_msg *msg, str *query, pvname_list_t* dest, int one_row) { db_res_t* db_res = NULL; @@ -688,7 +688,7 @@ static inline str* _query_id_add_filter(db_key_t* _k, db_op_t* _o, int _nk) -int db_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, +int sql_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, str *table, cJSON *Jfilter, str * order, pvname_list_t* dest, int one_row) { static map_t ps_map = NULL; @@ -779,7 +779,7 @@ int db_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, } -int db_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, +int sql_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, str *table, cJSON *Jfilter) { static map_t ps_map = NULL; @@ -842,7 +842,7 @@ int db_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, } -int db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, +int sql_api_insert(struct db_url *url, struct sip_msg* msg, str *table, cJSON *Jcols) { static map_t ps_map = NULL; @@ -891,7 +891,7 @@ int db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, } -int db_api_delete(struct db_url *url, struct sip_msg* msg, +int sql_api_delete(struct db_url *url, struct sip_msg* msg, str *table, cJSON *Jfilter) { static map_t ps_map = NULL; @@ -947,7 +947,7 @@ int db_api_delete(struct db_url *url, struct sip_msg* msg, } -int db_api_replace(struct db_url *url, struct sip_msg* msg, str *table, +int sql_api_replace(struct db_url *url, struct sip_msg* msg, str *table, cJSON *Jcols) { static map_t ps_map = NULL; diff --git a/modules/sqlops/sqlops_db.h b/modules/sqlops/sqlops_db.h index 7f58b112319..d936612307c 100644 --- a/modules/sqlops/sqlops_db.h +++ b/modules/sqlops/sqlops_db.h @@ -64,22 +64,22 @@ struct db_url* get_db_url(unsigned int idx); struct db_url* get_default_db_url(void); -int dbops_db_bind(void); +int sqlops_db_bind(void); -int dbops_db_init(const str* db_table, str **db_columns); +int sqlops_db_init(const str* db_table, str **db_columns); -db_res_t *db_avp_load(struct db_url *url,str *uuid, str *username, str *domain, +db_res_t *sql_avp_load(struct db_url *url,str *uuid, str *username,str *domain, char *attr, const str *table, struct db_scheme *scheme); void db_close_query( struct db_url *url, db_res_t *res ); -int db_avp_store( struct db_url *url, db_key_t *keys, db_val_t *vals, +int sql_avp_store( struct db_url *url, db_key_t *keys, db_val_t *vals, int n, const str *table); -int db_avp_delete( struct db_url *url, str *uuid, str *username, str *domain, +int sql_avp_delete( struct db_url *url, str *uuid, str *username, str *domain, char *attr, const str *table); -int db_query(struct db_url *url, struct sip_msg* msg, str *query, +int sql_query(struct db_url *url, struct sip_msg* msg, str *query, pvname_list_t* dest, int one_row); int add_avp_db_scheme( modparam_t type, void* val); @@ -92,20 +92,20 @@ int db_query_print_one_result(struct sip_msg *msg, const db_res_t *db_res, int db_query_print_results(struct sip_msg *msg, const db_res_t *db_res, pvname_list_t *dest); -int db_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, +int sql_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, str *table, cJSON *Jfilter, str * order, pvname_list_t* dest, int one_row); -int db_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, +int sql_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, str *table, cJSON *Jfilter); -int db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, +int sql_api_insert(struct db_url *url, struct sip_msg* msg, str *table, cJSON *Jcols); -int db_api_delete(struct db_url *url, struct sip_msg* msg, +int sql_api_delete(struct db_url *url, struct sip_msg* msg, str *table, cJSON *Jfilter); -int db_api_replace(struct db_url *url, struct sip_msg* msg, str *table, +int sql_api_replace(struct db_url *url, struct sip_msg* msg, str *table, cJSON *Jcols); #endif diff --git a/modules/sqlops/sqlops_impl.c b/modules/sqlops/sqlops_impl.c index 45663746c3c..dc7caad5bdd 100644 --- a/modules/sqlops/sqlops_impl.c +++ b/modules/sqlops/sqlops_impl.c @@ -37,11 +37,11 @@ #include "../../parser/parse_from.h" #include "../../parser/parse_uri.h" #include "../../mem/mem.h" -#include "dbops_impl.h" -#include "dbops_db.h" +#include "sqlops_impl.h" +#include "sqlops_db.h" -#define dbops_str2int_str(a, b) \ +#define sqlops_str2int_str(a, b) \ do { \ if(a.s==0) \ b.n = a.len; \ @@ -82,8 +82,8 @@ void init_store_avps(str **db_columns) store_vals[5].nul = 0; } -#define AVPOPS_ATTR_LEN 64 -static char dbops_attr_buf[AVPOPS_ATTR_LEN]; +#define SQLOPS_ATTR_LEN 64 +static char sqlops_attr_buf[SQLOPS_ATTR_LEN]; /* value 0 - attr value * value 1 - attr name @@ -122,8 +122,8 @@ static int dbrow2avp(struct db_row *row, struct db_param *dbp, int attr, /* check the content of flag field */ uint = (unsigned int)row->values[2].val.int_val; - db_flags = ((uint&AVPOPS_DB_NAME_INT)?0:AVP_NAME_STR) | - ((uint&AVPOPS_DB_VAL_INT)?0:AVP_VAL_STR); + db_flags = ((uint&SQLOPS_DB_NAME_INT)?0:AVP_NAME_STR) | + ((uint&SQLOPS_DB_VAL_INT)?0:AVP_VAL_STR); } else { /* check the validity of value column */ if (row->values[0].nul || (row->values[0].type!=DB_STRING && @@ -136,7 +136,7 @@ static int dbrow2avp(struct db_row *row, struct db_param *dbp, int attr, } /* is the avp name already known? */ - if ( (flags&AVPOPS_VAL_NONE)==0 ) + if ( (flags&SQLOPS_VAL_NONE)==0 ) { /* use the name */ avp_attr = attr; @@ -153,16 +153,16 @@ static int dbrow2avp(struct db_row *row, struct db_param *dbp, int attr, if (prefix) { - if (atmp.len + prefix->len > AVPOPS_ATTR_LEN) + if (atmp.len + prefix->len > SQLOPS_ATTR_LEN) { LM_ERR("name too long [%d/%.*s...]\n", prefix->len + atmp.len, 16, prefix->s); return -1; } - memcpy(dbops_attr_buf, prefix->s, prefix->len); - memcpy(dbops_attr_buf + prefix->len, atmp.s, atmp.len); - atmp.s = dbops_attr_buf; + memcpy(sqlops_attr_buf, prefix->s, prefix->len); + memcpy(sqlops_attr_buf + prefix->len, atmp.s, atmp.len); + atmp.s = sqlops_attr_buf; atmp.len += prefix->len; } @@ -225,7 +225,7 @@ static inline void int_str2db_val( int_str is_val, str *val, int is_s) } -int ops_db_avp_load (struct sip_msg* msg, struct fis_param *sp, +int ops_sql_avp_load (struct sip_msg* msg, struct fis_param *sp, struct db_param *dbp, struct db_url *url, int use_domain, str *prefix) { struct sip_uri uri; @@ -238,13 +238,13 @@ int ops_db_avp_load (struct sip_msg* msg, struct fis_param *sp, pv_value_t xvalue; s0 = s1 = s2 = NULL; - if (!((sp->opd&AVPOPS_VAL_PVAR)||(sp->opd&AVPOPS_VAL_STR))) { + if (!((sp->opd&SQLOPS_VAL_PVAR)||(sp->opd&SQLOPS_VAL_STR))) { LM_CRIT("invalid flag combination (%d/%d)\n", sp->opd, sp->ops); goto error; } /* get uuid from avp */ - if (sp->opd&AVPOPS_VAL_PVAR) + if (sp->opd&SQLOPS_VAL_PVAR) { if(pv_get_spec_value(msg, &(sp->u.sval), &xvalue)!=0) { @@ -262,7 +262,7 @@ int ops_db_avp_load (struct sip_msg* msg, struct fis_param *sp, uuid.len = sp->u.s.len; } - if(sp->opd&AVPOPS_FLAG_UUID0) + if(sp->opd&SQLOPS_FLAG_UUID0) { s0 = &uuid; } else { @@ -273,7 +273,7 @@ int ops_db_avp_load (struct sip_msg* msg, struct fis_param *sp, goto error; } - if((sp->opd&AVPOPS_FLAG_URI0)||(sp->opd&AVPOPS_FLAG_USER0)) + if((sp->opd&SQLOPS_FLAG_URI0)||(sp->opd&SQLOPS_FLAG_USER0)) { /* check that uri contains user part */ if(!uri.user.s|| !uri.user.len) @@ -286,7 +286,7 @@ int ops_db_avp_load (struct sip_msg* msg, struct fis_param *sp, s1 = &uri.user; } } - if((sp->opd&AVPOPS_FLAG_URI0)||(sp->opd&AVPOPS_FLAG_DOMAIN0)) + if((sp->opd&SQLOPS_FLAG_URI0)||(sp->opd&SQLOPS_FLAG_DOMAIN0)) { /* check that uri contains host part */ if(!uri.host.len|| !uri.host.s) @@ -302,7 +302,7 @@ int ops_db_avp_load (struct sip_msg* msg, struct fis_param *sp, } /* is dynamic avp name ? */ - if(dbp->a.type==AVPOPS_VAL_PVAR) + if(dbp->a.type==SQLOPS_VAL_PVAR) { if(pv_has_dname(&(dbp->a.u.sval))) { @@ -318,13 +318,13 @@ int ops_db_avp_load (struct sip_msg* msg, struct fis_param *sp, } if(xvalue.flags&PV_VAL_STR) { - if(xvalue.rs.len>=AVPOPS_ATTR_LEN) + if(xvalue.rs.len>=SQLOPS_ATTR_LEN) { LM_ERR("name too long [%d/%.*s...]\n", xvalue.rs.len, 16, xvalue.rs.s); goto error; } - dbp->sa.s = dbops_attr_buf; + dbp->sa.s = sqlops_attr_buf; memcpy(dbp->sa.s, xvalue.rs.s, xvalue.rs.len); dbp->sa.len = xvalue.rs.len; dbp->sa.s[dbp->sa.len] = '\0'; @@ -336,8 +336,8 @@ int ops_db_avp_load (struct sip_msg* msg, struct fis_param *sp, } /* do DB query */ - res = db_avp_load( url, s0, s1, - ((use_domain)||(sp->opd&AVPOPS_FLAG_DOMAIN0))?s2:0, + res = sql_avp_load( url, s0, s1, + ((use_domain)||(sp->opd&SQLOPS_FLAG_DOMAIN0))?s2:0, dbp->sa.s, &dbp->table, dbp->scheme); /* res query ? */ @@ -351,7 +351,7 @@ int ops_db_avp_load (struct sip_msg* msg, struct fis_param *sp, /* validate row */ avp_name = -1; - if(dbp->a.type==AVPOPS_VAL_PVAR) + if(dbp->a.type==SQLOPS_VAL_PVAR) { if(pv_has_dname(&dbp->a.u.sval)) { @@ -362,17 +362,17 @@ int ops_db_avp_load (struct sip_msg* msg, struct fis_param *sp, if (prefix) { - if (xvalue.rs.len + prefix->len > AVPOPS_ATTR_LEN) + if (xvalue.rs.len + prefix->len > SQLOPS_ATTR_LEN) { LM_ERR("name too long [%d/%.*s...]\n", prefix->len + xvalue.rs.len, 16, prefix->s); goto error; } - memcpy(dbops_attr_buf, prefix->s, prefix->len); - memcpy(dbops_attr_buf + prefix->len, xvalue.rs.s, + memcpy(sqlops_attr_buf, prefix->s, prefix->len); + memcpy(sqlops_attr_buf + prefix->len, xvalue.rs.s, xvalue.rs.len); - xvalue.rs.s = dbops_attr_buf; + xvalue.rs.s = sqlops_attr_buf; xvalue.rs.len = prefix->len + xvalue.rs.len; } @@ -406,7 +406,7 @@ int ops_db_avp_load (struct sip_msg* msg, struct fis_param *sp, } -int ops_db_avp_delete(struct sip_msg* msg, struct fis_param *sp, +int ops_sql_avp_delete(struct sip_msg* msg, struct fis_param *sp, struct db_param *dbp, struct db_url *url, int use_domain) { struct sip_uri uri; @@ -416,13 +416,13 @@ int ops_db_avp_delete(struct sip_msg* msg, struct fis_param *sp, str *s0, *s1, *s2; s0 = s1 = s2 = NULL; - if (!((sp->opd&AVPOPS_VAL_PVAR)||(sp->opd&AVPOPS_VAL_STR))) { + if (!((sp->opd&SQLOPS_VAL_PVAR)||(sp->opd&SQLOPS_VAL_STR))) { LM_CRIT("invalid flag combination (%d/%d)\n", sp->opd, sp->ops); goto error; } /* get uuid from avp */ - if (sp->opd&AVPOPS_VAL_PVAR) + if (sp->opd&SQLOPS_VAL_PVAR) { if(pv_get_spec_value(msg, &(sp->u.sval), &xvalue)!=0) { @@ -440,7 +440,7 @@ int ops_db_avp_delete(struct sip_msg* msg, struct fis_param *sp, uuid.len = sp->u.s.len; } - if(sp->opd&AVPOPS_FLAG_UUID0) + if(sp->opd&SQLOPS_FLAG_UUID0) { s0 = &uuid; } else { @@ -451,7 +451,7 @@ int ops_db_avp_delete(struct sip_msg* msg, struct fis_param *sp, goto error; } - if((sp->opd&AVPOPS_FLAG_URI0)||(sp->opd&AVPOPS_FLAG_USER0)) + if((sp->opd&SQLOPS_FLAG_URI0)||(sp->opd&SQLOPS_FLAG_USER0)) { /* check that uri contains user part */ if(!uri.user.s|| !uri.user.len) @@ -464,7 +464,7 @@ int ops_db_avp_delete(struct sip_msg* msg, struct fis_param *sp, s1 = &uri.user; } } - if((sp->opd&AVPOPS_FLAG_URI0)||(sp->opd&AVPOPS_FLAG_DOMAIN0)) + if((sp->opd&SQLOPS_FLAG_URI0)||(sp->opd&SQLOPS_FLAG_DOMAIN0)) { /* check tah uri contains host part */ if(!uri.host.len|| !uri.host.s) @@ -480,7 +480,7 @@ int ops_db_avp_delete(struct sip_msg* msg, struct fis_param *sp, } /* is dynamic avp name ? */ - if(dbp->a.type==AVPOPS_VAL_PVAR) + if(dbp->a.type==SQLOPS_VAL_PVAR) { if(pv_has_dname(&dbp->a.u.sval)) { @@ -496,13 +496,13 @@ int ops_db_avp_delete(struct sip_msg* msg, struct fis_param *sp, } if(xvalue.flags&PV_VAL_STR) { - if(xvalue.rs.len>=AVPOPS_ATTR_LEN) + if(xvalue.rs.len>=SQLOPS_ATTR_LEN) { LM_ERR("name too long [%d/%.*s...]\n", xvalue.rs.len, 16, xvalue.rs.s); goto error; } - dbp->sa.s = dbops_attr_buf; + dbp->sa.s = sqlops_attr_buf; memcpy(dbp->sa.s, xvalue.rs.s, xvalue.rs.len); dbp->sa.len = xvalue.rs.len; dbp->sa.s[dbp->sa.len] = '\0'; @@ -514,8 +514,8 @@ int ops_db_avp_delete(struct sip_msg* msg, struct fis_param *sp, } /* do DB delete */ - res = db_avp_delete( url, s0, s1, - (use_domain||(sp->opd&AVPOPS_FLAG_DOMAIN0))?s2:0, + res = sql_avp_delete( url, s0, s1, + (use_domain||(sp->opd&SQLOPS_FLAG_DOMAIN0))?s2:0, dbp->sa.s, &dbp->table); /* res ? */ @@ -531,7 +531,7 @@ int ops_db_avp_delete(struct sip_msg* msg, struct fis_param *sp, } -int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, +int ops_sql_avp_store(struct sip_msg* msg, struct fis_param *sp, struct db_param *dbp, struct db_url *url, int use_domain) { struct sip_uri uri; @@ -547,7 +547,7 @@ int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, str *sn; s0 = s1 = s2 = NULL; - if (!((sp->opd&AVPOPS_VAL_PVAR)||(sp->opd&AVPOPS_VAL_STR))) { + if (!((sp->opd&SQLOPS_VAL_PVAR)||(sp->opd&SQLOPS_VAL_STR))) { LM_CRIT("invalid flag combination (%d/%d)\n", sp->opd, sp->ops); goto error; } @@ -555,7 +555,7 @@ int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, keys_nr = 6; /* uuid, avp name, avp val, avp type, user, domain */ /* get uuid from avp */ - if (sp->opd&AVPOPS_VAL_PVAR) + if (sp->opd&SQLOPS_VAL_PVAR) { if(pv_get_spec_value(msg, &(sp->u.sval), &xvalue)!=0) { @@ -573,7 +573,7 @@ int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, uuid.len = sp->u.s.len; } - if(sp->opd&AVPOPS_FLAG_UUID0) + if(sp->opd&SQLOPS_FLAG_UUID0) { s0 = &uuid; } else { @@ -584,7 +584,7 @@ int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, goto error; } - if((sp->opd&AVPOPS_FLAG_URI0)||(sp->opd&AVPOPS_FLAG_USER0)) + if((sp->opd&SQLOPS_FLAG_URI0)||(sp->opd&SQLOPS_FLAG_USER0)) { /* check tha uri contains user part */ if(!uri.user.s|| !uri.user.len) @@ -597,7 +597,7 @@ int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, s1 = &uri.user; } } - if((sp->opd&AVPOPS_FLAG_URI0)||(sp->opd&AVPOPS_FLAG_DOMAIN0)) + if((sp->opd&SQLOPS_FLAG_URI0)||(sp->opd&SQLOPS_FLAG_DOMAIN0)) { /* check that uri contains host part */ if(!uri.host.len|| !uri.host.s) @@ -615,12 +615,12 @@ int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, /* set values for keys */ store_vals[0].val.str_val = (s0)?*s0:empty; store_vals[4].val.str_val = (s1)?*s1:empty; - if (use_domain || sp->opd&AVPOPS_FLAG_DOMAIN0) + if (use_domain || sp->opd&SQLOPS_FLAG_DOMAIN0) store_vals[5].val.str_val = (s2)?*s2:empty; avp_name = -1; /* is dynamic avp name ? */ - if(dbp->a.type==AVPOPS_VAL_PVAR) + if(dbp->a.type==SQLOPS_VAL_PVAR) { if(pv_has_dname(&dbp->a.u.sval)) { @@ -643,13 +643,13 @@ int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, } if(xvalue.flags&PV_VAL_STR) { - if(xvalue.rs.len>=AVPOPS_ATTR_LEN) + if(xvalue.rs.len>=SQLOPS_ATTR_LEN) { LM_ERR("name too long [%d/%.*s...]\n", xvalue.rs.len, 16, xvalue.rs.s); goto error; } - dbp->sa.s = dbops_attr_buf; + dbp->sa.s = sqlops_attr_buf; memcpy(dbp->sa.s, xvalue.rs.s, xvalue.rs.len); dbp->sa.len = xvalue.rs.len; dbp->sa.s[dbp->sa.len] = '\0'; @@ -675,7 +675,7 @@ int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, /* set uuid/(username and domain) fields */ n =0 ; - if ((dbp->a.opd&AVPOPS_VAL_NONE)==0) + if ((dbp->a.opd&SQLOPS_VAL_NONE)==0) { /* if avp wasn't found yet */ if (avp_name < 0) { @@ -696,13 +696,13 @@ int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, continue; /* set type */ store_vals[3].val.int_val = - (avp->flags&AVP_NAME_STR?0:AVPOPS_DB_NAME_INT)| - (avp->flags&AVP_VAL_STR?0:AVPOPS_DB_VAL_INT); + (avp->flags&AVP_NAME_STR?0:SQLOPS_DB_NAME_INT)| + (avp->flags&AVP_VAL_STR?0:SQLOPS_DB_VAL_INT); /* set value */ int_str2db_val( i_s, &store_vals[2].val.str_val, avp->flags&AVP_VAL_STR); /* save avp */ - if (db_avp_store( url, store_keys, store_vals, + if (sql_avp_store( url, store_keys, store_vals, keys_nr, &dbp->table)==0 ) { avp->flags |= AVP_IS_IN_DB; @@ -727,14 +727,14 @@ int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, i_s.s = *sn; int_str2db_val( i_s, &store_vals[1].val.str_val, AVP_NAME_STR); store_vals[3].val.int_val = - (avp->flags&AVP_NAME_STR?0:AVPOPS_DB_NAME_INT)| - (avp->flags&AVP_VAL_STR?0:AVPOPS_DB_VAL_INT); + (avp->flags&AVP_NAME_STR?0:SQLOPS_DB_NAME_INT)| + (avp->flags&AVP_VAL_STR?0:SQLOPS_DB_VAL_INT); /* set avp value */ get_avp_val( avp, &i_s); int_str2db_val( i_s, &store_vals[2].val.str_val, avp->flags&AVP_VAL_STR); /* save avp */ - if (db_avp_store( url, store_keys, store_vals, + if (sql_avp_store( url, store_keys, store_vals, keys_nr, &dbp->table)==0) { avp->flags |= AVP_IS_IN_DB; @@ -753,7 +753,7 @@ int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, /* @return : non-zero */ -int ops_db_query(struct sip_msg* msg, str* query, struct db_url *url, +int ops_sql_query(struct sip_msg* msg, str* query, struct db_url *url, pvname_list_t* dest, int one_row) { int ret; @@ -765,7 +765,7 @@ int ops_db_query(struct sip_msg* msg, str* query, struct db_url *url, } LM_DBG("query [%.*s]\n", query->len, query->s); - ret = db_query(url, msg, query, dest, one_row); + ret = sql_query(url, msg, query, dest, one_row); /* Empty return set */ if(ret==1) @@ -840,7 +840,7 @@ static inline int _parse_json_col_and_filter( str *cols, str *filter, } -int ops_db_api_select(struct db_url *url, struct sip_msg* msg, str *cols, +int ops_sql_api_select(struct db_url *url, struct sip_msg* msg, str *cols, str *table, str *filter, str * order, pvname_list_t* dest, int one_col) { cJSON *Jcols, *Jfilter; @@ -850,7 +850,7 @@ int ops_db_api_select(struct db_url *url, struct sip_msg* msg, str *cols, if (ret<0) { LM_ERR("failed to JSON parse cols and filter\n"); } else { - ret = db_api_select( url, msg, Jcols, table, Jfilter, + ret = sql_api_select( url, msg, Jcols, table, Jfilter, order, dest, one_col); if (ret<0) { LM_ERR("failed to perform DB select query\n"); @@ -865,7 +865,7 @@ int ops_db_api_select(struct db_url *url, struct sip_msg* msg, str *cols, } -int ops_db_api_update(struct db_url *url, struct sip_msg* msg, str *cols, +int ops_sql_api_update(struct db_url *url, struct sip_msg* msg, str *cols, str *table, str *filter) { cJSON *Jcols, *Jfilter; @@ -875,7 +875,7 @@ int ops_db_api_update(struct db_url *url, struct sip_msg* msg, str *cols, if (ret<0) { LM_ERR("failed to JSON parse cols and filter\n"); } else { - ret = db_api_update( url, msg, Jcols, table, Jfilter); + ret = sql_api_update( url, msg, Jcols, table, Jfilter); if (ret<0) { LM_ERR("failed to perform DB update query\n"); } else { @@ -889,7 +889,7 @@ int ops_db_api_update(struct db_url *url, struct sip_msg* msg, str *cols, } -int ops_db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, +int ops_sql_api_insert(struct db_url *url, struct sip_msg* msg, str *table, str *cols) { cJSON *Jcols, *Jfilter; @@ -899,7 +899,7 @@ int ops_db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, if (ret<0) { LM_ERR("failed to JSON parse cols and filter\n"); } else { - ret = db_api_insert( url, msg, table, Jcols); + ret = sql_api_insert( url, msg, table, Jcols); if (ret<0) { LM_ERR("failed to perform DB insert query\n"); } else { @@ -913,7 +913,7 @@ int ops_db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, } -int ops_db_api_delete(struct db_url *url, struct sip_msg* msg, +int ops_sql_api_delete(struct db_url *url, struct sip_msg* msg, str *table, str *filter) { cJSON *Jcols, *Jfilter; @@ -923,7 +923,7 @@ int ops_db_api_delete(struct db_url *url, struct sip_msg* msg, if (ret<0) { LM_ERR("failed to JSON parse cols and filter\n"); } else { - ret = db_api_delete( url, msg, table, Jfilter); + ret = sql_api_delete( url, msg, table, Jfilter); if (ret<0) { LM_ERR("failed to perform DB insert query\n"); } else { @@ -937,7 +937,7 @@ int ops_db_api_delete(struct db_url *url, struct sip_msg* msg, } -int ops_db_api_replace(struct db_url *url, struct sip_msg* msg, str *table, +int ops_sql_api_replace(struct db_url *url, struct sip_msg* msg, str *table, str *cols) { cJSON *Jcols, *Jfilter; @@ -947,7 +947,7 @@ int ops_db_api_replace(struct db_url *url, struct sip_msg* msg, str *table, if (ret<0) { LM_ERR("failed to JSON parse cols and filter\n"); } else { - ret = db_api_replace( url, msg, table, Jcols); + ret = sql_api_replace( url, msg, table, Jcols); if (ret<0) { LM_ERR("failed to perform DB replace query\n"); } else { @@ -961,7 +961,7 @@ int ops_db_api_replace(struct db_url *url, struct sip_msg* msg, str *table, } -int ops_async_db_query(struct sip_msg* msg, async_ctx *ctx, +int ops_async_sql_query(struct sip_msg* msg, async_ctx *ctx, str *query, struct db_url *url, pvname_list_t *dest, int one_row) { int rc, read_fd; @@ -980,7 +980,7 @@ int ops_async_db_query(struct sip_msg* msg, async_ctx *ctx, /* No async capabilities - just run it in blocking mode */ if (!DB_CAPABILITY(url->dbf, DB_CAP_ASYNC_RAW_QUERY)) { - rc = db_query(url, msg, query, dest, one_row); + rc = sql_query(url, msg, query, dest, one_row); LM_DBG("sync query \"%.*s\" returned: %d\n", query->len, query->s, rc); ctx->resume_param = NULL; @@ -1008,10 +1008,10 @@ int ops_async_db_query(struct sip_msg* msg, async_ctx *ctx, memset(param, '\0', sizeof *param); ctx->resume_param = param; - ctx->resume_f = resume_async_dbquery; + ctx->resume_f = resume_async_sqlquery; /* if supported in the backend */ if (url->dbf.async_timeout != NULL) - ctx->timeout_f = timeout_async_dbquery; + ctx->timeout_f = timeout_async_sqlquery; param->output_avps = dest; param->hdl = url->hdl; @@ -1023,7 +1023,7 @@ int ops_async_db_query(struct sip_msg* msg, async_ctx *ctx, return 1; } -int timeout_async_dbquery(int fd, struct sip_msg *msg, void *_param) +int timeout_async_sqlquery(int fd, struct sip_msg *msg, void *_param) { query_async_param *param = (query_async_param *)_param; @@ -1033,7 +1033,7 @@ int timeout_async_dbquery(int fd, struct sip_msg *msg, void *_param) return -1; } -int resume_async_dbquery(int fd, struct sip_msg *msg, void *_param) +int resume_async_sqlquery(int fd, struct sip_msg *msg, void *_param) { db_res_t *res = NULL; query_async_param *param = (query_async_param *)_param; diff --git a/modules/sqlops/sqlops_impl.h b/modules/sqlops/sqlops_impl.h index d6423385ba5..73e8a40d771 100644 --- a/modules/sqlops/sqlops_impl.h +++ b/modules/sqlops/sqlops_impl.h @@ -31,7 +31,7 @@ #include "../../re.h" #include "../../parser/msg_parser.h" -#include "dbops_db.h" +#include "sqlops_db.h" @@ -40,20 +40,20 @@ #define AVP_IS_IN_DB (1<<7) /* DB flags */ -#define AVPOPS_DB_NAME_INT (1<<1) -#define AVPOPS_DB_VAL_INT (1<<0) +#define SQLOPS_DB_NAME_INT (1<<1) +#define SQLOPS_DB_VAL_INT (1<<0) /* operand flags */ -#define AVPOPS_VAL_NONE (1<<0) -#define AVPOPS_VAL_INT (1<<1) -#define AVPOPS_VAL_STR (1<<2) -#define AVPOPS_VAL_PVAR (1<<3) +#define SQLOPS_VAL_NONE (1<<0) +#define SQLOPS_VAL_INT (1<<1) +#define SQLOPS_VAL_STR (1<<2) +#define SQLOPS_VAL_PVAR (1<<3) /* flags for operation flags 24..31 */ -#define AVPOPS_FLAG_USER0 (1<<24) -#define AVPOPS_FLAG_DOMAIN0 (1<<25) -#define AVPOPS_FLAG_URI0 (1<<26) -#define AVPOPS_FLAG_UUID0 (1<<27) +#define SQLOPS_FLAG_USER0 (1<<24) +#define SQLOPS_FLAG_DOMAIN0 (1<<25) +#define SQLOPS_FLAG_URI0 (1<<26) +#define SQLOPS_FLAG_UUID0 (1<<27) /* container structer for Flag+Int_Spec_value parameter */ struct fis_param @@ -87,38 +87,38 @@ typedef struct _query_async_param void init_store_avps(str **db_columns); -int ops_db_avp_load (struct sip_msg* msg, struct fis_param *sp, +int ops_sql_avp_load (struct sip_msg* msg, struct fis_param *sp, struct db_param *dbp, struct db_url *url, int use_domain, str *prefix); -int ops_db_avp_delete(struct sip_msg* msg, struct fis_param *sp, +int ops_sql_avp_delete(struct sip_msg* msg, struct fis_param *sp, struct db_param *dbp, struct db_url *url, int use_domain); -int ops_db_avp_store(struct sip_msg* msg, struct fis_param *sp, +int ops_sql_avp_store(struct sip_msg* msg, struct fis_param *sp, struct db_param *dbp, struct db_url *url, int use_domain); -int ops_db_query(struct sip_msg* msg, str* query, +int ops_sql_query(struct sip_msg* msg, str* query, struct db_url *url, pvname_list_t* dest, int one_row); -int ops_db_api_select(struct db_url *url, struct sip_msg* msg, str *cols, +int ops_sql_api_select(struct db_url *url, struct sip_msg* msg, str *cols, str *table, str *filter, str *order, pvname_list_t* dest, int one_row); -int ops_db_api_update(struct db_url *url, struct sip_msg* msg, str *cols, +int ops_sql_api_update(struct db_url *url, struct sip_msg* msg, str *cols, str *table, str *filter); -int ops_db_api_insert(struct db_url *url, struct sip_msg* msg, str *table, +int ops_sql_api_insert(struct db_url *url, struct sip_msg* msg, str *table, str *cols); -int ops_db_api_delete(struct db_url *url, struct sip_msg* msg, +int ops_sql_api_delete(struct db_url *url, struct sip_msg* msg, str *table, str *filter); -int ops_db_api_replace(struct db_url *url, struct sip_msg* msg, str *table, +int ops_sql_api_replace(struct db_url *url, struct sip_msg* msg, str *table, str *cols); -int ops_async_db_query(struct sip_msg* msg, async_ctx *ctx, +int ops_async_sql_query(struct sip_msg* msg, async_ctx *ctx, str *query, struct db_url *url, pvname_list_t *dest, int one_row); -int resume_async_dbquery(int fd, struct sip_msg *msg, void *_param); -int timeout_async_dbquery(int fd, struct sip_msg *msg, void *_param); +int resume_async_sqlquery(int fd, struct sip_msg *msg, void *_param); +int timeout_async_sqlquery(int fd, struct sip_msg *msg, void *_param); #endif diff --git a/modules/sqlops/sqlops_parse.c b/modules/sqlops/sqlops_parse.c index e275ecc4db1..fe979b723e1 100644 --- a/modules/sqlops/sqlops_parse.c +++ b/modules/sqlops/sqlops_parse.c @@ -29,7 +29,7 @@ #include "../../dprint.h" #include "../../usr_avp.h" #include "../../mem/mem.h" -#include "dbops_parse.h" +#include "sqlops_parse.h" #define SCHEME_UUID_COL "uuid_col" @@ -78,7 +78,7 @@ int parse_avp_db(char *s, struct db_param *dbp, int allow_scheme) case 's': case 'S': case 'i': case 'I': case '*': case 'a': case 'A': - dbp->a.opd = AVPOPS_VAL_NONE; + dbp->a.opd = SQLOPS_VAL_NONE; break; default: LM_ERR("bad param - expected : *, s or i AVP flag\n"); @@ -97,7 +97,7 @@ int parse_avp_db(char *s, struct db_param *dbp, int allow_scheme) } } dbp->a.u.sval.pvp.pvn.u.isname.type |= (flags<<8)&0xff00; - dbp->a.type = AVPOPS_VAL_NONE; + dbp->a.type = SQLOPS_VAL_NONE; } else { s0.s = s; s0.len = strlen(s0.s); p = pv_parse_spec(&s0, &dbp->a.u.sval); @@ -106,14 +106,14 @@ int parse_avp_db(char *s, struct db_param *dbp, int allow_scheme) LM_ERR("bad param - expected : $avp(name) or int/str value\n"); return E_UNSPEC; } - dbp->a.type = AVPOPS_VAL_PVAR; + dbp->a.type = SQLOPS_VAL_PVAR; } /* optimize and keep the attribute name as str also to * speed up db querie builds */ - if (dbp->a.type == AVPOPS_VAL_PVAR) + if (dbp->a.type == SQLOPS_VAL_PVAR) { - dbp->a.opd = AVPOPS_VAL_PVAR; + dbp->a.opd = SQLOPS_VAL_PVAR; if(pv_has_iname(&dbp->a.u.sval)) { s1 = get_avp_name_id(dbp->a.u.sval.pvp.pvn.u.isname.name.n); @@ -131,7 +131,7 @@ int parse_avp_db(char *s, struct db_param *dbp, int allow_scheme) memcpy(dbp->sa.s, s1->s, s1->len); dbp->sa.len = s1->len; dbp->sa.s[dbp->sa.len] = 0; - dbp->a.opd = AVPOPS_VAL_PVAR|AVPOPS_VAL_STR; + dbp->a.opd = SQLOPS_VAL_PVAR|SQLOPS_VAL_STR; } } @@ -150,7 +150,7 @@ int parse_avp_db(char *s, struct db_param *dbp, int allow_scheme) LM_ERR("function doesn't support DB schemes\n"); goto error; } - if (dbp->a.opd&AVPOPS_VAL_NONE) + if (dbp->a.opd&SQLOPS_VAL_NONE) { LM_ERR("inconsistent usage of " "DB scheme without complet specification of AVP name\n"); @@ -179,7 +179,7 @@ int parse_avp_db(char *s, struct db_param *dbp, int allow_scheme) goto error; } /* update scheme flags with AVP name type*/ - dbp->scheme->db_flags|=dbp->a.opd&AVPOPS_VAL_STR?AVP_NAME_STR:0; + dbp->scheme->db_flags|=dbp->a.opd&SQLOPS_VAL_STR?AVP_NAME_STR:0; } else { /* duplicate table str into the db_param struct */ pkg_str_dup( &dbp->table, &tmp); diff --git a/modules/sqlops/sqlops_parse.h b/modules/sqlops/sqlops_parse.h index b4d8a8448fb..e062af18f47 100644 --- a/modules/sqlops/sqlops_parse.h +++ b/modules/sqlops/sqlops_parse.h @@ -21,13 +21,13 @@ */ -#ifndef _DBOPS_PARSE_H_ -#define _DBOPS_PARSE_H_ +#ifndef _SQLOPS_PARSE_H_ +#define _SQLOPS_PARSE_H_ #include "../../str.h" #include "../../usr_avp.h" -#include "dbops_impl.h" -#include "dbops_db.h" +#include "sqlops_impl.h" +#include "sqlops_db.h" int parse_avp_db(char *s, struct db_param *dbp, int allow_scheme); From 5053c580b9d9f0f261583ce27b754c42e0d5bef8 Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Wed, 13 Mar 2024 13:15:29 +0200 Subject: [PATCH 16/79] [sqlops] migrate DB stuff from DBops to SQLops --- ...opensips-dbops.xml => opensips-sqlops.xml} | 2 +- db/schema/usr_preferences.xml | 2 +- .../{dbops-create.sql => sqlops-create.sql} | 0 .../{dbops-create.sql => sqlops-create.sql} | 0 scripts/pi_http/pi_framework.xml | 132 +++++++++--------- scripts/pi_http/{dbops-mod => sqlops-mod} | 0 scripts/pi_http/{dbops-table => sqlops-table} | 0 .../{dbops-create.sql => sqlops-create.sql} | 0 .../{dbops-create.sql => sqlops-create.sql} | 0 9 files changed, 68 insertions(+), 68 deletions(-) rename db/schema/{opensips-dbops.xml => opensips-sqlops.xml} (91%) rename scripts/mysql/{dbops-create.sql => sqlops-create.sql} (100%) rename scripts/oracle/{dbops-create.sql => sqlops-create.sql} (100%) rename scripts/pi_http/{dbops-mod => sqlops-mod} (100%) rename scripts/pi_http/{dbops-table => sqlops-table} (100%) rename scripts/postgres/{dbops-create.sql => sqlops-create.sql} (100%) rename scripts/sqlite/{dbops-create.sql => sqlops-create.sql} (100%) diff --git a/db/schema/opensips-dbops.xml b/db/schema/opensips-sqlops.xml similarity index 91% rename from db/schema/opensips-dbops.xml rename to db/schema/opensips-sqlops.xml index ca38f977cb0..64080856cb9 100644 --- a/db/schema/opensips-dbops.xml +++ b/db/schema/opensips-sqlops.xml @@ -7,6 +7,6 @@ ]> - DB Operations + SQL Operations diff --git a/db/schema/usr_preferences.xml b/db/schema/usr_preferences.xml index ed9f5cdff6a..0858fd0274b 100644 --- a/db/schema/usr_preferences.xml +++ b/db/schema/usr_preferences.xml @@ -12,7 +12,7 @@ 3 &MYSQL_TABLE_TYPE; - This table us used by the DBops module to implement Attribute Value Pairs (AVP's). More information about the DBops module can be found at: &OPENSIPS_MOD_DOC;dbops.html + This table us used by the SQLops module to implement Attribute Value Pairs (AVP's). More information about the SQLops module can be found at: &OPENSIPS_MOD_DOC;sqlops.html diff --git a/scripts/mysql/dbops-create.sql b/scripts/mysql/sqlops-create.sql similarity index 100% rename from scripts/mysql/dbops-create.sql rename to scripts/mysql/sqlops-create.sql diff --git a/scripts/oracle/dbops-create.sql b/scripts/oracle/sqlops-create.sql similarity index 100% rename from scripts/oracle/dbops-create.sql rename to scripts/oracle/sqlops-create.sql diff --git a/scripts/pi_http/pi_framework.xml b/scripts/pi_http/pi_framework.xml index ce69b6688ff..b258a7a533f 100644 --- a/scripts/pi_http/pi_framework.xml +++ b/scripts/pi_http/pi_framework.xml @@ -113,19 +113,6 @@ start_tsDB_INT end_tsDB_INT - - - usr_preferences - mysql - idDB_INT - uuidDB_STR - usernameDB_STR - domainDB_STR - attributeDB_STR - typeDB_INT - valueDB_STR - last_modifiedDB_DATETIME - b2b_entities @@ -986,6 +973,19 @@ lnameDB_STR descriptionDB_STR + + + usr_preferences + mysql + idDB_INT + uuidDB_STR + usernameDB_STR + domainDB_STR + attributeDB_STR + typeDB_INT + valueDB_STR + last_modifiedDB_DATETIME + version @@ -1492,59 +1492,6 @@ - - usr_preferences - show - usr_preferences - DB_QUERY - - idupdate - uuid - username - domain - attribute - type - value - last_modified - - - add - usr_preferences - DB_INSERT - - uuid - username - domain - attribute - type - value - last_modified - - - update - usr_preferences - DB_UPDATE - - id= - - - uuid - username - domain - attribute - type - value - last_modified - - - delete - usr_preferences - DB_DELETE - - id= - - - b2b_entities show @@ -4882,6 +4829,59 @@ + + usr_preferences + show + usr_preferences + DB_QUERY + + idupdate + uuid + username + domain + attribute + type + value + last_modified + + + add + usr_preferences + DB_INSERT + + uuid + username + domain + attribute + type + value + last_modified + + + update + usr_preferences + DB_UPDATE + + id= + + + uuid + username + domain + attribute + type + value + last_modified + + + delete + usr_preferences + DB_DELETE + + id= + + + version show diff --git a/scripts/pi_http/dbops-mod b/scripts/pi_http/sqlops-mod similarity index 100% rename from scripts/pi_http/dbops-mod rename to scripts/pi_http/sqlops-mod diff --git a/scripts/pi_http/dbops-table b/scripts/pi_http/sqlops-table similarity index 100% rename from scripts/pi_http/dbops-table rename to scripts/pi_http/sqlops-table diff --git a/scripts/postgres/dbops-create.sql b/scripts/postgres/sqlops-create.sql similarity index 100% rename from scripts/postgres/dbops-create.sql rename to scripts/postgres/sqlops-create.sql diff --git a/scripts/sqlite/dbops-create.sql b/scripts/sqlite/sqlops-create.sql similarity index 100% rename from scripts/sqlite/dbops-create.sql rename to scripts/sqlite/sqlops-create.sql From 71fd974fe6bcf3c389335a3160f5bd8fc3e1f551 Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Wed, 13 Mar 2024 13:27:17 +0200 Subject: [PATCH 17/79] [sqlops] migrate name from dbops to sqlops --- doc/build-contrib.sh | 2 +- doc/doxygen/opensips-doxygen | 2 +- menuconfig/configs/opensips_loadbalancer.m4 | 4 ++-- menuconfig/configs/opensips_trunking.m4 | 4 ++-- modules/osp/doc/osp_admin.xml | 2 +- modules/perl/doc/perl_samples.xml | 2 +- packaging/debian/copyright | 2 +- packaging/freebsd/Makefile | 2 +- packaging/netbsd/PLIST | 4 ++-- packaging/openbsd/pkg/PLIST | 4 ++-- packaging/redhat_fedora/opensips.spec | 4 ++-- packaging/solaris/base-Prototype | 4 ++-- packaging/solaris/mysql-Prototype | 2 +- packaging/solaris/pgsql-Prototype | 2 +- packaging/solaris/prototype | 4 ++-- packaging/solaris/tls-Prototype | 4 ++-- packaging/suse/opensips.spec.SuSE | 4 ++-- usr_avp.h | 2 +- 18 files changed, 27 insertions(+), 27 deletions(-) diff --git a/doc/build-contrib.sh b/doc/build-contrib.sh index 9c3c756d4c7..028666daeef 100755 --- a/doc/build-contrib.sh +++ b/doc/build-contrib.sh @@ -242,7 +242,7 @@ fix_authors=( ["13e9a5cbe14050e622a3ef65cd34b72260a74f01"]="Kennard White" ["26599d25cbc140373a5c24759dce688235e57589"]="Anatoly Pidruchny" - # dbops + # sqlops ["37eba4b6d38f379a227040397c569f0d0fe99c9c"]="Kennard White" ["d129377f64f13e85ea0baf6d215092b4b4776f6e"]="Norman Brandinger" ["b9247c08af07662c6e712179dc57bcc5f16794aa"]="Kobi Eshun" diff --git a/doc/doxygen/opensips-doxygen b/doc/doxygen/opensips-doxygen index a946e9383e9..cf6362041e3 100644 --- a/doc/doxygen/opensips-doxygen +++ b/doc/doxygen/opensips-doxygen @@ -501,7 +501,7 @@ INPUT = ./ \ modules/auth \ modules/auth_aaa \ modules/auth_db \ - modules/dbops \ + modules/sqlops \ modules/b2b_entities \ modules/b2b_logic \ modules/b2b_sca \ diff --git a/menuconfig/configs/opensips_loadbalancer.m4 b/menuconfig/configs/opensips_loadbalancer.m4 index 1a116fd1998..3e7ccf76fac 100644 --- a/menuconfig/configs/opensips_loadbalancer.m4 +++ b/menuconfig/configs/opensips_loadbalancer.m4 @@ -82,8 +82,8 @@ modparam("mi_fifo", "fifo_mode", 0666) #### MYSQL module loadmodule "db_mysql.so" -#### AVPOPS module -loadmodule "dbops.so" +#### SQLOPS module +loadmodule "sqlops.so" #### ACCounting module loadmodule "acc.so" diff --git a/menuconfig/configs/opensips_trunking.m4 b/menuconfig/configs/opensips_trunking.m4 index 2fad0830c82..63ed96ea482 100644 --- a/menuconfig/configs/opensips_trunking.m4 +++ b/menuconfig/configs/opensips_trunking.m4 @@ -82,8 +82,8 @@ modparam("mi_fifo", "fifo_mode", 0666) #### MYSQL module loadmodule "db_mysql.so" -#### AVPOPS module -loadmodule "dbops.so" +#### SQLOPS module +loadmodule "sqlops.so" #### DYNAMIC ROUTING module loadmodule "drouting.so" diff --git a/modules/osp/doc/osp_admin.xml b/modules/osp/doc/osp_admin.xml index ac941a3695d..1610cab18d2 100644 --- a/modules/osp/doc/osp_admin.xml +++ b/modules/osp/doc/osp_admin.xml @@ -26,7 +26,7 @@ auth -- Authentication Framework module - dbops -- DB operation module + sqlops -- SQL operation module maxfwd -- Max-Forward processor module diff --git a/modules/perl/doc/perl_samples.xml b/modules/perl/doc/perl_samples.xml index 34fb76d53f9..c1d01b77b3e 100644 --- a/modules/perl/doc/perl_samples.xml +++ b/modules/perl/doc/perl_samples.xml @@ -149,7 +149,7 @@ You might notice that there is no particular function for setting pseudo variables; you may use - the exported functions from the dbops module, though. + the exported functions from the sqlops module, though.
diff --git a/packaging/debian/copyright b/packaging/debian/copyright index f23cbcb6c26..13b1063d2df 100644 --- a/packaging/debian/copyright +++ b/packaging/debian/copyright @@ -236,7 +236,7 @@ Copyright: 2009, Voice System 2001-2004, FhG FOKUS License: GPL-2+ -Files: modules/dbops/* +Files: modules/sqlops/* Copyright: 2004-2009, Voice Sistem SRL License: GPL-2+ diff --git a/packaging/freebsd/Makefile b/packaging/freebsd/Makefile index d6d7486c445..bd8b368c5dc 100644 --- a/packaging/freebsd/Makefile +++ b/packaging/freebsd/Makefile @@ -28,7 +28,7 @@ PLIST_FILES= sbin/opensips PLIST_DIRS= lib/opensips/modules lib/opensips -MODULES= acc alias_db auth auth_db auth_diameter dbops benchmark \ +MODULES= acc alias_db auth auth_db auth_diameter sqlops benchmark \ cfgutils dbtext dialog dispatcher diversion domain \ domainpolicy enum exec flatstore gflags group imc \ mangler maxfwd mediaproxy mi_datagram mi_fifo msilo nathelper \ diff --git a/packaging/netbsd/PLIST b/packaging/netbsd/PLIST index a972ee5f21c..5ea56107487 100644 --- a/packaging/netbsd/PLIST +++ b/packaging/netbsd/PLIST @@ -5,7 +5,7 @@ lib/opensips/modules/alias_db.so lib/opensips/modules/auth.so lib/opensips/modules/auth_aaa.so lib/opensips/modules/auth_db.so -lib/opensips/modules/dbops.so +lib/opensips/modules/sqlops.so lib/opensips/modules/benchmark.so lib/opensips/modules/call_control.so lib/opensips/modules/cfgutils.so @@ -68,7 +68,7 @@ share/doc/opensips/README.alias_db share/doc/opensips/README.auth share/doc/opensips/README.auth_aaa share/doc/opensips/README.auth_db -share/doc/opensips/README.dbops +share/doc/opensips/README.sqlops share/doc/opensips/README.benchmark share/doc/opensips/README.call_control share/doc/opensips/README.cfgutils diff --git a/packaging/openbsd/pkg/PLIST b/packaging/openbsd/pkg/PLIST index 7332aa4b91a..030edeedc26 100644 --- a/packaging/openbsd/pkg/PLIST +++ b/packaging/openbsd/pkg/PLIST @@ -5,7 +5,7 @@ lib/opensips/modules/alias_db.so lib/opensips/modules/auth.so lib/opensips/modules/auth_aaa.so lib/opensips/modules/auth_db.so -lib/opensips/modules/dbops.so +lib/opensips/modules/sqlops.so lib/opensips/modules/benchmark.so lib/opensips/modules/call_control.so lib/opensips/modules/cfgutils.so @@ -68,7 +68,7 @@ share/doc/opensips/README.alias_db share/doc/opensips/README.auth share/doc/opensips/README.auth_aaa share/doc/opensips/README.auth_db -share/doc/opensips/README.dbops +share/doc/opensips/README.sqlops share/doc/opensips/README.benchmark share/doc/opensips/README.call_control share/doc/opensips/README.cfgutils diff --git a/packaging/redhat_fedora/opensips.spec b/packaging/redhat_fedora/opensips.spec index 92efd4cd842..4d8efe99303 100644 --- a/packaging/redhat_fedora/opensips.spec +++ b/packaging/redhat_fedora/opensips.spec @@ -1056,7 +1056,7 @@ fi %{_libdir}/opensips/modules/alias_db.so %{_libdir}/opensips/modules/auth_aaa.so %{_libdir}/opensips/modules/auth_db.so -%{_libdir}/opensips/modules/dbops.so +%{_libdir}/opensips/modules/sqlops.so %{_libdir}/opensips/modules/b2b_entities.so %{_libdir}/opensips/modules/b2b_logic.so %{_libdir}/opensips/modules/b2b_sca.so @@ -1154,7 +1154,7 @@ fi %doc docdir/README.alias_db %doc docdir/README.auth_aaa %doc docdir/README.auth_db -%doc docdir/README.dbops +%doc docdir/README.sqlops %doc docdir/README.b2b_entities %doc docdir/README.b2b_logic %doc docdir/README.b2b_sca diff --git a/packaging/solaris/base-Prototype b/packaging/solaris/base-Prototype index f8018754da1..4d1efed93f7 100644 --- a/packaging/solaris/base-Prototype +++ b/packaging/solaris/base-Prototype @@ -56,7 +56,7 @@ f none /opt/opensips/lib64/opensips/modules/alias_db.so 0755 root root f none /opt/opensips/lib64/opensips/modules/auth.so 0755 root root f none /opt/opensips/lib64/opensips/modules/auth_db.so 0755 root root f none /opt/opensips/lib64/opensips/modules/auth_diameter.so 0755 root root -f none /opt/opensips/lib64/opensips/modules/dbops.so 0755 root root +f none /opt/opensips/lib64/opensips/modules/sqlops.so 0755 root root f none /opt/opensips/lib64/opensips/modules/benchmark.so 0755 root root f none /opt/opensips/lib64/opensips/modules/call_control.so 0755 root root f none /opt/opensips/lib64/opensips/modules/cfgutils.so 0755 root root @@ -134,7 +134,7 @@ f none /opt/opensips/README.alias_db 0644 root root f none /opt/opensips/README.auth 0644 root root f none /opt/opensips/README.auth_db 0644 root root f none /opt/opensips/README.auth_diameter 0644 root root -f none /opt/opensips/README.dbops 0644 root root +f none /opt/opensips/README.sqlops 0644 root root f none /opt/opensips/README.benchmark 0644 root root f none /opt/opensips/README.call_control 0644 root root f none /opt/opensips/README.cfgutils 0644 root root diff --git a/packaging/solaris/mysql-Prototype b/packaging/solaris/mysql-Prototype index 23597ab57f0..84e1a12cc18 100644 --- a/packaging/solaris/mysql-Prototype +++ b/packaging/solaris/mysql-Prototype @@ -12,7 +12,7 @@ d none /opt/opensips/mysql 0755 root root f none /opt/opensips/mysql/acc-create.sql 0644 root root f none /opt/opensips/mysql/alias_db-create.sql 0644 root root f none /opt/opensips/mysql/auth_db-create.sql 0644 root root -f none /opt/opensips/mysql/dbops-create.sql 0644 root root +f none /opt/opensips/mysql/sqlops-create.sql 0644 root root f none /opt/opensips/mysql/carrierroute-create.sql 0644 root root f none /opt/opensips/mysql/closeddial-create.sql 0644 root root f none /opt/opensips/mysql/cpl_create.sql 0644 root root diff --git a/packaging/solaris/pgsql-Prototype b/packaging/solaris/pgsql-Prototype index 391eefc759a..272dec5965c 100644 --- a/packaging/solaris/pgsql-Prototype +++ b/packaging/solaris/pgsql-Prototype @@ -11,7 +11,7 @@ d none /opt/opensips/postgres 0755 root root f none /opt/opensips/postgres/acc-create.sql 0644 root root f none /opt/opensips/postgres/alias_db-create.sql 0644 root root f none /opt/opensips/postgres/auth_db-create.sql 0644 root root -f none /opt/opensips/postgres/dbops-create.sql 0644 root root +f none /opt/opensips/postgres/sqlops-create.sql 0644 root root f none /opt/opensips/postgres/carrierroute-create.sql 0644 root root f none /opt/opensips/postgres/cpl_create.sql 0644 root root f none /opt/opensips/postgres/dialog-create.sql 0644 root root diff --git a/packaging/solaris/prototype b/packaging/solaris/prototype index 9439f396ea0..ccd1aba8e2f 100644 --- a/packaging/solaris/prototype +++ b/packaging/solaris/prototype @@ -14,7 +14,7 @@ f none lib/opensips/modules/alias_db.so 0755 bin bin f none lib/opensips/modules/auth.so 0755 bin bin f none lib/opensips/modules/auth_db.so 0755 bin bin f none lib/opensips/modules/auth_diameter.so 0755 bin bin -f none lib/opensips/modules/dbops.so 0755 bin bin +f none lib/opensips/modules/sqlops.so 0755 bin bin f none lib/opensips/modules/dbtext.so 0755 bin bin f none lib/opensips/modules/dialog.so 0755 bin bin f none lib/opensips/modules/dispatcher.so 0755 bin bin @@ -65,7 +65,7 @@ f none doc/opensips/README.alias_db 0644 bin bin f none doc/opensips/README.auth 0644 bin bin f none doc/opensips/README.auth_db 0644 bin bin f none doc/opensips/README.auth_diameter 0644 bin bin -f none doc/opensips/README.dbops 0644 bin bin +f none doc/opensips/README.sqlops 0644 bin bin f none doc/opensips/README.dbtext 0644 bin bin f none doc/opensips/README.dialog 0644 bin bin f none doc/opensips/README.dispatcher 0644 bin bin diff --git a/packaging/solaris/tls-Prototype b/packaging/solaris/tls-Prototype index e9ffaf2349e..49d98f146d2 100644 --- a/packaging/solaris/tls-Prototype +++ b/packaging/solaris/tls-Prototype @@ -74,7 +74,7 @@ f none /opt/opensips/lib64/opensips/modules/alias_db.so 0755 root root f none /opt/opensips/lib64/opensips/modules/auth.so 0755 root root f none /opt/opensips/lib64/opensips/modules/auth_db.so 0755 root root f none /opt/opensips/lib64/opensips/modules/auth_diameter.so 0755 root root -f none /opt/opensips/lib64/opensips/modules/dbops.so 0755 root root +f none /opt/opensips/lib64/opensips/modules/sqlops.so 0755 root root f none /opt/opensips/lib64/opensips/modules/benchmark.so 0755 root root f none /opt/opensips/lib64/opensips/modules/call_control.so 0755 root root f none /opt/opensips/lib64/opensips/modules/cfgutils.so 0755 root root @@ -152,7 +152,7 @@ f none /opt/opensips/README.alias_db 0644 root root f none /opt/opensips/README.auth 0644 root root f none /opt/opensips/README.auth_db 0644 root root f none /opt/opensips/README.auth_diameter 0644 root root -f none /opt/opensips/README.dbops 0644 root root +f none /opt/opensips/README.sqlops 0644 root root f none /opt/opensips/README.benchmark 0644 root root f none /opt/opensips/README.call_control 0644 root root f none /opt/opensips/README.cfgutils 0644 root root diff --git a/packaging/suse/opensips.spec.SuSE b/packaging/suse/opensips.spec.SuSE index 59b1bdcf17a..0ae72fe0541 100644 --- a/packaging/suse/opensips.spec.SuSE +++ b/packaging/suse/opensips.spec.SuSE @@ -231,7 +231,7 @@ sbin/insserv etc/init.d/ %doc %{_docdir}/opensips/README.auth_aaa %doc %{_docdir}/opensips/README.auth_db %doc %{_docdir}/opensips/README.auth_diameter -%doc %{_docdir}/opensips/README.dbops +%doc %{_docdir}/opensips/README.sqlops %doc %{_docdir}/opensips/README.benchmark %doc %{_docdir}/opensips/README.call_control %doc %{_docdir}/opensips/README.cfgutils @@ -296,7 +296,7 @@ sbin/insserv etc/init.d/ %{_libdir}/opensips/modules/auth_aaa.so %{_libdir}/opensips/modules/auth_db.so %{_libdir}/opensips/modules/auth_diameter.so -%{_libdir}/opensips/modules/dbops.so +%{_libdir}/opensips/modules/sqlops.so %{_libdir}/opensips/modules/benchmark.so %{_libdir}/opensips/modules/call_control.so %{_libdir}/opensips/modules/cfgutils.so diff --git a/usr_avp.h b/usr_avp.h index f3cc81538a5..7c4630da89e 100644 --- a/usr_avp.h +++ b/usr_avp.h @@ -41,7 +41,7 @@ * 0 avp_core avp has a string name * 1 avp_core avp has a string value * 2 core contact avp qvalue change - * 7 dbops module avp was loaded from DB + * 7 sqlops module avp was loaded from DB * */ From 0793d71878ee9bf41f25e3e1d653cfc4933b401b Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Wed, 13 Mar 2024 13:55:44 +0200 Subject: [PATCH 18/79] [sqlops] improve prepare statements support Log warning when the buffer for building query ID is not large enaugh; Also point to the modparam to adjust the buffer size For "select", take the "order by" col into consideration too, when building the query ID --- modules/sqlops/sqlops_db.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/modules/sqlops/sqlops_db.c b/modules/sqlops/sqlops_db.c index 3633358531a..778a9bd1511 100644 --- a/modules/sqlops/sqlops_db.c +++ b/modules/sqlops/sqlops_db.c @@ -629,7 +629,7 @@ static inline int _json_to_filters(cJSON *Jfilter, } static str _query_id = {NULL,0}; -static inline str* _query_id_start( str *table) +static inline str* _query_id_start( str *table, str *order) { if (_query_id.s==NULL) { if ( !query_id_max_len || @@ -640,6 +640,13 @@ static inline str* _query_id_start( str *table) memcpy( _query_id.s + _query_id.len, table->s, table->len); _query_id.len += table->len; + + if (order) { + *(_query_id.s + _query_id.len++) = '|'; + memcpy( _query_id.s + _query_id.len, order->s, order->len); + _query_id.len += order->len; + } + *(_query_id.s + _query_id.len++) = '^'; return &_query_id; @@ -652,6 +659,9 @@ static inline str* _query_id_add_cols( db_key_t *_c, int _nc) for ( i=0 ; i<_nc ; i++) { if (query_id_max_len-_query_id.len < _c[i]->len+2 /* both |^ */) { _query_id.len = 0; // reset + LM_WARN("buffer too short (%d) for building the query ID for " + "prepare statement - consider increasing its value " + "via 'ps_id_max_buf_len' modparam\n", query_id_max_len); return NULL; } memcpy( _query_id.s + _query_id.len, _c[i]->s, _c[i]->len); @@ -671,6 +681,9 @@ static inline str* _query_id_add_filter(db_key_t* _k, db_op_t* _o, int _nk) l = strlen( _o[i] ); if (query_id_max_len-_query_id.len < _k[i]->len+l+3 /* all %|^ */) { _query_id.len = 0; // reset + LM_WARN("buffer too short (%d) for building the query ID for " + "prepare statement - consider increasing its value " + "via 'ps_id_max_buf_len' modparam\n", query_id_max_len); return NULL; } memcpy( _query_id.s + _query_id.len, _k[i]->s, _k[i]->len); @@ -730,7 +743,7 @@ int sql_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, if (set_table( url, table ,"API select")!=0) return -1; - if ( _query_id_start(table)==NULL || + if ( _query_id_start(table,order)==NULL || (cols && (id=_query_id_add_cols(cols,nc))==NULL) || (keys && (id=_query_id_add_filter(keys,ops,nk))==NULL) ) { LM_ERR("failed to build PS id\n"); @@ -815,7 +828,7 @@ int sql_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, if (set_table( url, table ,"API update")!=0) return -1; - if ( _query_id_start(table)==NULL || + if ( _query_id_start(table,NULL)==NULL || (id=_query_id_add_filter(ukeys,uops,unk))==NULL || (keys && (id=_query_id_add_filter(keys,ops,nk))==NULL) ) { LM_ERR("failed to build PS id\n"); @@ -865,7 +878,7 @@ int sql_api_insert(struct db_url *url, struct sip_msg* msg, str *table, return -1; /* set the PS to be used */ - if ( _query_id_start(table)==NULL || + if ( _query_id_start(table,NULL)==NULL || (id=_query_id_add_filter(ukeys,uops,unk))==NULL ) { LM_ERR("failed to build PS id\n"); } else { @@ -921,7 +934,7 @@ int sql_api_delete(struct db_url *url, struct sip_msg* msg, return -1; /* set the PS to be used */ - if ( _query_id_start(table)==NULL || + if ( _query_id_start(table,NULL)==NULL || (keys && (id=_query_id_add_filter(keys,ops,nk))==NULL) ) { LM_ERR("failed to build PS id\n"); } else { @@ -970,7 +983,7 @@ int sql_api_replace(struct db_url *url, struct sip_msg* msg, str *table, return -1; /* set the PS to be used */ - if ( _query_id_start(table)==NULL || + if ( _query_id_start(table,NULL)==NULL || (id=_query_id_add_filter(ukeys,uops,unk))==NULL ) { LM_ERR("failed to build PS id\n"); } else { From de922d293c217bb628bb602d74dc3fa198d4cc82 Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Wed, 13 Mar 2024 14:33:43 +0200 Subject: [PATCH 19/79] [db] added new DB_CAP_PREPARED_STMT capability to identify the db backends able to provide prepared statements. DB_MYSQL is for now the only condidate --- db/db_cap.h | 1 + modules/db_mysql/db_mysql.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/db/db_cap.h b/db/db_cap.h index 704ebcce205..635eada3541 100644 --- a/db/db_cap.h +++ b/db/db_cap.h @@ -48,6 +48,7 @@ typedef enum db_cap { DB_CAP_LAST_INSERTED_ID = 1 << 8, /**< driver can return the ID of the last insert operation */ DB_CAP_INSERT_UPDATE = 1 << 9, /**< driver can insert data into database and update on duplicate */ DB_CAP_MULTIPLE_INSERT = 1 << 10, /**< driver can insert multiple rows at once */ + DB_CAP_PREPARED_STMT = 1 << 11, /**< driver supports prep statements */ } db_cap_t; diff --git a/modules/db_mysql/db_mysql.c b/modules/db_mysql/db_mysql.c index 142e24b0904..1bba108bac5 100644 --- a/modules/db_mysql/db_mysql.c +++ b/modules/db_mysql/db_mysql.c @@ -179,7 +179,7 @@ int db_mysql_bind_api(const str* mod, db_func_t *dbb) dbb->async_free_result = db_mysql_async_free_result; dbb->async_timeout = db_mysql_async_timeout; - dbb->cap |= DB_CAP_MULTIPLE_INSERT; + dbb->cap |= DB_CAP_MULTIPLE_INSERT|DB_CAP_PREPARED_STMT; return 0; } From 9ec3a02e3c49ce60e3fda59b10f05a8479fdd4c9 Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Wed, 13 Mar 2024 14:36:16 +0200 Subject: [PATCH 20/79] [sqlops] improve the prepared statements support II Use the new DB_CAP_PREPARED_STMT capability to test if the backend support statements and build the query ID only if so. --- modules/sqlops/sqlops_db.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/modules/sqlops/sqlops_db.c b/modules/sqlops/sqlops_db.c index 778a9bd1511..d8612cabadc 100644 --- a/modules/sqlops/sqlops_db.c +++ b/modules/sqlops/sqlops_db.c @@ -743,10 +743,11 @@ int sql_api_select(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, if (set_table( url, table ,"API select")!=0) return -1; - if ( _query_id_start(table,order)==NULL || + if ( !DB_CAPABILITY(url->dbf, DB_CAP_PREPARED_STMT) || + _query_id_start(table,order)==NULL || (cols && (id=_query_id_add_cols(cols,nc))==NULL) || (keys && (id=_query_id_add_filter(keys,ops,nk))==NULL) ) { - LM_ERR("failed to build PS id\n"); + LM_DBG("not using PS\n"); } else { LM_DBG("PS id is <%.*s>\n",id->len,id->s); if (ps_map!=NULL || (ps_map=map_create(0))!=NULL) { @@ -828,10 +829,11 @@ int sql_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, if (set_table( url, table ,"API update")!=0) return -1; - if ( _query_id_start(table,NULL)==NULL || + if ( !DB_CAPABILITY(url->dbf, DB_CAP_PREPARED_STMT) || + _query_id_start(table,NULL)==NULL || (id=_query_id_add_filter(ukeys,uops,unk))==NULL || (keys && (id=_query_id_add_filter(keys,ops,nk))==NULL) ) { - LM_ERR("failed to build PS id\n"); + LM_DBG("not using PS\n"); } else { LM_DBG("PS id is <%.*s>\n",id->len,id->s); if (ps_map!=NULL || (ps_map=map_create(0))!=NULL) { @@ -878,9 +880,10 @@ int sql_api_insert(struct db_url *url, struct sip_msg* msg, str *table, return -1; /* set the PS to be used */ - if ( _query_id_start(table,NULL)==NULL || + if ( !DB_CAPABILITY(url->dbf, DB_CAP_PREPARED_STMT) || + _query_id_start(table,NULL)==NULL || (id=_query_id_add_filter(ukeys,uops,unk))==NULL ) { - LM_ERR("failed to build PS id\n"); + LM_DBG("not using PS\n"); } else { LM_DBG("PS id is <%.*s>\n",id->len,id->s); if (ps_map!=NULL || (ps_map=map_create(0))!=NULL) { @@ -934,9 +937,10 @@ int sql_api_delete(struct db_url *url, struct sip_msg* msg, return -1; /* set the PS to be used */ - if ( _query_id_start(table,NULL)==NULL || + if ( !DB_CAPABILITY(url->dbf, DB_CAP_PREPARED_STMT) || + _query_id_start(table,NULL)==NULL || (keys && (id=_query_id_add_filter(keys,ops,nk))==NULL) ) { - LM_ERR("failed to build PS id\n"); + LM_DBG("not using PS\n"); } else { LM_DBG("PS id is <%.*s>\n",id->len,id->s); if (ps_map!=NULL || (ps_map=map_create(0))!=NULL) { @@ -983,9 +987,10 @@ int sql_api_replace(struct db_url *url, struct sip_msg* msg, str *table, return -1; /* set the PS to be used */ - if ( _query_id_start(table,NULL)==NULL || + if ( !DB_CAPABILITY(url->dbf, DB_CAP_PREPARED_STMT) || + _query_id_start(table,NULL)==NULL || (id=_query_id_add_filter(ukeys,uops,unk))==NULL ) { - LM_ERR("failed to build PS id\n"); + LM_DBG("not using PS\n"); } else { LM_DBG("PS id is <%.*s>\n",id->len,id->s); if (ps_map!=NULL || (ps_map=map_create(0))!=NULL) { From f277fa881bce3e214af32ccc4ed352fb9d6072cc Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Wed, 13 Mar 2024 18:30:07 +0200 Subject: [PATCH 21/79] Contributors: Provision recent module renames --- doc/build-contrib.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/build-contrib.sh b/doc/build-contrib.sh index 028666daeef..d28a9d25bd2 100755 --- a/doc/build-contrib.sh +++ b/doc/build-contrib.sh @@ -617,6 +617,8 @@ mod_renames=( [event_stream]=event_jsonrpc [b2b_logic]=b2b_logic:1605638778 [b2b_logic_xml]=b2b_logic::1605638778 + [sqlops]=dbops + [dbops]=avpops ) mk_git_handle() { From b067f8cc1d0f5094fc556e60283179d370e7b9a5 Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Thu, 14 Mar 2024 15:05:10 +0200 Subject: [PATCH 22/79] Fix several mod_destroy() prototypes --- modules/httpd/httpd.c | 5 ++--- modules/mi_datagram/mi_datagram.c | 13 ++++--------- modules/mi_fifo/mi_fifo.c | 12 ++++-------- modules/mi_html/mi_html.c | 5 ++--- modules/mi_http/mi_http.c | 5 ++--- modules/mi_xmlrpc_ng/mi_xmlrpc_http.c | 5 ++--- modules/pi_http/pi_http.c | 5 ++--- 7 files changed, 18 insertions(+), 32 deletions(-) diff --git a/modules/httpd/httpd.c b/modules/httpd/httpd.c index e6a6fec0dd3..eadd98a48b5 100644 --- a/modules/httpd/httpd.c +++ b/modules/httpd/httpd.c @@ -51,7 +51,7 @@ /* module functions */ static int mod_init(); -static int destroy(void); +static void destroy(void); static mi_response_t *mi_list_root_path(const mi_params_t *params, struct mi_handler *async_hdl); @@ -187,7 +187,7 @@ static int mod_init(void) } -int destroy(void) +static void destroy(void) { struct httpd_cb *cb = httpd_cb_list; @@ -198,7 +198,6 @@ int destroy(void) shm_free(cb); cb = httpd_cb_list; } - return 0; } diff --git a/modules/mi_datagram/mi_datagram.c b/modules/mi_datagram/mi_datagram.c index 5cecc1a4c0e..ade3a3c57d8 100644 --- a/modules/mi_datagram/mi_datagram.c +++ b/modules/mi_datagram/mi_datagram.c @@ -60,7 +60,7 @@ static int mi_mod_init(void); static int mi_child_init(int rank); -static int mi_destroy(void); +static void mi_destroy(void); static int pre_datagram_process(void); static int post_datagram_process(void); static void datagram_process(int rank); @@ -345,7 +345,7 @@ static int post_datagram_process(void) } -static int mi_destroy(void) +static void mi_destroy(void) { int n; struct stat filestat; @@ -357,16 +357,11 @@ static int mi_destroy(void) if (unlink(mi_socket)<0){ LM_ERR("cannot delete the socket (%s): %s\n", mi_socket, strerror(errno)); - goto error; + return; } } else if (n<0 && errno!=ENOENT) { LM_ERR("socket stat failed: %s\n", strerror(errno)); - goto error; + return; } } - - return 0; -error: - return -1; - } diff --git a/modules/mi_fifo/mi_fifo.c b/modules/mi_fifo/mi_fifo.c index d7ea6143fa8..5750f66ad7a 100644 --- a/modules/mi_fifo/mi_fifo.c +++ b/modules/mi_fifo/mi_fifo.c @@ -45,7 +45,7 @@ static int mi_mod_init(void); static void fifo_process(int rank); -static int mi_destroy(void); +static void mi_destroy(void); /* FIFO server vars */ /* FIFO name */ @@ -228,7 +228,7 @@ static void fifo_process(int rank) } -static int mi_destroy(void) +static void mi_destroy(void) { int n; struct stat filestat; @@ -240,15 +240,11 @@ static int mi_destroy(void) if (unlink(mi_fifo)<0){ LM_ERR("cannot delete the fifo (%s): %s\n", mi_fifo, strerror(errno)); - goto error; + return; } } else if (n<0 && errno!=ENOENT) { LM_ERR("FIFO stat failed: %s\n", strerror(errno)); - goto error; + return; } - - return 0; -error: - return -1; } diff --git a/modules/mi_html/mi_html.c b/modules/mi_html/mi_html.c index 4b0d69491bc..15e3ed377c4 100644 --- a/modules/mi_html/mi_html.c +++ b/modules/mi_html/mi_html.c @@ -34,7 +34,7 @@ /* module functions */ static int mod_init(); -static int destroy(void); +static void destroy(void); int mi_http_answer_to_connection (void *cls, void *connection, const char *url, const char *method, const char *version, const char *upload_data, @@ -177,10 +177,9 @@ static int mod_init(void) } -int destroy(void) +static void destroy(void) { mi_http_destroy_async_lock(); - return 0; } diff --git a/modules/mi_http/mi_http.c b/modules/mi_http/mi_http.c index 1f9dffc72e2..685e12386cb 100644 --- a/modules/mi_http/mi_http.c +++ b/modules/mi_http/mi_http.c @@ -35,7 +35,7 @@ /* module functions */ static int mod_init(); -static int destroy(void); +static void destroy(void); int mi_json_answer_to_connection (void *cls, void *connection, const char *url, const char *method, const char *version, const char *upload_data, @@ -169,10 +169,9 @@ static int mod_init(void) } -int destroy(void) +static void destroy(void) { mi_json_destroy_async_lock(); - return 0; } diff --git a/modules/mi_xmlrpc_ng/mi_xmlrpc_http.c b/modules/mi_xmlrpc_ng/mi_xmlrpc_http.c index f99fcb8634f..80ad95fba00 100644 --- a/modules/mi_xmlrpc_ng/mi_xmlrpc_http.c +++ b/modules/mi_xmlrpc_ng/mi_xmlrpc_http.c @@ -45,7 +45,7 @@ str xml_strerr[] = { /* module functions */ static int mod_init(); -static int destroy(void); +static void destroy(void); int mi_xmlrpc_http_answer_to_connection (void *cls, void *connection, const char *url, const char *method, const char *version, const char *upload_data, @@ -172,10 +172,9 @@ static int mod_init(void) } -int destroy(void) +static void destroy(void) { mi_xmlrpc_http_destroy_async_lock(); - return 0; } diff --git a/modules/pi_http/pi_http.c b/modules/pi_http/pi_http.c index c36865e3903..c3e1a11d67f 100644 --- a/modules/pi_http/pi_http.c +++ b/modules/pi_http/pi_http.c @@ -42,7 +42,7 @@ extern ph_framework_t *ph_framework_data; /* module functions */ static int mod_init(); static int child_init(int); -static int destroy(void); +static void destroy(void); int ph_answer_to_connection (void *cls, void *connection, const char *url, const char *method, const char *version, const char *upload_data, @@ -225,11 +225,10 @@ static int child_init(int rank) } -int destroy(void) +static void destroy(void) { destroy_http_db(ph_framework_data); ph_destroy_async_lock(); - return 0; } From 1dd05c946d2234a19b91cf6ec4f19a503818019f Mon Sep 17 00:00:00 2001 From: OpenSIPS Date: Sun, 17 Mar 2024 00:49:27 +0200 Subject: [PATCH 23/79] Rebuild documentation --- modules/db_mysql/README | 16 +-- modules/db_mysql/doc/contributors.xml | 28 ++--- modules/httpd/README | 6 +- modules/httpd/doc/contributors.xml | 16 +-- modules/mi_datagram/README | 10 +- modules/mi_datagram/doc/contributors.xml | 28 ++--- modules/mi_fifo/README | 8 +- modules/mi_fifo/doc/contributors.xml | 20 ++-- modules/mi_html/README | 20 ++-- modules/mi_html/doc/contributors.xml | 38 +++---- modules/mi_http/README | 16 +-- modules/mi_http/doc/contributors.xml | 28 ++--- modules/mi_xmlrpc_ng/README | 14 +-- modules/mi_xmlrpc_ng/doc/contributors.xml | 26 ++--- modules/osp/README | 6 +- modules/osp/doc/contributors.xml | 10 +- modules/perl/README | 6 +- modules/perl/doc/contributors.xml | 10 +- modules/pi_http/README | 10 +- modules/pi_http/doc/contributors.xml | 22 ++-- modules/sqlops/README | 51 ++++++++- modules/sqlops/doc/contributors.xml | 131 ++++++++++++++++++++-- 22 files changed, 340 insertions(+), 180 deletions(-) diff --git a/modules/db_mysql/README b/modules/db_mysql/README index e5d28ce0bc3..7448d9538b2 100644 --- a/modules/db_mysql/README +++ b/modules/db_mysql/README @@ -223,7 +223,7 @@ Chapter 2. Contributors commits^(2) and lines added/removed^(3) Name DevScore Commits Lines ++ Lines -- 1. Jan Janak (@janakj) 150 53 5336 3190 - 2. Bogdan-Andrei Iancu (@bogdan-iancu) 97 71 1573 792 + 2. Bogdan-Andrei Iancu (@bogdan-iancu) 98 72 1574 793 3. Henning Westerholt (@henningw) 57 30 693 1239 4. Liviu Chircu (@liviuchircu) 38 31 484 177 5. Daniel-Constantin Mierla (@miconda) 28 20 571 154 @@ -262,13 +262,13 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Vlad Paiu (@vladpaiu) Feb 2011 - Jul 2023 - 2. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 - 3. Liviu Chircu (@liviuchircu) Mar 2014 - Dec 2022 - 4. Vlad Patrascu (@rvlad-patrascu) Apr 2017 - May 2021 - 5. Walter Doekes (@wdoekes) Apr 2021 - Apr 2021 - 6. Razvan Crainea (@razvancrainea) Oct 2011 - Jan 2021 - 7. Bogdan-Andrei Iancu (@bogdan-iancu) Aug 2002 - Apr 2019 + 1. Bogdan-Andrei Iancu (@bogdan-iancu) Aug 2002 - Mar 2024 + 2. Vlad Paiu (@vladpaiu) Feb 2011 - Jul 2023 + 3. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 + 4. Liviu Chircu (@liviuchircu) Mar 2014 - Dec 2022 + 5. Vlad Patrascu (@rvlad-patrascu) Apr 2017 - May 2021 + 6. Walter Doekes (@wdoekes) Apr 2021 - Apr 2021 + 7. Razvan Crainea (@razvancrainea) Oct 2011 - Jan 2021 8. Peter Lemenkov (@lemenkov) Nov 2017 - Jun 2018 9. Augusto Caringi Jul 2017 - Jul 2017 10. Ovidiu Sas (@ovidiusas) Mar 2017 - Mar 2017 diff --git a/modules/db_mysql/doc/contributors.xml b/modules/db_mysql/doc/contributors.xml index d00623df109..436ac2efa6b 100644 --- a/modules/db_mysql/doc/contributors.xml +++ b/modules/db_mysql/doc/contributors.xml @@ -29,10 +29,10 @@ 2. Bogdan-Andrei Iancu (@bogdan-iancu) - 97 - 71 - 1573 - 792 + 98 + 72 + 1574 + 793 3. @@ -128,39 +128,39 @@ 1. + Bogdan-Andrei Iancu (@bogdan-iancu) + Aug 2002 - Mar 2024 + + + 2. Vlad Paiu (@vladpaiu) Feb 2011 - Jul 2023 - 2. + 3. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 - 3. + 4. Liviu Chircu (@liviuchircu) Mar 2014 - Dec 2022 - 4. + 5. Vlad Patrascu (@rvlad-patrascu) Apr 2017 - May 2021 - 5. + 6. Walter Doekes (@wdoekes) Apr 2021 - Apr 2021 - 6. + 7. Razvan Crainea (@razvancrainea) Oct 2011 - Jan 2021 - - 7. - Bogdan-Andrei Iancu (@bogdan-iancu) - Aug 2002 - Apr 2019 - 8. Peter Lemenkov (@lemenkov) diff --git a/modules/httpd/README b/modules/httpd/README index 270f8841754..759674c8b81 100644 --- a/modules/httpd/README +++ b/modules/httpd/README @@ -271,7 +271,7 @@ Chapter 3. Contributors Name DevScore Commits Lines ++ Lines -- 1. Ovidiu Sas (@ovidiusas) 47 30 1667 147 2. Razvan Crainea (@razvancrainea) 24 21 118 68 - 3. Liviu Chircu (@liviuchircu) 21 17 138 79 + 3. Liviu Chircu (@liviuchircu) 22 18 140 82 4. Bogdan-Andrei Iancu (@bogdan-iancu) 14 12 103 54 5. Vlad Patrascu (@rvlad-patrascu) 10 7 52 89 6. Ionut Ionita (@ionutrazvanionita) 8 6 65 21 @@ -303,8 +303,8 @@ Chapter 3. Contributors Table 3.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 - 2. Liviu Chircu (@liviuchircu) Mar 2014 - Aug 2022 + 1. Liviu Chircu (@liviuchircu) Mar 2014 - Mar 2024 + 2. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 3. Razvan Crainea (@razvancrainea) Mar 2015 - Oct 2021 4. Bogdan-Andrei Iancu (@bogdan-iancu) Jan 2013 - Aug 2021 5. Fabian Gast (@fgast) Aug 2020 - Aug 2020 diff --git a/modules/httpd/doc/contributors.xml b/modules/httpd/doc/contributors.xml index fe81dd9f658..1c8bb959828 100644 --- a/modules/httpd/doc/contributors.xml +++ b/modules/httpd/doc/contributors.xml @@ -37,10 +37,10 @@ 3. Liviu Chircu (@liviuchircu) - 21 - 17 - 138 - 79 + 22 + 18 + 140 + 82 4. @@ -128,13 +128,13 @@ 1. - Maksym Sobolyev (@sobomax) - Feb 2023 - Feb 2023 + Liviu Chircu (@liviuchircu) + Mar 2014 - Mar 2024 2. - Liviu Chircu (@liviuchircu) - Mar 2014 - Aug 2022 + Maksym Sobolyev (@sobomax) + Feb 2023 - Feb 2023 3. diff --git a/modules/mi_datagram/README b/modules/mi_datagram/README index 472dbe69ce5..b3bad85eeaf 100644 --- a/modules/mi_datagram/README +++ b/modules/mi_datagram/README @@ -303,8 +303,8 @@ Chapter 3. Contributors 1. Bogdan-Andrei Iancu (@bogdan-iancu) 43 34 355 308 2. Ancuta Onofrei 27 2 2423 252 3. Vlad Patrascu (@rvlad-patrascu) 22 2 189 1049 - 4. Razvan Crainea (@razvancrainea) 12 10 50 34 - 5. Liviu Chircu (@liviuchircu) 11 9 27 56 + 4. Liviu Chircu (@liviuchircu) 13 10 31 65 + 5. Razvan Crainea (@razvancrainea) 12 10 50 34 6. Daniel-Constantin Mierla (@miconda) 9 7 18 16 7. Ionut Ionita (@ionutrazvanionita) 8 4 252 25 8. Henning Westerholt (@henningw) 5 3 9 11 @@ -335,9 +335,9 @@ Chapter 3. Contributors Table 3.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 - 2. Bogdan-Andrei Iancu (@bogdan-iancu) Jun 2007 - Apr 2021 - 3. Liviu Chircu (@liviuchircu) Mar 2014 - Jul 2020 + 1. Liviu Chircu (@liviuchircu) Mar 2014 - Mar 2024 + 2. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 + 3. Bogdan-Andrei Iancu (@bogdan-iancu) Jun 2007 - Apr 2021 4. Razvan Crainea (@razvancrainea) Oct 2011 - Sep 2019 5. Vlad Patrascu (@rvlad-patrascu) May 2017 - Jan 2019 6. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 diff --git a/modules/mi_datagram/doc/contributors.xml b/modules/mi_datagram/doc/contributors.xml index e8221f7bb28..53c328d1dfd 100644 --- a/modules/mi_datagram/doc/contributors.xml +++ b/modules/mi_datagram/doc/contributors.xml @@ -44,20 +44,20 @@ 4. + Liviu Chircu (@liviuchircu) + 13 + 10 + 31 + 65 + + + 5. Razvan Crainea (@razvancrainea) 12 10 50 34 - - 5. - Liviu Chircu (@liviuchircu) - 11 - 9 - 27 - 56 - 6. Daniel-Constantin Mierla (@miconda) @@ -128,18 +128,18 @@ 1. - Maksym Sobolyev (@sobomax) - Feb 2023 - Feb 2023 + Liviu Chircu (@liviuchircu) + Mar 2014 - Mar 2024 2. - Bogdan-Andrei Iancu (@bogdan-iancu) - Jun 2007 - Apr 2021 + Maksym Sobolyev (@sobomax) + Feb 2023 - Feb 2023 3. - Liviu Chircu (@liviuchircu) - Mar 2014 - Jul 2020 + Bogdan-Andrei Iancu (@bogdan-iancu) + Jun 2007 - Apr 2021 4. diff --git a/modules/mi_fifo/README b/modules/mi_fifo/README index 596f971be1c..efd7b42b2a1 100644 --- a/modules/mi_fifo/README +++ b/modules/mi_fifo/README @@ -265,7 +265,7 @@ Chapter 2. Contributors Name DevScore Commits Lines ++ Lines -- 1. Bogdan-Andrei Iancu (@bogdan-iancu) 61 37 2289 248 2. Razvan Crainea (@razvancrainea) 42 19 582 1072 - 3. Liviu Chircu (@liviuchircu) 15 12 29 62 + 3. Liviu Chircu (@liviuchircu) 16 13 33 70 4. Daniel-Constantin Mierla (@miconda) 12 10 20 22 5. Ionut Ionita (@ionutrazvanionita) 7 4 211 22 6. Maksym Sobolyev (@sobomax) 5 3 16 18 @@ -299,9 +299,9 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Maksym Sobolyev (@sobomax) Feb 2021 - Feb 2023 - 2. Alexey Vasilyev (@vasilevalex) Mar 2022 - Mar 2022 - 3. Liviu Chircu (@liviuchircu) Mar 2014 - Mar 2022 + 1. Liviu Chircu (@liviuchircu) Mar 2014 - Mar 2024 + 2. Maksym Sobolyev (@sobomax) Feb 2021 - Feb 2023 + 3. Alexey Vasilyev (@vasilevalex) Mar 2022 - Mar 2022 4. Razvan Crainea (@razvancrainea) Feb 2012 - Jul 2021 5. Bogdan-Andrei Iancu (@bogdan-iancu) Oct 2006 - Apr 2021 6. Zero King (@l2dy) Mar 2020 - Mar 2020 diff --git a/modules/mi_fifo/doc/contributors.xml b/modules/mi_fifo/doc/contributors.xml index e06c277c3de..01f73db520f 100644 --- a/modules/mi_fifo/doc/contributors.xml +++ b/modules/mi_fifo/doc/contributors.xml @@ -37,10 +37,10 @@ 3. Liviu Chircu (@liviuchircu) - 15 - 12 - 29 - 62 + 16 + 13 + 33 + 70 4. @@ -128,18 +128,18 @@ 1. - Maksym Sobolyev (@sobomax) - Feb 2021 - Feb 2023 + Liviu Chircu (@liviuchircu) + Mar 2014 - Mar 2024 2. - Alexey Vasilyev (@vasilevalex) - Mar 2022 - Mar 2022 + Maksym Sobolyev (@sobomax) + Feb 2021 - Feb 2023 3. - Liviu Chircu (@liviuchircu) - Mar 2014 - Mar 2022 + Alexey Vasilyev (@vasilevalex) + Mar 2022 - Mar 2022 4. diff --git a/modules/mi_html/README b/modules/mi_html/README index 6d9bff08029..c0f8f4ffc14 100644 --- a/modules/mi_html/README +++ b/modules/mi_html/README @@ -168,8 +168,8 @@ Chapter 2. Contributors Name DevScore Commits Lines ++ Lines -- 1. Ovidiu Sas (@ovidiusas) 63 32 2249 697 2. Vlad Patrascu (@rvlad-patrascu) 14 3 167 493 - 3. Razvan Crainea (@razvancrainea) 10 8 27 18 - 4. Liviu Chircu (@liviuchircu) 9 7 29 41 + 3. Liviu Chircu (@liviuchircu) 10 8 31 44 + 4. Razvan Crainea (@razvancrainea) 10 8 27 18 5. Bogdan-Andrei Iancu (@bogdan-iancu) 8 6 109 44 6. Ionut Ionita (@ionutrazvanionita) 8 5 217 10 7. Maksym Sobolyev (@sobomax) 5 3 3 4 @@ -197,14 +197,14 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Maksym Sobolyev (@sobomax) Oct 2020 - Feb 2023 - 2. Razvan Crainea (@razvancrainea) Mar 2015 - Jul 2020 - 3. Ovidiu Sas (@ovidiusas) Oct 2011 - Mar 2020 - 4. Zero King (@l2dy) Mar 2020 - Mar 2020 - 5. Vlad Patrascu (@rvlad-patrascu) May 2017 - Apr 2019 - 6. Bogdan-Andrei Iancu (@bogdan-iancu) Dec 2011 - Apr 2019 - 7. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 - 8. Liviu Chircu (@liviuchircu) Jul 2014 - Jun 2018 + 1. Liviu Chircu (@liviuchircu) Jul 2014 - Mar 2024 + 2. Maksym Sobolyev (@sobomax) Oct 2020 - Feb 2023 + 3. Razvan Crainea (@razvancrainea) Mar 2015 - Jul 2020 + 4. Ovidiu Sas (@ovidiusas) Oct 2011 - Mar 2020 + 5. Zero King (@l2dy) Mar 2020 - Mar 2020 + 6. Vlad Patrascu (@rvlad-patrascu) May 2017 - Apr 2019 + 7. Bogdan-Andrei Iancu (@bogdan-iancu) Dec 2011 - Apr 2019 + 8. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 9. Ionut Ionita (@ionutrazvanionita) Jan 2017 - Feb 2017 10. Vlad Paiu (@vladpaiu) Jan 2016 - Jan 2016 diff --git a/modules/mi_html/doc/contributors.xml b/modules/mi_html/doc/contributors.xml index bdeb0a7745a..f8784d581fe 100644 --- a/modules/mi_html/doc/contributors.xml +++ b/modules/mi_html/doc/contributors.xml @@ -36,19 +36,19 @@ 3. - Razvan Crainea (@razvancrainea) + Liviu Chircu (@liviuchircu) 10 8 - 27 - 18 + 31 + 44 4. - Liviu Chircu (@liviuchircu) - 9 - 7 - 29 - 41 + Razvan Crainea (@razvancrainea) + 10 + 8 + 27 + 18 5. @@ -128,44 +128,44 @@ 1. + Liviu Chircu (@liviuchircu) + Jul 2014 - Mar 2024 + + + 2. Maksym Sobolyev (@sobomax) Oct 2020 - Feb 2023 - 2. + 3. Razvan Crainea (@razvancrainea) Mar 2015 - Jul 2020 - 3. + 4. Ovidiu Sas (@ovidiusas) Oct 2011 - Mar 2020 - 4. + 5. Zero King (@l2dy) Mar 2020 - Mar 2020 - 5. + 6. Vlad Patrascu (@rvlad-patrascu) May 2017 - Apr 2019 - 6. + 7. Bogdan-Andrei Iancu (@bogdan-iancu) Dec 2011 - Apr 2019 - 7. + 8. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 - - 8. - Liviu Chircu (@liviuchircu) - Jul 2014 - Jun 2018 - 9. Ionut Ionita (@ionutrazvanionita) diff --git a/modules/mi_http/README b/modules/mi_http/README index b8c5998f3b1..29935dc0ebb 100644 --- a/modules/mi_http/README +++ b/modules/mi_http/README @@ -206,7 +206,7 @@ Chapter 2. Contributors 2. Vlad Patrascu (@rvlad-patrascu) 19 3 170 814 3. Razvan Crainea (@razvancrainea) 15 12 171 34 4. Ionut Ionita (@ionutrazvanionita) 14 10 273 52 - 5. Liviu Chircu (@liviuchircu) 9 7 30 36 + 5. Liviu Chircu (@liviuchircu) 10 8 32 39 6. Bogdan-Andrei Iancu (@bogdan-iancu) 8 6 102 36 7. Vlad Paiu (@vladpaiu) 5 3 8 3 8. Maksym Sobolyev (@sobomax) 4 2 2 3 @@ -233,13 +233,13 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 - 2. Ovidiu Sas (@ovidiusas) Mar 2020 - Mar 2020 - 3. Razvan Crainea (@razvancrainea) Dec 2013 - Sep 2019 - 4. Vlad Patrascu (@rvlad-patrascu) May 2017 - Apr 2019 - 5. Bogdan-Andrei Iancu (@bogdan-iancu) Jul 2014 - Apr 2019 - 6. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 - 7. Liviu Chircu (@liviuchircu) Jul 2014 - Jun 2018 + 1. Liviu Chircu (@liviuchircu) Jul 2014 - Mar 2024 + 2. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 + 3. Ovidiu Sas (@ovidiusas) Mar 2020 - Mar 2020 + 4. Razvan Crainea (@razvancrainea) Dec 2013 - Sep 2019 + 5. Vlad Patrascu (@rvlad-patrascu) May 2017 - Apr 2019 + 6. Bogdan-Andrei Iancu (@bogdan-iancu) Jul 2014 - Apr 2019 + 7. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 8. Ionut Ionita (@ionutrazvanionita) May 2016 - Feb 2017 9. Vlad Paiu (@vladpaiu) Nov 2013 - Jan 2016 10. Stephane Alnet Oct 2013 - Nov 2013 diff --git a/modules/mi_http/doc/contributors.xml b/modules/mi_http/doc/contributors.xml index c8bb6b84e93..447498672aa 100644 --- a/modules/mi_http/doc/contributors.xml +++ b/modules/mi_http/doc/contributors.xml @@ -53,10 +53,10 @@ 5. Liviu Chircu (@liviuchircu) - 9 - 7 - 30 - 36 + 10 + 8 + 32 + 39 6. @@ -128,39 +128,39 @@ 1. + Liviu Chircu (@liviuchircu) + Jul 2014 - Mar 2024 + + + 2. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 - 2. + 3. Ovidiu Sas (@ovidiusas) Mar 2020 - Mar 2020 - 3. + 4. Razvan Crainea (@razvancrainea) Dec 2013 - Sep 2019 - 4. + 5. Vlad Patrascu (@rvlad-patrascu) May 2017 - Apr 2019 - 5. + 6. Bogdan-Andrei Iancu (@bogdan-iancu) Jul 2014 - Apr 2019 - 6. + 7. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 - - 7. - Liviu Chircu (@liviuchircu) - Jul 2014 - Jun 2018 - 8. Ionut Ionita (@ionutrazvanionita) diff --git a/modules/mi_xmlrpc_ng/README b/modules/mi_xmlrpc_ng/README index 89b6aeaf35a..38b0026c396 100644 --- a/modules/mi_xmlrpc_ng/README +++ b/modules/mi_xmlrpc_ng/README @@ -223,7 +223,7 @@ Chapter 2. Contributors 2. Vlad Patrascu (@rvlad-patrascu) 25 3 614 1041 3. Ionut Ionita (@ionutrazvanionita) 15 9 383 68 4. Razvan Crainea (@razvancrainea) 14 12 49 33 - 5. Liviu Chircu (@liviuchircu) 13 11 44 52 + 5. Liviu Chircu (@liviuchircu) 14 12 46 55 6. Bogdan-Andrei Iancu (@bogdan-iancu) 12 9 126 55 7. Ionel Cerghit (@ionel-cerghit) 11 3 515 166 8. Maksym Sobolyev (@sobomax) 4 2 2 3 @@ -252,12 +252,12 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 - 2. Zero King (@l2dy) Mar 2020 - Mar 2020 - 3. Razvan Crainea (@razvancrainea) Nov 2014 - Sep 2019 - 4. Bogdan-Andrei Iancu (@bogdan-iancu) Oct 2014 - Apr 2019 - 5. Vlad Patrascu (@rvlad-patrascu) May 2017 - Jan 2019 - 6. Liviu Chircu (@liviuchircu) Jul 2014 - Nov 2018 + 1. Liviu Chircu (@liviuchircu) Jul 2014 - Mar 2024 + 2. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 + 3. Zero King (@l2dy) Mar 2020 - Mar 2020 + 4. Razvan Crainea (@razvancrainea) Nov 2014 - Sep 2019 + 5. Bogdan-Andrei Iancu (@bogdan-iancu) Oct 2014 - Apr 2019 + 6. Vlad Patrascu (@rvlad-patrascu) May 2017 - Jan 2019 7. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 8. Ionut Ionita (@ionutrazvanionita) May 2016 - Feb 2017 9. Vlad Paiu (@vladpaiu) Mar 2014 - Jan 2016 diff --git a/modules/mi_xmlrpc_ng/doc/contributors.xml b/modules/mi_xmlrpc_ng/doc/contributors.xml index 525026384b9..25b97f605dd 100644 --- a/modules/mi_xmlrpc_ng/doc/contributors.xml +++ b/modules/mi_xmlrpc_ng/doc/contributors.xml @@ -53,10 +53,10 @@ 5. Liviu Chircu (@liviuchircu) - 13 - 11 - 44 - 52 + 14 + 12 + 46 + 55 6. @@ -128,34 +128,34 @@ 1. + Liviu Chircu (@liviuchircu) + Jul 2014 - Mar 2024 + + + 2. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 - 2. + 3. Zero King (@l2dy) Mar 2020 - Mar 2020 - 3. + 4. Razvan Crainea (@razvancrainea) Nov 2014 - Sep 2019 - 4. + 5. Bogdan-Andrei Iancu (@bogdan-iancu) Oct 2014 - Apr 2019 - 5. + 6. Vlad Patrascu (@rvlad-patrascu) May 2017 - Jan 2019 - - 6. - Liviu Chircu (@liviuchircu) - Jul 2014 - Nov 2018 - 7. Peter Lemenkov (@lemenkov) diff --git a/modules/osp/README b/modules/osp/README index b374aff96a7..5fb2882e3d5 100644 --- a/modules/osp/README +++ b/modules/osp/README @@ -160,7 +160,7 @@ Chapter 1. Admin Guide The OSP module depends on the following modules which must be loaded before the OSP module. * auth -- Authentication Framework module - * dbops -- DB operation module + * sqlops -- SQL operation module * maxfwd -- Max-Forward processor module * mi_fifo -- FIFO support for Management Interface * options -- OPTIONS server replier module @@ -972,7 +972,7 @@ Chapter 3. Contributors Name DevScore Commits Lines ++ Lines -- 1. Di-Shi Sun (@di-shi) 274 101 10368 5372 2. Dmitry Isakbayev 42 5 4120 159 - 3. Bogdan-Andrei Iancu (@bogdan-iancu) 35 29 222 214 + 3. Bogdan-Andrei Iancu (@bogdan-iancu) 36 30 223 215 4. Di-Shi Sun 18 2 1006 386 5. Liviu Chircu (@liviuchircu) 17 13 117 126 6. Daniel-Constantin Mierla (@miconda) 15 13 81 48 @@ -1008,7 +1008,7 @@ Chapter 3. Contributors Table 3.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Bogdan-Andrei Iancu (@bogdan-iancu) Jan 2006 - Feb 2024 + 1. Bogdan-Andrei Iancu (@bogdan-iancu) Jan 2006 - Mar 2024 2. Razvan Crainea (@razvancrainea) Jun 2011 - Feb 2024 3. Liviu Chircu (@liviuchircu) Mar 2014 - May 2023 4. Vlad Patrascu (@rvlad-patrascu) May 2017 - May 2023 diff --git a/modules/osp/doc/contributors.xml b/modules/osp/doc/contributors.xml index ad0c613d079..172525980ba 100644 --- a/modules/osp/doc/contributors.xml +++ b/modules/osp/doc/contributors.xml @@ -37,10 +37,10 @@ 3. Bogdan-Andrei Iancu (@bogdan-iancu) - 35 - 29 - 222 - 214 + 36 + 30 + 223 + 215 4. @@ -129,7 +129,7 @@ 1. Bogdan-Andrei Iancu (@bogdan-iancu) - Jan 2006 - Feb 2024 + Jan 2006 - Mar 2024 2. diff --git a/modules/perl/README b/modules/perl/README index fc0f955cd13..332bb75e5bd 100644 --- a/modules/perl/README +++ b/modules/perl/README @@ -1315,7 +1315,7 @@ Chapter 3. Perl samples You might notice that there is no particular function for setting pseudo variables; you may use the exported functions - from the dbops module, though. + from the sqlops module, though. Chapter 4. Frequently Asked Questions @@ -1377,7 +1377,7 @@ Chapter 5. Contributors commits^(2) and lines added/removed^(3) Name DevScore Commits Lines ++ Lines -- 1. Bastian Friedrich 113 38 8597 284 - 2. Bogdan-Andrei Iancu (@bogdan-iancu) 48 30 703 683 + 2. Bogdan-Andrei Iancu (@bogdan-iancu) 49 31 704 684 3. Razvan Crainea (@razvancrainea) 23 21 99 60 4. Liviu Chircu (@liviuchircu) 20 17 62 73 5. Vlad Patrascu (@rvlad-patrascu) 20 10 264 408 @@ -1412,7 +1412,7 @@ Chapter 5. Contributors Table 5.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Bogdan-Andrei Iancu (@bogdan-iancu) Dec 2006 - Feb 2024 + 1. Bogdan-Andrei Iancu (@bogdan-iancu) Dec 2006 - Mar 2024 2. Maksym Sobolyev (@sobomax) Oct 2022 - Feb 2023 3. Liviu Chircu (@liviuchircu) Mar 2014 - Jul 2022 4. Vlad Patrascu (@rvlad-patrascu) May 2017 - Jan 2020 diff --git a/modules/perl/doc/contributors.xml b/modules/perl/doc/contributors.xml index 57d970b196e..907fa135b54 100644 --- a/modules/perl/doc/contributors.xml +++ b/modules/perl/doc/contributors.xml @@ -29,10 +29,10 @@ 2. Bogdan-Andrei Iancu (@bogdan-iancu) - 48 - 30 - 703 - 683 + 49 + 31 + 704 + 684 3. @@ -129,7 +129,7 @@ 1. Bogdan-Andrei Iancu (@bogdan-iancu) - Dec 2006 - Feb 2024 + Dec 2006 - Mar 2024 2. diff --git a/modules/pi_http/README b/modules/pi_http/README index 0324294cf0b..fe023c52cd6 100644 --- a/modules/pi_http/README +++ b/modules/pi_http/README @@ -271,7 +271,7 @@ Chapter 2. Contributors commits^(2) and lines added/removed^(3) Name DevScore Commits Lines ++ Lines -- 1. Ovidiu Sas (@ovidiusas) 71 31 4389 206 - 2. Liviu Chircu (@liviuchircu) 14 12 38 54 + 2. Liviu Chircu (@liviuchircu) 15 13 40 57 3. Razvan Crainea (@razvancrainea) 13 11 30 31 4. Bogdan-Andrei Iancu (@bogdan-iancu) 7 5 5 5 5. Vlad Patrascu (@rvlad-patrascu) 6 4 21 19 @@ -301,10 +301,10 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Maksym Sobolyev (@sobomax) Oct 2022 - Feb 2023 - 2. Bogdan-Andrei Iancu (@bogdan-iancu) Jan 2013 - Mar 2020 - 3. Zero King (@l2dy) Mar 2020 - Mar 2020 - 4. Liviu Chircu (@liviuchircu) Mar 2014 - Sep 2019 + 1. Liviu Chircu (@liviuchircu) Mar 2014 - Mar 2024 + 2. Maksym Sobolyev (@sobomax) Oct 2022 - Feb 2023 + 3. Bogdan-Andrei Iancu (@bogdan-iancu) Jan 2013 - Mar 2020 + 4. Zero King (@l2dy) Mar 2020 - Mar 2020 5. Razvan Crainea (@razvancrainea) Aug 2015 - Sep 2019 6. Vlad Patrascu (@rvlad-patrascu) May 2017 - Jan 2019 7. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 diff --git a/modules/pi_http/doc/contributors.xml b/modules/pi_http/doc/contributors.xml index 008c2a89af3..99652788214 100644 --- a/modules/pi_http/doc/contributors.xml +++ b/modules/pi_http/doc/contributors.xml @@ -29,10 +29,10 @@ 2. Liviu Chircu (@liviuchircu) - 14 - 12 - 38 - 54 + 15 + 13 + 40 + 57 3. @@ -128,24 +128,24 @@ 1. + Liviu Chircu (@liviuchircu) + Mar 2014 - Mar 2024 + + + 2. Maksym Sobolyev (@sobomax) Oct 2022 - Feb 2023 - 2. + 3. Bogdan-Andrei Iancu (@bogdan-iancu) Jan 2013 - Mar 2020 - 3. + 4. Zero King (@l2dy) Mar 2020 - Mar 2020 - - 4. - Liviu Chircu (@liviuchircu) - Mar 2014 - Sep 2019 - 5. Razvan Crainea (@razvancrainea) diff --git a/modules/sqlops/README b/modules/sqlops/README index 2c79be88d16..dc62096656a 100644 --- a/modules/sqlops/README +++ b/modules/sqlops/README @@ -703,7 +703,24 @@ Chapter 2. Contributors Table 2.1. Top contributors by DevScore^(1), authored commits^(2) and lines added/removed^(3) Name DevScore Commits Lines ++ Lines -- - 1. Bogdan-Andrei Iancu (@bogdan-iancu) 33 3 3495 18 + 1. Bogdan-Andrei Iancu (@bogdan-iancu) 245 73 6018 7507 + 2. Daniel-Constantin Mierla (@miconda) 104 44 2927 2158 + 3. Liviu Chircu (@liviuchircu) 53 27 1112 944 + 4. Elena-Ramona Modroiu 51 11 4040 390 + 5. Razvan Crainea (@razvancrainea) 21 14 149 246 + 6. Elena-Ramona Modroiu 18 5 1051 192 + 7. Henning Westerholt (@henningw) 12 8 112 133 + 8. Vlad Paiu (@vladpaiu) 9 7 39 2 + 9. Ionut Ionita (@ionutrazvanionita) 8 5 180 12 + 10. Kobi Eshun (@ekobi) 7 1 300 146 + + All remaining contributors: Andrei Pelinescu-Onciul, Anca + Vamanu, Maksym Sobolyev (@sobomax), Norman Brandinger (@NormB), + Vlad Patrascu (@rvlad-patrascu), Klaus Darilion, John Burke + (@john08burke), Andrey Vorobiev, Olle E. Johansson, Kennard + White, Julián Moreno Patiño, Konstantin Bokarius, Walter Doekes + (@wdoekes), Andreas Granig, Peter Lemenkov (@lemenkov), Sergio + Gutierrez, Edson Gellert Schubert, Ovidiu Sas (@ovidiusas). (1) DevScore = author_commits + author_lines_added / (project_lines_added / project_commits) + author_lines_deleted @@ -724,8 +741,26 @@ Chapter 2. Contributors 2.2. By Commit Activity Table 2.2. Most recently active contributors^(1) to this module - Name Commit Activity - 1. Bogdan-Andrei Iancu (@bogdan-iancu) Feb 2024 - Mar 2024 + Name Commit Activity + 1. Bogdan-Andrei Iancu (@bogdan-iancu) Jun 2005 - Mar 2024 + 2. Vlad Paiu (@vladpaiu) Jun 2011 - Jul 2023 + 3. Razvan Crainea (@razvancrainea) Jun 2011 - Mar 2023 + 4. Maksym Sobolyev (@sobomax) Oct 2022 - Feb 2023 + 5. John Burke (@john08burke) Jun 2022 - Jun 2022 + 6. Liviu Chircu (@liviuchircu) Mar 2013 - Mar 2020 + 7. Vlad Patrascu (@rvlad-patrascu) May 2017 - Jul 2019 + 8. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 + 9. Andrey Vorobiev Apr 2016 - Apr 2016 + 10. Julián Moreno Patiño Feb 2016 - Feb 2016 + + All remaining contributors: Ionut Ionita (@ionutrazvanionita), + Ovidiu Sas (@ovidiusas), Walter Doekes (@wdoekes), Anca Vamanu, + Kennard White, Norman Brandinger (@NormB), Sergio Gutierrez, + Kobi Eshun (@ekobi), Henning Westerholt (@henningw), Olle E. + Johansson, Daniel-Constantin Mierla (@miconda), Konstantin + Bokarius, Edson Gellert Schubert, Elena-Ramona Modroiu, Klaus + Darilion, Andreas Granig, Andrei Pelinescu-Onciul, Elena-Ramona + Modroiu. (1) including any documentation-related commits, excluding merge commits @@ -734,7 +769,15 @@ Chapter 3. Documentation 3.1. Contributors - Last edited by: Bogdan-Andrei Iancu (@bogdan-iancu). + Last edited by: Bogdan-Andrei Iancu (@bogdan-iancu), Razvan + Crainea (@razvancrainea), John Burke (@john08burke), Liviu + Chircu (@liviuchircu), Peter Lemenkov (@lemenkov), Ionut Ionita + (@ionutrazvanionita), Vlad Paiu (@vladpaiu), Anca Vamanu, + Norman Brandinger (@NormB), Kobi Eshun (@ekobi), Henning + Westerholt (@henningw), Daniel-Constantin Mierla (@miconda), + Konstantin Bokarius, Edson Gellert Schubert, Elena-Ramona + Modroiu, Klaus Darilion, Andrei Pelinescu-Onciul, Elena-Ramona + Modroiu. Documentation Copyrights: diff --git a/modules/sqlops/doc/contributors.xml b/modules/sqlops/doc/contributors.xml index d9ed441cdbf..2fd71546365 100644 --- a/modules/sqlops/doc/contributors.xml +++ b/modules/sqlops/doc/contributors.xml @@ -21,15 +21,87 @@ 1. Bogdan-Andrei Iancu (@bogdan-iancu) - 33 - 3 - 3495 + 245 + 73 + 6018 + 7507 + + + 2. + Daniel-Constantin Mierla (@miconda) + 104 + 44 + 2927 + 2158 + + + 3. + Liviu Chircu (@liviuchircu) + 53 + 27 + 1112 + 944 + + + 4. + Elena-Ramona Modroiu + 51 + 11 + 4040 + 390 + + + 5. + Razvan Crainea (@razvancrainea) + 21 + 14 + 149 + 246 + + + 6. + Elena-Ramona Modroiu 18 + 5 + 1051 + 192 + + + 7. + Henning Westerholt (@henningw) + 12 + 8 + 112 + 133 + + + 8. + Vlad Paiu (@vladpaiu) + 9 + 7 + 39 + 2 + + + 9. + Ionut Ionita (@ionutrazvanionita) + 8 + 5 + 180 + 12 + + + 10. + Kobi Eshun (@ekobi) + 7 + 1 + 300 + 146 - +All remaining contributors: Andrei Pelinescu-Onciul, Anca Vamanu, Maksym Sobolyev (@sobomax), Norman Brandinger (@NormB), Vlad Patrascu (@rvlad-patrascu), Klaus Darilion, John Burke (@john08burke), Andrey Vorobiev, Olle E. Johansson, Kennard White, Julián Moreno Patiño, Konstantin Bokarius, Walter Doekes (@wdoekes), Andreas Granig, Peter Lemenkov (@lemenkov), Sergio Gutierrez, Edson Gellert Schubert, Ovidiu Sas (@ovidiusas). (1) DevScore = author_commits + author_lines_added / (project_lines_added / project_commits) + author_lines_deleted / (project_lines_deleted / project_commits) @@ -57,12 +129,57 @@ 1. Bogdan-Andrei Iancu (@bogdan-iancu) - Feb 2024 - Mar 2024 + Jun 2005 - Mar 2024 + + + 2. + Vlad Paiu (@vladpaiu) + Jun 2011 - Jul 2023 + + + 3. + Razvan Crainea (@razvancrainea) + Jun 2011 - Mar 2023 + + + 4. + Maksym Sobolyev (@sobomax) + Oct 2022 - Feb 2023 + + + 5. + John Burke (@john08burke) + Jun 2022 - Jun 2022 + + + 6. + Liviu Chircu (@liviuchircu) + Mar 2013 - Mar 2020 + + + 7. + Vlad Patrascu (@rvlad-patrascu) + May 2017 - Jul 2019 + + + 8. + Peter Lemenkov (@lemenkov) + Jun 2018 - Jun 2018 + + + 9. + Andrey Vorobiev + Apr 2016 - Apr 2016 + + + 10. + Julián Moreno Patiño + Feb 2016 - Feb 2016 - +All remaining contributors: Ionut Ionita (@ionutrazvanionita), Ovidiu Sas (@ovidiusas), Walter Doekes (@wdoekes), Anca Vamanu, Kennard White, Norman Brandinger (@NormB), Sergio Gutierrez, Kobi Eshun (@ekobi), Henning Westerholt (@henningw), Olle E. Johansson, Daniel-Constantin Mierla (@miconda), Konstantin Bokarius, Edson Gellert Schubert, Elena-Ramona Modroiu, Klaus Darilion, Andreas Granig, Andrei Pelinescu-Onciul, Elena-Ramona Modroiu. (1) including any documentation-related commits, excluding merge commits @@ -73,7 +190,7 @@ Documentation
Contributors - Last edited by: Bogdan-Andrei Iancu (@bogdan-iancu). + Last edited by: Bogdan-Andrei Iancu (@bogdan-iancu), Razvan Crainea (@razvancrainea), John Burke (@john08burke), Liviu Chircu (@liviuchircu), Peter Lemenkov (@lemenkov), Ionut Ionita (@ionutrazvanionita), Vlad Paiu (@vladpaiu), Anca Vamanu, Norman Brandinger (@NormB), Kobi Eshun (@ekobi), Henning Westerholt (@henningw), Daniel-Constantin Mierla (@miconda), Konstantin Bokarius, Edson Gellert Schubert, Elena-Ramona Modroiu, Klaus Darilion, Andrei Pelinescu-Onciul, Elena-Ramona Modroiu.
From 471dab3130eea8788cbbf0884c6e5c2af8d61622 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Mon, 18 Mar 2024 12:25:15 +0200 Subject: [PATCH 24/79] rtp_relay: do not check for pending when late Many thanks to Voxtronic for spotting this issue! --- modules/rtp_relay/rtp_relay_ctx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/rtp_relay/rtp_relay_ctx.c b/modules/rtp_relay/rtp_relay_ctx.c index c2da555c144..31005d1926b 100644 --- a/modules/rtp_relay/rtp_relay_ctx.c +++ b/modules/rtp_relay/rtp_relay_ctx.c @@ -1819,7 +1819,7 @@ static void rtp_relay_ctx_initial_cb(struct cell* t, int type, struct tmcb_param /* first check if there's anything setup on this branch */ sess = rtp_relay_get_sess(ctx, rtp_relay_ctx_branch()); if (sess) { - if (!rtp_sess_pending(sess)) { + if (!rtp_sess_pending(sess) && !rtp_sess_late(sess)) { LM_DBG("no pending session on branch %d\n", rtp_relay_ctx_branch()); goto end; From 18e7d8dbb56289e83531f51983f63e58f152ef3f Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Tue, 19 Mar 2024 11:39:32 +0200 Subject: [PATCH 25/79] [db] fix wrong size for mangled_from/to_uri columns in dialog table They are URIs, so let's use the URI len, not USER len :) --- db/schema/dialog.xml | 4 ++-- scripts/mysql/dialog-create.sql | 4 ++-- scripts/oracle/dialog-create.sql | 4 ++-- scripts/postgres/dialog-create.sql | 4 ++-- scripts/sqlite/dialog-create.sql | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/db/schema/dialog.xml b/db/schema/dialog.xml index 0ce070a6422..a973dc664ec 100644 --- a/db/schema/dialog.xml +++ b/db/schema/dialog.xml @@ -69,7 +69,7 @@ mangled_from_uri string - &user_len; + &uri_len; The mangled from URI, in case uac_replace_from @@ -79,7 +79,7 @@ mangled_to_uri string - &user_len; + &uri_len; The mangled to URI, in case uac_replace_to diff --git a/scripts/mysql/dialog-create.sql b/scripts/mysql/dialog-create.sql index 406ebb693f8..9d48a852963 100644 --- a/scripts/mysql/dialog-create.sql +++ b/scripts/mysql/dialog-create.sql @@ -6,8 +6,8 @@ CREATE TABLE dialog ( from_tag CHAR(64) NOT NULL, to_uri CHAR(255) NOT NULL, to_tag CHAR(64) NOT NULL, - mangled_from_uri CHAR(64) DEFAULT NULL, - mangled_to_uri CHAR(64) DEFAULT NULL, + mangled_from_uri CHAR(255) DEFAULT NULL, + mangled_to_uri CHAR(255) DEFAULT NULL, caller_cseq CHAR(11) NOT NULL, callee_cseq CHAR(11) NOT NULL, caller_ping_cseq INT(11) UNSIGNED NOT NULL, diff --git a/scripts/oracle/dialog-create.sql b/scripts/oracle/dialog-create.sql index 00a0f683120..52e3c26d93c 100644 --- a/scripts/oracle/dialog-create.sql +++ b/scripts/oracle/dialog-create.sql @@ -6,8 +6,8 @@ CREATE TABLE dialog ( from_tag VARCHAR2(64), to_uri VARCHAR2(255), to_tag VARCHAR2(64), - mangled_from_uri VARCHAR2(64) DEFAULT NULL, - mangled_to_uri VARCHAR2(64) DEFAULT NULL, + mangled_from_uri VARCHAR2(255) DEFAULT NULL, + mangled_to_uri VARCHAR2(255) DEFAULT NULL, caller_cseq VARCHAR2(11), callee_cseq VARCHAR2(11), caller_ping_cseq NUMBER(10), diff --git a/scripts/postgres/dialog-create.sql b/scripts/postgres/dialog-create.sql index d2c0b4357ec..f57b37aa2cc 100644 --- a/scripts/postgres/dialog-create.sql +++ b/scripts/postgres/dialog-create.sql @@ -6,8 +6,8 @@ CREATE TABLE dialog ( from_tag VARCHAR(64) NOT NULL, to_uri VARCHAR(255) NOT NULL, to_tag VARCHAR(64) NOT NULL, - mangled_from_uri VARCHAR(64) DEFAULT NULL, - mangled_to_uri VARCHAR(64) DEFAULT NULL, + mangled_from_uri VARCHAR(255) DEFAULT NULL, + mangled_to_uri VARCHAR(255) DEFAULT NULL, caller_cseq VARCHAR(11) NOT NULL, callee_cseq VARCHAR(11) NOT NULL, caller_ping_cseq INTEGER NOT NULL, diff --git a/scripts/sqlite/dialog-create.sql b/scripts/sqlite/dialog-create.sql index 5c7a2e9f70d..33368847906 100644 --- a/scripts/sqlite/dialog-create.sql +++ b/scripts/sqlite/dialog-create.sql @@ -6,8 +6,8 @@ CREATE TABLE dialog ( from_tag CHAR(64) NOT NULL, to_uri CHAR(255) NOT NULL, to_tag CHAR(64) NOT NULL, - mangled_from_uri CHAR(64) DEFAULT NULL, - mangled_to_uri CHAR(64) DEFAULT NULL, + mangled_from_uri CHAR(255) DEFAULT NULL, + mangled_to_uri CHAR(255) DEFAULT NULL, caller_cseq CHAR(11) NOT NULL, callee_cseq CHAR(11) NOT NULL, caller_ping_cseq INTEGER NOT NULL, From 4a6c26c0bd26d502a7dd271d98462b23810c2ef9 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 9 Jan 2024 16:02:25 +0200 Subject: [PATCH 26/79] auth: make fixup_qop reusable --- modules/auth/auth_mod.c | 1 + modules/auth/challenge.c | 34 ---------------------- modules/auth/challenge.h | 2 -- modules/auth/qop.h | 63 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 36 deletions(-) create mode 100644 modules/auth/qop.h diff --git a/modules/auth/auth_mod.c b/modules/auth/auth_mod.c index 46983e1cb33..fcec5105619 100644 --- a/modules/auth/auth_mod.c +++ b/modules/auth/auth_mod.c @@ -47,6 +47,7 @@ #include "challenge.h" #include "rpid.h" #include "api.h" +#include "qop.h" #include "../../parser/digest/digest_parser.h" #include "../../lib/digest_auth/dauth_calc.h" #include "../../lib/digest_auth/dauth_nonce.h" diff --git a/modules/auth/challenge.c b/modules/auth/challenge.c index 5a1a84b69e4..e4a3289bff8 100644 --- a/modules/auth/challenge.c +++ b/modules/auth/challenge.c @@ -276,40 +276,6 @@ static inline int challenge(struct sip_msg* _msg, str *realm, qop_type_t _qop, return 0; } -int fixup_qop(void** param) -{ - str *s = (str*)*param; - qop_type_t qop_type = QOP_UNSPEC_D; - csv_record *q_csv, *q; - - q_csv = parse_csv_record(s); - if (!q_csv) { - LM_ERR("Failed to parse qop types\n"); - return -1; - } - for (q = q_csv; q; q = q->next) { - if (!str_strcmp(&q->s, const_str(QOP_AUTH_STR))) { - if (qop_type == QOP_AUTHINT_D) - qop_type = QOP_AUTHINT_AUTH_D; - else - qop_type = QOP_AUTH_D; - } else if (!str_strcmp(&q->s, const_str(QOP_AUTHINT_STR))) { - if (qop_type == QOP_AUTH_D) - qop_type = QOP_AUTH_AUTHINT_D; - else - qop_type = QOP_AUTHINT_D; - } else { - LM_ERR("Bad qop type\n"); - free_csv_record(q_csv); - return -1; - } - } - free_csv_record(q_csv); - - *param=(void*)(long)qop_type; - return 0; -} - /* * Challenge a user to send credentials using WWW-Authorize header field */ diff --git a/modules/auth/challenge.h b/modules/auth/challenge.h index ff60812295c..7bfe1e7e7ff 100644 --- a/modules/auth/challenge.h +++ b/modules/auth/challenge.h @@ -29,8 +29,6 @@ #include "../../lib/digest_auth/dauth_nonce.h" -int fixup_qop(void** param); - /* * Challenge a user agent using WWW-Authenticate header field */ diff --git a/modules/auth/qop.h b/modules/auth/qop.h new file mode 100644 index 00000000000..f3a6d63baa1 --- /dev/null +++ b/modules/auth/qop.h @@ -0,0 +1,63 @@ +/* + * Authentication QOP parsing functions + * + * Copyright (C) 2001-2003 FhG Fokus + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef AUTH_QOP_H +#define AUTH_QOP_H + +#include "../../lib/csv.h" + +static inline int fixup_qop(void** param) +{ + str *s = (str*)*param; + qop_type_t qop_type = QOP_UNSPEC_D; + csv_record *q_csv, *q; + + q_csv = parse_csv_record(s); + if (!q_csv) { + LM_ERR("Failed to parse qop types\n"); + return -1; + } + for (q = q_csv; q; q = q->next) { + if (!str_strcmp(&q->s, const_str(QOP_AUTH_STR))) { + if (qop_type == QOP_AUTHINT_D) + qop_type = QOP_AUTHINT_AUTH_D; + else + qop_type = QOP_AUTH_D; + } else if (!str_strcmp(&q->s, const_str(QOP_AUTHINT_STR))) { + if (qop_type == QOP_AUTH_D) + qop_type = QOP_AUTH_AUTHINT_D; + else + qop_type = QOP_AUTHINT_D; + } else { + LM_ERR("Bad qop type\n"); + free_csv_record(q_csv); + return -1; + } + } + free_csv_record(q_csv); + + *param=(void*)(long)qop_type; + return 0; +} + +#endif /* AUTH_QOP_H */ From 4786fc342d2cdeb1ebd0da2897ee0fefa83df940 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Fri, 12 Jan 2024 11:34:37 +0200 Subject: [PATCH 27/79] digest: add AKAv1 and AKAv2 parsers --- lib/digest_auth/digest_auth.h | 2 +- parser/digest/digest.c | 12 +++++++++ parser/digest/digest_parser.c | 26 +++++++++++++++++++ parser/digest/digest_parser.h | 47 ++++++++++++++++++++++++++++++++++- 4 files changed, 85 insertions(+), 2 deletions(-) diff --git a/lib/digest_auth/digest_auth.h b/lib/digest_auth/digest_auth.h index 651abf46e9d..e8cc25142b3 100644 --- a/lib/digest_auth/digest_auth.h +++ b/lib/digest_auth/digest_auth.h @@ -41,7 +41,7 @@ /* First/Last supported algorithm */ #define FIRST_ALG_SPTD (ALG_UNSPEC) -#define LAST_ALG_SPTD (ALG_SHA512_256SESS) +#define LAST_ALG_SPTD (ALG_OTHER-1) typedef union { HASH_MD5 MD5; diff --git a/parser/digest/digest.c b/parser/digest/digest.c index a957aee6f3b..1578f6e7cf8 100644 --- a/parser/digest/digest.c +++ b/parser/digest/digest.c @@ -163,6 +163,18 @@ void print_cred(dig_cred_t* _c) CASE_PRINTENUM(ALG_SHA256SESS); CASE_PRINTENUM(ALG_SHA512_256); CASE_PRINTENUM(ALG_SHA512_256SESS); + CASE_PRINTENUM(ALG_AKAv1_MD5); + CASE_PRINTENUM(ALG_AKAv1_MD5SESS); + CASE_PRINTENUM(ALG_AKAv1_SHA256); + CASE_PRINTENUM(ALG_AKAv1_SHA256SESS); + CASE_PRINTENUM(ALG_AKAv1_SHA512_256); + CASE_PRINTENUM(ALG_AKAv1_SHA512_256SESS); + CASE_PRINTENUM(ALG_AKAv2_MD5); + CASE_PRINTENUM(ALG_AKAv2_MD5SESS); + CASE_PRINTENUM(ALG_AKAv2_SHA256); + CASE_PRINTENUM(ALG_AKAv2_SHA256SESS); + CASE_PRINTENUM(ALG_AKAv2_SHA512_256); + CASE_PRINTENUM(ALG_AKAv2_SHA512_256SESS); CASE_PRINTENUM(ALG_OTHER); } diff --git a/parser/digest/digest_parser.c b/parser/digest/digest_parser.c index 51cc52143cb..2f54998541f 100644 --- a/parser/digest/digest_parser.c +++ b/parser/digest/digest_parser.c @@ -46,6 +46,18 @@ #define ALG_SHA256SESS_STR_LEN (sizeof(ALG_SHA256SESS_STR) - 1) #define ALG_SHA512_256_STR_LEN (sizeof(ALG_SHA512_256_STR) - 1) #define ALG_SHA512_256SESS_STR_LEN (sizeof(ALG_SHA512_256SESS_STR) - 1) +#define ALG_AKAv1_MD5_STR_LEN (sizeof(ALG_AKAv1_MD5_STR) - 1) +#define ALG_AKAv1_MD5SESS_STR_LEN (sizeof(ALG_AKAv1_MD5SESS_STR) - 1) +#define ALG_AKAv1_SHA256_STR_LEN (sizeof(ALG_AKAv1_SHA256_STR) - 1) +#define ALG_AKAv1_SHA256SESS_STR_LEN (sizeof(ALG_AKAv1_SHA256SESS_STR) - 1) +#define ALG_AKAv1_SHA512_256_STR_LEN (sizeof(ALG_AKAv1_SHA512_256_STR) - 1) +#define ALG_AKAv1_SHA512_256SESS_STR_LEN (sizeof(ALG_AKAv1_SHA512_256SESS_STR) - 1) +#define ALG_AKAv2_MD5_STR_LEN (sizeof(ALG_AKAv2_MD5_STR) - 1) +#define ALG_AKAv2_MD5SESS_STR_LEN (sizeof(ALG_AKAv2_MD5SESS_STR) - 1) +#define ALG_AKAv2_SHA256_STR_LEN (sizeof(ALG_AKAv2_SHA256_STR) - 1) +#define ALG_AKAv2_SHA256SESS_STR_LEN (sizeof(ALG_AKAv2_SHA256SESS_STR) - 1) +#define ALG_AKAv2_SHA512_256_STR_LEN (sizeof(ALG_AKAv2_SHA512_256_STR) - 1) +#define ALG_AKAv2_SHA512_256SESS_STR_LEN (sizeof(ALG_AKAv2_SHA512_256SESS_STR) - 1) /* * Parse quoted string in a parameter body @@ -230,6 +242,14 @@ static inline void parse_qop(struct qp* _q) return ALG_##alg; \ break; +#define CASE_ALG2(alg1, alg2, sptr) \ + case ALG_##alg1##_STR_LEN: \ + if (turbo_casematch((sptr)->s, ALG_##alg1##_STR, (sptr)->len)) \ + return ALG_##alg1; \ + if (turbo_casematch((sptr)->s, ALG_##alg2##_STR, (sptr)->len)) \ + return ALG_##alg2; \ + break; + /* * Parse algorithm parameter body */ @@ -243,6 +263,12 @@ alg_t parse_digest_algorithm(const str *sp) CASE_ALG(SHA256SESS, sp); CASE_ALG(SHA512_256, sp); CASE_ALG(SHA512_256SESS, sp); + CASE_ALG2(AKAv1_MD5, AKAv2_MD5, sp); + CASE_ALG2(AKAv1_MD5SESS, AKAv2_MD5SESS, sp); + CASE_ALG2(AKAv1_SHA256, AKAv2_SHA256, sp); + CASE_ALG2(AKAv1_SHA256SESS, AKAv2_SHA256SESS, sp); + CASE_ALG2(AKAv1_SHA512_256, AKAv2_SHA512_256, sp); + CASE_ALG2(AKAv1_SHA512_256SESS, AKAv2_SHA512_256SESS, sp); default: break; } diff --git a/parser/digest/digest_parser.h b/parser/digest/digest_parser.h index 9c92ec3f43b..af38fbccffe 100644 --- a/parser/digest/digest_parser.h +++ b/parser/digest/digest_parser.h @@ -41,7 +41,23 @@ typedef enum alg { ALG_SHA256SESS = 4, /* SHA-256-Session */ ALG_SHA512_256 = 5, /* SHA-512/256 */ ALG_SHA512_256SESS = 6, /* SHA-512/256-Session */ - ALG_OTHER = 7 /* Unknown */ + ALG_AKAv1_FIRST = 7, /* First AKAv1 algorithm */ + ALG_AKAv1_MD5 = 7, /* AKAv1-MD5 */ + ALG_AKAv1_MD5SESS = 8, /* AKAv1-MD5-Session */ + ALG_AKAv1_SHA256 = 9, /* AKAv1-SHA-256 */ + ALG_AKAv1_SHA256SESS = 10, /* AKAv1-SHA-256-Session */ + ALG_AKAv1_SHA512_256 = 11, /* AKAv1-SHA-512/256 */ + ALG_AKAv1_SHA512_256SESS = 12, /* AKAv1-SHA-512/256-Session */ + ALG_AKAv1_LAST = 12, /* Last AKAv1 algorithm */ + ALG_AKAv2_FIRST = 13, /* First AKAv2 algorithm */ + ALG_AKAv2_MD5 = 13, /* AKAv2-MD5 */ + ALG_AKAv2_MD5SESS = 14, /* AKAv2-MD5-Session */ + ALG_AKAv2_SHA256 = 15, /* AKAv2-SHA-256 */ + ALG_AKAv2_SHA256SESS = 16,/* AKAv2-SHA-256-Session */ + ALG_AKAv2_SHA512_256 = 17,/* AKAv2-SHA-512/256 */ + ALG_AKAv2_SHA512_256SESS = 18, /* AKAv2-SHA-512/256-Session */ + ALG_AKAv2_LAST = 18, /* Last AKAv2 algorithm */ + ALG_OTHER = 19 /* Unknown */ } alg_t; #define ALG2ALGFLG(_alg) (1 << (_alg)) @@ -54,6 +70,18 @@ typedef enum alg { #define ALGFLG_SHA256SESS ALG2ALGFLG(ALG_SHA256SESS) #define ALGFLG_SHA512_256 ALG2ALGFLG(ALG_SHA512_256) #define ALGFLG_SHA512_256SESS ALG2ALGFLG(ALG_SHA512_256SESS) +#define ALGFLG_AKAv1_MD5 ALG2ALGFLG(ALG_AKAv1_MD5) +#define ALGFLG_AKAv1_MD5SESS ALG2ALGFLG(ALG_AKAv1_MD5SESS) +#define ALGFLG_AKAv1_SHA256 ALG2ALGFLG(ALG_AKAv1_SHA256) +#define ALGFLG_AKAv1_SHA256SESS ALG2ALGFLG(ALG_AKAv1_SHA256SESS) +#define ALGFLG_AKAv1_SHA512_256 ALG2ALGFLG(ALG_AKAv1_SHA512_256) +#define ALGFLG_AKAv1_SHA512_256SESS ALG2ALGFLG(ALG_AKAv1_SHA512_256SESS) +#define ALGFLG_AKAv2_MD5 ALG2ALGFLG(ALG_AKAv2_MD5) +#define ALGFLG_AKAv2_MD5SESS ALG2ALGFLG(ALG_AKAv2_MD5SESS) +#define ALGFLG_AKAv2_SHA256 ALG2ALGFLG(ALG_AKAv2_SHA256) +#define ALGFLG_AKAv2_SHA256SESS ALG2ALGFLG(ALG_AKAv2_SHA256SESS) +#define ALGFLG_AKAv2_SHA512_256 ALG2ALGFLG(ALG_AKAv2_SHA512_256) +#define ALGFLG_AKAv2_SHA512_256SESS ALG2ALGFLG(ALG_AKAv2_SHA512_256SESS) /* Canonical algorithm names */ #define ALG_SESS_SFX "-sess" @@ -63,6 +91,23 @@ typedef enum alg { #define ALG_SHA256SESS_STR ALG_SHA256_STR ALG_SESS_SFX #define ALG_SHA512_256_STR "SHA-512-256" #define ALG_SHA512_256SESS_STR ALG_SHA512_256_STR ALG_SESS_SFX +#define ALG_AKAv1_PRX "AKAv1-" +#define ALG_AKAv1_MD5_STR ALG_AKAv1_PRX ALG_MD5_STR +#define ALG_AKAv1_MD5SESS_STR ALG_AKAv1_PRX ALG_MD5SESS_STR +#define ALG_AKAv1_SHA256_STR ALG_AKAv1_PRX ALG_SHA256_STR +#define ALG_AKAv1_SHA256SESS_STR ALG_AKAv1_PRX ALG_SHA256SESS_STR +#define ALG_AKAv1_SHA512_256_STR ALG_AKAv1_PRX ALG_SHA512_256_STR +#define ALG_AKAv1_SHA512_256SESS_STR ALG_AKAv1_PRX ALG_SHA512_256SESS_STR +#define ALG_AKAv2_PRX "AKAv2-" +#define ALG_AKAv2_MD5_STR ALG_AKAv2_PRX ALG_MD5_STR +#define ALG_AKAv2_MD5SESS_STR ALG_AKAv2_PRX ALG_MD5SESS_STR +#define ALG_AKAv2_SHA256_STR ALG_AKAv2_PRX ALG_SHA256_STR +#define ALG_AKAv2_SHA256SESS_STR ALG_AKAv2_PRX ALG_SHA256SESS_STR +#define ALG_AKAv2_SHA512_256_STR ALG_AKAv2_PRX ALG_SHA512_256_STR +#define ALG_AKAv2_SHA512_256SESS_STR ALG_AKAv2_PRX ALG_SHA512_256SESS_STR + +#define ALG_IS_AKAv1(alg) (alg >= ALG_AKAv1_FIRST && alg <= ALG_AKAv1_LAST) +#define ALG_IS_AKAv2(alg) (alg >= ALG_AKAv2_FIRST && alg <= ALG_AKAv2_LAST) /* Quality Of Protection used */ typedef enum qop_type { From c25af6c3bf83d92e99e6a3be336eda83c11af3e9 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 23 Jan 2024 18:45:28 +0200 Subject: [PATCH 28/79] auth: allow pre_auth to skip aditional checks --- modules/auth/api.c | 7 ++++++- modules/auth/api.h | 7 +++++-- modules/auth/auth_mod.c | 2 +- modules/auth_aaa/authorize.c | 2 +- modules/auth_db/authorize.c | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/modules/auth/api.c b/modules/auth/api.c index bd16f767d8b..b8def865069 100644 --- a/modules/auth/api.c +++ b/modules/auth/api.c @@ -150,7 +150,7 @@ static inline int find_credentials(struct sip_msg* _m, str* _realm, * ACK and CANCEL */ auth_result_t pre_auth(struct sip_msg* _m, str* _realm, hdr_types_t _hftype, - struct hdr_field** _h) + struct hdr_field** _h, unsigned skip_flags) { int ret, ecode; auth_body_t* c; @@ -197,6 +197,8 @@ auth_result_t pre_auth(struct sip_msg* _m, str* _realm, hdr_types_t _hftype, LM_DBG("credentials with given realm not found\n"); return NO_CREDENTIALS; } + if (skip_flags & AUTH_SKIP_CRED_CHECK) + return DO_AUTHORIZATION; /* Pointer to the parsed credentials */ c = (auth_body_t*)((*_h)->parsed); @@ -217,6 +219,9 @@ auth_result_t pre_auth(struct sip_msg* _m, str* _realm, hdr_types_t _hftype, goto ereply; } + if (skip_flags & AUTH_SKIP_NONCE_CHECK) + return DO_AUTHORIZATION; + struct nonce_params np; if (decr_nonce(ncp, str2const(&dcp->nonce), &np) != 0) { LM_DBG("failed to decrypt nonce (stale/invalid)\n"); diff --git a/modules/auth/api.h b/modules/auth/api.h index 2235bdb9869..5cd0d9573d9 100644 --- a/modules/auth/api.h +++ b/modules/auth/api.h @@ -47,6 +47,9 @@ typedef enum auth_result { /* Means to continue doing authorization */ } auth_result_t; +#define AUTH_SKIP_CRED_CHECK (1<<0) +#define AUTH_SKIP_NONCE_CHECK (1<<1) + /* * Purpose of this function is to find credentials with given realm, @@ -55,9 +58,9 @@ typedef enum auth_result { * ACK and CANCEL */ typedef auth_result_t (*pre_auth_t)(struct sip_msg* _m, str* _realm, - hdr_types_t _hftype, struct hdr_field** _h); + hdr_types_t _hftype, struct hdr_field** _h, unsigned skip_flags); auth_result_t pre_auth(struct sip_msg* _m, str* _realm, - hdr_types_t _hftype, struct hdr_field** _h); + hdr_types_t _hftype, struct hdr_field** _h, unsigned skip_flags); /* diff --git a/modules/auth/auth_mod.c b/modules/auth/auth_mod.c index fcec5105619..ca9daa90400 100644 --- a/modules/auth/auth_mod.c +++ b/modules/auth/auth_mod.c @@ -448,7 +448,7 @@ static inline int pv_authorize(struct sip_msg* msg, str *domain, if (domain->len==0) domain->s = 0; - ret = pre_auth(msg, domain, hftype, &h); + ret = pre_auth(msg, domain, hftype, &h, 0); if (ret != DO_AUTHORIZATION) return ret; diff --git a/modules/auth_aaa/authorize.c b/modules/auth_aaa/authorize.c index 57d7c258c0c..377d0883f00 100644 --- a/modules/auth_aaa/authorize.c +++ b/modules/auth_aaa/authorize.c @@ -89,7 +89,7 @@ static inline int authorize(struct sip_msg* _msg, str* _realm, domain.s = 0; } - ret = auth_api.pre_auth(_msg, &domain, _hftype, &h); + ret = auth_api.pre_auth(_msg, &domain, _hftype, &h, 0); if (ret != DO_AUTHORIZATION) return ret; diff --git a/modules/auth_db/authorize.c b/modules/auth_db/authorize.c index edbcb78cf3c..54e661c2ab0 100644 --- a/modules/auth_db/authorize.c +++ b/modules/auth_db/authorize.c @@ -262,7 +262,7 @@ static inline int authorize(struct sip_msg* _m, str *domain, auth_result_t ret; db_res_t* result = NULL; - ret = auth_api.pre_auth(_m, domain, _hftype, &h); + ret = auth_api.pre_auth(_m, domain, _hftype, &h, 0); if (ret != DO_AUTHORIZATION) return ret; From 0625eadfd8dd2490699cead5636f44e0969619ad Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Wed, 14 Feb 2024 12:30:43 +0200 Subject: [PATCH 29/79] auth: expose send_resp function in API --- modules/auth/api.c | 1 + modules/auth/api.h | 7 +++++++ modules/auth/common.c | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/auth/api.c b/modules/auth/api.c index b8def865069..a1160618d93 100644 --- a/modules/auth/api.c +++ b/modules/auth/api.c @@ -550,6 +550,7 @@ int bind_auth(auth_api_t* api) api->check_response = check_response; api->build_auth_hf = build_auth_hf; api->build_auth_info_hf = build_auth_info_hf; + api->send_resp = send_resp; get_rpid_avp( &api->rpid_avp, &api->rpid_avp_type ); diff --git a/modules/auth/api.h b/modules/auth/api.h index 5cd0d9573d9..34cc0414ce5 100644 --- a/modules/auth/api.h +++ b/modules/auth/api.h @@ -106,6 +106,12 @@ typedef char *(*build_auth_hf_t)(struct nonce_context *ncp, struct nonce_params typedef str *(*build_auth_info_hf_t)(str *msg_body, str *method, dig_cred_t *cred, struct digest_auth_credential *auth_data); +/* + * Helper function to send a reply + */ +typedef int (*send_resp_t)(struct sip_msg* _m, int _code, + const str* _reason, const str hdrs[], int nhdrs); + /* * Strip the beginning of realm */ @@ -124,6 +130,7 @@ typedef struct auth_api { check_response_t check_response; /* check auth response */ build_auth_hf_t build_auth_hf; /* build {WWW,Proxy}-Authenticate header field */ build_auth_info_hf_t build_auth_info_hf; /* build Authentication-Info header */ + send_resp_t send_resp;/* Helper function to send a response */ } auth_api_t; diff --git a/modules/auth/common.c b/modules/auth/common.c index babe45b460f..63f1c091450 100644 --- a/modules/auth/common.c +++ b/modules/auth/common.c @@ -88,5 +88,5 @@ int send_resp(struct sip_msg* _m, int _code, const str* _reason, } } - return sigb.reply(_m, _code, _reason, NULL); + return sigb.reply(_m, _code, (_reason?_reason:_str(error_text(_code))), NULL); } From dfb129d7fe58b80dd70ff20863365840e81914d0 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Wed, 14 Feb 2024 12:31:13 +0200 Subject: [PATCH 30/79] auth: make qop parameter build available --- modules/auth/challenge.c | 26 ++------------------------ modules/auth/qop.h | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/modules/auth/challenge.c b/modules/auth/challenge.c index e4a3289bff8..cdccee724c7 100644 --- a/modules/auth/challenge.c +++ b/modules/auth/challenge.c @@ -38,6 +38,7 @@ #include "../../mod_fix.h" #include "auth_mod.h" #include "common.h" +#include "qop.h" #include "challenge.h" #include "../../lib/digest_auth/dauth_nonce.h" #include "index.h" @@ -58,10 +59,6 @@ */ #define MESSAGE_401 "Unauthorized" -#define QOP_AUTH ", qop=\"" QOP_AUTH_STR "\"" -#define QOP_AUTH_INT ", qop=\"" QOP_AUTHINT_STR "\"" -#define QOP_AUTH_BOTH_AAI ", qop=\"" QOP_AUTH_STR "," QOP_AUTHINT_STR "\"" -#define QOP_AUTH_BOTH_AIA ", qop=\"" QOP_AUTHINT_STR "," QOP_AUTH_STR "\"" #define STALE_PARAM ", stale=true" #define DIGEST_REALM ": Digest realm=\"" #define DIGEST_NONCE "\", nonce=\"" @@ -78,31 +75,12 @@ char *build_auth_hf(struct nonce_context *ncp, struct nonce_params *calc_np, { char *hf, *p; str_const alg_param; - str_const qop_param = STR_NULL_const; + str_const qop_param = get_qop_param(calc_np->qop); str_const stale_param = STR_NULL_const; const str_const digest_realm = str_const_init(DIGEST_REALM); const str_const nonce_param = str_const_init(DIGEST_NONCE); str_const opaque_param; - if (calc_np->qop) { - switch (calc_np->qop) { - case QOP_AUTH_D: - qop_param = str_const_init(QOP_AUTH); - break; - case QOP_AUTHINT_D: - qop_param = str_const_init(QOP_AUTH_INT); - break; - case QOP_AUTHINT_AUTH_D: - qop_param = str_const_init(QOP_AUTH_BOTH_AAI); - break; - case QOP_AUTH_AUTHINT_D: - qop_param = str_const_init(QOP_AUTH_BOTH_AIA); - break; - default: - LM_ERR("Wrong _qop value: %d\n", calc_np->qop); - abort(); - } - } if (_stale) stale_param = str_const_init(STALE_PARAM); diff --git a/modules/auth/qop.h b/modules/auth/qop.h index f3a6d63baa1..41f8bf468bc 100644 --- a/modules/auth/qop.h +++ b/modules/auth/qop.h @@ -60,4 +60,40 @@ static inline int fixup_qop(void** param) return 0; } +#define QOP_AUTH ", qop=\"" QOP_AUTH_STR "\"" +#define QOP_AUTH_INT ", qop=\"" QOP_AUTHINT_STR "\"" +#define QOP_AUTH_BOTH_AAI ", qop=\"" QOP_AUTH_STR "," QOP_AUTHINT_STR "\"" +#define QOP_AUTH_BOTH_AIA ", qop=\"" QOP_AUTHINT_STR "," QOP_AUTH_STR "\"" + +static inline str_const get_qop_param(qop_type_t qop) +{ + static str_const qop_param; + switch (qop) { + case QOP_UNSPEC_D: + qop_param = STR_NULL_const; + break; + case QOP_AUTH_D: + qop_param = str_const_init(QOP_AUTH); + break; + case QOP_AUTHINT_D: + qop_param = str_const_init(QOP_AUTH_INT); + break; + case QOP_AUTHINT_AUTH_D: + qop_param = str_const_init(QOP_AUTH_BOTH_AAI); + break; + case QOP_AUTH_AUTHINT_D: + qop_param = str_const_init(QOP_AUTH_BOTH_AIA); + break; + default: + LM_ERR("Wrong _qop value: %d\n", qop); + abort(); + } + return qop_param; +} + +#undef QOP_AUTH +#undef QOP_AUTH_INT +#undef QOP_AUTH_BOTH_AAI +#undef QOP_AUTH_BOTH_AIA + #endif /* AUTH_QOP_H */ From cea69f861218b83e6f922a7379b6068ef6bef4f1 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 22 Feb 2024 12:27:55 +0200 Subject: [PATCH 31/79] digest: add auts parameter parsing --- parser/digest/digest_keys.h | 1 + parser/digest/digest_parser.c | 1 + parser/digest/digest_parser.h | 1 + parser/digest/param_parser.c | 9 ++++++++- parser/digest/param_parser.h | 1 + 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/parser/digest/digest_keys.h b/parser/digest/digest_keys.h index cbfde06fc82..321780e32ad 100644 --- a/parser/digest/digest_keys.h +++ b/parser/digest/digest_keys.h @@ -34,5 +34,6 @@ #define _opaq_ 0x7161706f /* "opaq" */ #define _algo_ 0x6f676c61 /* "algo" */ #define _rith_ 0x68746972 /* "rith" */ +#define _auts_ 0x73747561 /* "auts" */ #endif /* DIGEST_KEYS_H */ diff --git a/parser/digest/digest_parser.c b/parser/digest/digest_parser.c index 2f54998541f..381278377e3 100644 --- a/parser/digest/digest_parser.c +++ b/parser/digest/digest_parser.c @@ -194,6 +194,7 @@ static inline int parse_digest_param(str* _s, dig_cred_t* _c) case PAR_QOP: ptr = &_c->qop.qop_str; break; case PAR_NC: ptr = &_c->nc; break; case PAR_ALGORITHM: ptr = &_c->alg.alg_str; break; + case PAR_AUTS: ptr = &_c->auts; break; case PAR_OTHER: ptr = &dummy; break; default: ptr = &dummy; break; } diff --git a/parser/digest/digest_parser.h b/parser/digest/digest_parser.h index af38fbccffe..c78e3c3bd5b 100644 --- a/parser/digest/digest_parser.h +++ b/parser/digest/digest_parser.h @@ -160,6 +160,7 @@ typedef struct dig_cred { str opaque; /* Opaque data string */ struct qp qop; /* Quality Of Protection */ str nc; /* Nonce count parameter */ + str auts; /* Auts parameter */ } dig_cred_t; diff --git a/parser/digest/param_parser.c b/parser/digest/param_parser.c index 14b8e7f4971..3005fd02164 100644 --- a/parser/digest/param_parser.c +++ b/parser/digest/param_parser.c @@ -172,6 +172,12 @@ goto other +#define auts_CASE \ + *_type = PAR_AUTS;\ + p += 4; \ + goto end; \ + + #define FIRST_QUATERNIONS \ case _user_: user_CASE; \ case _real_: real_CASE; \ @@ -179,7 +185,8 @@ case _resp_: resp_CASE; \ case _cnon_: cnon_CASE; \ case _opaq_: opaq_CASE; \ - case _algo_: algo_CASE; + case _algo_: algo_CASE; \ + case _auts_: auts_CASE; diff --git a/parser/digest/param_parser.h b/parser/digest/param_parser.h index ea16b900a1f..f8809ee08bc 100644 --- a/parser/digest/param_parser.h +++ b/parser/digest/param_parser.h @@ -41,6 +41,7 @@ typedef enum dig_par { PAR_QOP, /* qop parameter */ PAR_NC, /* nonce-count parameter */ PAR_ALGORITHM, /* algorithm parameter */ + PAR_AUTS, /* auts parameter */ PAR_OTHER /* unknown parameter */ } dig_par_t; From 5cfbf1a633b23db9df94b0046b017b978c75f16c Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Mon, 26 Feb 2024 15:42:34 +0200 Subject: [PATCH 32/79] digest: print algorithm value --- parser/digest/digest_parser.c | 45 +++++++++++++++++++++++++++++++++++ parser/digest/digest_parser.h | 1 + 2 files changed, 46 insertions(+) diff --git a/parser/digest/digest_parser.c b/parser/digest/digest_parser.c index 381278377e3..f012d92c534 100644 --- a/parser/digest/digest_parser.c +++ b/parser/digest/digest_parser.c @@ -276,6 +276,51 @@ alg_t parse_digest_algorithm(const str *sp) return ALG_OTHER; } +const str *print_digest_algorithm(alg_t alg) +{ + switch (alg) { + case ALG_MD5: + return _str(ALG_MD5_STR); + case ALG_MD5SESS: + return _str(ALG_MD5SESS_STR); + case ALG_SHA256: + return _str(ALG_SHA256_STR); + case ALG_SHA256SESS: + return _str(ALG_SHA256SESS_STR); + case ALG_SHA512_256: + return _str(ALG_SHA512_256_STR); + case ALG_SHA512_256SESS: + return _str(ALG_SHA512_256SESS_STR); + case ALG_AKAv1_MD5: + return _str(ALG_AKAv1_MD5_STR); + case ALG_AKAv1_MD5SESS: + return _str(ALG_AKAv1_MD5SESS_STR); + case ALG_AKAv1_SHA256: + return _str(ALG_AKAv1_SHA256_STR); + case ALG_AKAv1_SHA256SESS: + return _str(ALG_AKAv1_SHA256SESS_STR); + case ALG_AKAv1_SHA512_256: + return _str(ALG_AKAv1_SHA512_256_STR); + case ALG_AKAv1_SHA512_256SESS: + return _str(ALG_AKAv1_SHA512_256SESS_STR); + case ALG_AKAv2_MD5: + return _str(ALG_AKAv2_MD5_STR); + case ALG_AKAv2_MD5SESS: + return _str(ALG_AKAv2_MD5SESS_STR); + case ALG_AKAv2_SHA256: + return _str(ALG_AKAv2_SHA256_STR); + case ALG_AKAv2_SHA256SESS: + return _str(ALG_AKAv2_SHA256SESS_STR); + case ALG_AKAv2_SHA512_256: + return _str(ALG_AKAv2_SHA512_256_STR); + case ALG_AKAv2_SHA512_256SESS: + return _str(ALG_AKAv2_SHA512_256SESS_STR); + default: + case ALG_OTHER: + case ALG_UNSPEC: + return _str("Unknown"); + } +} /* * Parse username for user and domain parts diff --git a/parser/digest/digest_parser.h b/parser/digest/digest_parser.h index c78e3c3bd5b..1992f3bf4a0 100644 --- a/parser/digest/digest_parser.h +++ b/parser/digest/digest_parser.h @@ -194,5 +194,6 @@ void init_dig_cred(dig_cred_t* _c); */ int parse_digest_cred(str* _s, dig_cred_t* _c); alg_t parse_digest_algorithm(const str *); +const str *print_digest_algorithm(alg_t alg); #endif /* DIGEST_PARSER_H */ From 1e94ebfca7e4da4b48f0070069bda62222889804 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Mon, 26 Feb 2024 16:30:25 +0200 Subject: [PATCH 33/79] str_list: provie support for adding str to list --- str_list.h | 62 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/str_list.h b/str_list.h index 61b1c149219..2e08493c2a8 100644 --- a/str_list.h +++ b/str_list.h @@ -38,6 +38,61 @@ typedef struct _str_dlist { struct list_head list; } str_dlist; +static inline str_list *_new_str_list(str *val, osips_malloc_t alloc_item) +{ + str_list *new_el; + if (!alloc_item) + return NULL; + new_el = alloc_item(sizeof *new_el + val->len + 1); + if (!new_el) + return NULL; + memset(new_el, 0, sizeof *new_el); + new_el->s.s = (char *)(new_el + 1); + str_cpy(&new_el->s, val); + new_el->s.s[new_el->s.len] = '\0'; + return new_el; +} + +#define new_pkg_str_list(val) \ + _new_str_list(val, osips_pkg_malloc) + +#define new_shm_str_list(val) \ + _new_str_list(val, osips_shm_malloc) + +static inline str_list *_insert_str_list(str_list **list, str *val, osips_malloc_t alloc_item) +{ + str_list *new_el = _new_str_list(val, alloc_item); + if (!new_el) + return NULL; + + new_el->next = *list; + *list = new_el; + return *list; +} + +#define insert_pkg_str_list(list, val) \ + _insert_str_list(list, val, osips_pkg_malloc) + +#define insert_shm_str_list(list, val) \ + _insert_str_list(list, val, osips_shm_malloc) + +static inline str_list *_add_str_list(str_list **list, str *val, osips_malloc_t alloc_item) +{ + str_list *new_el = _new_str_list(val, alloc_item); + if (!new_el) + return NULL; + + add_last(new_el, *list); + return *list; +} + +#define add_pkg_str_list(list, val) \ + _add_str_list(list, val, osips_pkg_malloc) + +#define add_shm_str_list(list, val) \ + _add_str_list(list, val, osips_shm_malloc) + + static inline void _free_str_list(str_list *list, osips_free_t free_item, osips_free_t free_str) { @@ -67,15 +122,10 @@ static inline str_list *dup_shm_str_list(const str_list *list) const str_list *it; for (it = list; it; it = it->next) { - item = shm_malloc(sizeof *item + it->s.len + 1); + item = new_shm_str_list((str *)&it->s); if (!item) goto oom; - item->s.s = (char *)(item + 1); - str_cpy(&item->s, &it->s); - item->s.s[item->s.len] = '\0'; - - item->next = NULL; add_last(item, ret); } From 904078b02cc66fd3e8fa4f4d36b192c1a35727bd Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Mon, 26 Feb 2024 16:31:13 +0200 Subject: [PATCH 34/79] csv: add support for printing a csv_record --- lib/csv.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/csv.h | 5 ++++ 2 files changed, 80 insertions(+) diff --git a/lib/csv.c b/lib/csv.c index 6a0952fcaad..96b65d6ba09 100644 --- a/lib/csv.c +++ b/lib/csv.c @@ -243,3 +243,78 @@ void free_csv_record(csv_record *record) free_f(prev); } } + +static int check_quote_csv_record(str *val, int *escape) +{ + char *p; + int quote = 0; + *escape = 0; + + for (p = val->s; p < val->s + val->len; p++) { + switch (*p) { + case '"': + (*escape)++; + /* fallthrough */ + case ',': + case '\n': + quote = 1; + break; + } + } + return quote; +} + +str *__print_csv_record(csv_record *record, enum csv_flags print_flags, + unsigned char sep) +{ + static str ret; + str_list *it; + int len = -1, esc; + char *p, *c; + + if (print_flags & CSV_SHM) + malloc_f = osips_shm_malloc; + else + malloc_f = osips_pkg_malloc; + + for (it = record; it; it = it->next) { + len += 1 /* sep */ + it->s.len; + /* check to see if ne need to encode */ + if (check_quote_csv_record(&it->s, &esc)) + len += 2 + esc; + } + + ret.s = malloc_f(len); + if (!ret.s) + return NULL; + p = ret.s; + for (it = record; it; it = it->next) { + if (it != record) + *p++ = sep; + + if (check_quote_csv_record(&it->s, &esc)) { + if (!esc) { + /* simply add the quotes */ + *p++ = '"'; + memcpy(p, it->s.s, it->s.len); + p+= it->s.len; + *p++ = '"'; + } else { + for (c = it->s.s; c < it->s.s + it->s.len; c++) { + switch (*c) { + case '"': + *p++ = '"'; + break; + } + *p++ = *c; + } + } + } else { + /* simply copy the content */ + memcpy(p, it->s.s, it->s.len); + p += it->s.len; + } + } + ret.len = len; + return &ret; +} diff --git a/lib/csv.h b/lib/csv.h index 0228bf8ea18..039315d6428 100644 --- a/lib/csv.h +++ b/lib/csv.h @@ -56,4 +56,9 @@ csv_record *__parse_csv_record(const str *in, enum csv_flags parse_flags, /* Easily free your CSV records, regardless of any flags set during parsing */ void free_csv_record(csv_record *record); +str *__print_csv_record(csv_record *record, enum csv_flags print_flags, + unsigned char sep); +#define _print_csv_record(in, flags) __print_csv_record(in, flags, ',') +#define print_csv_record(in) _print_csv_record(in, 0) + #endif /* __LIB_CSV__ */ From 55a587de2fe37826a13f04be2af8cb5b6cb55d68 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 27 Feb 2024 12:32:56 +0200 Subject: [PATCH 35/79] ut: add hex2string decoding --- modules/aaa_diameter/dm_impl.c | 25 +++++-------------------- ut.h | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/modules/aaa_diameter/dm_impl.c b/modules/aaa_diameter/dm_impl.c index 3630421cc17..80a02807532 100644 --- a/modules/aaa_diameter/dm_impl.c +++ b/modules/aaa_diameter/dm_impl.c @@ -2196,8 +2196,8 @@ static cJSON *dict_avp_dec_ip(struct avp_hdr * h, struct dict_avp_data *avp) static int dict_avp_enc_hex(cJSON *obj, struct dict_avp_data *avp, int _, str *ret) { - int len, i; - char *buf, *val; + int len; + char *buf; if ((obj->type & cJSON_String) == 0) return 1; /* encode it as it is */ @@ -2207,24 +2207,9 @@ static int dict_avp_enc_hex(cJSON *obj, struct dict_avp_data *avp, int _, str *r LM_ERR("oom for hex encoding\n"); return -1; } - val = obj->valuestring; - for (i = 0; i < len / 2; i++) { - if(val[2*i]>='0' && val[2*i]<='9') - buf[i] = (val[2*i]-'0') << 4; - else if(val[2*i]>='a' && val[2*i]<='f') - buf[i] = (val[2*i]-'a'+10) << 4; - else if(val[2*i]>='A' && val[2*i]<='F') - buf[i] = (val[2*i]-'A'+10) << 4; - else goto error; - - if(val[2*i+1]>='0' && val[2*i+1]<='9') - buf[i] += val[2*i+1]-'0'; - else if(val[2*i+1]>='a' && val[2*i+1]<='f') - buf[i] += val[2*i+1]-'a'+10; - else if(val[2*i+1]>='A' && val[2*i+1]<='F') - buf[i] += val[2*i+1]-'A'+10; - else goto error; - } + len = hex2string(obj->valuestring, len, buf); + if (len < 0) + goto error; ret->s = buf; ret->len = len/2; return 0; diff --git a/ut.h b/ut.h index db402414042..0a29d87a542 100644 --- a/ut.h +++ b/ut.h @@ -472,6 +472,33 @@ inline static int string2hex( return orig_len * 2; } +inline static int hex2string( + /* input */ const char *str, int len, + /* output */ char *hex ) +{ + int i; + for (i = 0; i < len / 2; i++) { + if(str[2*i]>='0' && str[2*i]<='9') + hex[i] = (str[2*i]-'0') << 4; + else if(str[2*i]>='a' && str[2*i]<='f') + hex[i] = (str[2*i]-'a'+10) << 4; + else if(str[2*i]>='A' && str[2*i]<='F') + hex[i] = (str[2*i]-'A'+10) << 4; + else goto error; + + if(str[2*i+1]>='0' && str[2*i+1]<='9') + hex[i] += str[2*i+1]-'0'; + else if(str[2*i+1]>='a' && str[2*i+1]<='f') + hex[i] += str[2*i+1]-'a'+10; + else if(str[2*i+1]>='A' && str[2*i+1]<='F') + hex[i] += str[2*i+1]-'A'+10; + else goto error; + } + return i; +error: + return -1; +} + /* portable sleep in microseconds (no interrupt handling now) */ inline static void sleep_us( unsigned int nusecs ) From ecac8076bcbc664a9416c35f49b528dc11f53dd5 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 27 Feb 2024 12:43:52 +0200 Subject: [PATCH 36/79] digest_auth: name parameters in headers Avoid compiler warnings --- lib/digest_auth/digest_auth.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/digest_auth/digest_auth.h b/lib/digest_auth/digest_auth.h index e8cc25142b3..b0905429a2f 100644 --- a/lib/digest_auth/digest_auth.h +++ b/lib/digest_auth/digest_auth.h @@ -83,9 +83,9 @@ struct dauth_algorithm_match { #define DAUTH_AHFM_MSKSUP(_am) (&MATCH_AUTH_HF(dauth_algorithm_check, \ &DAUTH_ALGMATCH_MSK(_am))) -int digest_algorithm_available(alg_t); -int dauth_algorithm_check(const struct authenticate_body *, - const struct match_auth_hf_desc *); +int digest_algorithm_available(alg_t alg); +int dauth_algorithm_check(const struct authenticate_body * body, + const struct match_auth_hf_desc * desc); int dauth_fixup_algorithms(void** param); #endif From 77773e572a7817b90d7ea5cb69aa76434ab9bbe6 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 27 Feb 2024 15:47:22 +0200 Subject: [PATCH 37/79] digest_auth: add functions for AKA digest --- lib/digest_auth/dauth_calc.c | 12 ++++++++++++ lib/digest_auth/digest_auth.c | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/digest_auth/dauth_calc.c b/lib/digest_auth/dauth_calc.c index 657c1bb2dfa..dc37b08bd80 100644 --- a/lib/digest_auth/dauth_calc.c +++ b/lib/digest_auth/dauth_calc.c @@ -30,28 +30,40 @@ const struct digest_auth_calc* get_digest_calc(alg_t algorithm) switch (algorithm) { case ALG_UNSPEC: case ALG_MD5: + case ALG_AKAv1_MD5: + case ALG_AKAv2_MD5: digest_calc = &md5_digest_calc; break; case ALG_MD5SESS: + case ALG_AKAv1_MD5SESS: + case ALG_AKAv2_MD5SESS: digest_calc = &md5sess_digest_calc; break; #if defined(SHA_256_ENABLE) case ALG_SHA256: + case ALG_AKAv1_SHA256: + case ALG_AKAv2_SHA256: digest_calc = &sha256_digest_calc; break; case ALG_SHA256SESS: + case ALG_AKAv1_SHA256SESS: + case ALG_AKAv2_SHA256SESS: digest_calc = &sha256sess_digest_calc; break; #endif #if defined(SHA_512_256_ENABLE) case ALG_SHA512_256: + case ALG_AKAv1_SHA512_256: + case ALG_AKAv2_SHA512_256: digest_calc = &sha512t256_digest_calc; break; case ALG_SHA512_256SESS: + case ALG_AKAv1_SHA512_256SESS: + case ALG_AKAv2_SHA512_256SESS: digest_calc = &sha512t256sess_digest_calc; break; #endif diff --git a/lib/digest_auth/digest_auth.c b/lib/digest_auth/digest_auth.c index 732e31108ee..b6a2408297d 100644 --- a/lib/digest_auth/digest_auth.c +++ b/lib/digest_auth/digest_auth.c @@ -31,13 +31,25 @@ int digest_algorithm_available(alg_t algorithm) case ALG_UNSPEC: case ALG_MD5: case ALG_MD5SESS: + case ALG_AKAv1_MD5: + case ALG_AKAv1_MD5SESS: + case ALG_AKAv2_MD5: + case ALG_AKAv2_MD5SESS: #if defined(SHA_256_ENABLE) case ALG_SHA256: case ALG_SHA256SESS: + case ALG_AKAv1_SHA256: + case ALG_AKAv1_SHA256SESS: + case ALG_AKAv2_SHA256: + case ALG_AKAv2_SHA256SESS: #endif #if defined(SHA_512_256_ENABLE) case ALG_SHA512_256: case ALG_SHA512_256SESS: + case ALG_AKAv1_SHA512_256: + case ALG_AKAv1_SHA512_256SESS: + case ALG_AKAv2_SHA512_256: + case ALG_AKAv2_SHA512_256SESS: #endif return (1); From 096cea00fad84354169e8c46ee89af14c325a23f Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Wed, 13 Mar 2024 12:44:49 +0200 Subject: [PATCH 38/79] lib: add pthread cond implementation --- lib/cond.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/cond.h | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 lib/cond.c create mode 100644 lib/cond.h diff --git a/lib/cond.c b/lib/cond.c new file mode 100644 index 00000000000..763ab97fbda --- /dev/null +++ b/lib/cond.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 OpenSIPS Solutions + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cond.h" +#include "../dprint.h" + +int cond_init(gen_cond_t *cond) +{ + int ret = -1; + pthread_condattr_t cattr; + pthread_mutexattr_t mattr; + + if (pthread_mutexattr_init(&mattr) != 0) { + LM_ERR("could not initialize mutex attributes\n"); + return -1; + } + if (pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED) != 0) { + LM_ERR("could not mark mutex attribute as shared\n"); + goto mutex_error; + } + if (pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST) != 0) { + LM_ERR("could not mark mutex attribute as robust\n"); + goto mutex_error; + } + if (pthread_mutex_init(&cond->m, &mattr) != 0) { + LM_ERR("could not initialize mutex\n"); + goto mutex_error; + } + if (pthread_condattr_init(&cattr) != 0) { + LM_ERR("could not initialize cond attributes\n"); + goto cond_error; + } + if (pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED) != 0) { + LM_ERR("could not mark mutex cond as shared\n"); + goto cond_error; + } + if (pthread_cond_init(&cond->c, &cattr) != 0) { + LM_ERR("could not initialize cond\n"); + goto cond_error; + } + return 0; +cond_error: + pthread_condattr_destroy(&cattr); +mutex_error: + pthread_mutexattr_destroy(&mattr); + return ret; +} + +void cond_destroy(gen_cond_t *cond) +{ + pthread_cond_destroy(&cond->c); + pthread_mutex_destroy(&cond->m); +} diff --git a/lib/cond.h b/lib/cond.h new file mode 100644 index 00000000000..c4a412b2934 --- /dev/null +++ b/lib/cond.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 OpenSIPS Solutions + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __OSIPS_COND__ +#define __OSIPS_COND__ + +#include + +typedef struct gen_cond { + pthread_mutex_t m; + pthread_cond_t c; +} gen_cond_t; + +/* initializes a condition allocated in shared memory */ +int cond_init(gen_cond_t *cond); + +/* destroyes a condition */ +void cond_destroy(gen_cond_t *cond); + +#define cond_lock(_c) pthread_mutex_lock(&(_c)->m) +#define cond_unlock(_c) pthread_mutex_unlock(&(_c)->m) +#define cond_wait(_c) pthread_cond_wait(&(_c)->c, &(_c)->m) +/* make sure we reset the errno, to avoid confusion when resumed */ +#define cond_timedwait(_c, _ts) \ + do { \ + errno = 0; \ + pthread_cond_timedwait(&(_c)->c, &(_c)->m, (_ts)); \ + } while (0) +#define cond_has_timedout(_c) (errno == ETIMEDOUT || errno == EAGAIN)/* TODO do we need to store this during wait? */ +#define cond_signal(_c) pthread_cond_signal(&(_c)->c) +#define cond_broadcast(_c) pthread_cond_broadcast(&(_c)->c) + +#endif /* __OSIPS_COND__ */ From 9489895719aa0413b6ee8a552a71b320095a21ed Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 27 Feb 2024 15:28:40 +0200 Subject: [PATCH 39/79] auth_aka: add new AKA auth module --- modules/auth_aka/Makefile | 10 + modules/auth_aka/aka_av_mgm.c | 553 ++++++++++ modules/auth_aka/aka_av_mgm.h | 46 + modules/auth_aka/auth_aka.c | 1253 +++++++++++++++++++++++ modules/auth_aka/auth_aka.h | 101 ++ modules/auth_aka/doc/auth_aka.xml | 27 + modules/auth_aka/doc/auth_aka_admin.xml | 733 +++++++++++++ 7 files changed, 2723 insertions(+) create mode 100644 modules/auth_aka/Makefile create mode 100644 modules/auth_aka/aka_av_mgm.c create mode 100644 modules/auth_aka/aka_av_mgm.h create mode 100644 modules/auth_aka/auth_aka.c create mode 100644 modules/auth_aka/auth_aka.h create mode 100644 modules/auth_aka/doc/auth_aka.xml create mode 100644 modules/auth_aka/doc/auth_aka_admin.xml diff --git a/modules/auth_aka/Makefile b/modules/auth_aka/Makefile new file mode 100644 index 00000000000..8e236ce5ece --- /dev/null +++ b/modules/auth_aka/Makefile @@ -0,0 +1,10 @@ +# +# AKA Authentication - AKA authentication support +# +# WARNING: do not run this directly, it should be run by the master Makefile + +include ../../Makefile.defs +auto_gen= +NAME=auth_aka.so +misclibs=../../lib/digest_auth/libdigest_auth.a +include ../../Makefile.modules diff --git a/modules/auth_aka/aka_av_mgm.c b/modules/auth_aka/aka_av_mgm.c new file mode 100644 index 00000000000..2dee7785e60 --- /dev/null +++ b/modules/auth_aka/aka_av_mgm.c @@ -0,0 +1,553 @@ +/* + * AKA Authentication - generic Authentication Manager support + * + * Copyright (C) 2024 Razvan Crainea + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "../../ut.h" +#include "../../lib/hash.h" +#include "aka_av_mgm.h" +#include "auth_aka.h" +#include + +static gen_hash_t *aka_users; +OSIPS_LIST_HEAD(aka_av_managers); + + +int aka_init_mgm(int hash_size) +{ + aka_users = hash_init(hash_size); + if (!aka_users) { + LM_ERR("cannot create AKA users hash\n"); + return -1; + } + return 0; +} + + +struct aka_av_mgm *aka_get_mgm(str *name) +{ + struct list_head *it; + struct aka_av_mgm *mgm; + list_for_each(it, &aka_av_managers) { + mgm = list_entry(it, struct aka_av_mgm, list); + if (str_casematch(&mgm->name, name)) + return mgm; + } + return 0; +} + +typedef int (*load_aka_av_mgm_f)(struct aka_av_mgm *mgm); + +struct aka_av_mgm *aka_load_mgm(str *name) +{ + char *aka_av_name; + struct aka_av_mgm *mgm = NULL; + load_aka_av_mgm_f load_aka_av_mgm; + + aka_av_name = pkg_malloc(sizeof(AKA_AV_MGM_PREFIX) + name->len); + if (!aka_av_name) { + LM_ERR("oom for AKA AV name\n"); + return NULL; + } + memcpy(aka_av_name, AKA_AV_MGM_PREFIX, sizeof(AKA_AV_MGM_PREFIX) - 1); + memcpy(aka_av_name + sizeof(AKA_AV_MGM_PREFIX) - 1, name->s, name->len); + aka_av_name[sizeof(AKA_AV_MGM_PREFIX) - 1 + name->len] = '\0'; + + load_aka_av_mgm = (load_aka_av_mgm_f)find_export(aka_av_name, 0); + if (!load_aka_av_mgm) { + LM_DBG("could not find binds for AV mgm <%.*s>(%s)\n", + name->len, name->s, aka_av_name); + pkg_free(aka_av_name); + return NULL; + } + pkg_free(aka_av_name); + /* found it - let's create it */ + mgm = pkg_malloc(sizeof *mgm + name->len); + if (!mgm) { + LM_ERR("oom for AV mgm\n"); + return NULL; + } + memset(mgm, 0, sizeof *mgm); + mgm->name.s = mgm->buf; + memcpy(mgm->name.s, name->s, name->len); + mgm->name.len = name->len; + if (load_aka_av_mgm(mgm) < 0) { + LM_ERR("could not load %.*s AV bindings\n", + name->len, name->s); + pkg_free(mgm); + return NULL; + } + + return mgm; +} + +static struct aka_user_pub *aka_user_pub_new(str *public_id) +{ + struct aka_user_pub *pub = shm_malloc(sizeof *pub + public_id->len); + if (!pub) { + LM_ERR("oom for user public identity!\n"); + return NULL; + } + pub->impu.s = pub->buf; + pub->impu.len = public_id->len; + memcpy(pub->impu.s, public_id->s, public_id->len); + INIT_LIST_HEAD(&pub->privates); + return pub; +} + +static struct aka_user *aka_user_new(struct aka_user_pub *pub, str *private_id) +{ + struct aka_user *user = shm_malloc(sizeof *user + private_id->len); + if (!user) { + LM_ERR("oom for user public identity!\n"); + return NULL; + } + memset(user, 0, sizeof *user); + if (cond_init(&user->cond) != 0) { + LM_ERR("could not initialize user cond\n"); + shm_free(user); + return NULL; + } + user->public = pub; + user->impi.s = user->buf; + user->impi.len = private_id->len; + memcpy(user->impi.s, private_id->s, private_id->len); + INIT_LIST_HEAD(&user->list); + INIT_LIST_HEAD(&user->avs); + INIT_LIST_HEAD(&user->async); + list_add(&user->list, &pub->privates); + return user; +} + +static void aka_user_pub_release(struct aka_user_pub *pub) +{ + if (!list_empty(&pub->privates)) + return; + /* no more privates pointing to us - remove and release */ + hash_remove_key(aka_users, pub->impu); + shm_free(pub); +} + +static struct aka_user *aka_user_pub_find(struct aka_user_pub *pub, str *private_id) +{ + struct aka_user *user; + struct list_head *it; + + list_for_each(it, &pub->privates) { + user = list_entry(it, struct aka_user, list); + if (str_match(private_id, &user->impi)) + return user; + } + return NULL; +} + +struct aka_user *aka_user_find(str *public_id, str *private_id) +{ + struct aka_user *user = NULL; + struct aka_user_pub **pub; + unsigned int hentry = hash_entry(aka_users, *public_id); + + hash_lock(aka_users, hentry); + pub = (struct aka_user_pub **)hash_find(aka_users, hentry, *public_id); + if (pub && *pub) { + user = aka_user_pub_find(*pub, private_id); + if (user) + user->ref++; + } + hash_unlock(aka_users, hentry); + return user; +} + +struct aka_user *aka_user_get(str *public_id, str *private_id) +{ + unsigned int hentry; + struct aka_user_pub **pub; + struct aka_user *user = NULL; + + hentry = hash_entry(aka_users, *public_id); + hash_lock(aka_users, hentry); + pub = (struct aka_user_pub **)hash_get(aka_users, hentry, *public_id); + if (!pub) + goto end; + if (*pub) { + user = aka_user_pub_find(*pub, private_id); + if (user) + goto ref; + } else { + *pub = aka_user_pub_new(public_id); + if (*pub == NULL) { + LM_ERR("cannot create user public identity!\n"); + goto end; + } + } + user = aka_user_new(*pub, private_id); + if (!user) { + LM_ERR("cannot create user public identity!\n"); + aka_user_pub_release(*pub); + goto end; + } +ref: + user->ref++; +end: + hash_unlock(aka_users, hentry); + return user; +} + +static void aka_user_try_free(struct aka_user *user) +{ + struct aka_user_pub *pub = user->public; + cond_lock(&user->cond); + if (!list_empty(&user->avs) || !list_empty(&user->async)) { + cond_unlock(&user->cond); + return; + } + cond_unlock(&user->cond); + list_del(&user->list); + cond_destroy(&user->cond); + shm_free(user); + /* release pub if not used anymore */ + aka_user_pub_release(pub); +} + +void aka_user_release(struct aka_user *user) +{ + unsigned int hentry; + hentry = hash_entry(aka_users, user->public->impu); + hash_lock(aka_users, hentry); + user->ref--; + if (user->ref == 0) + aka_user_try_free(user); + hash_unlock(aka_users, hentry); +} + +static struct aka_av *aka_av_get_state(struct aka_user *user, int algmask, enum aka_av_state state) +{ + struct list_head *it; + struct aka_av *av = NULL; + + /* find the first free AV */ + list_for_each(it, &user->avs) { + av = list_entry(it, struct aka_av, list); + /* check if AV algorithm is suitable */ + if (algmask >= -1 && av->algmask >= 0 && !(algmask & av->algmask)) + continue; + if (av->state == state) + break; + av = NULL; + } + return av; +} + +static struct aka_av *aka_av_match(struct aka_user *user, int algmask, str *nonce) +{ + struct list_head *it; + struct aka_av *av = NULL; + + list_for_each(it, &user->avs) { + av = list_entry(it, struct aka_av, list); + if (av->state == AKA_AV_INVALID) + continue; + /* check if AV algorithm is suitable */ + if (algmask >= 0 && av->algmask >= 0 && !(algmask & av->algmask)) + continue; + if (str_match(nonce, &av->authenticate)) + return av; + } + return NULL; +} + +struct aka_av *aka_av_get_nonce(struct aka_user *user, int algmask, str *nonce) +{ + struct aka_av *av = NULL; + + cond_lock(&user->cond); + av = aka_av_match(user, algmask, nonce); + if (av) { + if (av->state != AKA_AV_USING && av->state != AKA_AV_USED) + av = NULL; + else + av->state = AKA_AV_USED; + } + cond_unlock(&user->cond); + return av; +} + + +struct aka_av *aka_av_get_new_wait(struct aka_user *user, int algmask, long milliseconds) +{ + struct aka_av *av; + struct timespec spec, end, begin; + + cond_lock(&user->cond); + av = aka_av_get_state(user, algmask, AKA_AV_NEW); + if (!av) { + switch (milliseconds) { + case 0: /* just peaking */ + break; + case -1: /* blocking pop */ + do { + cond_wait(&user->cond); + } while ((av = aka_av_get_state(user, algmask, AKA_AV_NEW)) == NULL); + break; + default: + do { + timespec_get(&begin, TIME_UTC); + spec = begin; + spec.tv_sec += milliseconds / 1000; + spec.tv_nsec += (milliseconds % 1000) * 1000000; + errno = 0; + cond_timedwait(&user->cond, &spec); + av = aka_av_get_state(user, algmask, AKA_AV_NEW); /* one last time */ + if (cond_has_timedout(&user->cond)) + break; + if (!av) { + /* compute the drift/reminder */ + timespec_get(&end, TIME_UTC); + milliseconds -= (end.tv_sec - begin.tv_sec) * 1000 + + (end.tv_nsec - begin.tv_nsec) / 1000000; + } + } while (av == NULL && milliseconds > 0); + break; + } + } else { + av->state = AKA_AV_USING; + } + cond_unlock(&user->cond); + return av; +} + +static inline int aka_av_first_bit_mask(int algmask) +{ + int c; + for (c = 0; c < sizeof(algmask) * 8; c++) + if (algmask & (1<state = AKA_AV_USING; + /* + * an algorithm can only be used for one algorithm, so we mark + * it as being used only for the first algorithm in the mask + */ + av->alg = aka_av_first_bit_mask(algmask); +} + +struct aka_av *aka_av_get_new(struct aka_user *user, int algmask) +{ + struct aka_av *av; + + cond_lock(&user->cond); + av = aka_av_get_state(user, algmask, AKA_AV_NEW); + if (av) + aka_av_mark_using(av, algmask); + cond_unlock(&user->cond); + return av; +} + +static inline int aka_check_algmask(int algmask, int flags, + int len, int check_len, const char *debug) +{ + if (algmask & flags) { + if (len != check_len) { + LM_WARN("invalid authorize length %d, expected %d for MD5 hashing\n", + len, check_len); + algmask &= ~(flags); + } + } + return algmask; +} + +static struct aka_av *aka_av_new(int algmask, str *authenticate, str *authorize, str *ck, str *ik) +{ + char *p; + unsigned char *hex, *b64; + struct aka_av *av = NULL; + int b64len; + + b64len = calc_base64_encode_len(authenticate->len / 2); + hex = pkg_malloc((authenticate->len / 2) + b64len); + if (!hex) { + LM_ERR("oom for authenticate encoding\n"); + goto end; + } + b64 = hex + (authenticate->len / 2); + if (hex2string(authenticate->s, authenticate->len, (char *)hex) < 0) { + LM_ERR("could not hexa decode %.*s\n", authenticate->len, authenticate->s); + goto end; + } + base64encode(b64, hex, (authenticate->len / 2)); + av = shm_malloc(sizeof(*av) + b64len + (authorize->len / 2) + ck->len + ik->len); + if (!av) + goto end; + memset(av, 0, sizeof *av); + av->algmask = algmask; + p = av->buf; + av->authenticate.s = p; + av->authenticate.len = b64len; + memcpy(p, b64, b64len); + p += b64len; + + av->authorize.s = p; + if (hex2string(authorize->s, authorize->len, av->authorize.s) < 0) { + LM_ERR("could not hexa decode %.*s\n", authorize->len, authorize->s); + shm_free(av); + av = NULL; + goto end; + } + av->authorize.len = authorize->len / 2; + p += av->authorize.len; + + av->ck.s = p; + av->ck.len = ck->len; + memcpy(p, ck->s, ck->len); + p += ck->len; + + av->ik.s = p; + av->ik.len = ik->len; + memcpy(p, ik->s, ik->len); + p += ik->len; + INIT_LIST_HEAD(&av->list); + +end: + pkg_free(hex); + return av; +} + +#if 0 +static void aka_av_free(struct aka_av *av) +{ + shm_free(av); +} +#endif + +static void aka_av_insert(struct aka_user *user, struct aka_av *av) +{ + list_add_tail(&av->list, &user->avs); +} + + +int aka_av_add(str *pub_id, str *priv_id, int algmask, + str *authenticate, str *authorize, str *ck, str *ik) +{ + int ret = -1; + struct aka_av *av; + struct aka_user *user = aka_user_get(pub_id, priv_id); + if (!user) { + LM_INFO("cannot find or create user %.*s/%.*s\n", + pub_id->len, pub_id->s, priv_id->len, priv_id->s); + return -1; + } + av = aka_av_new(algmask, authenticate, authorize, ck, ik); + if (!av) { + LM_ERR("could not create new AV\n"); + goto end; + } + cond_lock(&user->cond); + aka_av_insert(user, av); + /* we also need to inform users we have an AV */ + if (!list_empty(&user->async)) + aka_signal_async(user, user->async.next); + cond_signal(&user->cond); + cond_unlock(&user->cond); + ret = 1; +end: + aka_user_release(user); + return ret; +} + +int aka_av_drop_all_user(struct aka_user *user) +{ + int count = 0; + struct aka_av *av; + struct list_head *it; + + cond_lock(&user->cond); + list_for_each(it, &user->avs) { + av = list_entry(it, struct aka_av, list); + if (av->state != AKA_AV_INVALID) { + count++; + av->state = AKA_AV_INVALID; + } + } + cond_unlock(&user->cond); + return count; +} + +int aka_av_drop_all(str *pub_id, str *priv_id) +{ + int count = 0; + struct aka_user *user = aka_user_find(pub_id, priv_id); + + if (!user) { + LM_DBG("cannot find user %.*s/%.*s\n", + pub_id->len, pub_id->s, priv_id->len, priv_id->s); + return 0; + } + count = aka_av_drop_all_user(user); + aka_user_release(user); + return count; +} + +int aka_av_drop(str *pub_id, str *priv_id, str *nonce) +{ + struct aka_av *av; + struct aka_user *user = aka_user_find(pub_id, priv_id); + + if (!user) { + LM_DBG("cannot find user %.*s/%.*s\n", + pub_id->len, pub_id->s, priv_id->len, priv_id->s); + return -1; + } + cond_lock(&user->cond); + av = aka_av_match(user, -1, nonce); + if (av && av->state != AKA_AV_INVALID) + av->state = AKA_AV_INVALID; + else + av = NULL; + cond_unlock(&user->cond); + aka_user_release(user); + return (av?1:0); +} + + +void aka_av_set_new(struct aka_user *user, struct aka_av *av) +{ + cond_lock(&user->cond); + av->state = AKA_AV_NEW; + cond_unlock(&user->cond); +} + +void aka_push_async(struct aka_user *user, struct list_head *subs) +{ + cond_lock(&user->cond); + list_add_tail(subs, &user->async); + cond_unlock(&user->cond); +} + +void aka_pop_async(struct aka_user *user, struct list_head *subs) +{ + cond_lock(&user->cond); + list_del(subs); + cond_unlock(&user->cond); +} diff --git a/modules/auth_aka/aka_av_mgm.h b/modules/auth_aka/aka_av_mgm.h new file mode 100644 index 00000000000..2151f5674ff --- /dev/null +++ b/modules/auth_aka/aka_av_mgm.h @@ -0,0 +1,46 @@ +/* + * AKA Authentication - generic Authentication Manager support + * + * Copyright (C) 2024 Razvan Crainea + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef AKA_AV_MGM_H +#define AKA_AV_MGM_H + +#include "../../str.h" +#include "../../lib/list.h" + +#define AKA_AV_MGM_PREFIX "load_aka_av_" + +struct aka_av_async_ctx { +}; + +struct aka_av_binds { + int (*fetch)(str *realm, str *impu, str *impi, int alg, str *resync, int no, struct aka_av_async_ctx *ctx); +}; + +struct aka_av_mgm { + str name; + struct aka_av_binds binds; + struct list_head list; + char buf[0]; +}; + +#endif /* AKA_AV_MGM_H */ diff --git a/modules/auth_aka/auth_aka.c b/modules/auth_aka/auth_aka.c new file mode 100644 index 00000000000..cc0a0876d2a --- /dev/null +++ b/modules/auth_aka/auth_aka.c @@ -0,0 +1,1253 @@ +/* + * AKA Authentication - AKA authentication support + * + * Copyright (C) 2024 Razvan Crainea + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#include "../../sr_module.h" +#include "../../error.h" +#include "../../config.h" +#include "../../mod_fix.h" +#include "../../ipc.h" + +#include "auth_aka.h" +#include "aka_av_mgm.h" +#include "../auth/api.h" +#include "../auth/qop.h" +#include "../auth/common.h" +#include "../auth/challenge.h" + +#include "../../parser/digest/digest.h" +#include "../../parser/digest/digest_parser.h" +#include "../../parser/parse_from.h" +#include "../../parser/parse_to.h" +#include "../../parser/parse_uri.h" + + +auth_api_t auth_api; + +static int aka_www_authorize(struct sip_msg *msg, str *realm); +static int aka_proxy_authorize(struct sip_msg *msg, str *realm); +static int aka_www_challenge(struct sip_msg *msg, struct aka_av_mgm *mgm, + str *realm, qop_type_t qop, intptr_t algmask); +static int aka_proxy_challenge(struct sip_msg *msg, struct aka_av_mgm *mgm, + str *realm, qop_type_t qop, intptr_t algmask); +static int aka_www_challenge_async(struct sip_msg *msg, async_ctx *ctx, + struct aka_av_mgm *mgm, str *realm, qop_type_t qop, intptr_t algmask); +static int aka_proxy_challenge_async(struct sip_msg *msg, async_ctx *ctx, + struct aka_av_mgm *mgm, str *realm, qop_type_t qop, intptr_t algmask); +static int fixup_av_mgm(void** param); +static int fixup_aka_qop(void** param); +static int fixup_aka_alg(void** param); +static int fixup_check_var(void** param); + +static int script_aka_av_add(struct sip_msg *msg, str *pub_id, str *priv_id, + str *authenticate, str *authorize, str *ck, str *ik, intptr_t algmask); +static int script_aka_av_drop(struct sip_msg *msg, str *pub_id, str *priv_id, + str *authenticate); +static int script_aka_av_drop_all(struct sip_msg *msg, str *pub_id, str *priv_id, + pv_spec_t *count); +static mi_response_t *mi_aka_av_add(const mi_params_t *params, + struct mi_handler *async_hdl); +static mi_response_t *mi_aka_av_drop(const mi_params_t *params, + struct mi_handler *async_hdl); +static mi_response_t *mi_aka_av_drop_all(const mi_params_t *params, + struct mi_handler *async_hdl); + +static int mod_init(void); /* Module initialization function */ + +/* + * Module parameter variables + */ +static str aka_default_av_mgm_s; +static str aka_default_qop_s = str_init("auth"); +static str aka_default_alg_s = str_init("AKAv1-MD5"); +static qop_type_t aka_default_qop = -1; /* XXX: use an invalid value */ +static intptr_t aka_default_alg = ALGFLG_UNSPEC; +static intptr_t aka_algs_mask = ALGFLG_UNSPEC; +static int aka_hash_size = 4096; +static int aka_sync_timeout = 100; /* ms */ +static int aka_async_timeout = 1000; /* ms */ + +/* + * Exported functions + */ + +static const cmd_export_t cmds[] = { + {"aka_www_authorize", (cmd_function)aka_www_authorize, { + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* realm */ + {0,0,0}}, + REQUEST_ROUTE}, + {"aka_proxy_authorize", (cmd_function)aka_proxy_authorize, { + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* realm */ + {0,0,0}}, + REQUEST_ROUTE}, + {"aka_www_challenge", (cmd_function)aka_www_challenge, { + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_av_mgm, 0}, /* AV mgm */ + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* realm */ + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_aka_qop, 0},/* qop */ + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_aka_alg, 0},/* alg */ + {0,0,0}}, + REQUEST_ROUTE}, + {"aka_proxy_challenge", (cmd_function)aka_proxy_challenge, { + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_av_mgm, 0}, /* AV mgm */ + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* realm */ + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_aka_qop, 0},/* qop */ + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_aka_alg, 0},/* alg */ + {0,0,0}}, + REQUEST_ROUTE}, + {"aka_av_add", (cmd_function)script_aka_av_add, { + {CMD_PARAM_STR, NULL, 0}, /* public_identity */ + {CMD_PARAM_STR, NULL, 0}, /* private_identity */ + {CMD_PARAM_STR, NULL, 0}, /* authenticate */ + {CMD_PARAM_STR, NULL, 0}, /* authorize */ + {CMD_PARAM_STR, NULL, 0}, /* ck */ + {CMD_PARAM_STR, NULL, 0}, /* ik */ + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_aka_alg, 0},/* alg */ + {0,0,0}}, + ALL_ROUTES}, + {"aka_av_drop", (cmd_function)script_aka_av_drop, { + {CMD_PARAM_STR, NULL, 0}, /* public_identity */ + {CMD_PARAM_STR, NULL, 0}, /* private_identity */ + {CMD_PARAM_STR, NULL, 0}, /* authenticate */ + {0,0,0}}, + ALL_ROUTES}, + {"aka_av_drop_all", (cmd_function)script_aka_av_drop_all, { + {CMD_PARAM_STR, NULL, 0}, /* public_identity */ + {CMD_PARAM_STR, NULL, 0}, /* private_identity */ + {CMD_PARAM_VAR|CMD_PARAM_OPT, fixup_check_var, 0}, /* count */ + {0,0,0}}, + ALL_ROUTES}, + {0,0,{{0,0,0}},0} +}; + +static const acmd_export_t acmds[] = { + {"aka_www_challenge", (acmd_function)aka_www_challenge_async, { + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_av_mgm, 0}, /* AV mgm */ + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* realm */ + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_aka_qop, 0},/* qop */ + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_aka_alg, 0},/* alg */ + {0,0,0}}}, + {"aka_proxy_challenge", (acmd_function)aka_proxy_challenge_async, { + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_av_mgm, 0}, /* AV mgm */ + {CMD_PARAM_STR|CMD_PARAM_OPT, 0, 0}, /* realm */ + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_aka_qop, 0},/* qop */ + {CMD_PARAM_STR|CMD_PARAM_OPT|CMD_PARAM_FIX_NULL, fixup_aka_alg, 0},/* alg */ + {0,0,0}}}, + {0,0,{{0,0,0}}} +}; + +/* + * Exported parameters + */ +static const param_export_t params[] = { + {"default_av_mgm", STR_PARAM, &aka_default_av_mgm_s.s }, + {"default_qop", STR_PARAM, &aka_default_qop_s.s }, + {"default_algorithm", STR_PARAM, &aka_default_alg_s.s }, + {"hash_size", INT_PARAM, &aka_hash_size }, + {"sync_timeout", INT_PARAM, &aka_sync_timeout }, + {"async_timeout", INT_PARAM, &aka_async_timeout }, + {0, 0, 0} +}; + +static const mi_export_t mi_cmds[] = { + { "aka_av_add", 0, 0, 0, { + {mi_aka_av_add, {"public_identity", "private_identity", "authenticate", + "authorize", "confidentiality-key", "integrity-key", 0}}, + {mi_aka_av_add, {"public_identity", "private_identity", "authenticate", + "authorize", "confidentiality-key", "integrity-key", + "algorithms", 0}}, + {mi_aka_av_drop, {"public_identity", "private_identity", + "authenticate", 0}}, + {mi_aka_av_drop_all, {"public_identity", "private_identity", 0}}, + {EMPTY_MI_RECIPE}} + }, + {EMPTY_MI_EXPORT} +}; + +static const dep_export_t deps = { + { /* OpenSIPS module dependencies */ + { MOD_TYPE_DEFAULT, "auth", DEP_ABORT }, + { MOD_TYPE_NULL, NULL, 0 }, + }, + { /* modparam dependencies */ + { NULL, NULL }, + }, +}; + + +/* + * Module interface + */ +struct module_exports exports = { + "auth_aka", + MOD_TYPE_DEFAULT,/* class of this module */ + MODULE_VERSION, /* module version */ + DEFAULT_DLFLAGS, /* dlopen flags */ + 0, /* load function */ + &deps, /* OpenSIPS module dependencies */ + cmds, /* Exported functions */ + acmds, /* Exported async functions */ + params, /* Exported parameters */ + 0, /* exported statistics */ + mi_cmds, /* exported MI functions */ + 0, /* exported pseudo-variables */ + 0, /* exported transformations */ + 0, /* extra processes */ + 0, /* module pre-initialization function */ + mod_init, /* module initialization function */ + 0, /* response function */ + 0, /* destroy function */ + 0, /* child initialization function */ + 0 /* reload confirm function */ +}; + + +/* + * Module initialization function + */ +static int mod_init(void) +{ + bind_auth_t bind_auth; + + LM_INFO("initializing...\n"); + + if (aka_sync_timeout < 0) { + LM_ERR("invalid sync_timeout value %d\n", aka_sync_timeout); + return -1; + } + if (aka_async_timeout < 0) { + LM_ERR("invalid async_timeout value %d\n", aka_async_timeout); + return -1; + } + + if (aka_init_mgm(aka_hash_size) < 0) { + LM_ERR("cannot initialize aka management hash\n"); + return -1; + } + + bind_auth = (bind_auth_t)find_export("bind_auth", 0); + if (!bind_auth) { + LM_ERR("unable to find bind_auth function. Check if you " + "loaded the auth module.\n"); + return -1; + } + + if (bind_auth(&auth_api) < 0) { + LM_ERR("cannot bind to auth module\n"); + return -4; + } + + return 0; +} + +static int fixup_av_mgm(void** param) +{ + struct aka_av_mgm *av_mgm; + str *aka_av_mgm_name = (str *)*param; + + if (!*param) { + if (aka_default_av_mgm_s.s == NULL) { + LM_ERR("no default AV manager provided\n"); + return -1; + } + aka_default_av_mgm_s.len = strlen(aka_default_av_mgm_s.s); + aka_av_mgm_name = &aka_default_av_mgm_s; + } + av_mgm = aka_get_mgm(aka_av_mgm_name); + if (!av_mgm) { + av_mgm = aka_load_mgm(aka_av_mgm_name); + if (!av_mgm) { + LM_ERR("no AV manager for <%.*s>\n", aka_av_mgm_name->len, aka_av_mgm_name->s); + return -1; + } + } + *param = av_mgm; + return 0; +} + +static int fixup_aka_qop(void** param) +{ + if (*param == NULL) { + if (aka_default_qop == -1) { + aka_default_qop_s.len = strlen(aka_default_qop_s.s); + *param = &aka_default_qop_s; + if (fixup_qop(param) < 0) { + LM_ERR("could not parse default_qop param [%s]\n", aka_default_qop_s.s); + return -2; + } + aka_default_qop = (qop_type_t)(long)(param); + } else { + *param = (void *)(long)(param); + } + return 0; + } else { + return fixup_qop(param); + } +} + +static int fixup_aka_alg(void** param) +{ + alg_t alg; + int algmask; + str *dbg = (str *)*param; + + if (aka_algs_mask == ALGFLG_UNSPEC) { + for (alg = ALG_AKAv1_FIRST; alg <= ALG_AKAv1_LAST; alg++) + aka_algs_mask |= (1<s); + return -2; + } + if (aka_default_alg == 0) + LM_DBG("using unknown algorithm for authentication\n"); + } else { + *param = (void *)(long)aka_default_alg; + } + } else if (dauth_fixup_algorithms(param) < 0) { + LM_ERR("could not parse default_algorithm param [%s]\n", ((str *)*param)->s); + return -2; + } + algmask = *(intptr_t *)(param); + if ((algmask | aka_algs_mask) != aka_algs_mask) { + LM_WARN("non-AKA algorithms have been used in 0x%x/%s; ignoring them...\n", + algmask, dbg->s); + algmask &= aka_algs_mask; + } + return 0; +} + +static int fixup_check_var(void** param) +{ + if (!pv_is_w((pv_spec_t *)*param)) { + LM_ERR("the return parameter must be a writable pseudo-variable\n"); + return E_SCRIPT; + } + + return 0; +} + +static struct to_body *aka_get_identity_body(struct sip_msg *msg, hdr_types_t hftype) +{ + switch (hftype) { + case HDR_AUTHORIZATION_T: + if (!msg->to && ((parse_headers(msg, HDR_TO_F, 0)==-1) || (!msg->to))) { + LM_ERR("failed to parse TO headers\n"); + return NULL; + } + /* force parsing */ + if (!parse_to_uri(msg)) { + LM_ERR("failed to parse TO URI\n"); + return NULL; + } + return get_to(msg); + + case HDR_PROXYAUTH_T: + if (parse_from_header(msg) < 0) { + LM_ERR("failed to parse From headers\n"); + return NULL; + } + /* force parsing */ + if (!parse_from_uri(msg)) { + LM_ERR("failed to parse From URI\n"); + return NULL; + } + return get_from(msg); + + default: + LM_ERR("Unhandld header type %d\n", hftype); + return NULL; + } +} + +static inline void aka_strip_uri_params(struct to_body *body, str *res) +{ + char *p; + *res = body->uri; + /* limit the result to the end of the host/port, to skip parameters */ + if (body->parsed_uri.port.len) + p = body->parsed_uri.port.s + body->parsed_uri.port.len; + else + p = body->parsed_uri.host.s + body->parsed_uri.host.len; + res->len = p - res->s; +} + +static str *aka_get_public_identity(struct sip_msg *msg, hdr_types_t hftype) +{ + static str res; + struct to_body *body = aka_get_identity_body(msg, hftype); + if (!body) + return NULL; + aka_strip_uri_params(body, &res); + return &res; +} + +static str *aka_get_private_identity(struct sip_msg *msg, auth_body_t *auth, hdr_types_t hftype) +{ + int len; + static str res; + struct to_body *body; + + if (auth) + return &auth->digest.username.whole; + + body = aka_get_identity_body(msg, hftype); + if (!body) + return NULL; + + aka_strip_uri_params(body, &res); + + if (body->parsed_uri.type != ERROR_URI_T) { + len = uri_typestrlen(body->parsed_uri.type); + res.s += len + 1; + res.len -= len + 1; + } + + return &res; +} + +/* according to ETSI TS 129 229 V17.2.0 (2022-07), the buffer is 30 bytes long */ +#define AKA_RAND_LEN 16 +#define AKA_AUTS_LEN 14 +#define AKA_AUTHORIZATION_LEN (AKA_RAND_LEN + AKA_AUTS_LEN) + +static int aka_build_resync(str *nonce, str* auts, str *resync) +{ + unsigned char auth_buf[AKA_AUTHORIZATION_LEN]; + int len; + + /* check what we have */ + if (calc_base64_encode_len(AKA_RAND_LEN) > nonce->len) { + LM_ERR("invalid RAND length - have %d, need %d\n", + nonce->len, calc_base64_encode_len(AKA_RAND_LEN)); + return -1; + } + if (calc_base64_encode_len(AKA_AUTS_LEN) != auts->len) { + if (calc_base64_encode_len(AKA_AUTS_LEN) > auts->len) { + LM_ERR("invalid AUTS length - have %d, need %d\n", + auts->len, calc_base64_encode_len(AKA_AUTS_LEN)); + return -1; + } else { + LM_WARN("AUTS length too long - have %d, need %d; dropping the tail\n", + auts->len, calc_base64_encode_len(AKA_AUTS_LEN)); + } + } + len = base64decode(auth_buf, (unsigned char *)nonce->s, calc_max_base64_decode_len(AKA_AUTHORIZATION_LEN)); + if (len < AKA_RAND_LEN) { + LM_ERR("not enough bytes for RAND - have %d, need %d\n", len, AKA_RAND_LEN); + return -1; + } + len = base64decode(auth_buf + AKA_RAND_LEN, (unsigned char *)auts->s, auts->len); + if (len + AKA_RAND_LEN != AKA_AUTHORIZATION_LEN) { + LM_ERR("mismatch bytes for RAND + AUTS - have %d, need %d\n", len, AKA_AUTHORIZATION_LEN); + return -1; + } + resync->s = (char *)auth_buf; + resync->len = AKA_AUTHORIZATION_LEN; + return 0; +} + +static int aka_challenge_pre(struct sip_msg *_msg, str *realm, int _code, + hdr_types_t *hftype, struct hdr_field **h, struct aka_user **user, + str *sync, int *sync_count) +{ + dig_err_t ret; + dig_cred_t *cred; + auth_body_t* auth = NULL; + auth_result_t auth_res = AUTH_ERROR; + str *public_id, *private_id, *auts = NULL, *nonce = NULL; + *user = NULL; + + sync->len = 0; + *sync_count = 0; + + switch(_code) { + case WWW_AUTH_CODE: + *hftype = HDR_AUTHORIZATION_T; + break; + case PROXY_AUTH_CODE: + *hftype = HDR_PROXYAUTH_T; + break; + default: + LM_BUG("unknown code %d\n", _code); + return -1; + } + + auth_res = auth_api.pre_auth(_msg, realm, *hftype, h, AUTH_SKIP_CRED_CHECK); + if (auth_res != DO_AUTHORIZATION && auth_res != NO_CREDENTIALS) + return auth_res; + + if (auth_res != NO_CREDENTIALS) { + auth = (auth_body_t*)(*h)->parsed; + cred = &auth->digest; + /* check the correct format, according to 3GPP TS 24.229, 5.1.1.2.2: + * - the "username" header field parameter, set to the value of the + * private user identity; + * - the "realm" header field parameter, set to the domain name of + * the home network; + * - the "uri" header field parameter, set to the SIP URI of the + * domain name of the home network; + * - the "nonce" header field parameter, set to an empty value; and + * - the "response" header field parameter, set to an empty value; + */ + ret = check_dig_cred(cred); + if (ret & E_DIG_USERNAME) { + LM_ERR("no username in credentials\n"); + return -1; + } + if (ret & E_DIG_REALM) { + LM_ERR("no realm in credentials\n"); + return -1; + } + if (ret & E_DIG_URI) { + LM_ERR("no uri in credentials\n"); + return -1; + } + if (cred->auts.len) { + /* if we have an auts, we need a nonce */ + if (ret & E_DIG_NONCE) { + LM_ERR("\"auts\" parameter without a \"nonce\"\n"); + return -1; + } + auts = &cred->auts; + nonce = &cred->nonce; + } + + if (mark_authorized_cred(_msg, *h) < 0) { + LM_ERR("could not mark credentials\n"); + return -1; + } + } + + public_id = aka_get_public_identity(_msg, *hftype); + if (!public_id) { + LM_ERR("could not get public identity/IMPU\n"); + return -1; + } + + private_id = aka_get_private_identity(_msg, auth, *hftype); + if (!private_id) { + LM_ERR("could not get private identity/IMPI\n"); + return -1; + } + LM_DBG("challenging realm=[%.*s] impu=[%.*s] impi=[%.*s]\n", + realm->len, realm->s, public_id->len, public_id->s, + private_id->len, private_id->s); + *user = aka_user_get(public_id, private_id); + if (*user == NULL) { + LM_ERR("could not get AKA user %.*s/%.*s\n", public_id->len, public_id->s, + private_id->len, private_id->s); + return -1; + } + if (auts && nonce) { + /* drop all vectors */ + *sync_count = aka_av_drop_all_user(*user); + if (aka_build_resync(nonce, auts, sync) < 0) { + LM_ERR("could not build resync!\n"); + aka_user_release(*user); + return -1; + } + } + + return 0; +} + +static int aka_count_avs(int algmask) +{ + int alg, n = 0; + for (alg = ALG_AKAv1_FIRST; alg <= ALG_AKAv1_LAST; alg++) + if (algmask & ALG2ALGFLG(alg)) + n++; + for (alg = ALG_AKAv2_FIRST; alg <= ALG_AKAv2_LAST; alg++) + if (algmask & ALG2ALGFLG(alg)) + n++; + return n; +} + +#define AKA_DIGEST_REALM ": Digest realm=\"" +#define AKA_DIGEST_NONCE "\", nonce=\"" +#define AKA_DIGEST_ALGORITHM "\", algorithm=" +#define AKA_DIGEST_CK ", ck=\"" +#define AKA_DIGEST_IK "\", ik=\"" +#define AKA_DIGEST_END "\"" +#define CSL(_c) (sizeof(_c) - 1) + +static char *build_aka_auth_hf(struct aka_av *av, str *_realm, + qop_type_t qop, alg_t alg, const str_const *_hf_name, int *_len) +{ + char *hf, *p; + str_const qop_param = get_qop_param(qop); + const str *alg_val = print_digest_algorithm(alg); + + LM_DBG("Challenging with av %p\n", av); + *_len =_hf_name->len + + CSL(AKA_DIGEST_REALM) + + _realm->len + + CSL(AKA_DIGEST_NONCE) + + av->authenticate.len + + CSL(AKA_DIGEST_ALGORITHM) + + alg_val->len + + CSL(AKA_DIGEST_CK) + + av->ck.len + + CSL(AKA_DIGEST_IK) + + av->ik.len + + qop_param.len + + CRLF_LEN + 1; + + + p = hf = pkg_malloc(*_len + 1); + if (!hf) { + LM_ERR("cannot allocate aka auth buffer\n"); + goto e1; + } + memcpy(p, _hf_name->s, _hf_name->len); + p += _hf_name->len; + memcpy(p, AKA_DIGEST_REALM, CSL(AKA_DIGEST_REALM)); + p+=CSL(AKA_DIGEST_REALM); + memcpy(p, _realm->s, _realm->len); + p += _realm->len; + memcpy(p, AKA_DIGEST_NONCE, CSL(AKA_DIGEST_NONCE)); + p += CSL(AKA_DIGEST_NONCE); + memcpy(p, av->authenticate.s, av->authenticate.len); + p += av->authenticate.len; + memcpy(p, AKA_DIGEST_ALGORITHM, CSL(AKA_DIGEST_ALGORITHM)); + p += CSL(AKA_DIGEST_ALGORITHM); + memcpy(p, alg_val->s, alg_val->len); + p += alg_val->len; + memcpy(p, AKA_DIGEST_CK, CSL(AKA_DIGEST_CK)); + p += CSL(AKA_DIGEST_CK); + memcpy(p, av->ck.s, av->ck.len); + p += av->ck.len; + memcpy(p, AKA_DIGEST_IK, CSL(AKA_DIGEST_IK)); + p += CSL(AKA_DIGEST_IK); + memcpy(p, av->ik.s, av->ik.len); + p += av->ik.len; + memcpy(p, AKA_DIGEST_END, CSL(AKA_DIGEST_END)); + p += CSL(AKA_DIGEST_END); + if (qop_param.len) { + memcpy(p, qop_param.s, qop_param.len); + p += qop_param.len; + } + + memcpy(p, CRLF, CRLF_LEN ); p+=CRLF_LEN; + *p=0; /* zero terminator, just in case */ + + LM_DBG("'%s'\n", hf); + return hf; +e1: + *_len = 0; + return NULL; +} + +#undef AKA_DIGEST_REALM +#undef AKA_DIGEST_NONCE +#undef AKA_DIGEST_ALGORITHM +#undef AKA_DIGEST_CK +#undef AKA_DIGEST_IK +#undef AKA_DIGEST_END +#undef CSL + +static int aka_send_resp(struct sip_msg *_msg, str *realm, struct aka_user *user, + struct aka_av **avs, int count, qop_type_t qop, int algmask, + int _code, const str_const *_challenge_msg) +{ + int ret = -1; + int nalgs, c; + str auth_hfs[LAST_ALG_SPTD - FIRST_ALG_SPTD + 1]; + + for (nalgs = 0; nalgs < count; nalgs++) { + auth_hfs[nalgs].s = build_aka_auth_hf(avs[nalgs], realm, qop, + avs[nalgs]->alg, _challenge_msg, &auth_hfs[nalgs].len); + if (!auth_hfs[nalgs].s) { + LM_ERR("could not build authentication vector!\n"); + goto reply; + } + } + +reply: + if (nalgs > 0) { + ret = nalgs; /* number of successful AVs/headers */ + /* release unused AVs */ + for (c = nalgs; c < count; c++) + aka_av_set_new(user, avs[c]); + } else { + ret = -3; + } + if (auth_api.send_resp(_msg, _code, NULL, auth_hfs, nalgs) < 0) + ret = -5; + while (--nalgs > 0) + pkg_free(auth_hfs[nalgs].s); + return ret; +} + +static int aka_challenge(struct sip_msg *_msg, struct aka_av_mgm *mgm, str *_realm, + qop_type_t qop, int algmask, int _code, const str_const *_challenge_msg) +{ + int ret = AUTH_ERROR; + str realm, auts; + hdr_types_t hftype; + struct hdr_field *h; + struct aka_user *user; + struct aka_av *av, **avs; + int count = aka_count_avs(algmask), new_count; + int sync_count; + + realm = (_realm?*_realm:str_init("")); + if (count > 1) { + avs = pkg_malloc(count * sizeof *av); + if (!avs) { + LM_ERR("could not allocate %d AVs\n", count); + return -1; + } + } else { + avs = &av; + } + + if (aka_challenge_pre(_msg, &realm, _code, &hftype, &h, &user, &auts, &sync_count) < 0) { + LM_ERR("cannot prepare challenge from message\n"); + goto end; + } + count += sync_count; + /* try to fetch as many local AVs as possible */ + new_count = 0; + for (new_count = 0; new_count < count; new_count++) { + avs[new_count] = aka_av_get_new(user, algmask); + if (!avs[new_count]) + break; + algmask &= ~(avs[new_count]->alg); + } + + /* if we need more, fetch them remotely */ + if (new_count != count) { + if (mgm->binds.fetch(&realm, &user->public->impu, &user->impi, + algmask, (auts.len?&auts:NULL), count - new_count, NULL) != 0) { + LM_INFO("Could not fetch %d authentication vector(s)!\n", count); + ret = -2; + goto release; + } + for (; new_count < count; new_count++) { + avs[new_count] = aka_av_get_new_wait(user, algmask, aka_sync_timeout); + if (!avs[new_count]) + break; + algmask &= ~(avs[new_count]->alg); + } + if (new_count < count) + LM_WARN("Could not get get %d (out of %d) authentication vectors!\n", + count - new_count, count); + } + + ret = aka_send_resp(_msg, &realm, user, avs, new_count, qop, + algmask, _code, _challenge_msg); + +release: + aka_user_release(user); +end: + if (count > 1) + pkg_free(avs); + return ret; +} + +static inline int aka_get_ha1(dig_cred_t* digest, str* realm, + struct aka_av *av, HASHHEX* _ha1) +{ + struct calc_HA1_arg cprms = { + .alg = digest->alg.alg_parsed + }; + struct digest_auth_credential ocreds = { + .realm = *realm, + .user = digest->username.whole, + .passwd = av->authorize + }; + cprms.creds.open = &ocreds; + cprms.use_hashed = 0; + cprms.nonce = &digest->nonce; + cprms.cnonce = &digest->cnonce; + if (auth_api.calc_HA1(&cprms, _ha1) != 0) + return (-1); + LM_DBG("HA1 string calculated: %s\n", _ha1->_start); + + return 0; +} + +static int aka_authorize(struct sip_msg *_msg, str *_realm, + int _code, const str_const *_challenge_msg) +{ + int ret; + str msg_body; + struct hdr_field *h; + struct aka_av *av; + struct aka_user *user; + dig_cred_t *digest; + hdr_types_t hftype; + auth_body_t* auth = NULL; + auth_result_t auth_res = AUTH_ERROR; + str *public_id, *private_id; + str realm = (_realm?*_realm:str_init("")); + HASHHEX ha1; + + switch(_code) { + case WWW_AUTH_CODE: + hftype = HDR_AUTHORIZATION_T; + break; + case PROXY_AUTH_CODE: + hftype = HDR_PROXYAUTH_T; + break; + default: + LM_BUG("unknown code %d\n", _code); + return -1; + } + + auth_res = auth_api.pre_auth(_msg, &realm, hftype, &h, AUTH_SKIP_NONCE_CHECK); + if (auth_res != DO_AUTHORIZATION) + return auth_res; + + public_id = aka_get_public_identity(_msg, hftype); + if (!public_id) { + LM_ERR("could not get public identity/IMPU\n"); + return AUTH_ERROR; + } + + private_id = aka_get_private_identity(_msg, auth, hftype); + if (!private_id) { + LM_ERR("could not get private identity/IMPI\n"); + return AUTH_ERROR; + } + auth = (auth_body_t*)h->parsed; + digest = &auth->digest; + LM_DBG("authorizing realm=[%.*s] impu=[%.*s] impi=[%.*s]\n", + realm.len, realm.s, public_id->len, public_id->s, + private_id->len, private_id->s); + user = aka_user_find(public_id, private_id); + if (user == NULL) { + if (digest->nonce.len) + LM_ERR("could not get AKA user %.*s/%.*s with nonce %.*s\n", + public_id->len, public_id->s, private_id->len, private_id->s, + digest->nonce.len, digest->nonce.s); + else + LM_DBG("could not get AKA user %.*s/%.*s\n", public_id->len, public_id->s, + private_id->len, private_id->s); + return STALE_NONCE; + } + av = aka_av_get_nonce(user, ALG2ALGFLG(digest->alg.alg_parsed), &digest->nonce); + if (!av) { + LM_ERR("could not find AKA AV for user %.*s/%.*s with nonce %.*s\n", + public_id->len, public_id->s, private_id->len, private_id->s, + digest->nonce.len, digest->nonce.s); + ret = STALE_NONCE; + goto release; + } + + /* now that we are trusting the user, check whether it has an auts + * parameter - if it does, we need to re-challenge him */ + if (digest->auts.len) { + LM_DBG("re-sync request for for user %.*s/%.*s with nonce %.*s\n", + public_id->len, public_id->s, private_id->len, private_id->s, + digest->nonce.len, digest->nonce.s); + ret = -6; + goto release; + } + + ret = AUTH_ERROR; + if (digest->qop.qop_parsed == QOP_AUTHINT_D && + get_body(_msg, &msg_body) < 0) { + LM_ERR("Failed to get body of SIP message\n"); + goto release; + } + if (aka_get_ha1(digest, &realm, av, &ha1) < 0) { + LM_ERR("Failed to compute HA1\n"); + goto release; + } + + if (!auth_api.check_response(digest, + &_msg->first_line.u.request.method, &msg_body, &ha1)) + ret = auth_api.post_auth(_msg, h); + else + ret = INVALID_PASSWORD; + +release: + aka_user_release(user); + return ret; +} + +struct aka_async_param { + int replied; + int ref; + str realm; + qop_type_t qop; + int algmask; + int code; + str challenge_msg; + struct aka_user *user; + struct aka_av **avs; + int avs_count, avs_fetched; + int process_no; + struct list_head list; + async_ctx *async; + char buf[0]; +}; + +static int aka_async_param_release(struct aka_async_param *param) +{ + if (list_is_valid(¶m->list)) + aka_pop_async(param->user, ¶m->list); + param->ref--; + if (param->ref == 0) { + /* the last user should also delete */ + aka_user_release(param->user); + shm_free(param); + return 1; + } else { + return 0; + } +} + +static int aka_challenge_async_resume_handle(struct sip_msg *msg, void *_param, int timeout) +{ + int left; + struct aka_async_param *param = _param; + + /* check to see how many events we got */ + for (;param->avs_fetched < param->avs_count; param->avs_fetched++) { + param->avs[param->avs_fetched] = aka_av_get_new(param->user, param->algmask); + if (!param->avs[param->avs_fetched]) + break; + param->algmask &= ~(param->avs[param->avs_fetched]->alg); + } + left = param->avs_fetched - param->avs_count; + /* check to see if we still have AVS to wait for */ + if (!timeout && param->avs_fetched != param->avs_count) { + async_status = ASYNC_CONTINUE; + LM_DBG("waiting for more %d AVs to a total of %d\n", left, param->avs_count); + return 1; + } + if (timeout && left) + LM_ERR("timeout waiting for AVs - got %d out of %d so far\n", + param->avs_fetched, param->avs_count); + else + LM_DBG("fetched all %d out of %d AVs\n", + param->avs_fetched, param->avs_count); + async_status = ASYNC_DONE_NO_IO; + if (!param->replied) { + if (param->avs_fetched) { + /* now send whatever we have fetched so far */ + aka_send_resp(msg, ¶m->realm, param->user, param->avs, param->avs_fetched, + param->qop, param->algmask, param->code, _cs2cc(¶m->challenge_msg)); + } else if (auth_api.send_resp(msg, 504, NULL, NULL, 0) < 0) { + LM_ERR("could not send timeout back\n"); + } + param->replied = 1; + } + if (!aka_async_param_release(param)) { + /* we still have some possible signals in the queue, so let's wait for + * everyone before releasing the context */ + async_status = ASYNC_CONTINUE; + } + return left; +} + +static int aka_challenge_async_resume(int fd, + struct sip_msg *msg, void *_param) +{ + return aka_challenge_async_resume_handle(msg, _param, 0); +} + +static int aka_challenge_async_resume_tout(int fd, + struct sip_msg *msg, void *_param) +{ + return aka_challenge_async_resume_handle(msg, _param, 1); +} + +static int aka_challenge_async(struct sip_msg *_msg, async_ctx *ctx, + struct aka_av_mgm *mgm, str *_realm, qop_type_t qop, + int algmask, int _code, const str_const *_challenge_msg) +{ + hdr_types_t hftype; + struct hdr_field *h; + str realm, sync; + struct aka_user *user; + struct aka_av *av, **avs; + int count = aka_count_avs(algmask); + struct aka_async_param *param = NULL; + int ret = AUTH_ERROR, c; + char *p; + int sync_count; + + realm = (_realm?*_realm:str_init("")); + + if (aka_challenge_pre(_msg, &realm, _code, &hftype, &h, &user, &sync, &sync_count) < 0) { + LM_ERR("cannot prepare challenge from message\n"); + return -1; + } + count += sync_count; + /* try to sort them out synchronously */ + if (count == 1) { + av = aka_av_get_new(user, algmask); + if (av) { + avs = &av; + goto synchronous; + } + } + /* unfortunately we might need to do it asynchronously */ + param = shm_malloc(sizeof *param + realm.len + _challenge_msg->len + + count * sizeof *av); + if (!param) { + LM_ERR("oom for preparing async params!\n"); + goto release; + } + memset(param, 0, sizeof *param); + p = param->buf; + param->realm.s = p; + p += realm.len; + param->challenge_msg.s = p; + p += _challenge_msg->len; + param->avs = (struct aka_av **)p; + avs = param->avs; + /* do not prepare the param yet, as we might still have AVs available */ + + for (c = 0; c < count; c++) { + avs[c] = aka_av_get_new(user, algmask); + if (!avs[c]) + break; + algmask &= ~(avs[c]->alg); + } + if (c == count) + goto synchronous; + LM_DBG("we still need %d out of %d AVs\n", count - c, count); + + /* now that we finished preparing, go fetch the vectors */ + if (mgm->binds.fetch(&realm, &user->public->impu, &user->impi, + algmask, (sync.len?&sync:NULL), count - c, NULL) != 0) { + LM_INFO("Could not fetch %d authentication vector(s)!\n", count); + ret = -2; + goto error; + } + + /* now prepare all parameters */ + memcpy(param->realm.s, realm.s, realm.len); + param->realm.len = realm.len; + memcpy(param->challenge_msg.s, _challenge_msg->s, _challenge_msg->len); + param->challenge_msg.len = _challenge_msg->len; + param->qop = qop; + param->algmask = algmask; + param->code = _code; + param->user = user; + param->avs_count = count; + param->avs_fetched = c; + param->async = ctx; + param->process_no = process_no; + + async_status = ASYNC_NO_FD; + + ctx->resume_f = aka_challenge_async_resume; + ctx->resume_param = param; + ctx->timeout_f = aka_challenge_async_resume_tout; + ctx->timeout_s = aka_async_timeout/1000; + + aka_push_async(user, ¶m->list); + return 1; + +synchronous: + async_status = ASYNC_NO_IO; + ret = aka_send_resp(_msg, &realm, user, avs, count, qop, + algmask, _code, _challenge_msg); +error: + if (param) + shm_free(param); +release: + aka_user_release(user); + return ret; +} + +static void aka_challenge_resume(int fd, void *_param) +{ + struct aka_async_param *param = (struct aka_async_param *)_param; + async_script_resume_f(ASYNC_FD_NONE, param->async, 0); + +} + +void aka_signal_async(struct aka_user *user, struct list_head *subs) +{ + struct aka_async_param *param = list_entry(subs, struct aka_async_param, list); + param->ref++; + if (ipc_send_rpc(param->process_no, aka_challenge_resume, param) < 0) { + LM_ERR("could not resume aka challenge\n"); + aka_async_param_release(param); + } +} + +static int aka_www_challenge(struct sip_msg *msg, struct aka_av_mgm *mgm, + str *realm, qop_type_t qop, intptr_t algmask) +{ + return aka_challenge(msg, mgm, realm, qop, algmask, + WWW_AUTH_CODE, &str_const_init(WWW_AUTH_HDR)); +} + +static int aka_proxy_challenge(struct sip_msg *msg, struct aka_av_mgm *mgm, + str *realm, qop_type_t qop, intptr_t algmask) +{ + return aka_challenge(msg, mgm, realm, qop, algmask, + PROXY_AUTH_CODE, &str_const_init(PROXY_AUTH_HDR)); +} + +static int aka_www_challenge_async(struct sip_msg *msg, async_ctx *ctx, + struct aka_av_mgm *mgm, str *realm, qop_type_t qop, intptr_t algmask) +{ + return aka_challenge_async(msg, ctx, mgm, realm, qop, algmask, + WWW_AUTH_CODE, &str_const_init(WWW_AUTH_HDR)); +} + +static int aka_proxy_challenge_async(struct sip_msg *msg, async_ctx *ctx, + struct aka_av_mgm *mgm, str *realm, qop_type_t qop, intptr_t algmask) +{ + return aka_challenge_async(msg, ctx, mgm, realm, qop, algmask, + PROXY_AUTH_CODE, &str_const_init(PROXY_AUTH_HDR)); +} + +static int aka_www_authorize(struct sip_msg *msg, str *realm) +{ + return aka_authorize(msg, realm, WWW_AUTH_CODE, &str_const_init(WWW_AUTH_HDR)); +} + +static int aka_proxy_authorize(struct sip_msg *msg, str *realm) +{ + return aka_authorize(msg, realm, PROXY_AUTH_CODE, &str_const_init(PROXY_AUTH_HDR)); +} + + +static int script_aka_av_add(struct sip_msg *msg, str *pub_id, str *priv_id, + str *authenticate, str *authorize, str *ck, str *ik, intptr_t algmask) +{ + return aka_av_add(pub_id, priv_id, algmask, authenticate, authorize, ck, ik); +} + +static int script_aka_av_drop(struct sip_msg *msg, str *pub_id, str *priv_id, str *auth) +{ + return (aka_av_drop(pub_id, priv_id, auth) < 0?-1:1); +} + +static int script_aka_av_drop_all(struct sip_msg *msg, str *pub_id, str *priv_id, pv_spec_t *count) +{ + pv_value_t val; + int ret = aka_av_drop_all(pub_id, priv_id); + if (count) { + memset(&val, 0, sizeof val); + val.flags = PV_TYPE_INT|PV_VAL_INT; + val.ri = ret; + if (str2int(&val.rs, (unsigned int *)&val.ri) == 0) + val.flags |= PV_VAL_STR; + return pv_set_value(msg, count, 0, &val)<0?-1:1; + } + return 1; +} + +static mi_response_t *mi_aka_av_add(const mi_params_t *params, + struct mi_handler *async_hdl) +{ + str public_identity, private_identity, algorithms, authenticate, authorize, ck, ik; + int algmask; + void *param = NULL; + + if (get_mi_string_param(params, "public_identity", + &public_identity.s, &public_identity.len) < 0) + return init_mi_param_error(); + if (get_mi_string_param(params, "private_identity", + &private_identity.s, &private_identity.len) < 0) + return init_mi_param_error(); + if (get_mi_string_param(params, "authenticate", + &authenticate.s, &authenticate.len) < 0) + return init_mi_param_error(); + if (get_mi_string_param(params, "authorize", + &authorize.s, &authorize.len) < 0) + return init_mi_param_error(); + if (get_mi_string_param(params, "confidentiality-key", &ck.s, &ck.len) < 0) + return init_mi_param_error(); + if (get_mi_string_param(params, "integrity-key", &ik.s, &ik.len) < 0) + return init_mi_param_error(); + switch (try_get_mi_string_param(params, "algorithms", + &algorithms.s, &algorithms.len)) { + case 0: + param = &algorithms; + /* fallthrough */ + case -1: + if (fixup_aka_alg(¶m) < 0) + return init_mi_error(400, MI_SSTR("could not parse algorithms")); + algmask = (intptr_t)param; + break; + default: + return init_mi_error(400, MI_SSTR("error while fetching algorithms")); + } + if (aka_av_add(&public_identity, &private_identity, algmask, &authenticate, + &authorize, &ck, &ik) < 0) + return init_mi_error(400, MI_SSTR("could not add AV")); + return init_mi_result_ok(); +} + +static mi_response_t *mi_aka_av_drop(const mi_params_t *params, + struct mi_handler *async_hdl) +{ + str public_identity, private_identity, authenticate; + + if (get_mi_string_param(params, "public_identity", + &public_identity.s, &public_identity.len) < 0) + return init_mi_param_error(); + if (get_mi_string_param(params, "private_identity", + &private_identity.s, &private_identity.len) < 0) + return init_mi_param_error(); + if (get_mi_string_param(params, "authenticate", + &authenticate.s, &authenticate.len) < 0) + if (aka_av_drop(&public_identity, &private_identity, &authenticate) <= 0) + return init_mi_error(404, MI_SSTR("AV not found")); + return init_mi_result_ok(); +} + +static mi_response_t *mi_aka_av_drop_all(const mi_params_t *params, + struct mi_handler *async_hdl) +{ + str public_identity, private_identity; + + if (get_mi_string_param(params, "public_identity", + &public_identity.s, &public_identity.len) < 0) + return init_mi_param_error(); + if (get_mi_string_param(params, "private_identity", + &private_identity.s, &private_identity.len) < 0) + return init_mi_param_error(); + return init_mi_result_number(aka_av_drop_all(&public_identity, &private_identity)); +} diff --git a/modules/auth_aka/auth_aka.h b/modules/auth_aka/auth_aka.h new file mode 100644 index 00000000000..c299b649f17 --- /dev/null +++ b/modules/auth_aka/auth_aka.h @@ -0,0 +1,101 @@ +/* + * AKA Authentication - generic Authentication Manager support + * + * Copyright (C) 2024 Razvan Crainea + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef AUTH_AKA_H +#define AUTH_AKA_H + +#include "../../lib/cond.h" +#include "../../lib/list.h" +#include "../../parser/digest/digest_parser.h" +#include "../../lib/digest_auth/digest_auth.h" +#include "aka_av_mgm.h" + +enum aka_user_state { + AKA_USER_STATE_INIT = 0, +}; + +enum aka_av_state { + AKA_AV_NEW = 0, + AKA_AV_USING, + AKA_AV_USED, + AKA_AV_INVALID, +}; + +struct aka_av { + enum aka_av_state state; + str authenticate; + str authorize; + str ck; + str ik; + alg_t alg; /* algorithm that this AV is being challenged for */ + int algmask; /* algorithms this AV is suitable for */ + struct list_head list; + char buf[0]; +}; + +struct aka_user_pub { + str impu; + struct list_head privates; + char buf[0]; +}; + +struct aka_user { + enum aka_user_state state; + unsigned int ref; + str impi; + struct aka_user_pub *public; + struct list_head avs; + struct list_head list; + struct list_head async; + gen_cond_t cond; + char buf[0]; +}; + + +int aka_init_mgm(int hash_size); + +struct aka_av_mgm *aka_get_mgm(str *name); +struct aka_av_mgm *aka_load_mgm(str *name); + +/* returns a user structure identified by user IMPU and IMPI */ +struct aka_user *aka_user_get(str *public_id, str *private_id); +struct aka_user *aka_user_find(str *public_id, str *private_id); +void aka_user_release(struct aka_user *user); + +/* gets an AV for a specific user */ +void aka_av_set_new(struct aka_user *user, struct aka_av *av); +struct aka_av *aka_av_get_new(struct aka_user *user, int algmask); +struct aka_av *aka_av_get_new_wait(struct aka_user *user, int algmask, long milliseconds); +struct aka_av *aka_av_get_nonce(struct aka_user *user, int algmask, str *nonce); + +int aka_av_add(str *pub_id, str *priv_id, int algmask, str *authenticate, + str *authorize, str *ck, str *ik); +int aka_av_drop(str *pub_id, str *priv_id, str *nonce); +int aka_av_drop_all(str *pub_id, str *priv_id); +int aka_av_drop_all_user(struct aka_user *user); + +void aka_push_async(struct aka_user *user, struct list_head *subs); +void aka_pop_async(struct aka_user *user, struct list_head *subs); +void aka_signal_async(struct aka_user *user, struct list_head *subs); + +#endif /* AUTH_AKA_H */ diff --git a/modules/auth_aka/doc/auth_aka.xml b/modules/auth_aka/doc/auth_aka.xml new file mode 100644 index 00000000000..3260f43f58d --- /dev/null +++ b/modules/auth_aka/doc/auth_aka.xml @@ -0,0 +1,27 @@ + + + + + + + +%docentities; + +]> + + + + Auth_aka Module + + + + &admin; + &faq; + &contrib; + + &docCopyrights; + ©right; 2024 OpenSIPS Solutions; + diff --git a/modules/auth_aka/doc/auth_aka_admin.xml b/modules/auth_aka/doc/auth_aka_admin.xml new file mode 100644 index 00000000000..3c8be07e8d7 --- /dev/null +++ b/modules/auth_aka/doc/auth_aka_admin.xml @@ -0,0 +1,733 @@ + + + + + &adminguide; + +
+ Overview + + This module contains functions that are used to perform digest + authentication using the AKA (Authentication and Key Agreement) + security protocol. This mechanism is being used in IMS networks to + provide mutual authentication between the UE (device) and the 3G/4G/5G + network. + + + The AKA protocol establishes a set of security keys, called + authentication vectors (or AVs), and uses them to generate the digest + challenge, as well as for computing the digest result and authenticating + the UE. AVs are exchanged over a separate communication channel. + + + Although the AKA protocol also requires to use the AVs to establish a + secure channel between the UE and the network (by means of IPSec + tunnels), this module does not handle that part - it just performs the + authentication of the user and passes along the cyphering and + integrity keys in the Authorization header, according to + the ETSI TS 129 229 specifications. These are later + on picked up by other components (such as P-CSCFs) to establish the + secure channel. + +
+
+ Authentication Vectors + + Authentication Vectors (or AVs) consist of a set of five parameter + (RAND, AUTN, XRES, CK, IK) that are being used for mutual + authentication. As these need to be exchanged between the device (UE) + and network through a different channel (i.e. Diameter Cx interface in + LTE networks), the module does not provide any means to fetch the AV + information. It does, however, provide a generic interface (called AV + Manage Interface) to store AVs (that are being fetched by other + modules/channels), manage them and use them in the digest + authentication algorithm. + + + Basic AV operations that the module performs: + + + Ask for a new AV to be fetched for a specific user identity + + + Manage an AV lifetime, including reuses + + + Mark an AV as being used in a digest challeng + + + Invalidate or discard an AV (due to various reasons) + + + + + A module that implements the AV Manage Interface (called AV Manager) + should be able to fetch all five parameters of an AV, and push them in + the AV Storage. + +
+
+ Supported algorithms + + The current implementation only supports the AKAv1 algorithms, with + the associated hashing functions (such as MD5, SHA-256). In the + challenge message, we send, one can advertise other algorithms as well, + but the response cannot be handled by this module, and an appropriate + error will be returned. + +
+ +
+ Dependencies +
+ &osips; Modules + + The module depends on the following modules (in the other words + the listed modules must be loaded before this module): + + + auth -- Authentication framework + + + + AV manage module + -- at least one module that fetches AVs and pushes + them in the AV storage + + + + +
+
+ External Libraries or Applications + + This module does not depend on any external library. + +
+
+ +
+ Exported Parameters +
+ <varname>default_av_mgm</varname> (string) + + The default AV Manager used in case the functions do not provide them explicitly. + + + <varname>default_av_mgm</varname> parameter usage + + +modparam("auth_aka", "default_av_mgm", "diameter") # fetch AVs through the Cx interface + + +
+
+ <varname>default_qop</varname> (string) + + The default qop parameter used during challenge, if the functions + do not provide them explicitly. + + + Default value is auth. + + + <varname>default_qop</varname> parameter usage + + +modparam("auth_aka", "default_qop", "auth,auth-int") + + +
+
+ <varname>default_algorithm</varname> (string) + + The default algorithm to be advertise during challenge, if the + functions do not provide them explicitly. + Note + that at least one of the algorithms provided should be an AKA + one, otherwise it makes no sense to use this module. + + + Default value is AKAv1-MD5. + + + WARNING: only AKAv1* algorithms are currently supported. + + + <varname>default_algorithm</varname> parameter usage + + +modparam("auth_aka", "default_algorithm", "AKAv2-MD5") + + +
+
+ <varname>hash_size</varname> (integer) + + The size of the hash that stores the AVs for each user. + Must be a power of 2 number. + + + Default value is 4096. + + + <varname>hash_size</varname> parameter usage + + +modparam("auth_aka", "hash_size", 1024) + + +
+
+ <varname>sync_timeout</varname> (string) + + The amount of milliseconds a synchronous call should + wait for getting an authentication vector. + + + Must be a positive value. A value of + 0 indicates to wait indefinitely. + + + Default value is 100 ms. + + + <varname>sync_timeout</varname> parameter usage + + +modparam("auth_aka", "sync_timeout", 200) + + +
+
+ <varname>async_timeout</varname> (string) + + The amount of milliseconds an asynchronous call should + wait for getting an authentication vector. + + + Must be a positive value, greater than 0. + + + NOTE: the current timeout mechanism only + has seconds granularity, therefore you should configure this + parameter as a multiple of 1000. + + + Default value is 1000 ms. + + + <varname>async_timeout</varname> parameter usage + + +modparam("auth_aka", "async_timeout", 2000) + + +
+
+ +
+ Exported Functions +
+ <function moreinfo="none">aka_www_authorize([realm]])</function> + + The function verifies credentials according to + RFC3310, by using + an authentication vector priorly allocated by an + aka_www_challenge() call, using + the av_mgm manager. If the credentials are + verified successfully the function will succeed, otherwise it will fail with + an appropriate error code, as follows: + + + + -6 (sync request) - the auts + parameter was was present, thus a sync was requested; + + + -5 (generic error) - some generic error + occurred and no reply was sent out; + + + -4 (no credentials) - credentials were not + found in request; + + + -3 (unknown nonce) - authentication vector + with the corresponding nonce was not found; + + + -2 (invalid password) - password does not + match the authentication vector; + + + -1 (invalid username) - no username found + in the Authorize header; + + + + In case the function succeeds, the WWW-Authenticate + header is being added to the reply, containing the challenge information, + as well as the Integrity-Key and the + Confidentiality-Key values associated to the + AV being used. + + Meaning of the parameters is as follows: + + + realm (string) - Realm is a opaque string that + the user agent should present to the user so he can decide what + username and password to use. This is usually + one of the domains the proxy is responsible for. + If an empty string is used then the server will + generate realm from host part of From header field URI. + + + + + If the credentials are verified successfully then the function will + succeed and mark the credentials as authorized (marked credentials + can be later used by some other functions). + + + This function can be used from REQUEST_ROUTE. + + + <function moreinfo="none">aka_www_authorize</function> usage + + +... +if (!aka_www_authorize("diameter", "siphub.com")) + aka_www_challenge("diameter", "siphub.com", "auth"); +... + + + +
+ +
+ <function moreinfo="none">aka_proxy_authorize([realm]])</function> + + The function behaves the same as , + but it authenticates the user from a proxy perspective. It receives the same + parameters, with the same meaning, and returns the same values. + + + + This function can be used from REQUEST_ROUTE. + + + <function moreinfo="none">aka_proxy_authorize</function> usage + + +... +if (!aka_proxy_authorize("siphub.com")) + aka_proxy_challenge("diameter", "siphub.com", "auth"); +... + + + +
+ +
+ <function moreinfo="none">aka_www_challenge([av_mgm[, realm[ ,qop[, alg]]]])</function> + + The function challenges a user agent. It fetches an authentication + vector for each algorigthm used through the + av_mgm Manager and generate one or more + WWW-Authenticate header fields containing digest challenges. It will + put the header field(s) into a response generated from the request the + server is processing and will send the reply. Upon reception of such a + reply the user agent should compute credentials using the used + authentication vector annd retry the request. + For more information regarding digest authentication + see RFC2617, RFC3261, RFC3310 and RFC8760. + + + Meaning of the parameters is as follows: + + + + av_mgm (string, optional) - the AV Manager + to be used for this challenge, in case an AV is not already available + for the challenged user identity. In case it is missing the value of the + is being used. + + realm (string) - Realm is an opaque string that + the user agent should present to the user so it can decide what + username and password to use. Usually this is domain of the host + the server is running on. If missing, the value of the + From domain is being used. + + + + qop (string, optional) - Value of this + parameter can be either auth, auth-int + or both (separated by ,). When this parameter is + set the server will put a qop parameter in the challenge. It + is recommended to use the qop parameter, however there are still some + user agents that cannot handle qop properly so we made this optional. + On the other hand there are still some user agents that cannot handle + request without a qop parameter too. If missing, the value of the + is being used. + + + + algorithms (string, optional) - Value of this + parameter is a comma-separated list of digest algorithms to be offered for + the UAC to use for authentication. Possible values are: + + AKAv1-MD5 + AKAv1-MD5-sess + AKAv1-SHA-256 + AKAv1-SHA-256-sess + AKAv1-SHA-512-256 + AKAv1-SHA-512-256-sess + AKAv2-MD5 + AKAv2-MD5-sess + AKAv2-SHA-256 + AKAv2-SHA-256-sess + AKAv2-SHA-512-256 + AKAv2-SHA-512-256-sess + + When the value is empty or not set, the only offered digest + the value of the is being used. + + + + + Possible return codes: + + + + -1 - generic parsing error, generated + when there is not enoough data to build the challange + + + + -2 - no AV vector could not be fetched + + + + -3 - authentication headers could not + be built + + + + -5 - a reply could not be sent + + + + positive - the number of successful + chalanges being sent in the reply; this value can be lower than + the number of algorithms being requested in case there was a + timeout waiting for some AVs. + + + + + This function can be used from REQUEST_ROUTE. + + + + aka_www_challenge usage + +... +if (!aka_www_authorize("siphub.com")) { + aka_www_challenge(,"siphub.com", "auth-int", "AKAv1-MD5"); +} +... + + +
+ +
+ <function moreinfo="none">aka_proxy_challenge([realm]])</function> + + The function behaves the same as , + but it challenges the user from a proxy perspective. It receives the same + parameters, with the same meaning, the only difference being that in case of + the realm is missing, then it is taken from the + the To domain, rather than from + From domain. The header added is + Proxy-Authenticate, rather than + WWW-Authenticate The rest of the parameters, behavior, + as well as return values are the same. + + + + This function can be used from REQUEST_ROUTE. + + + <function moreinfo="none">aka_proxy_challenge</function> usage + + +... +if (!aka_proxy_authorize("siphub.com")) + aka_proxy_challenge(,"siphub.com", "auth"); +... + + + +
+ +
+ <function moreinfo="none">aka_av_add(public_identity, private_identity, authenticate, authorize, confidentiality_key, integrity_key[, algorithms])</function> + + Adds an authentication vector for the user identitied by + public_identity and + private_identity. + + Meaning of the parameters is as follows: + + + public_identity (string) - the public identity + (IMPU) of the user to add authentication vector for. + + + private_identity (string) - the private identity + (IMPI) of the user to add authentication vector for. + + + authenticate (string) - the concatenation of the + authentication challenge RAND and the token AUTN, encoded in hexa format. + + + authorize (string) - the authorization string + (XRES) used for authorizing the user, encoded in hexa format. + + + confidentiality_key (string) - the Confidentiality-Key + used in the AKA IPSec process, encoded in hexa format. + + + integrity_key (string) - the Integrity-Key + used in the AKA IPSec process, encoded in hexa format. + + + algorithms (string, optional) - AKA algorithms + this AV should be used for. If missing, the AV can be used for any AKA + algorithm. + + + + This function can be used from any route. + + + <function moreinfo="none">aka_av_add</function> usage + + +... +aka_av_add("sip:test@siphub.com", "test@siphub.com", + "KFQ/MpR3cE3V9PxucEQS5KED8uUNYIAALFyk59sIJI4=", /* authenticate */ + "00000262c0000014000028af2d6398cbe26eea69", /* authorize */ + "db7f8c4a58e17083974bba3b936d34c4", /* ck */ + "6151667b9ef815c1dcb87473685f062a" /* ik */); +... + + + +
+ +
+ <function moreinfo="none">aka_av_drop(public_identity, private_identity, authenticate)</function> + + Drops the authentication vector corresponding to the + authenticate/nonce value + for an user identitied by + public_identity and + private_identity. + + Meaning of the parameters is as follows: + + + public_identity (string) - the public identity + (IMPU) of the user to drop authentication vector for. + + + private_identity (string) - the private identity + (IMPI) of the user to drop authentication vector for. + + + authenticate (string) - the authenticate/nonce + that identifies the authentication vector to be dropped. + + + + This function can be used from any route. + + + <function moreinfo="none">aka_av_drop</function> usage + + +... +aka_av_drop("sip:test@siphub.com", "test@siphub.com", + "KFQ/MpR3cE3V9PxucEQS5KED8uUNYIAALFyk59sIJI4="); +... + + + +
+
+ <function moreinfo="none">aka_av_drop_all(public_identity, private_identity[, count])</function> + + Drops all authentication vectors for an user identitied by + public_identity and + private_identity. This function is useful + when a synchronization must be done. + + Meaning of the parameters is as follows: + + + public_identity (string) - the public identity + (IMPU) of the user to drop authentication vectors for. + + + private_identity (string) - the private identity + (IMPI) of the user to drop authentication vectors for. + + + count (variable, optional) - a variable to return the number + of authentication vectors dropped. + + + + This function can be used from any route. + + + <function moreinfo="none">aka_av_drop_all</function> usage + + +... +aka_av_drop_all("sip:test@siphub.com", "test@siphub.com", $var(count)); +... + + + +
+
+ +
+ Exported MI Functions +
+ <function moreinfo="none">aka_av_add</function> + + Adds an Authentication Vector through the MI interface. + + Parameters: + + + public_identity (string) - the public identity + (IMPU) of the user to add authentication vector for. + + + private_identity (string) - the private identity + (IMPI) of the user to add authentication vector for. + + + authenticate (string) - the concatenation of the + authentication challenge RAND and the token AUTN, encoded in hexa format. + + + authorize (string) - the authorization string + (XRES) used for authorizing the user, encoded in hexa format. + + + confidentiality_key (string) - the Confidentiality-Key + used in the AKA IPSec process, encoded in hexa format. + + + integrity_key (string) - the Integrity-Key + used in the AKA IPSec process, encoded in hexa format. + + + algorithms (string, optional) - AKA algorithms + this AV should be used for. If missing, the AV can be used for any AKA + algorithm. + + + + + <function moreinfo="none">aka_av_add</function> usage + +... +## adds an AKA AV +$ opensips-cli -x mi aka_av_add \ + sip:test@siphub.com + test@siphub.com + KFQ/MpR3cE3V9PxucEQS5KED8uUNYIAALFyk59sIJI4= + 00000262c0000014000028af2d6398cbe26eea69 + db7f8c4a58e17083974bba3b936d34c4 + 6151667b9ef815c1dcb87473685f062a +... + + +
+
+ <function moreinfo="none">aka_av_drop</function> + + Invalidates an Authentication Vector of an user identified + by its authenticate value. + + Parameters: + + + public_identity (string) - the public identity + (IMPU) of the user to add authentication vector for. + + + private_identity (string) - the private identity + (IMPI) of the user to add authentication vector for. + + + authenticate (string) - the authenticate/nonce + to indentify the authentication vector. + + + + + <function moreinfo="none">aka_av_drop</function> usage + +... +## adds an AKA AV +$ opensips-cli -x mi aka_av_drop \ + sip:test@siphub.com + test@siphub.com + KFQ/MpR3cE3V9PxucEQS5KED8uUNYIAALFyk59sIJI4= +... + + +
+
+ <function moreinfo="none">aka_av_drop_all</function> + + Invalidates all Authentication Vectors of an user through the + MI interface. + + Parameters: + + + public_identity (string) - the public identity + (IMPU) of the user to drop authentication vectors for. + + + private_identity (string) - the private identity + (IMPI) of the user to drop authentication vectors for. + + + + + <function moreinfo="none">aka_av_drop_all</function> usage + +... +## adds an AKA AV +$ opensips-cli -x mi aka_av_drop_all \ + sip:test@siphub.com + test@siphub.com +... + + +
+
+
+ From f3ded66f92ae92717ebf62656148d99f515e5c72 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 29 Feb 2024 12:37:51 +0200 Subject: [PATCH 40/79] auth_aka: add support for timing out async queries --- modules/auth_aka/aka_av_mgm.c | 34 ++++++++++++++++++++++++++--- modules/auth_aka/auth_aka.c | 40 ++++++++++++++++++++++++++++++----- modules/auth_aka/auth_aka.h | 4 ++++ 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/modules/auth_aka/aka_av_mgm.c b/modules/auth_aka/aka_av_mgm.c index 2dee7785e60..ce9b31bc973 100644 --- a/modules/auth_aka/aka_av_mgm.c +++ b/modules/auth_aka/aka_av_mgm.c @@ -538,16 +538,44 @@ void aka_av_set_new(struct aka_user *user, struct aka_av *av) cond_unlock(&user->cond); } -void aka_push_async(struct aka_user *user, struct list_head *subs) +void aka_push_async(struct aka_user *user, struct list_head *subs) { cond_lock(&user->cond); list_add_tail(subs, &user->async); cond_unlock(&user->cond); } -void aka_pop_async(struct aka_user *user, struct list_head *subs) +void aka_pop_unsafe_async(struct aka_user *user, struct list_head *subs) { - cond_lock(&user->cond); list_del(subs); +} + +void aka_pop_async(struct aka_user *user, struct list_head *subs) +{ + cond_lock(&user->cond); + aka_pop_unsafe_async(user, subs); cond_unlock(&user->cond); } + +static int aka_async_hash_iterator(void *param, str key, void *value) +{ + struct list_head *it, *safe, *uit; + unsigned int ticks = *(unsigned int*)param; + struct aka_user *user; + struct aka_user_pub *pub = (struct aka_user_pub *)value; + + list_for_each(uit, &pub->privates) { + user = list_entry(uit, struct aka_user, list); + cond_lock(&user->cond); + list_for_each_safe(it, safe, &user->async) { + aka_check_expire_async(ticks, it); + } + cond_unlock(&user->cond); + } + return 0; +} + +void aka_async_expire(unsigned int ticks, void* param) +{ + hash_for_each_locked(aka_users, aka_async_hash_iterator, &ticks); +} diff --git a/modules/auth_aka/auth_aka.c b/modules/auth_aka/auth_aka.c index cc0a0876d2a..ee9bab053cd 100644 --- a/modules/auth_aka/auth_aka.c +++ b/modules/auth_aka/auth_aka.c @@ -243,6 +243,7 @@ static int mod_init(void) LM_ERR("invalid async_timeout value %d\n", aka_async_timeout); return -1; } + aka_async_timeout /= 1000; /* XXX: add support for milliseconds */ if (aka_init_mgm(aka_hash_size) < 0) { LM_ERR("cannot initialize aka management hash\n"); @@ -261,6 +262,13 @@ static int mod_init(void) return -4; } + if (register_timer("AKA timeout", aka_async_expire, NULL, 1, + TIMER_FLAG_SKIP_ON_DELAY)<0 ) { + LM_ERR("failed to register timer, halting..."); + return -1; + } + + return 0; } @@ -919,6 +927,7 @@ struct aka_async_param { struct aka_av **avs; int avs_count, avs_fetched; int process_no; + unsigned int ticks; struct list_head list; async_ctx *async; char buf[0]; @@ -1073,13 +1082,14 @@ static int aka_challenge_async(struct sip_msg *_msg, async_ctx *ctx, param->avs_fetched = c; param->async = ctx; param->process_no = process_no; + param->ticks = get_ticks(); async_status = ASYNC_NO_FD; ctx->resume_f = aka_challenge_async_resume; ctx->resume_param = param; ctx->timeout_f = aka_challenge_async_resume_tout; - ctx->timeout_s = aka_async_timeout/1000; + ctx->timeout_s = aka_async_timeout; aka_push_async(user, ¶m->list); return 1; @@ -1100,19 +1110,39 @@ static void aka_challenge_resume(int fd, void *_param) { struct aka_async_param *param = (struct aka_async_param *)_param; async_script_resume_f(ASYNC_FD_NONE, param->async, 0); - } -void aka_signal_async(struct aka_user *user, struct list_head *subs) +static void aka_challenge_resume_tout(int fd, void *_param) +{ + struct aka_async_param *param = (struct aka_async_param *)_param; + async_script_resume_f(ASYNC_FD_NONE, param->async, 1); +} + +static void aka_signal_async_resume(struct aka_async_param *param, ipc_rpc_f *func) { - struct aka_async_param *param = list_entry(subs, struct aka_async_param, list); param->ref++; - if (ipc_send_rpc(param->process_no, aka_challenge_resume, param) < 0) { + if (ipc_send_rpc(param->process_no, func, param) < 0) { LM_ERR("could not resume aka challenge\n"); aka_async_param_release(param); } } +void aka_signal_async(struct aka_user *user, struct list_head *subs) +{ + struct aka_async_param *param = list_entry(subs, struct aka_async_param, list); + aka_signal_async_resume(param, aka_challenge_resume); +} + +void aka_check_expire_async(unsigned int ticks, struct list_head *subs) +{ + struct aka_async_param *param = list_entry(subs, struct aka_async_param, list); + if (param->ticks + aka_async_timeout > ticks) + return; + /* this subscription expired - should drop it */ + aka_pop_unsafe_async(param->user, subs); + aka_signal_async_resume(param, aka_challenge_resume_tout); +} + static int aka_www_challenge(struct sip_msg *msg, struct aka_av_mgm *mgm, str *realm, qop_type_t qop, intptr_t algmask) { diff --git a/modules/auth_aka/auth_aka.h b/modules/auth_aka/auth_aka.h index c299b649f17..3ac0ed0d713 100644 --- a/modules/auth_aka/auth_aka.h +++ b/modules/auth_aka/auth_aka.h @@ -96,6 +96,10 @@ int aka_av_drop_all_user(struct aka_user *user); void aka_push_async(struct aka_user *user, struct list_head *subs); void aka_pop_async(struct aka_user *user, struct list_head *subs); +void aka_pop_unsafe_async(struct aka_user *user, struct list_head *subs); void aka_signal_async(struct aka_user *user, struct list_head *subs); +void aka_check_expire_async(unsigned int ticks, struct list_head *subs); + +void aka_async_expire(unsigned int ticks, void* param); #endif /* AUTH_AKA_H */ From cb658ab4756e467cb8e97bb7c4fc61e155145c93 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 29 Feb 2024 15:31:07 +0200 Subject: [PATCH 41/79] auth_aka: make count per algorithm, rather than for all --- modules/auth_aka/aka_av_mgm.h | 14 ++++++++++---- modules/auth_aka/auth_aka.c | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/modules/auth_aka/aka_av_mgm.h b/modules/auth_aka/aka_av_mgm.h index 2151f5674ff..be9093406e1 100644 --- a/modules/auth_aka/aka_av_mgm.h +++ b/modules/auth_aka/aka_av_mgm.h @@ -29,11 +29,17 @@ #define AKA_AV_MGM_PREFIX "load_aka_av_" -struct aka_av_async_ctx { -}; - struct aka_av_binds { - int (*fetch)(str *realm, str *impu, str *impi, int alg, str *resync, int no, struct aka_av_async_ctx *ctx); + /* + * realm - the Realm of the authentication vector + * impu - Public identity of the user + * impi - Private identity of the user + * resync - Resync/auts token, or NULL if not a resync request + * algmask - Masks of algorithms to request + * no - number of AVs for each algorithm + * async - indicates whether the request is asynchronous or not + */ + int (*fetch)(str *realm, str *impu, str *impi, str *resync, int algmask, int no, int async); }; struct aka_av_mgm { diff --git a/modules/auth_aka/auth_aka.c b/modules/auth_aka/auth_aka.c index ee9bab053cd..07d255e2e6b 100644 --- a/modules/auth_aka/auth_aka.c +++ b/modules/auth_aka/auth_aka.c @@ -766,7 +766,7 @@ static int aka_challenge(struct sip_msg *_msg, struct aka_av_mgm *mgm, str *_rea /* if we need more, fetch them remotely */ if (new_count != count) { if (mgm->binds.fetch(&realm, &user->public->impu, &user->impi, - algmask, (auts.len?&auts:NULL), count - new_count, NULL) != 0) { + (auts.len?&auts:NULL), algmask, 1, 0) != 0) { LM_INFO("Could not fetch %d authentication vector(s)!\n", count); ret = -2; goto release; @@ -1063,7 +1063,7 @@ static int aka_challenge_async(struct sip_msg *_msg, async_ctx *ctx, /* now that we finished preparing, go fetch the vectors */ if (mgm->binds.fetch(&realm, &user->public->impu, &user->impi, - algmask, (sync.len?&sync:NULL), count - c, NULL) != 0) { + (sync.len?&sync:NULL), algmask, 1, 1) != 0) { LM_INFO("Could not fetch %d authentication vector(s)!\n", count); ret = -2; goto error; From 052b3a844842dd47f0f748a3df90625faec9f863 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 7 Mar 2024 15:22:44 +0200 Subject: [PATCH 42/79] aaa_diameter: fix locking on transactions hash --- modules/aaa_diameter/dm_impl.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/aaa_diameter/dm_impl.c b/modules/aaa_diameter/dm_impl.c index 80a02807532..bdbe7365c9d 100644 --- a/modules/aaa_diameter/dm_impl.c +++ b/modules/aaa_diameter/dm_impl.c @@ -292,6 +292,7 @@ static int dm_auth_reply(struct msg **_msg, struct avp * avp, struct session * s int rc; str callid; struct dm_cond **prpl_cond, *rpl_cond; + unsigned int hentry; FD_CHECK(fd_msg_hdr(msg, &hdr)); @@ -311,8 +312,12 @@ static int dm_auth_reply(struct msg **_msg, struct avp * avp, struct session * s LM_DBG("MAA reply %d, Acct-Session-Id: %.*s\n", rc, callid.len, callid.s); - prpl_cond = (struct dm_cond **)hash_find_key(pending_replies, callid); + hentry = hash_entry(pending_replies, callid); + hash_lock(pending_replies, hentry); + + prpl_cond = (struct dm_cond **)hash_find(pending_replies, hentry, callid); if (!prpl_cond) { + hash_unlock(pending_replies, hentry); LM_ERR("failed to match Call-ID %.*s to a pending request\n", callid.len, callid.s); goto out; @@ -321,6 +326,7 @@ static int dm_auth_reply(struct msg **_msg, struct avp * avp, struct session * s rpl_cond->rc = rc; hash_remove_key(pending_replies, callid); + hash_unlock(pending_replies, hentry); FD_CHECK(fd_msg_search_avp(msg, dm_dict.Error_Message, &a)); if (a) { @@ -596,6 +602,7 @@ static int dm_receive_msg(struct msg **_msg, struct avp * avp, struct session * int rc; str tid; struct dm_cond **prpl_cond, *rpl_cond; + unsigned int hentry; FD_CHECK(fd_msg_hdr(msg, &hdr)); @@ -643,24 +650,23 @@ static int dm_receive_msg(struct msg **_msg, struct avp * avp, struct session * hdr->msg_code, tid.len, tid.s); } - prpl_cond = (struct dm_cond **)hash_find_key(pending_replies, tid); + hentry = hash_entry(pending_replies, tid); + hash_lock(pending_replies, hentry); + prpl_cond = (struct dm_cond **)hash_find(pending_replies, hentry, tid); if (!prpl_cond) { + hash_unlock(pending_replies, hentry); LM_ERR("failed to match Transaction_Id %.*s to a pending request\n", tid.len, tid.s); goto out; } rpl_cond = *prpl_cond; - if (!hash_find_key(pending_replies, tid)) { - LM_ERR("Transaction_Id %.*s already processed!\n", tid.len, tid.s); - goto out; - } - if (rpl_cond->rpl_avps_json) shm_free(rpl_cond->rpl_avps_json); rpl_cond->rpl_avps_json = cJSON_PrintUnformatted(avps); hash_remove_key(pending_replies, tid); + hash_unlock(pending_replies, hentry); fd_msg_search_avp(msg, dm_dict.Error_Message, &a); if (a) { From f190be11408b5c1de1a932abf4c94e4ce9aca1ae Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 7 Mar 2024 16:09:47 +0200 Subject: [PATCH 43/79] aaa_diameter: properly pass reply return code --- modules/aaa_diameter/aaa_diameter.c | 4 ++++ modules/aaa_diameter/dm_impl.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/aaa_diameter/aaa_diameter.c b/modules/aaa_diameter/aaa_diameter.c index 9e494c3e4be..563dc6da7fb 100644 --- a/modules/aaa_diameter/aaa_diameter.c +++ b/modules/aaa_diameter/aaa_diameter.c @@ -479,6 +479,10 @@ static int dm_send_request_async_reply(int fd, goto error; } ret = _dm_get_message_response(amsg->cond, &rpl_avps); + if (ret == 0) + ret = 1; + else + LM_ERR("Diameter request failed\n"); if (ret > 0 && amsg->ret && rpl_avps) { val.rs.s = rpl_avps; val.rs.len = strlen(rpl_avps); diff --git a/modules/aaa_diameter/dm_impl.c b/modules/aaa_diameter/dm_impl.c index bdbe7365c9d..597e0dbf6fc 100644 --- a/modules/aaa_diameter/dm_impl.c +++ b/modules/aaa_diameter/dm_impl.c @@ -1930,7 +1930,7 @@ int _dm_get_message_response(struct dm_cond *cond, char **rpl_avps) if (cond->is_error) return -1; - return 1; + return 0; } From 5f21b6c14612c9209b4bff166425494023253cb3 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Wed, 13 Mar 2024 12:41:06 +0200 Subject: [PATCH 44/79] auth_aka: fix algmask filtering --- modules/auth_aka/aka_av_mgm.c | 43 ++++++++++---------- modules/auth_aka/auth_aka.c | 74 +++++++++++++++++++---------------- 2 files changed, 63 insertions(+), 54 deletions(-) diff --git a/modules/auth_aka/aka_av_mgm.c b/modules/auth_aka/aka_av_mgm.c index ce9b31bc973..b48e97eb93e 100644 --- a/modules/auth_aka/aka_av_mgm.c +++ b/modules/auth_aka/aka_av_mgm.c @@ -247,8 +247,10 @@ static struct aka_av *aka_av_get_state(struct aka_user *user, int algmask, enum list_for_each(it, &user->avs) { av = list_entry(it, struct aka_av, list); /* check if AV algorithm is suitable */ - if (algmask >= -1 && av->algmask >= 0 && !(algmask & av->algmask)) + if (algmask >= -1 && av->algmask >= 0 && !(algmask & av->algmask)) { + av = NULL; continue; + } if (av->state == state) break; av = NULL; @@ -290,6 +292,24 @@ struct aka_av *aka_av_get_nonce(struct aka_user *user, int algmask, str *nonce) return av; } +static inline int aka_av_first_bit_mask(int algmask) +{ + int c; + for (c = 0; c < sizeof(algmask) * 8; c++) + if (algmask & (1<state = AKA_AV_USING; + /* + * an algorithm can only be used for one algorithm, so we mark + * it as being used only for the first algorithm in the mask + */ + av->alg = aka_av_first_bit_mask(algmask); +} struct aka_av *aka_av_get_new_wait(struct aka_user *user, int algmask, long milliseconds) { @@ -330,29 +350,12 @@ struct aka_av *aka_av_get_new_wait(struct aka_user *user, int algmask, long mill } else { av->state = AKA_AV_USING; } + if (av) + aka_av_mark_using(av, algmask); cond_unlock(&user->cond); return av; } -static inline int aka_av_first_bit_mask(int algmask) -{ - int c; - for (c = 0; c < sizeof(algmask) * 8; c++) - if (algmask & (1<state = AKA_AV_USING; - /* - * an algorithm can only be used for one algorithm, so we mark - * it as being used only for the first algorithm in the mask - */ - av->alg = aka_av_first_bit_mask(algmask); -} - struct aka_av *aka_av_get_new(struct aka_user *user, int algmask) { struct aka_av *av; diff --git a/modules/auth_aka/auth_aka.c b/modules/auth_aka/auth_aka.c index 07d255e2e6b..c46a35ecca9 100644 --- a/modules/auth_aka/auth_aka.c +++ b/modules/auth_aka/auth_aka.c @@ -694,7 +694,7 @@ static char *build_aka_auth_hf(struct aka_av *av, str *_realm, #undef CSL static int aka_send_resp(struct sip_msg *_msg, str *realm, struct aka_user *user, - struct aka_av **avs, int count, qop_type_t qop, int algmask, + struct aka_av **avs, int count, qop_type_t qop, int _code, const str_const *_challenge_msg) { int ret = -1; @@ -726,6 +726,35 @@ static int aka_send_resp(struct sip_msg *_msg, str *realm, struct aka_user *user return ret; } +static inline int aka_avs_get_new_wait(struct aka_user *user, int *algmask, + struct aka_av **avs, int count) +{ + int c; + for (c = 0; c < count; c++) { + avs[c] = aka_av_get_new_wait(user, *algmask, aka_sync_timeout); + if (!avs[c]) + break; + *algmask &= ~ALG2ALGFLG(avs[c]->alg); + } + LM_DBG("got %d AVs out of %d\n", c, count); + return c; +} + + +static inline int aka_avs_get_new(struct aka_user *user, int *algmask, + struct aka_av **avs, int count) +{ + int c; + for (c = 0; c < count; c++) { + avs[c] = aka_av_get_new(user, *algmask); + if (!avs[c]) + break; + *algmask &= ~ALG2ALGFLG(avs[c]->alg); + } + LM_DBG("got %d AVs out of %d\n", c, count); + return c; +} + static int aka_challenge(struct sip_msg *_msg, struct aka_av_mgm *mgm, str *_realm, qop_type_t qop, int algmask, int _code, const str_const *_challenge_msg) { @@ -755,13 +784,7 @@ static int aka_challenge(struct sip_msg *_msg, struct aka_av_mgm *mgm, str *_rea } count += sync_count; /* try to fetch as many local AVs as possible */ - new_count = 0; - for (new_count = 0; new_count < count; new_count++) { - avs[new_count] = aka_av_get_new(user, algmask); - if (!avs[new_count]) - break; - algmask &= ~(avs[new_count]->alg); - } + new_count = aka_avs_get_new(user, &algmask, avs, count); /* if we need more, fetch them remotely */ if (new_count != count) { @@ -771,19 +794,14 @@ static int aka_challenge(struct sip_msg *_msg, struct aka_av_mgm *mgm, str *_rea ret = -2; goto release; } - for (; new_count < count; new_count++) { - avs[new_count] = aka_av_get_new_wait(user, algmask, aka_sync_timeout); - if (!avs[new_count]) - break; - algmask &= ~(avs[new_count]->alg); - } + new_count += aka_avs_get_new_wait(user, &algmask, avs + new_count, count - new_count); if (new_count < count) LM_WARN("Could not get get %d (out of %d) authentication vectors!\n", count - new_count, count); } ret = aka_send_resp(_msg, &realm, user, avs, new_count, qop, - algmask, _code, _challenge_msg); + _code, _challenge_msg); release: aka_user_release(user); @@ -954,13 +972,9 @@ static int aka_challenge_async_resume_handle(struct sip_msg *msg, void *_param, struct aka_async_param *param = _param; /* check to see how many events we got */ - for (;param->avs_fetched < param->avs_count; param->avs_fetched++) { - param->avs[param->avs_fetched] = aka_av_get_new(param->user, param->algmask); - if (!param->avs[param->avs_fetched]) - break; - param->algmask &= ~(param->avs[param->avs_fetched]->alg); - } - left = param->avs_fetched - param->avs_count; + param->avs_fetched += aka_avs_get_new(param->user, ¶m->algmask, + param->avs + param->avs_fetched, param->avs_count - param->avs_fetched); + left = param->avs_count - param->avs_fetched; /* check to see if we still have AVS to wait for */ if (!timeout && param->avs_fetched != param->avs_count) { async_status = ASYNC_CONTINUE; @@ -978,7 +992,7 @@ static int aka_challenge_async_resume_handle(struct sip_msg *msg, void *_param, if (param->avs_fetched) { /* now send whatever we have fetched so far */ aka_send_resp(msg, ¶m->realm, param->user, param->avs, param->avs_fetched, - param->qop, param->algmask, param->code, _cs2cc(¶m->challenge_msg)); + param->qop, param->code, _cs2cc(¶m->challenge_msg)); } else if (auth_api.send_resp(msg, 504, NULL, NULL, 0) < 0) { LM_ERR("could not send timeout back\n"); } @@ -1028,11 +1042,8 @@ static int aka_challenge_async(struct sip_msg *_msg, async_ctx *ctx, count += sync_count; /* try to sort them out synchronously */ if (count == 1) { - av = aka_av_get_new(user, algmask); - if (av) { - avs = &av; + if (aka_avs_get_new(user, &algmask, &av, 1) == 1) goto synchronous; - } } /* unfortunately we might need to do it asynchronously */ param = shm_malloc(sizeof *param + realm.len + _challenge_msg->len + @@ -1051,12 +1062,7 @@ static int aka_challenge_async(struct sip_msg *_msg, async_ctx *ctx, avs = param->avs; /* do not prepare the param yet, as we might still have AVs available */ - for (c = 0; c < count; c++) { - avs[c] = aka_av_get_new(user, algmask); - if (!avs[c]) - break; - algmask &= ~(avs[c]->alg); - } + c = aka_avs_get_new(user, &algmask, avs, count); if (c == count) goto synchronous; LM_DBG("we still need %d out of %d AVs\n", count - c, count); @@ -1097,7 +1103,7 @@ static int aka_challenge_async(struct sip_msg *_msg, async_ctx *ctx, synchronous: async_status = ASYNC_NO_IO; ret = aka_send_resp(_msg, &realm, user, avs, count, qop, - algmask, _code, _challenge_msg); + _code, _challenge_msg); error: if (param) shm_free(param); From d5ddb255b267f882b7725b17f3d335aafe6e301e Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Wed, 13 Mar 2024 12:41:50 +0200 Subject: [PATCH 45/79] auth_aka: provide API for AV management --- modules/auth_aka/aka_av_mgm.c | 4 +- modules/auth_aka/aka_av_mgm.h | 75 +++++++++++++++++++++++++++-------- modules/auth_aka/auth_aka.c | 11 +++++ modules/auth_aka/auth_aka.h | 8 ++++ 4 files changed, 80 insertions(+), 18 deletions(-) diff --git a/modules/auth_aka/aka_av_mgm.c b/modules/auth_aka/aka_av_mgm.c index b48e97eb93e..327f12e127d 100644 --- a/modules/auth_aka/aka_av_mgm.c +++ b/modules/auth_aka/aka_av_mgm.c @@ -54,7 +54,7 @@ struct aka_av_mgm *aka_get_mgm(str *name) return 0; } -typedef int (*load_aka_av_mgm_f)(struct aka_av_mgm *mgm); +typedef int (*load_aka_av_mgm_f)(struct aka_av_binds *binds); struct aka_av_mgm *aka_load_mgm(str *name) { @@ -89,7 +89,7 @@ struct aka_av_mgm *aka_load_mgm(str *name) mgm->name.s = mgm->buf; memcpy(mgm->name.s, name->s, name->len); mgm->name.len = name->len; - if (load_aka_av_mgm(mgm) < 0) { + if (load_aka_av_mgm(&mgm->binds) < 0) { LM_ERR("could not load %.*s AV bindings\n", name->len, name->s); pkg_free(mgm); diff --git a/modules/auth_aka/aka_av_mgm.h b/modules/auth_aka/aka_av_mgm.h index be9093406e1..70c82ab316b 100644 --- a/modules/auth_aka/aka_av_mgm.h +++ b/modules/auth_aka/aka_av_mgm.h @@ -29,24 +29,67 @@ #define AKA_AV_MGM_PREFIX "load_aka_av_" +/* + * realm - the Realm of the authentication vector + * impu - Public identity of the user + * impi - Private identity of the user + * resync - Resync/auts token, or NULL if not a resync request + * algmask - Masks of algorithms to request + * no - number of AVs for each algorithm + * async - indicates whether the request is asynchronous or not + */ +typedef int (*aka_av_fetch_f)(str *realm, str *impu, str *impi, str *resync, int algmask, int no, int async); + struct aka_av_binds { - /* - * realm - the Realm of the authentication vector - * impu - Public identity of the user - * impi - Private identity of the user - * resync - Resync/auts token, or NULL if not a resync request - * algmask - Masks of algorithms to request - * no - number of AVs for each algorithm - * async - indicates whether the request is asynchronous or not - */ - int (*fetch)(str *realm, str *impu, str *impi, str *resync, int algmask, int no, int async); + aka_av_fetch_f fetch; }; -struct aka_av_mgm { - str name; - struct aka_av_binds binds; - struct list_head list; - char buf[0]; -}; +/* + * Adds a new AV for a user + * - pub_id - Public Identity of the user + * - priv_id - Private identity of the user + * - algmask - Algorithm Mask this AV should be used for + * - authenticate - The authenticate string used in the digest + * - authorize - The authenticate string used in digest + * - ck - The Confidentiality key used in AKA + * - ik - The Integrity key used in AKA + */ +typedef int (*aka_av_add_f)(str *pub_id, str *priv_id, int algmask, str *authenticate, + str *authorize, str *ck, str *ik); + +/* + * Drops one of the identities of the user, identified by the + * nonce/authenticate string + * - pub_id - Public Identity of the user + * - priv_id - Private identity of the user + * - nonce - The authenticate string used in the digest + */ +typedef int (*aka_av_drop_f)(str *pub_id, str *priv_id, str *nonce); + +/* + * Drops all the identities of a user + * - pub_id - Public Identity of the user + * - priv_id - Private identity of the user + */ +typedef int (*aka_av_drop_all_f)(str *pub_id, str *priv_id); + + +typedef struct aka_av_api { + aka_av_add_f add; + aka_av_drop_f drop; + aka_av_drop_all_f drop_all; +} aka_av_api; + +typedef int (*aka_av_api_bind_f)(aka_av_api *api); + +static inline int aka_av_bind_api(aka_av_api *api) +{ + aka_av_api_bind_f bind_f = (aka_av_api_bind_f)find_export("aka_av_api_bind", 0); + if (!bind_f) { + LM_INFO("could not find AKA AV API\n"); + return -1; + } + return bind_f(api); +} #endif /* AKA_AV_MGM_H */ diff --git a/modules/auth_aka/auth_aka.c b/modules/auth_aka/auth_aka.c index c46a35ecca9..c9546d92d85 100644 --- a/modules/auth_aka/auth_aka.c +++ b/modules/auth_aka/auth_aka.c @@ -76,6 +76,7 @@ static mi_response_t *mi_aka_av_drop(const mi_params_t *params, struct mi_handler *async_hdl); static mi_response_t *mi_aka_av_drop_all(const mi_params_t *params, struct mi_handler *async_hdl); +int load_aka_av_api_bind(aka_av_api *api); static int mod_init(void); /* Module initialization function */ @@ -141,6 +142,8 @@ static const cmd_export_t cmds[] = { {CMD_PARAM_VAR|CMD_PARAM_OPT, fixup_check_var, 0}, /* count */ {0,0,0}}, ALL_ROUTES}, + {"aka_av_api_bind", (cmd_function)load_aka_av_api_bind, { + {0,0,0}}, 0}, {0,0,{{0,0,0}},0} }; @@ -1287,3 +1290,11 @@ static mi_response_t *mi_aka_av_drop_all(const mi_params_t *params, return init_mi_param_error(); return init_mi_result_number(aka_av_drop_all(&public_identity, &private_identity)); } + +int load_aka_av_api_bind(aka_av_api *api) +{ + api->add = aka_av_add; + api->drop = aka_av_drop; + api->drop_all = aka_av_drop_all; + return 1; +} diff --git a/modules/auth_aka/auth_aka.h b/modules/auth_aka/auth_aka.h index 3ac0ed0d713..4cb464a87ca 100644 --- a/modules/auth_aka/auth_aka.h +++ b/modules/auth_aka/auth_aka.h @@ -71,6 +71,14 @@ struct aka_user { char buf[0]; }; +struct aka_av_mgm { + str name; + struct aka_av_binds binds; + struct list_head list; + char buf[0]; +}; + + int aka_init_mgm(int hash_size); From b97eb0ee92790039faab917324b923330998d192 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Wed, 13 Mar 2024 12:42:08 +0200 Subject: [PATCH 46/79] auth_aka: fix default QOP resolve --- modules/auth_aka/auth_aka.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auth_aka/auth_aka.c b/modules/auth_aka/auth_aka.c index c9546d92d85..aa0b1253fee 100644 --- a/modules/auth_aka/auth_aka.c +++ b/modules/auth_aka/auth_aka.c @@ -310,9 +310,9 @@ static int fixup_aka_qop(void** param) LM_ERR("could not parse default_qop param [%s]\n", aka_default_qop_s.s); return -2; } - aka_default_qop = (qop_type_t)(long)(param); + aka_default_qop = (qop_type_t)(long)(*param); } else { - *param = (void *)(long)(param); + *param = (void *)(long)aka_default_qop; } return 0; } else { From e8b354860ca24ddcc3f2a57929e84c20bbdd3ff0 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Wed, 13 Mar 2024 12:42:57 +0200 Subject: [PATCH 47/79] aaa_diameter: provide internal API for diameter commands --- modules/aaa_diameter/aaa_diameter.c | 58 +++-- modules/aaa_diameter/diameter_api.h | 111 +++++++++ modules/aaa_diameter/diameter_api_impl.h | 35 +++ modules/aaa_diameter/dm_impl.c | 298 +++++++++++++++++------ modules/aaa_diameter/dm_impl.h | 13 +- 5 files changed, 415 insertions(+), 100 deletions(-) create mode 100644 modules/aaa_diameter/diameter_api.h create mode 100644 modules/aaa_diameter/diameter_api_impl.h diff --git a/modules/aaa_diameter/aaa_diameter.c b/modules/aaa_diameter/aaa_diameter.c index 563dc6da7fb..02a47a683e7 100644 --- a/modules/aaa_diameter/aaa_diameter.c +++ b/modules/aaa_diameter/aaa_diameter.c @@ -28,6 +28,7 @@ #include "dm_impl.h" #include "dm_evi.h" #include "dm_peer.h" +#include "diameter_api_impl.h" static int mod_init(void); static int child_init(int rank); @@ -42,7 +43,8 @@ static int dm_send_request(struct sip_msg *msg, int *app_id, int *cmd_code, static int dm_send_request_async(struct sip_msg *msg, async_ctx *ctx, int *app_id, int *cmd_code, str *avp_json, pv_spec_t *rpl_avps_pv); static int dm_send_answer(struct sip_msg *msg, str *avp_json, int *is_error); -static int dm_bind_api(aaa_prot *api); +static int dm_aaa_bind_api(aaa_prot *api); +static int dm_bind_api(diameter_api *api); int fd_log_level = FD_LOG_NOTICE; str dm_realm = str_init("diameter.test"); @@ -64,7 +66,8 @@ static const cmd_export_t cmds[]= { {CMD_PARAM_INT|CMD_PARAM_OPT,0,0}, {0,0,0}}, EVENT_ROUTE}, - {"aaa_bind_api", (cmd_function) dm_bind_api, {{0, 0, 0}}, 0}, + {"aaa_bind_api", (cmd_function) dm_aaa_bind_api, {{0, 0, 0}}, 0}, + {"diameter_bind_api", (cmd_function) dm_bind_api, {{0, 0, 0}}, 0}, {0,0,{{0,0,0}},0} }; @@ -229,7 +232,7 @@ static void mod_destroy(void) } -static int dm_bind_api(aaa_prot *api) +static int dm_aaa_bind_api(aaa_prot *api) { if (!api) return -1; @@ -247,6 +250,25 @@ static int dm_bind_api(aaa_prot *api) return 0; } +static int dm_bind_api(diameter_api *api) +{ + if (!api) + return -1; + + memset(api, 0, sizeof *api); + + api->init = dm_init_prot; + api->find_cmd = dm_api_find_cmd; + api->send_request = dm_api_send_req; + api->send_request_async = dm_api_send_req_async; + api->get_reply = dm_api_get_reply; + api->get_reply_status = dm_api_get_reply_status; + api->free_reply = dm_api_free_reply; + + return 0; +} + + static int dm_send_request(struct sip_msg *msg, int *app_id, int *cmd_code, str *avp_json, pv_spec_t *rpl_avps_pv) @@ -255,6 +277,7 @@ static int dm_send_request(struct sip_msg *msg, int *app_id, int *cmd_code, struct dict_object *req; cJSON *avps; int rc; + struct dm_cond *rpl = NULL; char *rpl_avps = NULL; if ((rc = fd_dict_search(fd_g_config->cnf_dict, DICT_COMMAND,CMD_BY_CODE_R, @@ -295,24 +318,27 @@ static int dm_send_request(struct sip_msg *msg, int *app_id, int *cmd_code, avps->child) != 0) { LM_ERR("failed to unpack JSON ('%.*s' ..., total: %d)\n", avp_json->len > 512 ? 512 : avp_json->len, avp_json->s, avp_json->len); + _dm_destroy_message(dmsg); goto error; } + cJSON_Delete(avps); - rc = _dm_send_message(NULL, dmsg, NULL, &rpl_avps); + if (_dm_send_message(NULL, dmsg, &rpl) != 0) + goto error; + rc = _dm_get_message_response(rpl, (rpl_avps_pv?&rpl_avps:NULL)); - if (rpl_avps_pv && rpl_avps) { + if (rpl_avps_pv) { pv_value_t val = {(str){rpl_avps, strlen(rpl_avps)}, 0, PV_VAL_STR}; if (pv_set_value(msg, rpl_avps_pv, 0, &val) != 0) LM_ERR("failed to set output rpl_avps pv to: %s\n", rpl_avps); + _dm_release_message_response(rpl, rpl_avps); } - if (rc < 0) { + if (rc != 0) { LM_ERR("Diameter request failed (rc: %d)\n", rc); - cJSON_Delete(avps); return rc; } - cJSON_Delete(avps); return 1; error: @@ -322,7 +348,6 @@ static int dm_send_request(struct sip_msg *msg, int *app_id, int *cmd_code, LM_ERR("failed to set output rpl_avps pv to NULL\n"); } - _dm_destroy_message(dmsg); cJSON_Delete(avps); return -1; } @@ -411,7 +436,7 @@ static int dm_send_answer(struct sip_msg *msg, str *avp_json, int *is_error) goto error; } - rc = _dm_send_message(NULL, dmsg, NULL, NULL); + rc = _dm_send_message(NULL, dmsg, NULL); if (rc < 0) { evp.pvn.u.isname.name.s = dmev_req_pname_sessid; route_params_run(msg, &evp, &res); @@ -466,7 +491,7 @@ static int dm_send_request_async_reply(int fd, { int ret; unsigned long r; - char *rpl_avps; + char *rpl_avps = NULL; pv_value_t val = {STR_NULL, 0, PV_VAL_NULL}; struct dm_async_msg *amsg = (struct dm_async_msg *)param; @@ -478,19 +503,21 @@ static int dm_send_request_async_reply(int fd, LM_ERR("could not resume async route!\n"); goto error; } - ret = _dm_get_message_response(amsg->cond, &rpl_avps); + ret = _dm_get_message_response(amsg->cond, (amsg->ret?&rpl_avps:NULL)); if (ret == 0) ret = 1; else LM_ERR("Diameter request failed\n"); - if (ret > 0 && amsg->ret && rpl_avps) { + if (ret > 0 && rpl_avps) { val.rs.s = rpl_avps; val.rs.len = strlen(rpl_avps); val.flags = PV_VAL_STR; } error: - if (pv_set_value(msg, amsg->ret, 0, &val) != 0) + if (amsg->ret && pv_set_value(msg, amsg->ret, 0, &val) != 0) LM_ERR("failed to set output rpl_avps pv to NULL\n"); + if (rpl_avps) + _dm_release_message_response(amsg->cond, rpl_avps); dm_free_sync_msg(amsg); return ret; } @@ -554,6 +581,7 @@ static int dm_send_request_async(struct sip_msg *msg, async_ctx *ctx, avps->child) != 0) { LM_ERR("failed to unpack JSON ('%.*s' ..., total: %d)\n", avp_json->len > 512 ? 512 : avp_json->len, avp_json->s, avp_json->len); + _dm_destroy_message(dmsg); goto error; } if (_dm_send_message_async(NULL, dmsg, &async_status) < 0) { @@ -564,13 +592,13 @@ static int dm_send_request_async(struct sip_msg *msg, async_ctx *ctx, amsg = dm_get_async_msg(rpl_avps_pv, dmsg); if (!amsg) goto error; + cJSON_Delete(avps); ctx->resume_f = dm_send_request_async_reply; ctx->resume_param = amsg; ctx->timeout_s = dm_answer_timeout / 1000; ctx->timeout_f = dm_send_request_async_tout; - cJSON_Delete(avps); return 1; error: diff --git a/modules/aaa_diameter/diameter_api.h b/modules/aaa_diameter/diameter_api.h new file mode 100644 index 00000000000..982fcdfbbaf --- /dev/null +++ b/modules/aaa_diameter/diameter_api.h @@ -0,0 +1,111 @@ +/** + * Copyright (C) 2024 OpenSIPS Solutions + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DIAMETER_API_H +#define DIAMETER_API_H + +/* + * XXX: most of the functions overlap, so we are currently trying to keep them + * all together + */ +#include "../../aaa/aaa.h" +#include "../../lib/cJSON.h" + +/* structures */ +#define diameter_conn aaa_conn + +typedef struct { + cJSON *json; + int is_error; + int rc; +} diameter_reply; + +/* + Initialize Diameter protocol implementation + + This function initializes the protocol and returns a pointer to the + connection variable that represents it. + The return value is a pointer to a connection variable. + */ +typedef diameter_conn* (diameter_init_f)(str *); + +/* + Search a command in the dictionary + + This function searches a command's code in the dictionary. + It returns true if found, false otherwise. + */ +typedef int (diameter_find_cmd_f)(diameter_conn*, int code); + +/* + Sends a diameter request and returns a reply handle + */ +typedef int (diameter_send_req_f)(diameter_conn*, int app_id, int code, + cJSON *req, diameter_reply *reply); + +/* + Callback run for an asynchornous command reply + */ +typedef int (diameter_reply_cb)(diameter_conn *conn, diameter_reply *reply); + +/* + Sends an asynchornous diameter request and calls the callback in the reply + */ +typedef int (diameter_send_req_async_f)(diameter_conn*, int app_id, int code, + cJSON *req, diameter_reply_cb *reply_cb); + +/* + Retrieves a JSON from a reply handle + */ +typedef cJSON *(diameter_get_reply_f)(diameter_reply *rpl); + +/* + Retrieves the status from a reply handle + */ +typedef int (diameter_get_reply_status_f)(diameter_reply *rpl); + +/* + Frees a reply handle + */ +typedef void (diameter_free_reply_f)(diameter_reply *rpl); + +typedef struct diameter_api { + diameter_init_f *init; + diameter_find_cmd_f *find_cmd; + diameter_send_req_f *send_request; + diameter_send_req_async_f *send_request_async; + diameter_get_reply_f *get_reply; + diameter_get_reply_status_f *get_reply_status; + diameter_free_reply_f *free_reply; +} diameter_api; + +typedef int (*diameter_bind_f)(diameter_api *api); + +static inline int diameter_bind_api(diameter_api *api) +{ + diameter_bind_f bind_f = (diameter_bind_f)find_export("diameter_bind_api", 0); + if (!bind_f) { + LM_INFO("could not bind Diameter API\n"); + return -1; + } + return bind_f(api); +} + +#endif /* DIAMETER_API_H */ diff --git a/modules/aaa_diameter/diameter_api_impl.h b/modules/aaa_diameter/diameter_api_impl.h new file mode 100644 index 00000000000..a40dc99bc51 --- /dev/null +++ b/modules/aaa_diameter/diameter_api_impl.h @@ -0,0 +1,35 @@ +/** + * Copyright (C) 2024 OpenSIPS Solutions + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DIAMETER_API_IMPL_H +#define DIAMETER_API_IMPL_H + +#include "diameter_api.h" + +int dm_api_find_cmd(diameter_conn *conn, int cmd_code); +int dm_api_send_req(diameter_conn *conn, int app_id, int cmd_code, cJSON *req, + diameter_reply *reply); +int dm_api_send_req_async(diameter_conn *conn, int app_id, int cmd_code, cJSON *req, + diameter_reply_cb *reply); +cJSON *dm_api_get_reply(diameter_reply *rpl); +int dm_api_get_reply_status(diameter_reply *rpl); +void dm_api_free_reply(diameter_reply *rpl); + +#endif /* DIAMETER_API_IMPL_H */ diff --git a/modules/aaa_diameter/dm_impl.c b/modules/aaa_diameter/dm_impl.c index 597e0dbf6fc..9f35c825e1e 100644 --- a/modules/aaa_diameter/dm_impl.c +++ b/modules/aaa_diameter/dm_impl.c @@ -32,6 +32,8 @@ #include "dm_evi.h" #include "dm_peer.h" #include "app_opensips/avps.h" +#include "diameter_api.h" +#include "diameter_api_impl.h" struct local_rules_definition { char *avp_name; @@ -103,8 +105,7 @@ static int dm_avp_inttype[] = { AAA_TYPE_FLOAT64, }; - -static struct dm_cond *dm_get_cond(int type) +static struct dm_cond *dm_get_cond(int type, diameter_reply_cb *cb) { struct dm_cond *cond = shm_malloc(sizeof *cond); if (!cond) { @@ -113,7 +114,8 @@ static struct dm_cond *dm_get_cond(int type) } memset(cond, 0, sizeof *cond); cond->type = type; - if (type == DM_TYPE_EVENT) { + switch (type) { + case DM_TYPE_EVENT: cond->sync.event.pid = process_no; cond->sync.event.fd = eventfd(0, 0); if (cond->sync.event.fd < 0) { @@ -121,8 +123,15 @@ static struct dm_cond *dm_get_cond(int type) shm_free(cond); return NULL; } - } else { + break; + case DM_TYPE_COND: init_mutex_cond(&cond->sync.cond.mutex, &cond->sync.cond.cond); + break; + case DM_TYPE_CB: + if (!cb) + LM_WARN("no callback specified\n"); + cond->sync.cb = cb; + break; } return cond; @@ -130,7 +139,7 @@ static struct dm_cond *dm_get_cond(int type) int dm_init_reply_cond(int proc_rank) { - my_reply_cond = dm_get_cond(DM_TYPE_COND); + my_reply_cond = dm_get_cond(DM_TYPE_COND, NULL); return my_reply_cond?0:-1; } @@ -266,19 +275,28 @@ static void dm_cond_event_resume(int sender, void *param) LM_ERR("could not notify resume: %s\n", strerror(errno)); } -/* assumes that the cond's mutex is taken */ static void dm_cond_signal(struct dm_cond *cond) { - if (cond->type == DM_TYPE_EVENT) { + LM_INFO("singalling %p/%d\n", cond, cond->type); + switch (cond->type) { + case DM_TYPE_EVENT: if (ipc_send_rpc(cond->sync.event.pid, dm_cond_event_resume, cond) < 0) { LM_ERR("could not resume async MI command!\n"); shm_free(cond); } - } else { + break; + case DM_TYPE_COND: /* signal the blocked SIP worker that the auth result is available! */ pthread_mutex_lock(&cond->sync.cond.mutex); pthread_cond_signal(&cond->sync.cond.cond); pthread_mutex_unlock(&cond->sync.cond.mutex); + break; + case DM_TYPE_CB: + LM_INFO("callback %p\n", cond->sync.cb); + if (cond->sync.cb) + cond->sync.cb(NULL, &cond->rpl); + shm_free(cond); + break; } } @@ -323,19 +341,20 @@ static int dm_auth_reply(struct msg **_msg, struct avp * avp, struct session * s goto out; } rpl_cond = *prpl_cond; - rpl_cond->rc = rc; + rpl_cond->rpl.rc = rc; hash_remove_key(pending_replies, callid); + LM_INFO("XXX: removing %.*s\n", callid.len, callid.s); hash_unlock(pending_replies, hentry); FD_CHECK(fd_msg_search_avp(msg, dm_dict.Error_Message, &a)); if (a) { - rpl_cond->is_error = 1; + rpl_cond->rpl.is_error = 1; FD_CHECK(fd_msg_avp_hdr(a, &h)); LM_DBG("auth failure, rc: %d (%.*s)\n", rc, (int)h->avp_value->os.len, h->avp_value->os.data); } else { - rpl_cond->is_error = 0; + rpl_cond->rpl.is_error = 0; } dm_cond_signal(rpl_cond); @@ -661,16 +680,19 @@ static int dm_receive_msg(struct msg **_msg, struct avp * avp, struct session * } rpl_cond = *prpl_cond; - if (rpl_cond->rpl_avps_json) - shm_free(rpl_cond->rpl_avps_json); - rpl_cond->rpl_avps_json = cJSON_PrintUnformatted(avps); + if (!hash_find_key(pending_replies, tid)) { + LM_ERR("Transaction_Id %.*s already processed!\n", tid.len, tid.s); + goto out; + } + + rpl_cond->rpl.json = avps; hash_remove_key(pending_replies, tid); hash_unlock(pending_replies, hentry); fd_msg_search_avp(msg, dm_dict.Error_Message, &a); if (a) { - rpl_cond->is_error = 1; + rpl_cond->rpl.is_error = 1; rc = fd_msg_avp_hdr(a, &h); if (rc != 0) { goto out; @@ -679,12 +701,11 @@ static int dm_receive_msg(struct msg **_msg, struct avp * avp, struct session * LM_DBG("transaction failed (%.*s)\n", (int)h->avp_value->os.len, h->avp_value->os.data); } else { - rpl_cond->is_error = 0; + rpl_cond->rpl.is_error = 0; } dm_cond_signal(rpl_cond); out: - cJSON_Delete(avps); cJSON_InitHooks(NULL); FD_CHECK(fd_msg_free(msg)); @@ -1658,6 +1679,12 @@ int dm_find(aaa_conn *_, aaa_map *map, int op) return -1; } +int dm_api_find_cmd(diameter_conn *conn, int cmd_code) +{ + struct dict_object *req; + return (fd_dict_search(fd_g_config->cnf_dict, DICT_COMMAND, CMD_BY_CODE_R, + &cmd_code, &req, ENOENT) != ENOENT); +} aaa_message *_dm_create_message(aaa_conn *_, int msg_type, unsigned int app_id, unsigned int cmd_code, void *fd_msg) @@ -1886,124 +1913,237 @@ int dm_build_avps(struct list_head *out_avps, cJSON *array) return -1; } +static void dm_push_queue(aaa_message *req, struct dm_cond *cond) +{ + struct dm_message *dm = (struct dm_message *)(req->avpair); + dm->reply_cond = cond; + req->last_found = DM_MSG_SENT; + + pthread_mutex_lock(msg_send_lk); + + list_add_tail(&dm->list, msg_send_queue); + pthread_cond_signal(msg_send_cond); + + pthread_mutex_unlock(msg_send_lk); +} + int _dm_send_message_async(aaa_conn *_, aaa_message *req, int *fd) { - struct dm_message *dm; struct dm_cond *cond; if (!req) return -1; - cond = dm_get_cond(DM_TYPE_EVENT); + cond = dm_get_cond(DM_TYPE_EVENT, NULL); if (!cond) { LM_ERR("out of memory for cond\n"); return -1; } - dm = (struct dm_message *)(req->avpair); *fd = cond->sync.event.fd; - dm->reply_cond = cond; + dm_push_queue(req, cond); - req->last_found = DM_MSG_SENT; + LM_DBG("message queued for async sending\n"); - pthread_mutex_lock(msg_send_lk); + return 0; +} - list_add_tail(&dm->list, msg_send_queue); - pthread_cond_signal(msg_send_cond); +int _dm_send_message_callback(aaa_conn *_, aaa_message *req, diameter_reply_cb *cb) +{ + struct dm_cond *cond; - pthread_mutex_unlock(msg_send_lk); + if (!req) + return -1; + + cond = dm_get_cond(DM_TYPE_CB, cb); + if (!cond) { + LM_ERR("out of memory for cond\n"); + return -1; + } + + dm_push_queue(req, cond); LM_DBG("message queued for async sending\n"); return 0; } -int _dm_get_message_response(struct dm_cond *cond, char **rpl_avps) +void _dm_release_message_response(struct dm_cond *cond, char *rpl_avps) { + cJSON_PurgeString(rpl_avps); + if (cond->rpl.json) { + cJSON_InitHooks(&shm_mem_hooks); + cJSON_Delete(cond->rpl.json); + cJSON_InitHooks(NULL); + cond->rpl.json = NULL; + } +} - LM_DBG("reply received, Result-Code: %d, is_error: %d\n", cond->rc, - cond->is_error); - LM_DBG("AVPs: %s\n", cond->rpl_avps_json); - - if (rpl_avps) - *rpl_avps = cond->rpl_avps_json; +static int _dm_get_message_reply(struct dm_cond *cond, diameter_reply *rpl) +{ + LM_DBG("reply received, Result-Code: %d (%s)\n", cond->rpl.rc, + cond->rpl.is_error ? "FAILURE" : "SUCCESS"); - if (cond->is_error) - return -1; - return 0; + memcpy(rpl, &cond->rpl, sizeof *rpl); + cond->rpl.json = NULL; /* also detach the data */ + return (cond->rpl.is_error?-1:0); } - -int _dm_send_message(aaa_conn *_, aaa_message *msg, aaa_message **reply, - char **rpl_avps) +int _dm_get_message_response(struct dm_cond *cond, char **rpl_avps) { - struct dm_message *dm; - int await_reply = 0; + cJSON *obj; + diameter_reply rpl; + int rc = _dm_get_message_reply(cond, &rpl); + + if (rpl_avps) { + obj = dm_api_get_reply(&rpl); + *rpl_avps = cJSON_PrintUnformatted(obj); + LM_DBG("AVPs: %s\n", *rpl_avps); + } + return rc; +} - if (!msg || !my_reply_cond) +int _dm_send_message(aaa_conn *_, aaa_message *req, struct dm_cond **reply_cond) +{ + if (!req || !my_reply_cond) return -1; - dm = (struct dm_message *)(msg->avpair); - dm->reply_cond = my_reply_cond; + dm_push_queue(req, my_reply_cond); + + LM_DBG("message queued for sending\n"); + + if (req->type == AAA_AUTH || req->type == AAA_CUSTOM_REQ) { + struct timespec wait_until; + struct timeval now, wait_time, res; + int rc; + + gettimeofday(&now, NULL); + wait_time.tv_sec = dm_answer_timeout / 1000; + wait_time.tv_usec = dm_answer_timeout % 1000 * 1000UL; + LM_DBG("awaiting auth reply (%ld s, %ld us)...\n", wait_time.tv_sec, wait_time.tv_usec); + + timeradd(&now, &wait_time, &res); + + wait_until.tv_sec = res.tv_sec; + wait_until.tv_nsec = res.tv_usec * 1000UL; + + pthread_mutex_lock(&my_reply_cond->sync.cond.mutex); + rc = pthread_cond_timedwait(&my_reply_cond->sync.cond.cond, + &my_reply_cond->sync.cond.mutex, &wait_until); + if (rc != 0) { + LM_ERR("timeout (errno: %d '%s') while awaiting Diameter " + "reply\n", rc, strerror(rc)); + pthread_mutex_unlock(&my_reply_cond->sync.cond.mutex); + + return -2; + } + if (reply_cond) + *reply_cond = my_reply_cond; + + return 1; + } + return 0; +} +int dm_send_message(aaa_conn *_, aaa_message *req, aaa_message **reply) +{ /* never provide the reply, just grab the result code, if any */ if (reply) *reply = NULL; + return _dm_send_message(_, req, NULL); +} - msg->last_found = DM_MSG_SENT; - if (msg->type == AAA_AUTH || msg->type == AAA_CUSTOM_REQ) - await_reply = 1; +int dm_api_send_req(diameter_conn *conn, int app_id, int cmd_code, cJSON *req, diameter_reply *rpl) +{ + aaa_message *dmsg = NULL; + struct dm_cond *rpl_cond = NULL; + int rc = -1; - pthread_mutex_lock(msg_send_lk); + if (!req) { + LM_ERR("no request provided\n"); + return -1; + } - list_add_tail(&dm->list, msg_send_queue); - pthread_cond_signal(msg_send_cond); + if (req->type != cJSON_Array) { + LM_ERR("request must be an array\n"); + return -2; + } - pthread_mutex_lock(&my_reply_cond->sync.cond.mutex); - pthread_mutex_unlock(msg_send_lk); + dmsg = _dm_create_message(NULL, AAA_CUSTOM_REQ, app_id, cmd_code, NULL); + if (!dmsg) { + LM_ERR("oom\n"); + return -1; + } - LM_DBG("message queued for sending, await_reply: %d\n", await_reply); + if (dm_build_avps(&((struct dm_message *)(dmsg->avpair))->avps, + req->child) != 0) { + LM_ERR("failed to unpack JSON\n"); + _dm_destroy_message(dmsg); + goto end; + } - if (!await_reply) { - pthread_mutex_unlock(&my_reply_cond->sync.cond.mutex); - return 1; + if (_dm_send_message(NULL, dmsg, &rpl_cond) != 0) { + LM_ERR("could not send Diameter message\n"); + goto end; } + rc = _dm_get_message_reply(rpl_cond, rpl); +end: + return rc; +} - struct timespec wait_until; - struct timeval now, wait_time, res; - int rc; +int dm_api_send_req_async(diameter_conn *conn, int app_id, int cmd_code, cJSON *req, diameter_reply_cb *reply_cb) +{ + aaa_message *dmsg = NULL; - gettimeofday(&now, NULL); - wait_time.tv_sec = dm_answer_timeout / 1000; - wait_time.tv_usec = dm_answer_timeout % 1000 * 1000UL; - LM_DBG("awaiting reply (%ld s, %ld us)...\n", wait_time.tv_sec, wait_time.tv_usec); + if (!req) { + LM_ERR("no request provided\n"); + return -1; + } - timeradd(&now, &wait_time, &res); + if (req->type != cJSON_Array) { + LM_ERR("request must be an array\n"); + return -2; + } - wait_until.tv_sec = res.tv_sec; - wait_until.tv_nsec = res.tv_usec * 1000UL; + dmsg = _dm_create_message(NULL, AAA_CUSTOM_REQ, app_id, cmd_code, NULL); + if (!dmsg) { + LM_ERR("oom\n"); + return -1; + } - rc = pthread_cond_timedwait(&my_reply_cond->sync.cond.cond, - &my_reply_cond->sync.cond.mutex, &wait_until); - if (rc != 0) { - LM_ERR("timeout (errno: %d '%s') while awaiting Diameter " - "reply\n", rc, strerror(rc)); - pthread_mutex_unlock(&my_reply_cond->sync.cond.mutex); + if (dm_build_avps(&((struct dm_message *)(dmsg->avpair))->avps, + req->child) != 0) { + LM_ERR("failed to unpack JSON\n"); + _dm_destroy_message(dmsg); + return -1; + } - if (rpl_avps) - *rpl_avps = NULL; - return -2; + if (_dm_send_message_callback(NULL, dmsg, reply_cb) != 0) { + LM_ERR("could not send Diameter callback message\n"); + return -1; } + return 0; - pthread_mutex_unlock(&my_reply_cond->sync.cond.mutex); +} - return _dm_get_message_response(my_reply_cond, rpl_avps); +cJSON *dm_api_get_reply(diameter_reply *rpl) +{ + return rpl->json; } -int dm_send_message(aaa_conn *_, aaa_message *msg, aaa_message **reply) +int dm_api_get_reply_status(diameter_reply *rpl) { - return _dm_send_message(_, msg, reply, NULL); + return (rpl->is_error?0:1); +} + +void dm_api_free_reply(diameter_reply *rpl) +{ + if (!rpl) + return; + cJSON_InitHooks(&shm_mem_hooks); + cJSON_Delete(rpl->json); + cJSON_InitHooks(NULL); } diff --git a/modules/aaa_diameter/dm_impl.h b/modules/aaa_diameter/dm_impl.h index c8bb28db03c..0366a96499b 100644 --- a/modules/aaa_diameter/dm_impl.h +++ b/modules/aaa_diameter/dm_impl.h @@ -22,6 +22,7 @@ #define AAA_DIAMETER_IMPL #include "../../aaa/aaa.h" +#include "diameter_api.h" #define __FD_CHECK(__call__, __retok__, __retval__) \ do { \ @@ -116,8 +117,9 @@ struct dm_avp { struct list_head list; }; -#define DM_TYPE_COND (1<<0) +#define DM_TYPE_COND (1<<0) #define DM_TYPE_EVENT (1<<1) +#define DM_TYPE_CB (1<<2) struct dm_cond { int type; @@ -130,11 +132,10 @@ struct dm_cond { int fd; int pid; } event; + diameter_reply_cb *cb; } sync; - int rc; /* the Diameter Result-Code AVP value */ - int is_error; - char *rpl_avps_json; /* JSON with all reply AVPs and their values */ + diameter_reply rpl; }; int init_mutex_cond(pthread_mutex_t *mutex, pthread_cond_t *cond); @@ -164,10 +165,10 @@ int dm_avp_add(aaa_conn *_, aaa_message *msg, aaa_map *avp, void *val, int val_length, int vendor); int dm_build_avps(struct list_head *subavps, cJSON *array); int dm_send_message(aaa_conn *_, aaa_message *req, aaa_message **__); -int _dm_send_message(aaa_conn *_, aaa_message *req, aaa_message **reply, - char **rpl_avps); +int _dm_send_message(aaa_conn *_, aaa_message *req, struct dm_cond **reply_cond); int _dm_send_message_async(aaa_conn *_, aaa_message *req, int *fd); int _dm_get_message_response(struct dm_cond *cond, char **rpl_avps); +void _dm_release_message_response(struct dm_cond *cond, char *rpl_avps); int dm_destroy_message(aaa_conn *con, aaa_message *msg); void _dm_destroy_message(aaa_message *msg); From 623c3718471425600fb1b21c03e10455f0a74ab7 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Wed, 13 Mar 2024 17:43:26 +0200 Subject: [PATCH 48/79] auth_aka: add support for marking an AV as failure --- modules/auth_aka/aka_av_mgm.c | 78 ++++++++++--- modules/auth_aka/aka_av_mgm.h | 9 ++ modules/auth_aka/auth_aka.c | 141 ++++++++++++++++++------ modules/auth_aka/auth_aka.h | 7 +- modules/auth_aka/doc/auth_aka_admin.xml | 82 ++++++++++++++ 5 files changed, 263 insertions(+), 54 deletions(-) diff --git a/modules/auth_aka/aka_av_mgm.c b/modules/auth_aka/aka_av_mgm.c index 327f12e127d..a96c0344cc5 100644 --- a/modules/auth_aka/aka_av_mgm.c +++ b/modules/auth_aka/aka_av_mgm.c @@ -311,21 +311,30 @@ static void aka_av_mark_using(struct aka_av *av, int algmask) av->alg = aka_av_first_bit_mask(algmask); } -struct aka_av *aka_av_get_new_wait(struct aka_user *user, int algmask, long milliseconds) +int aka_av_get_new_wait(struct aka_user *user, int algmask, + long milliseconds, struct aka_av **av) { - struct aka_av *av; + int ret = -1; struct timespec spec, end, begin; cond_lock(&user->cond); - av = aka_av_get_state(user, algmask, AKA_AV_NEW); - if (!av) { + if (user->error_count) { + user->error_count--; + goto end; + } + *av = aka_av_get_state(user, algmask, AKA_AV_NEW); + if (*av == NULL) { switch (milliseconds) { case 0: /* just peaking */ break; case -1: /* blocking pop */ do { + if (user->error_count) { + user->error_count--; + goto end; + } cond_wait(&user->cond); - } while ((av = aka_av_get_state(user, algmask, AKA_AV_NEW)) == NULL); + } while ((*av = aka_av_get_state(user, algmask, AKA_AV_NEW)) == NULL); break; default: do { @@ -335,7 +344,11 @@ struct aka_av *aka_av_get_new_wait(struct aka_user *user, int algmask, long mill spec.tv_nsec += (milliseconds % 1000) * 1000000; errno = 0; cond_timedwait(&user->cond, &spec); - av = aka_av_get_state(user, algmask, AKA_AV_NEW); /* one last time */ + if (user->error_count) { + user->error_count--; + goto end; + } + *av = aka_av_get_state(user, algmask, AKA_AV_NEW); /* one last time */ if (cond_has_timedout(&user->cond)) break; if (!av) { @@ -344,28 +357,39 @@ struct aka_av *aka_av_get_new_wait(struct aka_user *user, int algmask, long mill milliseconds -= (end.tv_sec - begin.tv_sec) * 1000 + (end.tv_nsec - begin.tv_nsec) / 1000000; } - } while (av == NULL && milliseconds > 0); + } while (*av == NULL && milliseconds > 0); break; } + } + if (*av) { + aka_av_mark_using(*av, algmask); + ret = 1; } else { - av->state = AKA_AV_USING; + ret = 0; } - if (av) - aka_av_mark_using(av, algmask); +end: cond_unlock(&user->cond); - return av; + return ret; } -struct aka_av *aka_av_get_new(struct aka_user *user, int algmask) +int aka_av_get_new(struct aka_user *user, int algmask, struct aka_av **av) { - struct aka_av *av; - + int ret; cond_lock(&user->cond); - av = aka_av_get_state(user, algmask, AKA_AV_NEW); - if (av) - aka_av_mark_using(av, algmask); + if (!user->error_count) { + ret = 0; + *av = aka_av_get_state(user, algmask, AKA_AV_NEW); + if (*av) { + aka_av_mark_using(*av, algmask); + ret = 1; + } + } else { + /* account for one error */ + ret = -1; + user->error_count--; + } cond_unlock(&user->cond); - return av; + return ret; } static inline int aka_check_algmask(int algmask, int flags, @@ -533,6 +557,24 @@ int aka_av_drop(str *pub_id, str *priv_id, str *nonce) return (av?1:0); } +int aka_av_fail(str *pub_id, str *priv_id, int count) +{ + struct aka_user *user = aka_user_find(pub_id, priv_id); + + if (!user) { + LM_DBG("cannot find user %.*s/%.*s\n", + pub_id->len, pub_id->s, priv_id->len, priv_id->s); + return -1; + } + cond_lock(&user->cond); + user->error_count += count; + if (!list_empty(&user->async)) + aka_signal_async(user, user->async.next); + cond_signal(&user->cond); + cond_unlock(&user->cond); + aka_user_release(user); + return 0; +} void aka_av_set_new(struct aka_user *user, struct aka_av *av) { diff --git a/modules/auth_aka/aka_av_mgm.h b/modules/auth_aka/aka_av_mgm.h index 70c82ab316b..6826cc71374 100644 --- a/modules/auth_aka/aka_av_mgm.h +++ b/modules/auth_aka/aka_av_mgm.h @@ -73,11 +73,20 @@ typedef int (*aka_av_drop_f)(str *pub_id, str *priv_id, str *nonce); */ typedef int (*aka_av_drop_all_f)(str *pub_id, str *priv_id); +/* + * Indicates that the fetching of an AV has failed + * - pub_id - Public Identity of the user + * - priv_id - Private identity of the user + * - count - The number of AV that failed + */ +typedef int (*aka_av_fail_f)(str *pub_id, str *priv_id, int count); + typedef struct aka_av_api { aka_av_add_f add; aka_av_drop_f drop; aka_av_drop_all_f drop_all; + aka_av_fail_f fail; } aka_av_api; typedef int (*aka_av_api_bind_f)(aka_av_api *api); diff --git a/modules/auth_aka/auth_aka.c b/modules/auth_aka/auth_aka.c index aa0b1253fee..b4d47f5ef1c 100644 --- a/modules/auth_aka/auth_aka.c +++ b/modules/auth_aka/auth_aka.c @@ -70,12 +70,16 @@ static int script_aka_av_drop(struct sip_msg *msg, str *pub_id, str *priv_id, str *authenticate); static int script_aka_av_drop_all(struct sip_msg *msg, str *pub_id, str *priv_id, pv_spec_t *count); +static int script_aka_av_fail(struct sip_msg *msg, str *pub_id, str *priv_id, + int *count); static mi_response_t *mi_aka_av_add(const mi_params_t *params, struct mi_handler *async_hdl); static mi_response_t *mi_aka_av_drop(const mi_params_t *params, struct mi_handler *async_hdl); static mi_response_t *mi_aka_av_drop_all(const mi_params_t *params, struct mi_handler *async_hdl); +static mi_response_t *mi_aka_av_fail(const mi_params_t *params, + struct mi_handler *async_hdl); int load_aka_av_api_bind(aka_av_api *api); static int mod_init(void); /* Module initialization function */ @@ -142,6 +146,12 @@ static const cmd_export_t cmds[] = { {CMD_PARAM_VAR|CMD_PARAM_OPT, fixup_check_var, 0}, /* count */ {0,0,0}}, ALL_ROUTES}, + {"aka_av_fail", (cmd_function)script_aka_av_fail, { + {CMD_PARAM_STR, NULL, 0}, /* public_identity */ + {CMD_PARAM_STR, NULL, 0}, /* private_identity */ + {CMD_PARAM_INT|CMD_PARAM_OPT, NULL, 0}, /* count */ + {0,0,0}}, + ALL_ROUTES}, {"aka_av_api_bind", (cmd_function)load_aka_av_api_bind, { {0,0,0}}, 0}, {0,0,{{0,0,0}},0} @@ -183,11 +193,19 @@ static const mi_export_t mi_cmds[] = { {mi_aka_av_add, {"public_identity", "private_identity", "authenticate", "authorize", "confidentiality-key", "integrity-key", "algorithms", 0}}, + {EMPTY_MI_RECIPE}}}, + { "aka_av_drop", 0, 0, 0, { {mi_aka_av_drop, {"public_identity", "private_identity", "authenticate", 0}}, + {EMPTY_MI_RECIPE}}}, + { "aka_av_drop_all", 0, 0, 0, { {mi_aka_av_drop_all, {"public_identity", "private_identity", 0}}, - {EMPTY_MI_RECIPE}} - }, + {EMPTY_MI_RECIPE}}}, + { "aka_av_fail", 0, 0, 0, { + {mi_aka_av_fail, {"public_identity", "private_identity", 0}}, + {mi_aka_av_fail, {"public_identity", "private_identity", + "count", 0}}, + {EMPTY_MI_RECIPE}}}, {EMPTY_MI_EXPORT} }; @@ -729,32 +747,48 @@ static int aka_send_resp(struct sip_msg *_msg, str *realm, struct aka_user *user return ret; } -static inline int aka_avs_get_new_wait(struct aka_user *user, int *algmask, +static inline int aka_avs_new_wait(struct aka_user *user, int *algmask, struct aka_av **avs, int count) { - int c; - for (c = 0; c < count; c++) { - avs[c] = aka_av_get_new_wait(user, *algmask, aka_sync_timeout); - if (!avs[c]) - break; - *algmask &= ~ALG2ALGFLG(avs[c]->alg); + int c, err = 0; + for (c = 0; c < count - err;) { + switch (aka_av_get_new_wait(user, *algmask, aka_sync_timeout, &avs[c])) { + case -1: /* error */ + case 0: /* no AV found within the expected time */ + err++; + break; + case 1: /* a proper AV was found */ + *algmask &= ~ALG2ALGFLG(avs[c]->alg); + c++; + break; + } } - LM_DBG("got %d AVs out of %d\n", c, count); + LM_DBG("got %d AVs out of %d (%d errors)\n", c, count, err); return c; } static inline int aka_avs_get_new(struct aka_user *user, int *algmask, - struct aka_av **avs, int count) + struct aka_av **avs, int count, int *err_count) { int c; - for (c = 0; c < count; c++) { - avs[c] = aka_av_get_new(user, *algmask); - if (!avs[c]) - break; - *algmask &= ~ALG2ALGFLG(avs[c]->alg); + *err_count = 0; + for (c = 0; c < count - *err_count;) { + switch (aka_av_get_new(user, *algmask, &avs[c])) { + case -1: /* error */ + c--; + (*err_count)++; + break; + case 0: /* no AV found within the expected time */ + goto end; + case 1: /* a proper AV was found */ + *algmask &= ~ALG2ALGFLG(avs[c]->alg); + c++; + break; + } } - LM_DBG("got %d AVs out of %d\n", c, count); +end: + LM_DBG("got %d AVs out of %d (%d error)\n", c, count, *err_count); return c; } @@ -768,7 +802,7 @@ static int aka_challenge(struct sip_msg *_msg, struct aka_av_mgm *mgm, str *_rea struct aka_user *user; struct aka_av *av, **avs; int count = aka_count_avs(algmask), new_count; - int sync_count; + int sync_count, err_count; realm = (_realm?*_realm:str_init("")); if (count > 1) { @@ -787,17 +821,17 @@ static int aka_challenge(struct sip_msg *_msg, struct aka_av_mgm *mgm, str *_rea } count += sync_count; /* try to fetch as many local AVs as possible */ - new_count = aka_avs_get_new(user, &algmask, avs, count); + new_count = aka_avs_get_new(user, &algmask, avs, count, &err_count); /* if we need more, fetch them remotely */ - if (new_count != count) { + if (new_count + err_count != count) { if (mgm->binds.fetch(&realm, &user->public->impu, &user->impi, (auts.len?&auts:NULL), algmask, 1, 0) != 0) { LM_INFO("Could not fetch %d authentication vector(s)!\n", count); ret = -2; goto release; } - new_count += aka_avs_get_new_wait(user, &algmask, avs + new_count, count - new_count); + new_count += aka_avs_new_wait(user, &algmask, avs + new_count, count - new_count - err_count); if (new_count < count) LM_WARN("Could not get get %d (out of %d) authentication vectors!\n", count - new_count, count); @@ -946,7 +980,7 @@ struct aka_async_param { str challenge_msg; struct aka_user *user; struct aka_av **avs; - int avs_count, avs_fetched; + int avs_count, avs_fetched, avs_error; int process_no; unsigned int ticks; struct list_head list; @@ -971,31 +1005,35 @@ static int aka_async_param_release(struct aka_async_param *param) static int aka_challenge_async_resume_handle(struct sip_msg *msg, void *_param, int timeout) { - int left; + int left, err_count; struct aka_async_param *param = _param; /* check to see how many events we got */ param->avs_fetched += aka_avs_get_new(param->user, ¶m->algmask, - param->avs + param->avs_fetched, param->avs_count - param->avs_fetched); - left = param->avs_count - param->avs_fetched; + param->avs + param->avs_fetched, param->avs_count - param->avs_fetched, &err_count); + param->avs_error += err_count; + left = param->avs_count - param->avs_fetched - param->avs_error; /* check to see if we still have AVS to wait for */ - if (!timeout && param->avs_fetched != param->avs_count) { + if (!timeout && (param->avs_fetched + param->avs_error) != param->avs_count) { async_status = ASYNC_CONTINUE; LM_DBG("waiting for more %d AVs to a total of %d\n", left, param->avs_count); return 1; } if (timeout && left) - LM_ERR("timeout waiting for AVs - got %d out of %d so far\n", - param->avs_fetched, param->avs_count); + LM_ERR("timeout waiting for AVs - got %d out of %d so far (%d error)\n", + param->avs_fetched, param->avs_count, param->avs_error); else - LM_DBG("fetched all %d out of %d AVs\n", - param->avs_fetched, param->avs_count); + LM_DBG("fetched all %d out of %d AVs (%d error)\n", + param->avs_fetched, param->avs_count, param->avs_error); async_status = ASYNC_DONE_NO_IO; if (!param->replied) { if (param->avs_fetched) { /* now send whatever we have fetched so far */ aka_send_resp(msg, ¶m->realm, param->user, param->avs, param->avs_fetched, param->qop, param->code, _cs2cc(¶m->challenge_msg)); + } else if (param->avs_error) { + if (auth_api.send_resp(msg, 500, NULL, NULL, 0) < 0) + LM_ERR("could not send error back\n"); } else if (auth_api.send_resp(msg, 504, NULL, NULL, 0) < 0) { LM_ERR("could not send timeout back\n"); } @@ -1034,7 +1072,7 @@ static int aka_challenge_async(struct sip_msg *_msg, async_ctx *ctx, struct aka_async_param *param = NULL; int ret = AUTH_ERROR, c; char *p; - int sync_count; + int sync_count, err_count; realm = (_realm?*_realm:str_init("")); @@ -1045,8 +1083,10 @@ static int aka_challenge_async(struct sip_msg *_msg, async_ctx *ctx, count += sync_count; /* try to sort them out synchronously */ if (count == 1) { - if (aka_avs_get_new(user, &algmask, &av, 1) == 1) + if (aka_avs_get_new(user, &algmask, &av, 1, &err_count) == 1) goto synchronous; + if (err_count) + goto error; } /* unfortunately we might need to do it asynchronously */ param = shm_malloc(sizeof *param + realm.len + _challenge_msg->len + @@ -1065,8 +1105,8 @@ static int aka_challenge_async(struct sip_msg *_msg, async_ctx *ctx, avs = param->avs; /* do not prepare the param yet, as we might still have AVs available */ - c = aka_avs_get_new(user, &algmask, avs, count); - if (c == count) + c = aka_avs_get_new(user, &algmask, avs, count, &err_count); + if (c + err_count == count) goto synchronous; LM_DBG("we still need %d out of %d AVs\n", count - c, count); @@ -1217,6 +1257,12 @@ static int script_aka_av_drop_all(struct sip_msg *msg, str *pub_id, str *priv_id return 1; } +static int script_aka_av_fail(struct sip_msg *msg, str *pub_id, str *priv_id, + int *count) +{ + return aka_av_fail(pub_id, priv_id, (count?*count:1)); +} + static mi_response_t *mi_aka_av_add(const mi_params_t *params, struct mi_handler *async_hdl) { @@ -1291,10 +1337,37 @@ static mi_response_t *mi_aka_av_drop_all(const mi_params_t *params, return init_mi_result_number(aka_av_drop_all(&public_identity, &private_identity)); } +static mi_response_t *mi_aka_av_fail(const mi_params_t *params, + struct mi_handler *async_hdl) +{ + int count; + str public_identity, private_identity; + + if (get_mi_string_param(params, "public_identity", + &public_identity.s, &public_identity.len) < 0) + return init_mi_param_error(); + if (get_mi_string_param(params, "private_identity", + &private_identity.s, &private_identity.len) < 0) + return init_mi_param_error(); + switch (try_get_mi_int_param(params, "count", &count)) { + case -2: + return init_mi_param_error(); + case -1: + count = 1; + break; + case 0: + break; + } + if (aka_av_fail(&public_identity, &private_identity, count) < 0) + return init_mi_error(404, MI_SSTR("User not found")); + return init_mi_result_ok(); +} + int load_aka_av_api_bind(aka_av_api *api) { api->add = aka_av_add; api->drop = aka_av_drop; api->drop_all = aka_av_drop_all; + api->fail = aka_av_fail; return 1; } diff --git a/modules/auth_aka/auth_aka.h b/modules/auth_aka/auth_aka.h index 4cb464a87ca..2f60ef6c11b 100644 --- a/modules/auth_aka/auth_aka.h +++ b/modules/auth_aka/auth_aka.h @@ -63,6 +63,7 @@ struct aka_user { enum aka_user_state state; unsigned int ref; str impi; + int error_count; struct aka_user_pub *public; struct list_head avs; struct list_head list; @@ -92,13 +93,15 @@ void aka_user_release(struct aka_user *user); /* gets an AV for a specific user */ void aka_av_set_new(struct aka_user *user, struct aka_av *av); -struct aka_av *aka_av_get_new(struct aka_user *user, int algmask); -struct aka_av *aka_av_get_new_wait(struct aka_user *user, int algmask, long milliseconds); +int aka_av_get_new(struct aka_user *user, int algmask, struct aka_av **av); +int aka_av_get_new_wait(struct aka_user *user, int algmask, + long milliseconds, struct aka_av **av); struct aka_av *aka_av_get_nonce(struct aka_user *user, int algmask, str *nonce); int aka_av_add(str *pub_id, str *priv_id, int algmask, str *authenticate, str *authorize, str *ck, str *ik); int aka_av_drop(str *pub_id, str *priv_id, str *nonce); +int aka_av_fail(str *pub_id, str *priv_id, int no); int aka_av_drop_all(str *pub_id, str *priv_id); int aka_av_drop_all_user(struct aka_user *user); diff --git a/modules/auth_aka/doc/auth_aka_admin.xml b/modules/auth_aka/doc/auth_aka_admin.xml index 3c8be07e8d7..0999ae14537 100644 --- a/modules/auth_aka/doc/auth_aka_admin.xml +++ b/modules/auth_aka/doc/auth_aka_admin.xml @@ -604,6 +604,47 @@ aka_av_drop_all("sip:test@siphub.com", "test@siphub.com", $var(count)); +
+
+ <function moreinfo="none">aka_av_fail(public_identity, private_identity[, count])</function> + + Marks the engine that an authentication vector query for a user has + failed, unlocking the processing of the message. + + + Note: this function is useful when you + know that fetching a new authentication vector is not possible + (due to various reasons) - calling it will resume the message + procesing, using only the available AVs fetched so far. + + Meaning of the parameters is as follows: + + + public_identity (string) - the public identity + (IMPU) of the user to drop authentication vectors for. + + + private_identity (string) - the private identity + (IMPI) of the user to drop authentication vectors for. + + + count (integer, optional) - the number of + authentication vectors that failed. If missing, + 1 is considered. + + + + This function can be used from any route. + + + <function moreinfo="none">aka_av_fail</function> usage + +... +aka_av_fail("sip:test@siphub.com", "test@siphub.com", 3); +... + + +
@@ -724,6 +765,47 @@ $ opensips-cli -x mi aka_av_drop \ $ opensips-cli -x mi aka_av_drop_all \ sip:test@siphub.com test@siphub.com +... + + +
+
+ <function moreinfo="none">aka_av_fail</function> + + Indicates the fact that the fetching of an authentication + vector has failed, unlocking the processing of the message. + + + Note: this function is useful when you + know that fetching a new authentication vector is not possible + (due to various reasons) - calling it will resume the message + procesing, using only the available AVs fetched so far. + + Parameters: + + + public_identity (string) - the public identity + (IMPU) of the user to add authentication vector for. + + + private_identity (string) - the private identity + (IMPI) of the user to add authentication vector for. + + + count (integer, optional) - the number of + authentication vectors failures. + + + + + <function moreinfo="none">aka_av_drop</function> usage + +... +## adds an AKA AV +$ opensips-cli -x mi aka_av_drop \ + sip:test@siphub.com + test@siphub.com + KFQ/MpR3cE3V9PxucEQS5KED8uUNYIAALFyk59sIJI4= ... From ef83f8ce0bd13f11b36dc7ef86cd96df48ce1cdc Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 14 Mar 2024 10:52:23 +0200 Subject: [PATCH 49/79] auth_aka: prevent ref leaking during timeout --- modules/auth_aka/auth_aka.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/modules/auth_aka/auth_aka.c b/modules/auth_aka/auth_aka.c index b4d47f5ef1c..f0902e7393d 100644 --- a/modules/auth_aka/auth_aka.c +++ b/modules/auth_aka/auth_aka.c @@ -988,10 +988,14 @@ struct aka_async_param { char buf[0]; }; -static int aka_async_param_release(struct aka_async_param *param) +static inline void aka_async_param_remove(struct aka_async_param *param) { if (list_is_valid(¶m->list)) aka_pop_async(param->user, ¶m->list); +} + +static int aka_async_param_unref(struct aka_async_param *param) +{ param->ref--; if (param->ref == 0) { /* the last user should also delete */ @@ -1015,9 +1019,9 @@ static int aka_challenge_async_resume_handle(struct sip_msg *msg, void *_param, left = param->avs_count - param->avs_fetched - param->avs_error; /* check to see if we still have AVS to wait for */ if (!timeout && (param->avs_fetched + param->avs_error) != param->avs_count) { - async_status = ASYNC_CONTINUE; - LM_DBG("waiting for more %d AVs to a total of %d\n", left, param->avs_count); - return 1; + LM_DBG("waiting for more %d AVs to a total of %d (%d errors)\n", + left, param->avs_count, param->avs_error); + goto end; } if (timeout && left) LM_ERR("timeout waiting for AVs - got %d out of %d so far (%d error)\n", @@ -1039,7 +1043,10 @@ static int aka_challenge_async_resume_handle(struct sip_msg *msg, void *_param, } param->replied = 1; } - if (!aka_async_param_release(param)) { + aka_async_param_remove(param); + aka_async_param_unref(param); /* finish everything */ +end: + if (!aka_async_param_unref(param)) { /* we still have some possible signals in the queue, so let's wait for * everyone before releasing the context */ async_status = ASYNC_CONTINUE; @@ -1123,6 +1130,7 @@ static int aka_challenge_async(struct sip_msg *_msg, async_ctx *ctx, param->realm.len = realm.len; memcpy(param->challenge_msg.s, _challenge_msg->s, _challenge_msg->len); param->challenge_msg.len = _challenge_msg->len; + param->ref = 1; param->qop = qop; param->algmask = algmask; param->code = _code; @@ -1172,7 +1180,8 @@ static void aka_signal_async_resume(struct aka_async_param *param, ipc_rpc_f *fu param->ref++; if (ipc_send_rpc(param->process_no, func, param) < 0) { LM_ERR("could not resume aka challenge\n"); - aka_async_param_release(param); + aka_async_param_remove(param); + aka_async_param_unref(param); } } From 3b1f6268e7a7409a0aecc29899c04e6d55e18fd1 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 14 Mar 2024 10:57:58 +0200 Subject: [PATCH 50/79] aaa_diameter: add parameter to reply callback --- modules/aaa_diameter/diameter_api.h | 4 ++-- modules/aaa_diameter/diameter_api_impl.h | 4 ++-- modules/aaa_diameter/dm_impl.c | 23 ++++++++++++----------- modules/aaa_diameter/dm_impl.h | 5 ++++- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/modules/aaa_diameter/diameter_api.h b/modules/aaa_diameter/diameter_api.h index 982fcdfbbaf..1071b3d9c26 100644 --- a/modules/aaa_diameter/diameter_api.h +++ b/modules/aaa_diameter/diameter_api.h @@ -63,13 +63,13 @@ typedef int (diameter_send_req_f)(diameter_conn*, int app_id, int code, /* Callback run for an asynchornous command reply */ -typedef int (diameter_reply_cb)(diameter_conn *conn, diameter_reply *reply); +typedef int (diameter_reply_cb)(diameter_conn *conn, diameter_reply *reply, void *param); /* Sends an asynchornous diameter request and calls the callback in the reply */ typedef int (diameter_send_req_async_f)(diameter_conn*, int app_id, int code, - cJSON *req, diameter_reply_cb *reply_cb); + cJSON *req, diameter_reply_cb *reply_cb, void *reply_param); /* Retrieves a JSON from a reply handle diff --git a/modules/aaa_diameter/diameter_api_impl.h b/modules/aaa_diameter/diameter_api_impl.h index a40dc99bc51..f4f756bc5f2 100644 --- a/modules/aaa_diameter/diameter_api_impl.h +++ b/modules/aaa_diameter/diameter_api_impl.h @@ -26,8 +26,8 @@ int dm_api_find_cmd(diameter_conn *conn, int cmd_code); int dm_api_send_req(diameter_conn *conn, int app_id, int cmd_code, cJSON *req, diameter_reply *reply); -int dm_api_send_req_async(diameter_conn *conn, int app_id, int cmd_code, cJSON *req, - diameter_reply_cb *reply); +int dm_api_send_req_async(diameter_conn *conn, int app_id, int cmd_code, + cJSON *req, diameter_reply_cb *reply, void *reply_param); cJSON *dm_api_get_reply(diameter_reply *rpl); int dm_api_get_reply_status(diameter_reply *rpl); void dm_api_free_reply(diameter_reply *rpl); diff --git a/modules/aaa_diameter/dm_impl.c b/modules/aaa_diameter/dm_impl.c index 9f35c825e1e..d42657e96ed 100644 --- a/modules/aaa_diameter/dm_impl.c +++ b/modules/aaa_diameter/dm_impl.c @@ -105,7 +105,7 @@ static int dm_avp_inttype[] = { AAA_TYPE_FLOAT64, }; -static struct dm_cond *dm_get_cond(int type, diameter_reply_cb *cb) +static struct dm_cond *dm_get_cond(int type, diameter_reply_cb *cb, void *param) { struct dm_cond *cond = shm_malloc(sizeof *cond); if (!cond) { @@ -130,7 +130,8 @@ static struct dm_cond *dm_get_cond(int type, diameter_reply_cb *cb) case DM_TYPE_CB: if (!cb) LM_WARN("no callback specified\n"); - cond->sync.cb = cb; + cond->sync.cb.f = cb; + cond->sync.cb.p = param; break; } @@ -139,7 +140,7 @@ static struct dm_cond *dm_get_cond(int type, diameter_reply_cb *cb) int dm_init_reply_cond(int proc_rank) { - my_reply_cond = dm_get_cond(DM_TYPE_COND, NULL); + my_reply_cond = dm_get_cond(DM_TYPE_COND, NULL, NULL); return my_reply_cond?0:-1; } @@ -292,9 +293,8 @@ static void dm_cond_signal(struct dm_cond *cond) pthread_mutex_unlock(&cond->sync.cond.mutex); break; case DM_TYPE_CB: - LM_INFO("callback %p\n", cond->sync.cb); - if (cond->sync.cb) - cond->sync.cb(NULL, &cond->rpl); + if (cond->sync.cb.f) + cond->sync.cb.f(NULL, &cond->rpl, cond->sync.cb.p); shm_free(cond); break; } @@ -1934,7 +1934,7 @@ int _dm_send_message_async(aaa_conn *_, aaa_message *req, int *fd) if (!req) return -1; - cond = dm_get_cond(DM_TYPE_EVENT, NULL); + cond = dm_get_cond(DM_TYPE_EVENT, NULL, NULL); if (!cond) { LM_ERR("out of memory for cond\n"); return -1; @@ -1948,14 +1948,14 @@ int _dm_send_message_async(aaa_conn *_, aaa_message *req, int *fd) return 0; } -int _dm_send_message_callback(aaa_conn *_, aaa_message *req, diameter_reply_cb *cb) +int _dm_send_message_callback(aaa_conn *_, aaa_message *req, diameter_reply_cb *cb, void *param) { struct dm_cond *cond; if (!req) return -1; - cond = dm_get_cond(DM_TYPE_CB, cb); + cond = dm_get_cond(DM_TYPE_CB, cb, param); if (!cond) { LM_ERR("out of memory for cond\n"); return -1; @@ -2091,7 +2091,8 @@ int dm_api_send_req(diameter_conn *conn, int app_id, int cmd_code, cJSON *req, d return rc; } -int dm_api_send_req_async(diameter_conn *conn, int app_id, int cmd_code, cJSON *req, diameter_reply_cb *reply_cb) +int dm_api_send_req_async(diameter_conn *conn, int app_id, int cmd_code, cJSON *req, + diameter_reply_cb *reply_cb, void *reply_param) { aaa_message *dmsg = NULL; @@ -2118,7 +2119,7 @@ int dm_api_send_req_async(diameter_conn *conn, int app_id, int cmd_code, cJSON * return -1; } - if (_dm_send_message_callback(NULL, dmsg, reply_cb) != 0) { + if (_dm_send_message_callback(NULL, dmsg, reply_cb, reply_param) != 0) { LM_ERR("could not send Diameter callback message\n"); return -1; } diff --git a/modules/aaa_diameter/dm_impl.h b/modules/aaa_diameter/dm_impl.h index 0366a96499b..2de25d5fae8 100644 --- a/modules/aaa_diameter/dm_impl.h +++ b/modules/aaa_diameter/dm_impl.h @@ -132,7 +132,10 @@ struct dm_cond { int fd; int pid; } event; - diameter_reply_cb *cb; + struct { + diameter_reply_cb *f; + void *p; + } cb; } sync; diameter_reply rpl; From 306e3bc7a814f12d5a9c1da3f01aea6322efa5e9 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 14 Mar 2024 12:27:36 +0200 Subject: [PATCH 51/79] aka_av_diameter: add new module --- modules/aka_av_diameter/Makefile | 9 + modules/aka_av_diameter/aka_av_diameter.c | 598 ++++++++++++++++++ modules/aka_av_diameter/diameter_mar.h | 52 ++ .../aka_av_diameter/doc/aka_av_diameter.xml | 27 + .../doc/aka_av_diameter_admin.xml | 193 ++++++ .../example/aka_av_diameter.dictionary | 59 ++ 6 files changed, 938 insertions(+) create mode 100644 modules/aka_av_diameter/Makefile create mode 100644 modules/aka_av_diameter/aka_av_diameter.c create mode 100644 modules/aka_av_diameter/diameter_mar.h create mode 100644 modules/aka_av_diameter/doc/aka_av_diameter.xml create mode 100644 modules/aka_av_diameter/doc/aka_av_diameter_admin.xml create mode 100644 modules/aka_av_diameter/example/aka_av_diameter.dictionary diff --git a/modules/aka_av_diameter/Makefile b/modules/aka_av_diameter/Makefile new file mode 100644 index 00000000000..7bc6d07508a --- /dev/null +++ b/modules/aka_av_diameter/Makefile @@ -0,0 +1,9 @@ +# +# AKA Authentication - AKA authentication Diameter support +# +# WARNING: do not run this directly, it should be run by the master Makefile + +include ../../Makefile.defs +auto_gen= +NAME=aka_av_diameter.so +include ../../Makefile.modules diff --git a/modules/aka_av_diameter/aka_av_diameter.c b/modules/aka_av_diameter/aka_av_diameter.c new file mode 100644 index 00000000000..c09a660a68e --- /dev/null +++ b/modules/aka_av_diameter/aka_av_diameter.c @@ -0,0 +1,598 @@ +/* + * AKA Authentication - Diameter support + * + * Copyright (C) 2024 Razvan Crainea + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#include +#include +#include +#include + +#include "../../sr_module.h" +#include "../../pt.h" +#include "../../error.h" +#include "../../config.h" +#include "../../mod_fix.h" +#include "../../map.h" +#include "../../lib/cond.h" +#include "../../str_list.h" +#include "../../parser/digest/digest_parser.h" + +#include "../auth_aka/aka_av_mgm.h" +#include "diameter_mar.h" + +#include "../aaa_diameter/diameter_api.h" + +static diameter_api dm_api; +static aka_av_api aka_api; +static diameter_conn *dm_conn; +static str dm_aaa_url = {NULL, 0}; +static str aka_av_dm_realm = {"diameter.test", 0}; + +static int aka_av_diameter_fetch(str *realm, str *impu, str *impi, + str *resync, int algmask, int no, int async); + +static int mod_init(void); /* Module initialization function */ +static void mod_destroy(void); /* Module destroy function */ + +/* + * Module parameter variables + */ + +/* + * Exported functions + */ + +int load_aka_av_event(struct aka_av_binds *binds) +{ + binds->fetch = aka_av_diameter_fetch; + return 0; +}; + +static const cmd_export_t cmds[] = { + {AKA_AV_MGM_PREFIX"diameter", (cmd_function)load_aka_av_event, { + {0,0,0}}, 0}, + {0,0,{{0,0,0}},0} +}; + +static const dep_export_t deps = { + { /* OpenSIPS module dependencies */ + { MOD_TYPE_DEFAULT, "auth_aka", DEP_ABORT }, + { MOD_TYPE_DEFAULT, "aaa_diameter", DEP_ABORT }, + { MOD_TYPE_NULL, NULL, 0 }, + }, + { /* modparam dependencies */ + { NULL, NULL }, + }, +}; + + +/* + * Exported parameters + */ +static const param_export_t params[] = { + { "aaa_url", STR_PARAM, &dm_aaa_url.s }, + { "realm", STR_PARAM, &aka_av_dm_realm.s }, + {0, 0, 0} +}; + +/* + * Module interface + */ +struct module_exports exports = { + "aka_av_diameter", + MOD_TYPE_DEFAULT,/* class of this module */ + MODULE_VERSION, /* module version */ + DEFAULT_DLFLAGS, /* dlopen flags */ + 0, /* load function */ + &deps, /* OpenSIPS module dependencies */ + cmds, /* Exported functions */ + 0, /* Exported async functions */ + params, /* Exported parameters */ + 0, /* exported statistics */ + 0, /* exported MI functions */ + 0, /* exported pseudo-variables */ + 0, /* exported transformations */ + 0, /* extra processes */ + 0, /* module pre-initialization function */ + mod_init, /* module initialization function */ + 0, /* response function */ + mod_destroy,/* destroy function */ + 0, /* child initialization function */ + 0 /* reload confirm function */ +}; + + +/* + * Module initialization function + */ +static int mod_init(void) +{ + LM_INFO("initializing...\n"); + + /* handling the event */ + if (!dm_aaa_url.s) { + LM_ERR("Diameter URL not provided!\n"); + return -1; + } + + dm_aaa_url.len = strlen(dm_aaa_url.s); + aka_av_dm_realm.len = strlen(aka_av_dm_realm.s); + + if (diameter_bind_api(&dm_api) < 0) + return -1; + + dm_conn = dm_api.init(&dm_aaa_url); + if (!dm_conn) { + LM_ERR("Diameter protocol initialization failure\n"); + return -1; + } + if (!dm_api.find_cmd(dm_conn, AKA_AV_DM_MAR_CODE)) { + LM_ERR("could not find Multimedia-Auth-Request command!\n"); + return -1; + } + + if (aka_av_bind_api(&aka_api) < 0) { + LM_ERR("could not bind AKA AV API\n"); + return -1; + } + + return 0; +} + +static void mod_destroy(void) +{ +} + +static inline char *aka_av_diameter_get_session(void) +{ + int l; + char *sess, *p, *t; + static int seq = 0; + + if (seq == 0) + seq = rand(); + else + seq++; + + sess = pkg_malloc(aka_av_dm_realm.len + 3 * INT2STR_MAX_LEN + 5 /* separators */ + + 1 /* iteration id */ + 1 /* '\0' */); + if (!sess) { + LM_ERR("oom for session\n"); + return NULL; + } + p = sess; + memcpy(p, aka_av_dm_realm.s, aka_av_dm_realm.len); + p += aka_av_dm_realm.len; + *p++ = ';'; + t = int2str(time(NULL), &l); + memcpy(p, t, l); + p += l; + *p++ = ';'; + t = int2str(process_no, &l); + memcpy(p, t, l); + p += l; + *p++ = ';'; + t = int2str(seq, &l); + memcpy(p, t, l); + p += l; + *p++ = ';'; + *p++ = '0'; /* XXX: start from 0 up to 'Z' */ + *p++ = '\0'; + return sess; +} + +static inline void aka_av_diameter_update_session(cJSON *sess, alg_t alg) +{ + /* hack to make the Session id unique */ + int len = strlen(sess->valuestring); + sess->valuestring[len - 1] = '0' + alg; +} + +#define cJSON_SWITCH(_el, _str, _type, _block) \ + do { \ + if (strcmp(_el->string, _str) == 0) { \ + if (_el->type != _type) { \ + LM_ERR("invalid type %d (expected %d) for %s\n", \ + _el->type, _type, _str); \ + goto end; \ + } \ + LM_DBG("found %s JSON node\n", _str); \ + _block; \ + } \ + } while (0) + +struct aka_av_dm { + int index; + alg_t alg; + str authenticate; + str authorize; + str confidentiality; + str integrity; +}; + +static int aka_av_diameter_sort(const void *d1, const void *d2) +{ + const struct aka_av_dm *av1 = d1; + const struct aka_av_dm *av2 = d2; + + if (av1->index == av2->index) + return 0; + return av2->index - av1->index; /* reverse order to push them accordingly */ +} + +static alg_t aka_av_parse_scheme(char *algorithm) +{ + str algs; + algs.s = algorithm; + algs.len = strlen(algorithm); + if (algs.len > 7 && memcmp(algs.s, "Digest-", 7) == 0) { + algs.s += 7; + algs.len -= 7; + } + return parse_digest_algorithm(&algs); +} + + +static int aka_av_diameter_handle_reply(cJSON *rpl, str *impu, str *impi) +{ + cJSON *it, *el, *it2; + struct aka_av_dm *arr = NULL; + int ret = -1; + int nr = 0, nr2 = 0; + str pub_id, priv_id; + + /* we've got the response in rpl - start parsing it */ + if (!rpl) { + LM_ERR("bad diameter reply\n"); + goto end; + } + if (rpl->type != cJSON_Array) { + LM_ERR("bad json type %d\n", rpl->type); + goto end; + } + pub_id.s = priv_id.s = NULL; + + /* first, search for the number of AVs and identities */ + for (it = rpl->child; it; it = it->next) { + if (it->type != cJSON_Object) { + LM_ERR("bad json type %d in array\n", it->type); + continue; + } + el = it->child; + cJSON_SWITCH(el, AKA_AV_DM_AUTH_ITEMS, cJSON_Number, + nr = el->valueint; + ); + cJSON_SWITCH(el, AKA_AV_DM_PUBLIC_ID, cJSON_String, + pub_id.s = el->valuestring; + pub_id.len = strlen(el->valuestring); + ); + cJSON_SWITCH(el, AKA_AV_DM_USER_NAME, cJSON_String, + priv_id.s = el->valuestring; + priv_id.len = strlen(el->valuestring); + ); + cJSON_SWITCH(el, AKA_AV_DM_AUTH_ITEM, cJSON_Array, + if (cJSON_GetArraySize(el) != 0) + nr2++; + ); + } + /* TODO: should we check if these match with the request? */ + if (pub_id.s == NULL) { + LM_ERR(AKA_AV_DM_PUBLIC_ID "not present in response\n"); + goto end; + } + if (priv_id.s == NULL) { + LM_ERR(AKA_AV_DM_USER_NAME "not present in response\n"); + goto end; + } + if (nr == 0 || nr2 == 0) { + LM_DBG("no AVs present in reply\n"); + goto end; + } + if (nr != nr2) { + LM_WARN("invalid response says contains %d AV, but has %d\n", nr, nr2); + nr = nr2; /* we store all that we have */ + } + arr = pkg_malloc(nr * sizeof *arr); + if (!arr) { + LM_ERR("oom for tmp array\n"); + goto end; + } + memset(arr, 0, nr * sizeof *arr); + ret = 0; + for (it = rpl->child; it; it = it->next) { + if (strcmp(it->child->string, AKA_AV_DM_AUTH_ITEM) != 0) + continue; + arr[ret].index = -1; + el = cJSON_GetObjectItem(it, AKA_AV_DM_AUTH_ITEM); + if (!el) { + LM_ERR("invalid data item in JSON\n"); + continue; + } + for (it2 = el->child; it2; it2 = it2->next) { + el = it2->child; + cJSON_SWITCH(el, AKA_AV_DM_AUTH_ITEM_NO, cJSON_Number, + arr[ret].index = el->valueint; + ); + cJSON_SWITCH(el, AKA_AV_DM_AUTH_SCHEME, cJSON_String, + arr[ret].alg = aka_av_parse_scheme(el->valuestring); + if (arr[ret].alg == ALG_OTHER) { + LM_ERR("bad item scheme %s for item %d/%d\n", el->valuestring, + arr[ret].index, ret); + arr[ret].alg = ALG_UNSPEC; + break; + } + ); + cJSON_SWITCH(el, AKA_AV_DM_AUTH_ITEM_AUTHENTICATE, cJSON_String, + arr[ret].authenticate.s = el->valuestring; + arr[ret].authenticate.len = strlen(el->valuestring); + ); + cJSON_SWITCH(el, AKA_AV_DM_AUTH_ITEM_AUTHORIZE, cJSON_String, + arr[ret].authorize.s = el->valuestring; + arr[ret].authorize.len = strlen(el->valuestring); + ); + cJSON_SWITCH(el, AKA_AV_DM_AUTH_ITEM_CK, cJSON_String, + arr[ret].confidentiality.s = el->valuestring; + arr[ret].confidentiality.len = strlen(el->valuestring); + ); + cJSON_SWITCH(el, AKA_AV_DM_AUTH_ITEM_IK, cJSON_String, + arr[ret].integrity.s = el->valuestring; + arr[ret].integrity.len = strlen(el->valuestring); + ); + } + if (arr[ret].index == -1) { + LM_ERR("no item number for entry %d\n", ret); + continue; + } + if (arr[ret].alg == ALG_UNSPEC) { + LM_ERR("no item scheme for item %d/%d\n", arr[ret].index, ret); + continue; + } + if (!arr[ret].authenticate.s) { + LM_ERR("no item authenticate for item %d/%d\n", arr[ret].index, ret); + continue; + } + if (!arr[ret].authorize.s) { + LM_ERR("no item authorize for item %d/%d\n", arr[ret].index, ret); + continue; + } + if (!arr[ret].confidentiality.s) { + LM_ERR("no item confidentiality for item %d/%d\n", arr[ret].index, ret); + continue; + } + if (!arr[ret].integrity.s) { + LM_ERR("no item itegrity for item %d/%d\n", arr[ret].index, ret); + continue; + } + ret++; + } + nr = ret; + qsort(arr, ret, sizeof *arr, aka_av_diameter_sort); + while (nr-- > 0) { + if (aka_api.add(&pub_id, &priv_id, ALG2ALGFLG(arr[nr].alg), + &arr[nr].authenticate, &arr[nr].authorize, + &arr[nr].confidentiality, &arr[nr].integrity) < 0) { + ret--; + } + } + + LM_DBG("found %d AVs in reply\n", ret); +end: + if (arr) + pkg_free(arr); + return ret; +} + +#define cJSON_ADD_OBJ(_a, _n, _b) \ + do { \ + cJSON *_t, *_o; \ + _o = cJSON_CreateObject(); \ + if (_o == NULL) { \ + LM_ERR("oom for object\n"); \ + goto end; \ + } \ + _t = (_b); \ + if (_t == NULL) { \ + cJSON_Delete(_o); \ + LM_ERR("oom for "#_b"\n"); \ + goto end; \ + } \ + cJSON_AddItemToObject(_o, _n, _t); \ + cJSON_AddItemToArray(_a, _o); \ + } while (0); + +static int aka_av_diameter_print_alg(cJSON *alg_arr, alg_t alg) +{ + const str *algs = print_digest_algorithm(alg); + char *p; + int ret; + + if (!algs) + return -1; + /* append Digest at the end */ + p = pkg_malloc(algs->len + 7 /* 'Digest-' */ + 1 /* '\0' */); + if (!p) { + LM_ERR("oom for Digest algorithm\n"); + return -1; + } + memcpy(p, "Digest-", 7); + memcpy(p + 7, algs->s, algs->len); + p[algs->len + 7] = '\0'; + cJSON_DeleteItemFromArray(alg_arr, 0); /* remove whatever was there */ + cJSON_ADD_OBJ(alg_arr, AKA_AV_DM_AUTH_SCHEME, cJSON_CreateString(p)); + ret = 0; +end: + pkg_free(p); + + return ret; +} + + +struct aka_av_param { + str impu; + str impi; + int count; + char _buf[0]; +}; + +static struct aka_av_param *aka_av_param_new(str *impu, str *impi, int count) +{ + struct aka_av_param *param = shm_malloc(sizeof(*param) + impu->len + impi->len); + if (!param) { + LM_ERR("oom for impi/impu\n"); + return NULL; + } + param->impu.s = param->_buf; + memcpy(param->impu.s, impu->s, impu->len); + param->impu.len = impu->len; + param->impi.s = param->impu.s + impu->len; + memcpy(param->impi.s, impi->s, impi->len); + param->impi.len = impi->len; + param->count = count; + return param; +} + +static void aka_av_param_free(struct aka_av_param *param) +{ + shm_free(param); +} + + +static int aka_av_dm_reply(diameter_conn *conn, diameter_reply *reply, void *param) +{ + int ret; + struct aka_av_param *p = (struct aka_av_param *)param; + if (dm_api.get_reply_status(reply)) { + ret = aka_av_diameter_handle_reply(dm_api.get_reply(reply), &p->impu, &p->impi); + if (ret != p->count) + ret = 0; + aka_api.fail(&p->impu, &p->impi, p->count - ret); + } else { + aka_api.fail(&p->impu, &p->impi, p->count); /* mark all as failed */ + } + dm_api.free_reply(reply); + return -1; +} + +static int aka_av_diameter_fetch(str *realm, str *impu, str *impi, + str *resync, int algmask, int count, int async) +{ + diameter_reply reply; + cJSON *req = NULL, *tmp = NULL, *alg_arr, *sess_obj; + int ret = -2; + char *sess = NULL; + alg_t alg; + struct aka_av_param *param = NULL; + + sess = aka_av_diameter_get_session(); + if (!sess) + goto end; + + /* create a session */ + req = cJSON_CreateArray(); + if (!req) { + LM_ERR("oom for array\n"); + goto end; + } + + cJSON_ADD_OBJ(req, AKA_AV_DM_SESSION, cJSON_CreateString(sess)); + sess_obj = cJSON_GetArrayItem(req, 0)->child; /* the session is the first */ + cJSON_ADD_OBJ(req, AKA_AV_DM_ORIGIN_HOST, cJSON_CreateString(aka_av_dm_realm.s)); + cJSON_ADD_OBJ(req, AKA_AV_DM_ORIGIN_REALM, cJSON_CreateStr(realm->s, realm->len)); + cJSON_ADD_OBJ(req, AKA_AV_DM_DST_REALM, cJSON_CreateStr(realm->s, realm->len)); + + tmp = cJSON_CreateArray(); + if (!tmp) { + LM_ERR("oom for vendor id\n"); + goto end; + } + cJSON_ADD_OBJ(tmp, AKA_AV_DM_VENDOR_ID_S, cJSON_CreateNumber(AKA_AV_DM_VENDOR_ID)); + cJSON_ADD_OBJ(tmp, AKA_AV_DM_AUTH_APP_ID, cJSON_CreateNumber(AKA_AV_DM_APP_ID)); + cJSON_ADD_OBJ(req, AKA_AV_DM_VENDOR_APP_ID, tmp); + tmp = NULL; + cJSON_ADD_OBJ(req, AKA_AV_DM_AUTH_SESS, cJSON_CreateNumber(1)); /* NO_STATE_MAINTAINED */ + cJSON_ADD_OBJ(req, AKA_AV_DM_USER_NAME, cJSON_CreateStr(impi->s, impi->len)); + cJSON_ADD_OBJ(req, AKA_AV_DM_PUBLIC_ID, cJSON_CreateStr(impu->s, impu->len)); + cJSON_ADD_OBJ(req, AKA_AV_DM_SERVER_NAME, cJSON_CreateStr(realm->s, realm->len)); /* TODO */ + cJSON_ADD_OBJ(req, AKA_AV_DM_AUTH_ITEMS, cJSON_CreateNumber(count)); + tmp = cJSON_CreateArray(); + if (!tmp) { + LM_ERR("oom for auth data id\n"); + goto end; + } + cJSON_ADD_OBJ(req, AKA_AV_DM_AUTH_ITEM, tmp); + /* we need to store the Scheme separately because it changes between + * different requests */ + alg_arr = tmp; + tmp = NULL; + + if (resync) + cJSON_ADD_OBJ(req, AKA_AV_DM_AUTH_ITEM_AUTHORIZE, cJSON_CreateStr(resync->s, resync->len)); + + ret = -1; + /* we send a diameter request for each algorithm used */ + for (alg = ALG_MD5; alg < ALG_OTHER; alg++) { + if ((algmask & ALG2ALGFLG(alg)) == 0) + continue; + if (aka_av_diameter_print_alg(alg_arr, alg) < 0) + continue; + aka_av_diameter_update_session(sess_obj, alg); + if (!async) { + if (dm_api.send_request(dm_conn, AKA_AV_DM_APP_ID, + AKA_AV_DM_MAR_CODE, req, &reply) < 0) { + LM_ERR("could not send diameter request\n"); + goto end; + } + if (!dm_api.get_reply_status(&reply)) { + dm_api.free_reply(&reply); + continue; + } + if (aka_av_diameter_handle_reply(dm_api.get_reply(&reply), impu, impi) < 0) { + LM_ERR("could not parse json reply\n"); + dm_api.free_reply(&reply); + continue; + } + dm_api.free_reply(&reply); + } else { + param = aka_av_param_new(impu, impi, count); + if (!param) + goto end; + if (dm_api.send_request_async(dm_conn, AKA_AV_DM_APP_ID, + AKA_AV_DM_MAR_CODE, req, aka_av_dm_reply, param) < 0) { + LM_ERR("could not send diameter request\n"); + aka_av_param_free(param); + goto end; + } + } + } + + ret = 0; +end: + if (sess) + pkg_free(sess); + if (req) + cJSON_Delete(req); + if (tmp) + cJSON_Delete(tmp); + return ret; +} +#undef cJSON_CREATE_OBJ diff --git a/modules/aka_av_diameter/diameter_mar.h b/modules/aka_av_diameter/diameter_mar.h new file mode 100644 index 00000000000..8a75f19e92c --- /dev/null +++ b/modules/aka_av_diameter/diameter_mar.h @@ -0,0 +1,52 @@ +/* + * AKA Authentication - Diameter Support + * + * Copyright (C) 2024 Razvan Crainea + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef AKA_AV_DIAMETER_MAR_H +#define AKA_AV_DIAMETER_MAR_H + +#define AKA_AV_DM_APP_ID 16777216 +#define AKA_AV_DM_VENDOR_ID 10415 +#define AKA_AV_DM_MAR_CODE 303 +#define AKA_AV_DM_MAA_CODE 303 + +#define AKA_AV_DM_SESSION "Session-Id" +#define AKA_AV_DM_ORIGIN_HOST "Origin-Host" +#define AKA_AV_DM_ORIGIN_REALM "Origin-Realm" +#define AKA_AV_DM_DST_REALM "Destination-Realm" +#define AKA_AV_DM_VENDOR_ID_S "Vendor-Id" +#define AKA_AV_DM_AUTH_APP_ID "Auth-Application-Id" +#define AKA_AV_DM_VENDOR_APP_ID "Vendor-Specific-Application-Id" +#define AKA_AV_DM_AUTH_SESS "Auth-Session-State" +#define AKA_AV_DM_USER_NAME "User-Name" +#define AKA_AV_DM_PUBLIC_ID "Public-Identity" +#define AKA_AV_DM_SERVER_NAME "Server-Name" +#define AKA_AV_DM_AUTH_ITEMS "3GPP-SIP-Number-Auth-Items" +#define AKA_AV_DM_AUTH_SCHEME "3GPP-SIP-Authentication-Scheme" +#define AKA_AV_DM_AUTH_ITEM "3GPP-SIP-Auth-Data-Item" +#define AKA_AV_DM_AUTH_ITEM_NO "3GPP-SIP-Item-Number" +#define AKA_AV_DM_AUTH_ITEM_AUTHENTICATE "3GPP-SIP-Authenticate" +#define AKA_AV_DM_AUTH_ITEM_AUTHORIZE "3GPP-SIP-Authorization" +#define AKA_AV_DM_AUTH_ITEM_CK "Confidentiality-Key" +#define AKA_AV_DM_AUTH_ITEM_IK "Integrity-Key" + +#endif /* AKA_AV_DIAMETER_MAR_H */ diff --git a/modules/aka_av_diameter/doc/aka_av_diameter.xml b/modules/aka_av_diameter/doc/aka_av_diameter.xml new file mode 100644 index 00000000000..41d4548ac8f --- /dev/null +++ b/modules/aka_av_diameter/doc/aka_av_diameter.xml @@ -0,0 +1,27 @@ + + + + + + + +%docentities; + +]> + + + + AKA Authentication Vector Diameter Module + + + + &admin; + &faq; + &contrib; + + &docCopyrights; + ©right; 2024 OpenSIPS Solutions; + diff --git a/modules/aka_av_diameter/doc/aka_av_diameter_admin.xml b/modules/aka_av_diameter/doc/aka_av_diameter_admin.xml new file mode 100644 index 00000000000..3c190c7d6a9 --- /dev/null +++ b/modules/aka_av_diameter/doc/aka_av_diameter_admin.xml @@ -0,0 +1,193 @@ + + + + + &adminguide; + +
+ Overview + + This module is an extension to the AKA_AUTH module + providing a Diameter AKA AV Manager that implements the Multimedia-Auth-Request + and Multimedia-Auth-Answer Diameter commands defined in the + Cx interface of the ETSI TS 129 229 + specifications in order to fetch a set of authentication vectors and feed + them in the AKA authentication process. + + + When the AKA_AUTH module needs a new authentication + vector to do an aka_challenge(), it may require this + module to fetch a set of authentication vectors for the purpose. The module + packs the query in a MAR (Multimedia-Auth-Request) + command and sends it to an HSS Diameter server. When an + MAA (Multimedia-Auth-Answer) command is received in + response, the corresponding authentication vectors are gathered and fed back + to the AUTH_AKA engine. + + + It uses the AAA_Diameter module to perform the Diameter + requests. It may run in both a synchronous and asynchronous mode, + depending on how the AUTH_AKA module performs the query. + +
+ +
+ Setup + + The module requires an aaa_diameter connection to an + HSS Diameter server that implements the + Cx interfaces and is able to provide authentication vectors + through the Multimedia-Auth-Request and Multimedia-Auth-Answer commands. + + + The format of the command, along with the required fields can be found in the + example/aka_av_diameter.dictionary file located in the + module's source directory, as well as in the + section. + + + Note: the module internals uses the AVPs names + found in the provided dictionary - changing the file may break the behavior + of the module. + +
+ +
+ Dependencies +
+ &osips; Modules + + The module depends on the following modules (in the other words + the listed modules must be loaded before this module): + + + auth_aka -- AKA Authentication + module that triggers the AKA authentication process + + + aaa_diameter -- AAA Diameter + module that implements the Diameter communication to the + HSS Server. + + + +
+
+ External Libraries or Applications + + This module does not depend on any external library. + +
+
+ +
+ Exported Parameters +
+ <varname>aaa_url</varname> (string) + + This is the url representing the connection to the AAA server. + + Note: Currently the module only supports + connections to a Diameter server. The path to the AVPs + configuration file is also required, otherwise the module will + not start, or not work properly. + + + + <varname>aaa_url</varname> parameter usage + +modparam("auth_aaa", "aaa_url", "diameter:freeDiameter.conf;extra-avps-file:/etc/freeDiameter/aka_av_diameter.dictionary") + + +
+
+ <varname>realm</varname> (string) + + The Realm used in the Origin Diameter commands. + + + Default value is diameter.test. + + + <varname>realm</varname> parameter usage + + +modparam("aka_av_diameter", "realm", "scscf.ims.mnc001.mcc001.3gppnetwork.org") + + +
+ +
+ +
+ Diameter Commands File + + File that should be provided to the aaa_diameter connection. + + + Diameter Commands File Example + + +VENDOR 10415 TGPP + +ATTRIBUTE Public-Identity 601 string 10415 +ATTRIBUTE Server-Name 602 string 10415 +ATTRIBUTE 3GPP-SIP-Number-Auth-Items 607 unsigned32 10415 +ATTRIBUTE 3GPP-SIP-Authentication-Scheme 608 utf8string 10415 +ATTRIBUTE 3GPP-SIP-Authenticate 609 hexstring 10415 +ATTRIBUTE 3GPP-SIP-Authorization 610 hexstring 10415 +ATTRIBUTE 3GPP-SIP-Authentication-Context 611 string 10415 +ATTRIBUTE 3GPP-SIP-Item-Number 613 unsigned32 10415 +ATTRIBUTE Confidentiality-Key 625 hexstring 10415 +ATTRIBUTE Integrity-Key 626 hexstring 10415 + + +ATTRIBUTE 3GPP-SIP-Auth-Data-Item 612 grouped 10415 +{ + 3GPP-SIP-Item-Number | OPTIONAL | 1 + 3GPP-SIP-Authentication-Scheme | OPTIONAL | 1 + 3GPP-SIP-Authenticate | OPTIONAL | 1 + 3GPP-SIP-Authorization | OPTIONAL | 1 + 3GPP-SIP-Authentication-Context | OPTIONAL | 1 + Confidentiality-Key | OPTIONAL | 1 + Integrity-Key | OPTIONAL | 1 +} + +APPLICATION-AUTH 16777216/10415 3GPP Cx + +REQUEST 303 Multimedia-Auth Request +{ + Session-Id | REQUIRED | 1 + Origin-Host | REQUIRED | 1 + Origin-Realm | REQUIRED | 1 + Destination-Realm | REQUIRED | 1 + Vendor-Specific-Application-Id | REQUIRED | 1 + Auth-Session-State | REQUIRED | 1 + User-Name | REQUIRED | 1 + Public-Identity | REQUIRED | 1 + 3GPP-SIP-Number-Auth-Items | REQUIRED | 1 + 3GPP-SIP-Auth-Data-Item | REQUIRED | 1 + Server-Name | REQUIRED | 1 +} + +ANSWER 303 Multimedia-Auth Answer +{ + Session-Id | REQUIRED | 1 + Origin-Host | REQUIRED | 1 + Origin-Realm | REQUIRED | 1 + Destination-Host | OPTIONAL | 1 + Destination-Realm | OPTIONAL | 1 + Vendor-Specific-Application-Id | REQUIRED | 1 + Auth-Session-State | REQUIRED | 1 + User-Name | REQUIRED | 1 + Public-Identity | REQUIRED | 1 + 3GPP-SIP-Number-Auth-Items | REQUIRED | 1 + 3GPP-SIP-Auth-Data-Item | REQUIRED | 1 + Result-Code | REQUIRED | 1 +} + + +
+ +
+ diff --git a/modules/aka_av_diameter/example/aka_av_diameter.dictionary b/modules/aka_av_diameter/example/aka_av_diameter.dictionary new file mode 100644 index 00000000000..b5f2e6290a3 --- /dev/null +++ b/modules/aka_av_diameter/example/aka_av_diameter.dictionary @@ -0,0 +1,59 @@ +# Diameter AVPs for AKA AV Diameter command + +VENDOR 10415 TGPP + +ATTRIBUTE Public-Identity 601 string 10415 +ATTRIBUTE Server-Name 602 string 10415 +ATTRIBUTE 3GPP-SIP-Number-Auth-Items 607 unsigned32 10415 +ATTRIBUTE 3GPP-SIP-Authentication-Scheme 608 utf8string 10415 +ATTRIBUTE 3GPP-SIP-Authenticate 609 hexstring 10415 +ATTRIBUTE 3GPP-SIP-Authorization 610 hexstring 10415 +ATTRIBUTE 3GPP-SIP-Authentication-Context 611 string 10415 +ATTRIBUTE 3GPP-SIP-Item-Number 613 unsigned32 10415 +ATTRIBUTE Confidentiality-Key 625 hexstring 10415 +ATTRIBUTE Integrity-Key 626 hexstring 10415 + + +ATTRIBUTE 3GPP-SIP-Auth-Data-Item 612 grouped 10415 +{ + 3GPP-SIP-Item-Number | OPTIONAL | 1 + 3GPP-SIP-Authentication-Scheme | OPTIONAL | 1 + 3GPP-SIP-Authenticate | OPTIONAL | 1 + 3GPP-SIP-Authorization | OPTIONAL | 1 + 3GPP-SIP-Authentication-Context | OPTIONAL | 1 + Confidentiality-Key | OPTIONAL | 1 + Integrity-Key | OPTIONAL | 1 +} + +APPLICATION-AUTH 16777216/10415 3GPP Cx + +REQUEST 303 Multimedia-Auth Request +{ + Session-Id | REQUIRED | 1 + Origin-Host | REQUIRED | 1 + Origin-Realm | REQUIRED | 1 + Destination-Realm | REQUIRED | 1 + Vendor-Specific-Application-Id | REQUIRED | 1 + Auth-Session-State | REQUIRED | 1 + User-Name | REQUIRED | 1 + Public-Identity | REQUIRED | 1 + 3GPP-SIP-Number-Auth-Items | REQUIRED | 1 + 3GPP-SIP-Auth-Data-Item | REQUIRED | 1 + Server-Name | REQUIRED | 1 +} + +ANSWER 303 Multimedia-Auth Answer +{ + Session-Id | REQUIRED | 1 + Origin-Host | REQUIRED | 1 + Origin-Realm | REQUIRED | 1 + Destination-Host | OPTIONAL | 1 + Destination-Realm | OPTIONAL | 1 + Vendor-Specific-Application-Id | REQUIRED | 1 + Auth-Session-State | REQUIRED | 1 + User-Name | REQUIRED | 1 + Public-Identity | REQUIRED | 1 + 3GPP-SIP-Number-Auth-Items | REQUIRED | 1 + 3GPP-SIP-Auth-Data-Item | REQUIRED | 1 + Result-Code | REQUIRED | 1 +} From a3f4b82dd0d321279b257b5e0fc87354f4f557e0 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 14 Mar 2024 14:14:35 +0200 Subject: [PATCH 52/79] auth_aka: add authentication vectors expire --- modules/auth_aka/aka_av_mgm.c | 8 ++-- modules/auth_aka/auth_aka.c | 36 ++++++++++++++++++ modules/auth_aka/auth_aka.h | 3 ++ modules/auth_aka/doc/auth_aka_admin.xml | 49 +++++++++++++++++++++++-- 4 files changed, 90 insertions(+), 6 deletions(-) diff --git a/modules/auth_aka/aka_av_mgm.c b/modules/auth_aka/aka_av_mgm.c index a96c0344cc5..a2c649b77d4 100644 --- a/modules/auth_aka/aka_av_mgm.c +++ b/modules/auth_aka/aka_av_mgm.c @@ -461,12 +461,11 @@ static struct aka_av *aka_av_new(int algmask, str *authenticate, str *authorize, return av; } -#if 0 -static void aka_av_free(struct aka_av *av) +void aka_av_free(struct aka_av *av) { + list_del(&av->list); shm_free(av); } -#endif static void aka_av_insert(struct aka_user *user, struct aka_av *av) { @@ -615,6 +614,9 @@ static int aka_async_hash_iterator(void *param, str key, void *value) list_for_each_safe(it, safe, &user->async) { aka_check_expire_async(ticks, it); } + list_for_each_safe(it, safe, &user->avs) { + aka_check_expire_av(ticks, list_entry(it, struct aka_av, list)); + } cond_unlock(&user->cond); } return 0; diff --git a/modules/auth_aka/auth_aka.c b/modules/auth_aka/auth_aka.c index f0902e7393d..5a7ada69e73 100644 --- a/modules/auth_aka/auth_aka.c +++ b/modules/auth_aka/auth_aka.c @@ -96,6 +96,8 @@ static intptr_t aka_algs_mask = ALGFLG_UNSPEC; static int aka_hash_size = 4096; static int aka_sync_timeout = 100; /* ms */ static int aka_async_timeout = 1000; /* ms */ +static int aka_unused_timeout = 60; /* s */ +static int aka_pending_timeout = 30; /* s */ /* * Exported functions @@ -264,6 +266,14 @@ static int mod_init(void) LM_ERR("invalid async_timeout value %d\n", aka_async_timeout); return -1; } + if (aka_unused_timeout < 0) { + LM_ERR("invalid unused_timeout value %d\n", aka_unused_timeout); + return -1; + } + if (aka_pending_timeout < 0) { + LM_ERR("invalid pending_timeout value %d\n", aka_pending_timeout); + return -1; + } aka_async_timeout /= 1000; /* XXX: add support for milliseconds */ if (aka_init_mgm(aka_hash_size) < 0) { @@ -1201,6 +1211,32 @@ void aka_check_expire_async(unsigned int ticks, struct list_head *subs) aka_signal_async_resume(param, aka_challenge_resume_tout); } +void aka_check_expire_av(unsigned int ticks, struct aka_av *av) +{ + int timeout; + switch (av->state) { + case AKA_AV_NEW: + timeout = aka_unused_timeout; + break; + case AKA_AV_INVALID: /* for invalid, drop it asap */ + timeout = 0; + av->ts = ticks; + break; + case AKA_AV_USING: + case AKA_AV_USED: + timeout = aka_pending_timeout; + break; + default: + return; + } + if (av->ts + timeout > ticks) + return; + LM_DBG("removing av %p in state %d after %ds now %ds\n", + av, av->state, timeout, ticks); + aka_av_free(av); +} + + static int aka_www_challenge(struct sip_msg *msg, struct aka_av_mgm *mgm, str *realm, qop_type_t qop, intptr_t algmask) { diff --git a/modules/auth_aka/auth_aka.h b/modules/auth_aka/auth_aka.h index 2f60ef6c11b..95a1f8d476b 100644 --- a/modules/auth_aka/auth_aka.h +++ b/modules/auth_aka/auth_aka.h @@ -49,6 +49,7 @@ struct aka_av { str ik; alg_t alg; /* algorithm that this AV is being challenged for */ int algmask; /* algorithms this AV is suitable for */ + time_t ts; struct list_head list; char buf[0]; }; @@ -110,6 +111,8 @@ void aka_pop_async(struct aka_user *user, struct list_head *subs); void aka_pop_unsafe_async(struct aka_user *user, struct list_head *subs); void aka_signal_async(struct aka_user *user, struct list_head *subs); void aka_check_expire_async(unsigned int ticks, struct list_head *subs); +void aka_check_expire_av(unsigned int ticks, struct aka_av *av); +void aka_av_free(struct aka_av *av); void aka_async_expire(unsigned int ticks, void* param); diff --git a/modules/auth_aka/doc/auth_aka_admin.xml b/modules/auth_aka/doc/auth_aka_admin.xml index 0999ae14537..d17081c4355 100644 --- a/modules/auth_aka/doc/auth_aka_admin.xml +++ b/modules/auth_aka/doc/auth_aka_admin.xml @@ -179,7 +179,7 @@ modparam("auth_aka", "hash_size", 1024)
- <varname>sync_timeout</varname> (string) + <varname>sync_timeout</varname> (integer) The amount of milliseconds a synchronous call should wait for getting an authentication vector. @@ -200,7 +200,7 @@ modparam("auth_aka", "sync_timeout", 200)
- <varname>async_timeout</varname> (string) + <varname>async_timeout</varname> (integer) The amount of milliseconds an asynchronous call should wait for getting an authentication vector. @@ -219,11 +219,54 @@ modparam("auth_aka", "sync_timeout", 200) <varname>async_timeout</varname> parameter usage - + modparam("auth_aka", "async_timeout", 2000)
+
+ <varname>unused_timeout</varname> (integer) + + The amount of seconds an authentication vector that has + not been used can stay in memory. Once this timeout is + reached, the authentication vector is removed. + + + Must be a positive value, greater than 0. + + + Default value is 60 s. + + + <varname>unused_timeout</varname> parameter usage + + +modparam("auth_aka", "unused_timeout", 120) + + +
+
+ <varname>unused_timeout</varname> (integer) + + The amount of seconds an authentication vector that is being + used in the authentication process shall stay in memory. + Once this timeout is reached, the authentication vector is + removed, and the authentication using it will fail. + + + Must be a positive value, greater than 0. + + + Default value is 30 s. + + + <varname>pending_timeout</varname> parameter usage + + +modparam("auth_aka", "pending_timeout", 10) + + +
From 04c685e87f988428a75106670319c2931f4bd99d Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 14 Mar 2024 16:23:34 +0200 Subject: [PATCH 53/79] auth_aka: release identity when there are no more AVs --- modules/auth_aka/aka_av_mgm.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/auth_aka/aka_av_mgm.c b/modules/auth_aka/aka_av_mgm.c index a2c649b77d4..40afd8086e4 100644 --- a/modules/auth_aka/aka_av_mgm.c +++ b/modules/auth_aka/aka_av_mgm.c @@ -215,7 +215,7 @@ static void aka_user_try_free(struct aka_user *user) { struct aka_user_pub *pub = user->public; cond_lock(&user->cond); - if (!list_empty(&user->avs) || !list_empty(&user->async)) { + if (user->ref != 0 || !list_empty(&user->avs) || !list_empty(&user->async)) { cond_unlock(&user->cond); return; } @@ -233,8 +233,7 @@ void aka_user_release(struct aka_user *user) hentry = hash_entry(aka_users, user->public->impu); hash_lock(aka_users, hentry); user->ref--; - if (user->ref == 0) - aka_user_try_free(user); + aka_user_try_free(user); hash_unlock(aka_users, hentry); } @@ -603,12 +602,12 @@ void aka_pop_async(struct aka_user *user, struct list_head *subs) static int aka_async_hash_iterator(void *param, str key, void *value) { - struct list_head *it, *safe, *uit; + struct list_head *it, *safe, *uit, *usafe; unsigned int ticks = *(unsigned int*)param; struct aka_user *user; struct aka_user_pub *pub = (struct aka_user_pub *)value; - list_for_each(uit, &pub->privates) { + list_for_each_safe(uit, usafe, &pub->privates) { user = list_entry(uit, struct aka_user, list); cond_lock(&user->cond); list_for_each_safe(it, safe, &user->async) { @@ -618,6 +617,7 @@ static int aka_async_hash_iterator(void *param, str key, void *value) aka_check_expire_av(ticks, list_entry(it, struct aka_av, list)); } cond_unlock(&user->cond); + aka_user_try_free(user); } return 0; } From 8977732d5ca68f62010fb7430336ad3272ece0e0 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 19 Mar 2024 10:09:51 +0200 Subject: [PATCH 54/79] aka_av_diameter: fix AAA dependency to aaa_diameter --- modules/aka_av_diameter/aka_av_diameter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aka_av_diameter/aka_av_diameter.c b/modules/aka_av_diameter/aka_av_diameter.c index c09a660a68e..041cdc53369 100644 --- a/modules/aka_av_diameter/aka_av_diameter.c +++ b/modules/aka_av_diameter/aka_av_diameter.c @@ -77,7 +77,7 @@ static const cmd_export_t cmds[] = { static const dep_export_t deps = { { /* OpenSIPS module dependencies */ { MOD_TYPE_DEFAULT, "auth_aka", DEP_ABORT }, - { MOD_TYPE_DEFAULT, "aaa_diameter", DEP_ABORT }, + { MOD_TYPE_AAA, "aaa_diameter", DEP_ABORT }, { MOD_TYPE_NULL, NULL, 0 }, }, { /* modparam dependencies */ From 794f3c0425587f448dc6fc95ad9229e23babb835 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 19 Mar 2024 10:20:32 +0200 Subject: [PATCH 55/79] auth_aka: properly manage AV timestamps Properly store the timestamps when the AV is created, and also store the value in case the AV is not used and it needs to be reverted. --- modules/auth_aka/aka_av_mgm.c | 4 ++++ modules/auth_aka/auth_aka.h | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/auth_aka/aka_av_mgm.c b/modules/auth_aka/aka_av_mgm.c index 40afd8086e4..8e5a80fb778 100644 --- a/modules/auth_aka/aka_av_mgm.c +++ b/modules/auth_aka/aka_av_mgm.c @@ -308,6 +308,7 @@ static void aka_av_mark_using(struct aka_av *av, int algmask) * it as being used only for the first algorithm in the mask */ av->alg = aka_av_first_bit_mask(algmask); + av->ts = get_ticks(); } int aka_av_get_new_wait(struct aka_user *user, int algmask, @@ -495,7 +496,9 @@ int aka_av_add(str *pub_id, str *priv_id, int algmask, aka_signal_async(user, user->async.next); cond_signal(&user->cond); cond_unlock(&user->cond); + av->ts = av->new_ts = get_ticks(); ret = 1; + LM_DBG("adding av %p\n", av); end: aka_user_release(user); return ret; @@ -578,6 +581,7 @@ void aka_av_set_new(struct aka_user *user, struct aka_av *av) { cond_lock(&user->cond); av->state = AKA_AV_NEW; + av->ts = av->new_ts; /* restore the new timestamp */ cond_unlock(&user->cond); } diff --git a/modules/auth_aka/auth_aka.h b/modules/auth_aka/auth_aka.h index 95a1f8d476b..fce35e017a1 100644 --- a/modules/auth_aka/auth_aka.h +++ b/modules/auth_aka/auth_aka.h @@ -49,7 +49,7 @@ struct aka_av { str ik; alg_t alg; /* algorithm that this AV is being challenged for */ int algmask; /* algorithms this AV is suitable for */ - time_t ts; + time_t ts, new_ts; struct list_head list; char buf[0]; }; From 72a1b253642d47676106258d98aee7c52ea4f1b8 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 19 Mar 2024 12:53:39 +0200 Subject: [PATCH 56/79] auth_aka: fix uninitilized variable --- modules/auth_aka/auth_aka.c | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/auth_aka/auth_aka.c b/modules/auth_aka/auth_aka.c index 5a7ada69e73..6a7cdedeef4 100644 --- a/modules/auth_aka/auth_aka.c +++ b/modules/auth_aka/auth_aka.c @@ -1100,6 +1100,7 @@ static int aka_challenge_async(struct sip_msg *_msg, async_ctx *ctx, count += sync_count; /* try to sort them out synchronously */ if (count == 1) { + avs = &av; if (aka_avs_get_new(user, &algmask, &av, 1, &err_count) == 1) goto synchronous; if (err_count) From 9a06143c8abdb114c0bf6261eff847fa7335444d Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 19 Mar 2024 12:57:38 +0200 Subject: [PATCH 57/79] auth_aka: drop unused function --- modules/auth_aka/aka_av_mgm.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/modules/auth_aka/aka_av_mgm.c b/modules/auth_aka/aka_av_mgm.c index 8e5a80fb778..22945f3adab 100644 --- a/modules/auth_aka/aka_av_mgm.c +++ b/modules/auth_aka/aka_av_mgm.c @@ -392,19 +392,6 @@ int aka_av_get_new(struct aka_user *user, int algmask, struct aka_av **av) return ret; } -static inline int aka_check_algmask(int algmask, int flags, - int len, int check_len, const char *debug) -{ - if (algmask & flags) { - if (len != check_len) { - LM_WARN("invalid authorize length %d, expected %d for MD5 hashing\n", - len, check_len); - algmask &= ~(flags); - } - } - return algmask; -} - static struct aka_av *aka_av_new(int algmask, str *authenticate, str *authorize, str *ck, str *ik) { char *p; From 00c4cbeaf88004fd3fec663bf8f0ea47bcce16d7 Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Tue, 19 Mar 2024 14:13:08 +0200 Subject: [PATCH 58/79] aaa_diameter: Fix locking issues when sending requests - avoid READ ops on the @msg pointer, after it's queued for sending (subject to race condition with the Diameter Peer process, which can free the memory before we read it) - lock the "reply_cond" variable *before* queueing the msg for sending (avoids race condition where the reply signal arrives *before* we even call pthread_cond_timedwait()) - rename "req" to "msg", as _dm_send_message() also originates Answers - normalize return code 1 (req sent, ignoring reply) to 0 (success) --- modules/aaa_diameter/dm_impl.c | 67 +++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/modules/aaa_diameter/dm_impl.c b/modules/aaa_diameter/dm_impl.c index d42657e96ed..56319ab1d1a 100644 --- a/modules/aaa_diameter/dm_impl.c +++ b/modules/aaa_diameter/dm_impl.c @@ -1913,11 +1913,11 @@ int dm_build_avps(struct list_head *out_avps, cJSON *array) return -1; } -static void dm_push_queue(aaa_message *req, struct dm_cond *cond) +static void dm_push_queue(aaa_message *msg, struct dm_cond *cond) { - struct dm_message *dm = (struct dm_message *)(req->avpair); + struct dm_message *dm = (struct dm_message *)(msg->avpair); dm->reply_cond = cond; - req->last_found = DM_MSG_SENT; + msg->last_found = DM_MSG_SENT; pthread_mutex_lock(msg_send_lk); @@ -2003,45 +2003,52 @@ int _dm_get_message_response(struct dm_cond *cond, char **rpl_avps) return rc; } -int _dm_send_message(aaa_conn *_, aaa_message *req, struct dm_cond **reply_cond) +int _dm_send_message(aaa_conn *_, aaa_message *msg, struct dm_cond **reply_cond) { - if (!req || !my_reply_cond) + struct timespec wait_until; + struct timeval now, wait_time, res; + int rc, await_reply = 0; + + if (!msg || !my_reply_cond) return -1; - dm_push_queue(req, my_reply_cond); + if (msg->type == AAA_AUTH || msg->type == AAA_CUSTOM_REQ) + await_reply = 1; - LM_DBG("message queued for sending\n"); + LM_DBG("queue message for sending, type %d\n", msg->type); - if (req->type == AAA_AUTH || req->type == AAA_CUSTOM_REQ) { - struct timespec wait_until; - struct timeval now, wait_time, res; - int rc; + pthread_mutex_lock(&my_reply_cond->sync.cond.mutex); + dm_push_queue(msg, my_reply_cond); + /* WARNING: @msg *cannot* be read anymore here! (dangling pointer) */ - gettimeofday(&now, NULL); - wait_time.tv_sec = dm_answer_timeout / 1000; - wait_time.tv_usec = dm_answer_timeout % 1000 * 1000UL; - LM_DBG("awaiting auth reply (%ld s, %ld us)...\n", wait_time.tv_sec, wait_time.tv_usec); + if (!await_reply) { + pthread_mutex_unlock(&my_reply_cond->sync.cond.mutex); + return 0; + } - timeradd(&now, &wait_time, &res); + gettimeofday(&now, NULL); + wait_time.tv_sec = dm_answer_timeout / 1000; + wait_time.tv_usec = dm_answer_timeout % 1000 * 1000UL; + LM_DBG("awaiting auth reply (%ld s, %ld us)...\n", wait_time.tv_sec, wait_time.tv_usec); - wait_until.tv_sec = res.tv_sec; - wait_until.tv_nsec = res.tv_usec * 1000UL; + timeradd(&now, &wait_time, &res); - pthread_mutex_lock(&my_reply_cond->sync.cond.mutex); - rc = pthread_cond_timedwait(&my_reply_cond->sync.cond.cond, - &my_reply_cond->sync.cond.mutex, &wait_until); - if (rc != 0) { - LM_ERR("timeout (errno: %d '%s') while awaiting Diameter " - "reply\n", rc, strerror(rc)); - pthread_mutex_unlock(&my_reply_cond->sync.cond.mutex); + wait_until.tv_sec = res.tv_sec; + wait_until.tv_nsec = res.tv_usec * 1000UL; - return -2; - } - if (reply_cond) - *reply_cond = my_reply_cond; + rc = pthread_cond_timedwait(&my_reply_cond->sync.cond.cond, + &my_reply_cond->sync.cond.mutex, &wait_until); + if (rc != 0) { + LM_ERR("timeout (errno: %d '%s') while awaiting Diameter " + "reply\n", rc, strerror(rc)); + pthread_mutex_unlock(&my_reply_cond->sync.cond.mutex); - return 1; + return -2; } + + if (reply_cond) + *reply_cond = my_reply_cond; + return 0; } From 9b87a8687ea424b7af7176f390cd78cab963705b Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 19 Mar 2024 17:27:57 +0200 Subject: [PATCH 59/79] auth_aka: do not wait for more AVs than needed --- modules/auth_aka/auth_aka.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/auth_aka/auth_aka.c b/modules/auth_aka/auth_aka.c index 6a7cdedeef4..2c001645074 100644 --- a/modules/auth_aka/auth_aka.c +++ b/modules/auth_aka/auth_aka.c @@ -1022,6 +1022,12 @@ static int aka_challenge_async_resume_handle(struct sip_msg *msg, void *_param, int left, err_count; struct aka_async_param *param = _param; + /* if this handling has already been completed, drop it */ + if (param->avs_fetched + param->avs_error >= param->avs_count) { + left = 0; + goto end; + } + /* check to see how many events we got */ param->avs_fetched += aka_avs_get_new(param->user, ¶m->algmask, param->avs + param->avs_fetched, param->avs_count - param->avs_fetched, &err_count); From f494aac3ee2b7c948bc825d0d582ae418a1d6c14 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 19 Mar 2024 17:28:23 +0200 Subject: [PATCH 60/79] aka_av_diameter: do not fail for more AVs --- modules/aka_av_diameter/aka_av_diameter.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/aka_av_diameter/aka_av_diameter.c b/modules/aka_av_diameter/aka_av_diameter.c index 041cdc53369..99da96d31fd 100644 --- a/modules/aka_av_diameter/aka_av_diameter.c +++ b/modules/aka_av_diameter/aka_av_diameter.c @@ -483,9 +483,8 @@ static int aka_av_dm_reply(diameter_conn *conn, diameter_reply *reply, void *par struct aka_av_param *p = (struct aka_av_param *)param; if (dm_api.get_reply_status(reply)) { ret = aka_av_diameter_handle_reply(dm_api.get_reply(reply), &p->impu, &p->impi); - if (ret != p->count) - ret = 0; - aka_api.fail(&p->impu, &p->impi, p->count - ret); + if (ret < p->count) + aka_api.fail(&p->impu, &p->impi, p->count - ret); } else { aka_api.fail(&p->impu, &p->impi, p->count); /* mark all as failed */ } From ae77d52743eabdd72e718b318ad9d6896482c3a3 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 19 Mar 2024 17:30:43 +0200 Subject: [PATCH 61/79] aka_av_diameter: fix uninitilized variable --- modules/aka_av_diameter/aka_av_diameter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aka_av_diameter/aka_av_diameter.c b/modules/aka_av_diameter/aka_av_diameter.c index 99da96d31fd..391c175bfe8 100644 --- a/modules/aka_av_diameter/aka_av_diameter.c +++ b/modules/aka_av_diameter/aka_av_diameter.c @@ -424,7 +424,7 @@ static int aka_av_diameter_print_alg(cJSON *alg_arr, alg_t alg) { const str *algs = print_digest_algorithm(alg); char *p; - int ret; + int ret = -1; if (!algs) return -1; From 7b24df60bfdd9ec519fc34c7a9cba1dbedd38ce5 Mon Sep 17 00:00:00 2001 From: Bogdan-Andrei Iancu Date: Wed, 20 Mar 2024 09:35:50 +0200 Subject: [PATCH 62/79] [stir_shaken] fix using the right Identity hdr... Iterate all present Identity hdrs and pick the one with passport type "shaken". This allows the usage of Identity hdrs for both Stir-Shaken and Rich Call Data --- modules/stir_shaken/stir_shaken.c | 115 ++++++++++++++---------------- parser/msg_parser.h | 17 +++++ 2 files changed, 70 insertions(+), 62 deletions(-) diff --git a/modules/stir_shaken/stir_shaken.c b/modules/stir_shaken/stir_shaken.c index c8c2f4eca36..5a9df2d00ac 100644 --- a/modules/stir_shaken/stir_shaken.c +++ b/modules/stir_shaken/stir_shaken.c @@ -1842,17 +1842,34 @@ static int verify_signature(X509 *cert, return rc; } -static int get_parsed_identity(struct hdr_field *identity_hdr, +static int get_parsed_identity(struct sip_msg *msg, struct parsed_identity **parsed) { - int rc = 0; + struct hdr_field *identity_hdr; + int rc; *parsed = parsed_ctx_get(); - if (*parsed == NULL) { - if (!current_processing_ctx) { - LM_ERR("no processing ctx found!\n"); - return -1; - } + if (*parsed) + /* we are lucky, the parsing was already done */ + return 0; + + if (!current_processing_ctx) { + LM_ERR("no processing ctx found!\n"); + return -1; + } + + /* look for the Identity hdr */ + if (parse_headers(msg, HDR_EOH_F, 0) < 0) { + LM_ERR("Failed to parse headers\n"); + return -1; + } + + if (!(identity_hdr = get_header_by_static_name(msg, "Identity"))) { + LM_INFO("No Identity header found\n"); + return -1; + } + + do { *parsed = pkg_malloc(sizeof **parsed); if (*parsed == NULL) { @@ -1862,13 +1879,34 @@ static int get_parsed_identity(struct hdr_field *identity_hdr, memset(*parsed, 0, sizeof **parsed); rc = parse_identity_hf(&identity_hdr->body, *parsed); - if (rc == 0) - parsed_ctx_set(*parsed); - else + if (rc < 0) { pkg_free(*parsed); - } + *parsed = NULL; + } else { + /* check the pss type to be "shaken" */ + if (str_strcmp(&(*parsed)->ppt_hdr_param, const_str(PPORT_HDR_PPT_VAL))) { + LM_INFO("Unsupported 'ppt' extension\n"); + parsed_ctx_free(*parsed); + *parsed = NULL; + rc = -4; // invalid format + } + } - return rc; + if (*parsed==NULL) { + /* let's check other Identity hdr, if present */ + identity_hdr = get_next_header_by_static_name ( identity_hdr, + "Identity"); + if (identity_hdr==NULL) { + LM_INFO("No valid Identity header found\n"); + return rc; + } + } + + }while(*parsed==NULL); + + parsed_ctx_set(*parsed); + + return 0; } static int set_err_resp_vars(struct sip_msg *msg, pv_spec_t *err_code_var, @@ -1903,7 +1941,6 @@ static int w_stir_verify(struct sip_msg *msg, str *cert_buf, pv_spec_t *err_code_var, pv_spec_t *err_reason_var, str *orig_tn_p, str *dest_tn_p) { - struct hdr_field *identity_hdr; str orig_tn, dest_tn, pport_orig_tn, pport_dest_tn; time_t now, date_ts, iat_ts; struct hdr_field *date_hf = NULL; @@ -1913,19 +1950,6 @@ static int w_stir_verify(struct sip_msg *msg, str *cert_buf, int rc, err_code, orig_log_lev = L_ERR, dest_log_lev = L_ERR; char *err_reason; - /* looking for 'Identity' and 'Date' */ - if (parse_headers(msg, HDR_EOH_F, 0) < 0) { - LM_ERR("Failed to parse headers\n"); - SET_VERIFY_ERR_VARS(IERROR_CODE, IERROR_REASON); - return -1; - } - - if (!(identity_hdr = get_header_by_static_name(msg, "Identity"))) { - LM_NOTICE("No Identity header found\n"); - SET_VERIFY_ERR_VARS(USE_IDENTITY_CODE, USE_IDENTITY_REASON); - return -2; - } - if (!orig_tn_p) { err_code = BADREQ_CODE; err_reason = BADREQ_ORIG_REASON; @@ -1979,7 +2003,7 @@ static int w_stir_verify(struct sip_msg *msg, str *cert_buf, return -3; } - if ((rc = get_parsed_identity(identity_hdr, &parsed)) < 0) { + if ((rc = get_parsed_identity( msg, &parsed)) < 0) { if (rc == -1) { LM_ERR("Failed to parse identity header\n"); SET_VERIFY_ERR_VARS(IERROR_CODE, IERROR_REASON); @@ -1991,13 +2015,6 @@ static int w_stir_verify(struct sip_msg *msg, str *cert_buf, return rc; } - if (str_strcmp(&parsed->ppt_hdr_param, const_str(PPORT_HDR_PPT_VAL))) { - LM_NOTICE("Unsupported 'ppt' extension: %.*s\n", - parsed->ppt_hdr_param.len, parsed->ppt_hdr_param.s); - SET_VERIFY_ERR_VARS(INVALID_IDENTITY_CODE, INVALID_IDENTITY_REASON); - rc = -5; - goto error; - } if (parsed->alg_hdr_param.s && str_strcmp(&parsed->alg_hdr_param, const_str(PPORT_HDR_ALG_VAL))) { LM_NOTICE("Unsupported 'alg': %.*s\n", @@ -2149,21 +2166,10 @@ static int w_stir_verify(struct sip_msg *msg, str *cert_buf, static int w_stir_check(struct sip_msg *msg) { - struct hdr_field *identity_hdr; struct parsed_identity *parsed; int rc; - if (parse_headers(msg, HDR_EOH_F, 0) < 0) { - LM_ERR("Failed to parse headers\n"); - return -1; - } - - if (!(identity_hdr = get_header_by_static_name(msg, "Identity"))) { - LM_INFO("No Identity header found\n"); - return -2; - } - - if ((rc = get_parsed_identity(identity_hdr, &parsed)) < 0) { + if ((rc = get_parsed_identity( msg, &parsed)) < 0) { if (rc == -1) { LM_ERR("Failed to parse identity header\n"); return -1; @@ -2173,10 +2179,6 @@ static int w_stir_check(struct sip_msg *msg) } } - if (str_strcmp(&parsed->ppt_hdr_param, const_str(PPORT_HDR_PPT_VAL))) { - LM_INFO("Unsupported 'ppt' extension\n"); - return -4; - } if (parsed->alg_hdr_param.s && str_strcmp(&parsed->alg_hdr_param, const_str(PPORT_HDR_ALG_VAL))) { LM_INFO("Unsupported 'alg'\n"); @@ -2251,21 +2253,10 @@ int pv_parse_identity_name(pv_spec_p sp, const str *in) int pv_get_identity(struct sip_msg *msg, pv_param_t *param, pv_value_t *res) { - struct hdr_field *identity_hdr; struct parsed_identity *parsed; int rc; - if (parse_headers(msg, HDR_EOH_F, 0) < 0) { - LM_ERR("Failed to parse headers\n"); - return pv_get_null(msg, param, res); - } - - if (!(identity_hdr = get_header_by_static_name(msg, "Identity"))) { - LM_INFO("No Identity header found\n"); - return pv_get_null(msg, param, res); - } - - if ((rc = get_parsed_identity(identity_hdr, &parsed)) < 0) { + if ((rc = get_parsed_identity( msg, &parsed)) < 0) { if (rc == -1) LM_ERR("Failed to parse identity header\n"); else diff --git a/parser/msg_parser.h b/parser/msg_parser.h index a1508a89b23..cb9bba97228 100644 --- a/parser/msg_parser.h +++ b/parser/msg_parser.h @@ -498,6 +498,23 @@ inline static struct hdr_field *get_header_by_name( struct sip_msg *msg, } +#define get_next_header_by_static_name(_hdr, _name) \ + get_next_header_by_name(_hdr, _name, sizeof(_name)-1) +inline static struct hdr_field *get_next_header_by_name( + struct hdr_field *first, char *s, unsigned int len) +{ + struct hdr_field *hdr; + + for( hdr=first->next ; hdr ; hdr=hdr->next ) { + if(len==hdr->name.len && strncasecmp(hdr->name.s,s,len)==0) + return hdr; + } + return NULL; +} + + + + /* * Make a private copy of the string and assign it to new_uri (new RURI) */ From ad418ff746b3c6a4ddea06cd349581ece9b8ceb6 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Wed, 20 Mar 2024 16:40:31 +0200 Subject: [PATCH 63/79] aka: add contributors file --- modules/aka_av_diameter/doc/contributors.xml | 79 ++++++++++++++++++++ modules/auth_aka/doc/contributors.xml | 79 ++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 modules/aka_av_diameter/doc/contributors.xml create mode 100644 modules/auth_aka/doc/contributors.xml diff --git a/modules/aka_av_diameter/doc/contributors.xml b/modules/aka_av_diameter/doc/contributors.xml new file mode 100644 index 00000000000..93ff9b91074 --- /dev/null +++ b/modules/aka_av_diameter/doc/contributors.xml @@ -0,0 +1,79 @@ + + + &contributors; + +
+ By Commit Statistics + + Top contributors by DevScore<superscript>(1)</superscript>, authored commits<superscript>(2)</superscript> and lines added/removed<superscript>(3)</superscript> + + + + + Name + DevScore + Commits + Lines ++ + Lines -- + + + + + 1. + Razvan Crainea (@razvancrainea) + 13 + 4 + 941 + 4 + + + +
+ + + (1) DevScore = author_commits + author_lines_added / (project_lines_added / project_commits) + author_lines_deleted / (project_lines_deleted / project_commits) + + + (2) including any documentation-related commits, excluding merge commits. Regarding imported patches/code, we do our best to count the work on behalf of the proper owner, as per the "fix_authors" and "mod_renames" arrays in opensips/doc/build-contrib.sh. If you identify any patches/commits which do not get properly attributed to you, please submit a pull request which extends "fix_authors" and/or "mod_renames". + + + (3) ignoring whitespace edits, renamed files and auto-generated files + +
+ +
+ By Commit Activity + + Most recently active contributors<superscript>(1)</superscript> to this module + + + + + Name + Commit Activity + + + + + 1. + Razvan Crainea (@razvancrainea) + Mar 2024 - Mar 2024 + + + +
+ + + (1) including any documentation-related commits, excluding merge commits + +
+ +
+ + Documentation +
+ Contributors + Last edited by: Razvan Crainea (@razvancrainea). +
+ +
diff --git a/modules/auth_aka/doc/contributors.xml b/modules/auth_aka/doc/contributors.xml new file mode 100644 index 00000000000..81277aa2580 --- /dev/null +++ b/modules/auth_aka/doc/contributors.xml @@ -0,0 +1,79 @@ + + + &contributors; + +
+ By Commit Statistics + + Top contributors by DevScore<superscript>(1)</superscript>, authored commits<superscript>(2)</superscript> and lines added/removed<superscript>(3)</superscript> + + + + + Name + DevScore + Commits + Lines ++ + Lines -- + + + + + 1. + Razvan Crainea (@razvancrainea) + 45 + 14 + 3319 + 157 + + + +
+ + + (1) DevScore = author_commits + author_lines_added / (project_lines_added / project_commits) + author_lines_deleted / (project_lines_deleted / project_commits) + + + (2) including any documentation-related commits, excluding merge commits. Regarding imported patches/code, we do our best to count the work on behalf of the proper owner, as per the "fix_authors" and "mod_renames" arrays in opensips/doc/build-contrib.sh. If you identify any patches/commits which do not get properly attributed to you, please submit a pull request which extends "fix_authors" and/or "mod_renames". + + + (3) ignoring whitespace edits, renamed files and auto-generated files + +
+ +
+ By Commit Activity + + Most recently active contributors<superscript>(1)</superscript> to this module + + + + + Name + Commit Activity + + + + + 1. + Razvan Crainea (@razvancrainea) + Feb 2024 - Mar 2024 + + + +
+ + + (1) including any documentation-related commits, excluding merge commits + +
+ +
+ + Documentation +
+ Contributors + Last edited by: Razvan Crainea (@razvancrainea). +
+ +
From 760932ecdef5776f090617a6f50893372d8671bf Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Fri, 22 Mar 2024 10:22:56 +0200 Subject: [PATCH 64/79] aaa_diameter: remove log Thank you Liviu Chircu for spotting it :) --- modules/aaa_diameter/dm_impl.c | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/aaa_diameter/dm_impl.c b/modules/aaa_diameter/dm_impl.c index 56319ab1d1a..6387c58d775 100644 --- a/modules/aaa_diameter/dm_impl.c +++ b/modules/aaa_diameter/dm_impl.c @@ -344,7 +344,6 @@ static int dm_auth_reply(struct msg **_msg, struct avp * avp, struct session * s rpl_cond->rpl.rc = rc; hash_remove_key(pending_replies, callid); - LM_INFO("XXX: removing %.*s\n", callid.len, callid.s); hash_unlock(pending_replies, hentry); FD_CHECK(fd_msg_search_avp(msg, dm_dict.Error_Message, &a)); From 7f23d8d9887d28dfc64faa8db5ee65025e51dbb3 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Fri, 22 Mar 2024 17:41:01 +0200 Subject: [PATCH 65/79] stir_shaken: return -2 if ppt Identity header is not found --- modules/stir_shaken/stir_shaken.c | 45 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/modules/stir_shaken/stir_shaken.c b/modules/stir_shaken/stir_shaken.c index 5a9df2d00ac..a5fdb00befe 100644 --- a/modules/stir_shaken/stir_shaken.c +++ b/modules/stir_shaken/stir_shaken.c @@ -1866,9 +1866,11 @@ static int get_parsed_identity(struct sip_msg *msg, if (!(identity_hdr = get_header_by_static_name(msg, "Identity"))) { LM_INFO("No Identity header found\n"); - return -1; + return -2; } + rc = -2; + do { *parsed = pkg_malloc(sizeof **parsed); @@ -1879,34 +1881,29 @@ static int get_parsed_identity(struct sip_msg *msg, memset(*parsed, 0, sizeof **parsed); rc = parse_identity_hf(&identity_hdr->body, *parsed); - if (rc < 0) { - pkg_free(*parsed); - *parsed = NULL; - } else { - /* check the pss type to be "shaken" */ - if (str_strcmp(&(*parsed)->ppt_hdr_param, const_str(PPORT_HDR_PPT_VAL))) { - LM_INFO("Unsupported 'ppt' extension\n"); - parsed_ctx_free(*parsed); - *parsed = NULL; - rc = -4; // invalid format - } + if (rc >= 0) { + if (str_strcmp(&(*parsed)->ppt_hdr_param, const_str(PPORT_HDR_PPT_VAL)) == 0) + break; + LM_INFO("Unsupported '%.*s' extension\n", + (*parsed)->ppt_hdr_param.len, (*parsed)->ppt_hdr_param.s); + rc = -2; /* consider we did not find a proper Identity header */ } - - if (*parsed==NULL) { - /* let's check other Identity hdr, if present */ - identity_hdr = get_next_header_by_static_name ( identity_hdr, - "Identity"); - if (identity_hdr==NULL) { - LM_INFO("No valid Identity header found\n"); - return rc; - } + pkg_free(*parsed); + *parsed = NULL; + /* let's check other Identity hdr, if present */ + identity_hdr = get_next_header_by_static_name ( identity_hdr, + "Identity"); + if (identity_hdr==NULL) { + LM_INFO("No valid Identity header found\n"); + return rc; } - }while(*parsed==NULL); + }while(rc < 0); - parsed_ctx_set(*parsed); + if (rc >= 0) + parsed_ctx_set(*parsed); - return 0; + return rc; } static int set_err_resp_vars(struct sip_msg *msg, pv_spec_t *err_code_var, From 51be49f0307bc45f3d0f6ad5bb664e54967747d0 Mon Sep 17 00:00:00 2001 From: OpenSIPS Date: Sun, 24 Mar 2024 00:47:54 +0200 Subject: [PATCH 66/79] Rebuild documentation --- modules/aaa_diameter/README | 8 +- modules/aaa_diameter/doc/contributors.xml | 24 +- modules/aka_av_diameter/README | 231 ++++++++ modules/auth/README | 16 +- modules/auth/doc/contributors.xml | 34 +- modules/auth_aaa/README | 10 +- modules/auth_aaa/doc/contributors.xml | 22 +- modules/auth_aka/README | 689 ++++++++++++++++++++++ modules/auth_db/README | 14 +- modules/auth_db/doc/contributors.xml | 32 +- modules/rtp_relay/README | 6 +- modules/rtp_relay/doc/contributors.xml | 16 +- modules/stir_shaken/README | 48 +- modules/stir_shaken/doc/contributors.xml | 66 +-- 14 files changed, 1070 insertions(+), 146 deletions(-) create mode 100644 modules/aka_av_diameter/README create mode 100644 modules/auth_aka/README diff --git a/modules/aaa_diameter/README b/modules/aaa_diameter/README index 614cca7f8ea..b3d2a2f9ba8 100644 --- a/modules/aaa_diameter/README +++ b/modules/aaa_diameter/README @@ -460,8 +460,8 @@ Chapter 2. Contributors Table 2.1. Top contributors by DevScore^(1), authored commits^(2) and lines added/removed^(3) Name DevScore Commits Lines ++ Lines -- - 1. Liviu Chircu (@liviuchircu) 109 35 6827 1089 - 2. Razvan Crainea (@razvancrainea) 21 11 910 109 + 1. Liviu Chircu (@liviuchircu) 111 36 6848 1103 + 2. Razvan Crainea (@razvancrainea) 34 17 1360 246 3. Maksym Sobolyev (@sobomax) 3 1 5 5 (1) DevScore = author_commits + author_lines_added / @@ -484,8 +484,8 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Liviu Chircu (@liviuchircu) May 2021 - Feb 2024 - 2. Razvan Crainea (@razvancrainea) May 2023 - Dec 2023 + 1. Razvan Crainea (@razvancrainea) May 2023 - Mar 2024 + 2. Liviu Chircu (@liviuchircu) May 2021 - Mar 2024 3. Maksym Sobolyev (@sobomax) Feb 2023 - Feb 2023 (1) including any documentation-related commits, excluding diff --git a/modules/aaa_diameter/doc/contributors.xml b/modules/aaa_diameter/doc/contributors.xml index faf00bf536c..b215a81fc2d 100644 --- a/modules/aaa_diameter/doc/contributors.xml +++ b/modules/aaa_diameter/doc/contributors.xml @@ -21,18 +21,18 @@ 1. Liviu Chircu (@liviuchircu) - 109 - 35 - 6827 - 1089 + 111 + 36 + 6848 + 1103 2. Razvan Crainea (@razvancrainea) - 21 - 11 - 910 - 109 + 34 + 17 + 1360 + 246 3. @@ -72,13 +72,13 @@ 1. - Liviu Chircu (@liviuchircu) - May 2021 - Feb 2024 + Razvan Crainea (@razvancrainea) + May 2023 - Mar 2024 2. - Razvan Crainea (@razvancrainea) - May 2023 - Dec 2023 + Liviu Chircu (@liviuchircu) + May 2021 - Mar 2024 3. diff --git a/modules/aka_av_diameter/README b/modules/aka_av_diameter/README new file mode 100644 index 00000000000..a7755a001b1 --- /dev/null +++ b/modules/aka_av_diameter/README @@ -0,0 +1,231 @@ +AKA Authentication Vector Diameter Module + __________________________________________________________ + + Table of Contents + + 1. Admin Guide + + 1.1. Overview + 1.2. Setup + 1.3. Dependencies + + 1.3.1. OpenSIPS Modules + 1.3.2. External Libraries or Applications + + 1.4. Exported Parameters + + 1.4.1. aaa_url (string) + 1.4.2. realm (string) + + 1.5. Diameter Commands File + + 2. Contributors + + 2.1. By Commit Statistics + 2.2. By Commit Activity + + 3. Documentation + + 3.1. Contributors + + List of Tables + + 2.1. Top contributors by DevScore^(1), authored commits^(2) and + lines added/removed^(3) + + 2.2. Most recently active contributors^(1) to this module + + List of Examples + + 1.1. aaa_url parameter usage + 1.2. realm parameter usage + 1.3. Diameter Commands File Example + +Chapter 1. Admin Guide + +1.1. Overview + + This module is an extension to the AKA_AUTH module providing a + Diameter AKA AV Manager that implements the + Multimedia-Auth-Request and Multimedia-Auth-Answer Diameter + commands defined in the Cx interface of the ETSI TS 129 229 + specifications in order to fetch a set of authentication + vectors and feed them in the AKA authentication process. + + When the AKA_AUTH module needs a new authentication vector to + do an aka_challenge(), it may require this module to fetch a + set of authentication vectors for the purpose. The module packs + the query in a MAR (Multimedia-Auth-Request) command and sends + it to an HSS Diameter server. When an MAA + (Multimedia-Auth-Answer) command is received in response, the + corresponding authentication vectors are gathered and fed back + to the AUTH_AKA engine. + + It uses the AAA_Diameter module to perform the Diameter + requests. It may run in both a synchronous and asynchronous + mode, depending on how the AUTH_AKA module performs the query. + +1.2. Setup + + The module requires an aaa_diameter connection to an HSS + Diameter server that implements the Cx interfaces and is able + to provide authentication vectors through the + Multimedia-Auth-Request and Multimedia-Auth-Answer commands. + + The format of the command, along with the required fields can + be found in the example/aka_av_diameter.dictionary file located + in the module's source directory, as well as in the Diameter + Commands Example section. + + Note: the module internals uses the AVPs names found in the + provided dictionary - changing the file may break the behavior + of the module. + +1.3. Dependencies + +1.3.1. OpenSIPS Modules + + The module depends on the following modules (in the other words + the listed modules must be loaded before this module): + * auth_aka -- AKA Authentication module that triggers the AKA + authentication process + * aaa_diameter -- AAA Diameter module that implements the + Diameter communication to the HSS Server. + +1.3.2. External Libraries or Applications + + This module does not depend on any external library. + +1.4. Exported Parameters + +1.4.1. aaa_url (string) + + This is the url representing the connection to the AAA server. + + Note: Currently the module only supports connections to a + Diameter server. The path to the AVPs configuration file is + also required, otherwise the module will not start, or not work + properly. + + Example 1.1. aaa_url parameter usage +modparam("auth_aaa", "aaa_url", "diameter:freeDiameter.conf;extra-avps-f +ile:/etc/freeDiameter/aka_av_diameter.dictionary") + +1.4.2. realm (string) + + The Realm used in the Origin Diameter commands. + + Default value is “diameter.test”. + + Example 1.2. realm parameter usage + +modparam("aka_av_diameter", "realm", "scscf.ims.mnc001.mcc001.3gppnetwor +k.org") + +1.5. Diameter Commands File + + File that should be provided to the aaa_diameter connection. + + Example 1.3. Diameter Commands File Example + +VENDOR 10415 TGPP + +ATTRIBUTE Public-Identity 601 string 10415 +ATTRIBUTE Server-Name 602 string 10415 +ATTRIBUTE 3GPP-SIP-Number-Auth-Items 607 unsigned32 10415 +ATTRIBUTE 3GPP-SIP-Authentication-Scheme 608 utf8string 10415 +ATTRIBUTE 3GPP-SIP-Authenticate 609 hexstring 10415 +ATTRIBUTE 3GPP-SIP-Authorization 610 hexstring 10415 +ATTRIBUTE 3GPP-SIP-Authentication-Context 611 string 10415 +ATTRIBUTE 3GPP-SIP-Item-Number 613 unsigned32 10415 +ATTRIBUTE Confidentiality-Key 625 hexstring 10415 +ATTRIBUTE Integrity-Key 626 hexstring 10415 + + +ATTRIBUTE 3GPP-SIP-Auth-Data-Item 612 grouped 10415 +{ + 3GPP-SIP-Item-Number | OPTIONAL | 1 + 3GPP-SIP-Authentication-Scheme | OPTIONAL | 1 + 3GPP-SIP-Authenticate | OPTIONAL | 1 + 3GPP-SIP-Authorization | OPTIONAL | 1 + 3GPP-SIP-Authentication-Context | OPTIONAL | 1 + Confidentiality-Key | OPTIONAL | 1 + Integrity-Key | OPTIONAL | 1 +} + +APPLICATION-AUTH 16777216/10415 3GPP Cx + +REQUEST 303 Multimedia-Auth Request +{ + Session-Id | REQUIRED | 1 + Origin-Host | REQUIRED | 1 + Origin-Realm | REQUIRED | 1 + Destination-Realm | REQUIRED | 1 + Vendor-Specific-Application-Id | REQUIRED | 1 + Auth-Session-State | REQUIRED | 1 + User-Name | REQUIRED | 1 + Public-Identity | REQUIRED | 1 + 3GPP-SIP-Number-Auth-Items | REQUIRED | 1 + 3GPP-SIP-Auth-Data-Item | REQUIRED | 1 + Server-Name | REQUIRED | 1 +} + +ANSWER 303 Multimedia-Auth Answer +{ + Session-Id | REQUIRED | 1 + Origin-Host | REQUIRED | 1 + Origin-Realm | REQUIRED | 1 + Destination-Host | OPTIONAL | 1 + Destination-Realm | OPTIONAL | 1 + Vendor-Specific-Application-Id | REQUIRED | 1 + Auth-Session-State | REQUIRED | 1 + User-Name | REQUIRED | 1 + Public-Identity | REQUIRED | 1 + 3GPP-SIP-Number-Auth-Items | REQUIRED | 1 + 3GPP-SIP-Auth-Data-Item | REQUIRED | 1 + Result-Code | REQUIRED | 1 +} + +Chapter 2. Contributors + +2.1. By Commit Statistics + + Table 2.1. Top contributors by DevScore^(1), authored + commits^(2) and lines added/removed^(3) + Name DevScore Commits Lines ++ Lines -- + 1. Razvan Crainea (@razvancrainea) 13 4 941 4 + + (1) DevScore = author_commits + author_lines_added / + (project_lines_added / project_commits) + author_lines_deleted + / (project_lines_deleted / project_commits) + + (2) including any documentation-related commits, excluding + merge commits. Regarding imported patches/code, we do our best + to count the work on behalf of the proper owner, as per the + "fix_authors" and "mod_renames" arrays in + opensips/doc/build-contrib.sh. If you identify any + patches/commits which do not get properly attributed to you, + please submit a pull request which extends "fix_authors" and/or + "mod_renames". + + (3) ignoring whitespace edits, renamed files and auto-generated + files + +2.2. By Commit Activity + + Table 2.2. Most recently active contributors^(1) to this module + Name Commit Activity + 1. Razvan Crainea (@razvancrainea) Mar 2024 - Mar 2024 + + (1) including any documentation-related commits, excluding + merge commits + +Chapter 3. Documentation + +3.1. Contributors + + Last edited by: Razvan Crainea (@razvancrainea). + + Documentation Copyrights: + + Copyright © 2024 OpenSIPS Solutions; diff --git a/modules/auth/README b/modules/auth/README index 30ce91cc4b6..0749b99392d 100644 --- a/modules/auth/README +++ b/modules/auth/README @@ -525,8 +525,8 @@ Chapter 2. Contributors 4. Maksym Sobolyev (@sobomax) 33 13 587 862 5. Liviu Chircu (@liviuchircu) 27 20 206 284 6. Jiri Kuthan (@jiriatipteldotorg) 26 19 660 51 - 7. Vlad Patrascu (@rvlad-patrascu) 18 10 420 236 - 8. Razvan Crainea (@razvancrainea) 12 9 87 102 + 7. Razvan Crainea (@razvancrainea) 18 13 212 169 + 8. Vlad Patrascu (@rvlad-patrascu) 18 10 420 236 9. Anca Vamanu 12 5 497 77 10. Henning Westerholt (@henningw) 11 8 107 100 @@ -557,12 +557,12 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Liviu Chircu (@liviuchircu) Mar 2014 - Apr 2023 - 2. Maksym Sobolyev (@sobomax) Jan 2005 - Mar 2023 - 3. Vlad Patrascu (@rvlad-patrascu) May 2017 - Jun 2022 - 4. Bogdan-Andrei Iancu (@bogdan-iancu) Dec 2002 - Jan 2021 - 5. Zero King (@l2dy) Mar 2020 - Mar 2020 - 6. Razvan Crainea (@razvancrainea) Jun 2011 - Sep 2019 + 1. Razvan Crainea (@razvancrainea) Jun 2011 - Feb 2024 + 2. Liviu Chircu (@liviuchircu) Mar 2014 - Apr 2023 + 3. Maksym Sobolyev (@sobomax) Jan 2005 - Mar 2023 + 4. Vlad Patrascu (@rvlad-patrascu) May 2017 - Jun 2022 + 5. Bogdan-Andrei Iancu (@bogdan-iancu) Dec 2002 - Jan 2021 + 6. Zero King (@l2dy) Mar 2020 - Mar 2020 7. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 8. Dusan Klinec (@ph4r05) Dec 2015 - Dec 2015 9. Walter Doekes (@wdoekes) Feb 2014 - Feb 2014 diff --git a/modules/auth/doc/contributors.xml b/modules/auth/doc/contributors.xml index 4096303a1bf..d3bfa5dacb0 100644 --- a/modules/auth/doc/contributors.xml +++ b/modules/auth/doc/contributors.xml @@ -68,20 +68,20 @@ 7. + Razvan Crainea (@razvancrainea) + 18 + 13 + 212 + 169 + + + 8. Vlad Patrascu (@rvlad-patrascu) 18 10 420 236 - - 8. - Razvan Crainea (@razvancrainea) - 12 - 9 - 87 - 102 - 9. Anca Vamanu @@ -128,34 +128,34 @@ 1. + Razvan Crainea (@razvancrainea) + Jun 2011 - Feb 2024 + + + 2. Liviu Chircu (@liviuchircu) Mar 2014 - Apr 2023 - 2. + 3. Maksym Sobolyev (@sobomax) Jan 2005 - Mar 2023 - 3. + 4. Vlad Patrascu (@rvlad-patrascu) May 2017 - Jun 2022 - 4. + 5. Bogdan-Andrei Iancu (@bogdan-iancu) Dec 2002 - Jan 2021 - 5. + 6. Zero King (@l2dy) Mar 2020 - Mar 2020 - - 6. - Razvan Crainea (@razvancrainea) - Jun 2011 - Sep 2019 - 7. Peter Lemenkov (@lemenkov) diff --git a/modules/auth_aaa/README b/modules/auth_aaa/README index 078a95020d1..d7a33fd4304 100644 --- a/modules/auth_aaa/README +++ b/modules/auth_aaa/README @@ -319,7 +319,7 @@ Chapter 2. Contributors 4. Daniel-Constantin Mierla (@miconda) 15 13 67 55 5. Irina-Maria Stanescu 15 8 185 299 6. Maksym Sobolyev (@sobomax) 13 8 171 175 - 7. Razvan Crainea (@razvancrainea) 9 7 12 14 + 7. Razvan Crainea (@razvancrainea) 10 8 13 15 8. Juha Heinanen (@juha-h) 8 5 142 53 9. Andrei Pelinescu-Onciul 7 5 8 2 10. Vlad Patrascu (@rvlad-patrascu) 7 2 55 213 @@ -349,10 +349,10 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Maksym Sobolyev (@sobomax) Dec 2003 - Feb 2023 - 2. Liviu Chircu (@liviuchircu) Jan 2013 - Jun 2021 - 3. Bogdan-Andrei Iancu (@bogdan-iancu) Jun 2005 - May 2020 - 4. Razvan Crainea (@razvancrainea) Feb 2012 - Sep 2019 + 1. Razvan Crainea (@razvancrainea) Feb 2012 - Jan 2024 + 2. Maksym Sobolyev (@sobomax) Dec 2003 - Feb 2023 + 3. Liviu Chircu (@liviuchircu) Jan 2013 - Jun 2021 + 4. Bogdan-Andrei Iancu (@bogdan-iancu) Jun 2005 - May 2020 5. Vlad Patrascu (@rvlad-patrascu) May 2017 - Apr 2019 6. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 7. Irina-Maria Stanescu Aug 2009 - Apr 2010 diff --git a/modules/auth_aaa/doc/contributors.xml b/modules/auth_aaa/doc/contributors.xml index 1e0956990dc..7ef674769a6 100644 --- a/modules/auth_aaa/doc/contributors.xml +++ b/modules/auth_aaa/doc/contributors.xml @@ -69,10 +69,10 @@ 7. Razvan Crainea (@razvancrainea) - 9 - 7 - 12 - 14 + 10 + 8 + 13 + 15 8. @@ -128,24 +128,24 @@ 1. + Razvan Crainea (@razvancrainea) + Feb 2012 - Jan 2024 + + + 2. Maksym Sobolyev (@sobomax) Dec 2003 - Feb 2023 - 2. + 3. Liviu Chircu (@liviuchircu) Jan 2013 - Jun 2021 - 3. + 4. Bogdan-Andrei Iancu (@bogdan-iancu) Jun 2005 - May 2020 - - 4. - Razvan Crainea (@razvancrainea) - Feb 2012 - Sep 2019 - 5. Vlad Patrascu (@rvlad-patrascu) diff --git a/modules/auth_aka/README b/modules/auth_aka/README new file mode 100644 index 00000000000..3bbaee9b324 --- /dev/null +++ b/modules/auth_aka/README @@ -0,0 +1,689 @@ +Auth_aka Module + __________________________________________________________ + + Table of Contents + + 1. Admin Guide + + 1.1. Overview + 1.2. Authentication Vectors + 1.3. Supported algorithms + 1.4. Dependencies + + 1.4.1. OpenSIPS Modules + 1.4.2. External Libraries or Applications + + 1.5. Exported Parameters + + 1.5.1. default_av_mgm (string) + 1.5.2. default_qop (string) + 1.5.3. default_algorithm (string) + 1.5.4. hash_size (integer) + 1.5.5. sync_timeout (integer) + 1.5.6. async_timeout (integer) + 1.5.7. unused_timeout (integer) + 1.5.8. unused_timeout (integer) + + 1.6. Exported Functions + + 1.6.1. aka_www_authorize([realm]]) + 1.6.2. aka_proxy_authorize([realm]]) + 1.6.3. aka_www_challenge([av_mgm[, realm[ ,qop[, + alg]]]]) + + 1.6.4. aka_proxy_challenge([realm]]) + 1.6.5. aka_av_add(public_identity, private_identity, + authenticate, authorize, + confidentiality_key, integrity_key[, + algorithms]) + + 1.6.6. aka_av_drop(public_identity, + private_identity, authenticate) + + 1.6.7. aka_av_drop_all(public_identity, + private_identity[, count]) + + 1.6.8. aka_av_fail(public_identity, + private_identity[, count]) + + 1.7. Exported MI Functions + + 1.7.1. aka_av_add + 1.7.2. aka_av_drop + 1.7.3. aka_av_drop_all + 1.7.4. aka_av_fail + + 2. Contributors + + 2.1. By Commit Statistics + 2.2. By Commit Activity + + 3. Documentation + + 3.1. Contributors + + List of Tables + + 2.1. Top contributors by DevScore^(1), authored commits^(2) and + lines added/removed^(3) + + 2.2. Most recently active contributors^(1) to this module + + List of Examples + + 1.1. default_av_mgm parameter usage + 1.2. default_qop parameter usage + 1.3. default_algorithm parameter usage + 1.4. hash_size parameter usage + 1.5. sync_timeout parameter usage + 1.6. async_timeout parameter usage + 1.7. unused_timeout parameter usage + 1.8. pending_timeout parameter usage + 1.9. aka_www_authorize usage + 1.10. aka_proxy_authorize usage + 1.11. aka_www_challenge usage + 1.12. aka_proxy_challenge usage + 1.13. aka_av_add usage + 1.14. aka_av_drop usage + 1.15. aka_av_drop_all usage + 1.16. aka_av_fail usage + 1.17. aka_av_add usage + 1.18. aka_av_drop usage + 1.19. aka_av_drop_all usage + 1.20. aka_av_drop usage + +Chapter 1. Admin Guide + +1.1. Overview + + This module contains functions that are used to perform digest + authentication using the AKA (Authentication and Key Agreement) + security protocol. This mechanism is being used in IMS networks + to provide mutual authentication between the UE (device) and + the 3G/4G/5G network. + + The AKA protocol establishes a set of security keys, called + authentication vectors (or AVs), and uses them to generate the + digest challenge, as well as for computing the digest result + and authenticating the UE. AVs are exchanged over a separate + communication channel. + + Although the AKA protocol also requires to use the AVs to + establish a secure channel between the UE and the network (by + means of IPSec tunnels), this module does not handle that part + - it just performs the authentication of the user and passes + along the cyphering and integrity keys in the Authorization + header, according to the ETSI TS 129 229 specifications. These + are later on picked up by other components (such as P-CSCFs) to + establish the secure channel. + +1.2. Authentication Vectors + + Authentication Vectors (or AVs) consist of a set of five + parameter (RAND, AUTN, XRES, CK, IK) that are being used for + mutual authentication. As these need to be exchanged between + the device (UE) and network through a different channel (i.e. + Diameter Cx interface in LTE networks), the module does not + provide any means to fetch the AV information. It does, + however, provide a generic interface (called AV Manage + Interface) to store AVs (that are being fetched by other + modules/channels), manage them and use them in the digest + authentication algorithm. + + Basic AV operations that the module performs: + * Ask for a new AV to be fetched for a specific user identity + * Manage an AV lifetime, including reuses + * Mark an AV as being used in a digest challeng + * Invalidate or discard an AV (due to various reasons) + + A module that implements the AV Manage Interface (called AV + Manager) should be able to fetch all five parameters of an AV, + and push them in the AV Storage. + +1.3. Supported algorithms + + The current implementation only supports the AKAv1 algorithms, + with the associated hashing functions (such as MD5, SHA-256). + In the challenge message, we send, one can advertise other + algorithms as well, but the response cannot be handled by this + module, and an appropriate error will be returned. + +1.4. Dependencies + +1.4.1. OpenSIPS Modules + + The module depends on the following modules (in the other words + the listed modules must be loaded before this module): + * auth -- Authentication framework + * AV manage module -- at least one module that fetches AVs + and pushes them in the AV storage + +1.4.2. External Libraries or Applications + + This module does not depend on any external library. + +1.5. Exported Parameters + +1.5.1. default_av_mgm (string) + + The default AV Manager used in case the functions do not + provide them explicitly. + + Example 1.1. default_av_mgm parameter usage + +modparam("auth_aka", "default_av_mgm", "diameter") # fetch AVs through t +he Cx interface + +1.5.2. default_qop (string) + + The default qop parameter used during challenge, if the + functions do not provide them explicitly. + + Default value is auth. + + Example 1.2. default_qop parameter usage + +modparam("auth_aka", "default_qop", "auth,auth-int") + +1.5.3. default_algorithm (string) + + The default algorithm to be advertise during challenge, if the + functions do not provide them explicitly. Note that at least + one of the algorithms provided should be an AKA one, otherwise + it makes no sense to use this module. + + Default value is AKAv1-MD5. + + WARNING: only AKAv1* algorithms are currently supported. + + Example 1.3. default_algorithm parameter usage + +modparam("auth_aka", "default_algorithm", "AKAv2-MD5") + +1.5.4. hash_size (integer) + + The size of the hash that stores the AVs for each user. Must be + a power of 2 number. + + Default value is 4096. + + Example 1.4. hash_size parameter usage + +modparam("auth_aka", "hash_size", 1024) + +1.5.5. sync_timeout (integer) + + The amount of milliseconds a synchronous call should wait for + getting an authentication vector. + + Must be a positive value. A value of 0 indicates to wait + indefinitely. + + Default value is 100 ms. + + Example 1.5. sync_timeout parameter usage + +modparam("auth_aka", "sync_timeout", 200) + +1.5.6. async_timeout (integer) + + The amount of milliseconds an asynchronous call should wait for + getting an authentication vector. + + Must be a positive value, greater than 0. + + NOTE: the current timeout mechanism only has seconds + granularity, therefore you should configure this parameter as a + multiple of 1000. + + Default value is 1000 ms. + + Example 1.6. async_timeout parameter usage + +modparam("auth_aka", "async_timeout", 2000) + +1.5.7. unused_timeout (integer) + + The amount of seconds an authentication vector that has not + been used can stay in memory. Once this timeout is reached, the + authentication vector is removed. + + Must be a positive value, greater than 0. + + Default value is 60 s. + + Example 1.7. unused_timeout parameter usage + +modparam("auth_aka", "unused_timeout", 120) + +1.5.8. unused_timeout (integer) + + The amount of seconds an authentication vector that is being + used in the authentication process shall stay in memory. Once + this timeout is reached, the authentication vector is removed, + and the authentication using it will fail. + + Must be a positive value, greater than 0. + + Default value is 30 s. + + Example 1.8. pending_timeout parameter usage + +modparam("auth_aka", "pending_timeout", 10) + +1.6. Exported Functions + +1.6.1. aka_www_authorize([realm]]) + + The function verifies credentials according to RFC3310, by + using an authentication vector priorly allocated by an + aka_www_challenge() call, using the av_mgm manager. If the + credentials are verified successfully the function will + succeed, otherwise it will fail with an appropriate error code, + as follows: + * -6 (sync request) - the auts parameter was was present, + thus a sync was requested; + * -5 (generic error) - some generic error occurred and no + reply was sent out; + * -4 (no credentials) - credentials were not found in + request; + * -3 (unknown nonce) - authentication vector with the + corresponding nonce was not found; + * -2 (invalid password) - password does not match the + authentication vector; + * -1 (invalid username) - no username found in the Authorize + header; + + In case the function succeeds, the WWW-Authenticate header is + being added to the reply, containing the challenge information, + as well as the Integrity-Key and the Confidentiality-Key values + associated to the AV being used. + + Meaning of the parameters is as follows: + * realm (string) - Realm is a opaque string that the user + agent should present to the user so he can decide what + username and password to use. This is usually one of the + domains the proxy is responsible for. If an empty string “” + is used then the server will generate realm from host part + of From header field URI. + + If the credentials are verified successfully then the function + will succeed and mark the credentials as authorized (marked + credentials can be later used by some other functions). + + This function can be used from REQUEST_ROUTE. + + Example 1.9. aka_www_authorize usage + +... +if (!aka_www_authorize("diameter", "siphub.com")) + aka_www_challenge("diameter", "siphub.com", "auth"); +... + + +1.6.2. aka_proxy_authorize([realm]]) + + The function behaves the same as aka_www_authorize(), but it + authenticates the user from a proxy perspective. It receives + the same parameters, with the same meaning, and returns the + same values. + + This function can be used from REQUEST_ROUTE. + + Example 1.10. aka_proxy_authorize usage + +... +if (!aka_proxy_authorize("siphub.com")) + aka_proxy_challenge("diameter", "siphub.com", "auth"); +... + + +1.6.3. aka_www_challenge([av_mgm[, realm[ ,qop[, alg]]]]) + + The function challenges a user agent. It fetches an + authentication vector for each algorigthm used through the + av_mgm Manager and generate one or more WWW-Authenticate header + fields containing digest challenges. It will put the header + field(s) into a response generated from the request the server + is processing and will send the reply. Upon reception of such a + reply the user agent should compute credentials using the used + authentication vector annd retry the request. For more + information regarding digest authentication see RFC2617, + RFC3261, RFC3310 and RFC8760. + + Meaning of the parameters is as follows: + * av_mgm (string, optional) - the AV Manager to be used for + this challenge, in case an AV is not already available for + the challenged user identity. In case it is missing the + value of the default_av_mgm is being used. + realm (string) - Realm is an opaque string that the user + agent should present to the user so it can decide what + username and password to use. Usually this is domain of the + host the server is running on. If missing, the value of the + From domain is being used. + * qop (string, optional) - Value of this parameter can be + either “auth”, “auth-int” or both (separated by ,). When + this parameter is set the server will put a qop parameter + in the challenge. It is recommended to use the qop + parameter, however there are still some user agents that + cannot handle qop properly so we made this optional. On the + other hand there are still some user agents that cannot + handle request without a qop parameter too. If missing, the + value of the default_qop is being used. + * algorithms (string, optional) - Value of this parameter is + a comma-separated list of digest algorithms to be offered + for the UAC to use for authentication. Possible values are: + + “AKAv1-MD5” + + “AKAv1-MD5-sess” + + “AKAv1-SHA-256” + + “AKAv1-SHA-256-sess” + + “AKAv1-SHA-512-256” + + “AKAv1-SHA-512-256-sess” + + “AKAv2-MD5” + + “AKAv2-MD5-sess” + + “AKAv2-SHA-256” + + “AKAv2-SHA-256-sess” + + “AKAv2-SHA-512-256” + + “AKAv2-SHA-512-256-sess” + When the value is empty or not set, the only offered digest + the value of the default_algorithm is being used. + + Possible return codes: + * -1 - generic parsing error, generated when there is not + enoough data to build the challange + * -2 - no AV vector could not be fetched + * -3 - authentication headers could not be built + * -5 - a reply could not be sent + * positive - the number of successful chalanges being sent in + the reply; this value can be lower than the number of + algorithms being requested in case there was a timeout + waiting for some AVs. + + This function can be used from REQUEST_ROUTE. + + Example 1.11. aka_www_challenge usage +... +if (!aka_www_authorize("siphub.com")) { + aka_www_challenge(,"siphub.com", "auth-int", "AKAv1-MD5"); +} +... + +1.6.4. aka_proxy_challenge([realm]]) + + The function behaves the same as aka_www_challenge(), but it + challenges the user from a proxy perspective. It receives the + same parameters, with the same meaning, the only difference + being that in case of the realm is missing, then it is taken + from the the To domain, rather than from From domain. The + header added is Proxy-Authenticate, rather than + WWW-Authenticate The rest of the parameters, behavior, as well + as return values are the same. + + This function can be used from REQUEST_ROUTE. + + Example 1.12. aka_proxy_challenge usage + +... +if (!aka_proxy_authorize("siphub.com")) + aka_proxy_challenge(,"siphub.com", "auth"); +... + + +1.6.5. aka_av_add(public_identity, private_identity, authenticate, +authorize, confidentiality_key, integrity_key[, algorithms]) + + Adds an authentication vector for the user identitied by + public_identity and private_identity. + + Meaning of the parameters is as follows: + * public_identity (string) - the public identity (IMPU) of + the user to add authentication vector for. + * private_identity (string) - the private identity (IMPI) of + the user to add authentication vector for. + * authenticate (string) - the concatenation of the + authentication challenge RAND and the token AUTN, encoded + in hexa format. + * authorize (string) - the authorization string (XRES) used + for authorizing the user, encoded in hexa format. + * confidentiality_key (string) - the Confidentiality-Key used + in the AKA IPSec process, encoded in hexa format. + * integrity_key (string) - the Integrity-Key used in the AKA + IPSec process, encoded in hexa format. + * algorithms (string, optional) - AKA algorithms this AV + should be used for. If missing, the AV can be used for any + AKA algorithm. + + This function can be used from any route. + + Example 1.13. aka_av_add usage + +... +aka_av_add("sip:test@siphub.com", "test@siphub.com", + "KFQ/MpR3cE3V9PxucEQS5KED8uUNYIAALFyk59sIJI4=", +/* authenticate */ + "00000262c0000014000028af2d6398cbe26eea69", /* a +uthorize */ + "db7f8c4a58e17083974bba3b936d34c4", /* ck */ + "6151667b9ef815c1dcb87473685f062a" /* ik */); +... + +1.6.6. aka_av_drop(public_identity, private_identity, authenticate) + + Drops the authentication vector corresponding to the + authenticate/nonce value for an user identitied by + public_identity and private_identity. + + Meaning of the parameters is as follows: + * public_identity (string) - the public identity (IMPU) of + the user to drop authentication vector for. + * private_identity (string) - the private identity (IMPI) of + the user to drop authentication vector for. + * authenticate (string) - the authenticate/nonce that + identifies the authentication vector to be dropped. + + This function can be used from any route. + + Example 1.14. aka_av_drop usage + +... +aka_av_drop("sip:test@siphub.com", "test@siphub.com", + "KFQ/MpR3cE3V9PxucEQS5KED8uUNYIAALFyk59sIJI4="); +... + +1.6.7. aka_av_drop_all(public_identity, private_identity[, count]) + + Drops all authentication vectors for an user identitied by + public_identity and private_identity. This function is useful + when a synchronization must be done. + + Meaning of the parameters is as follows: + * public_identity (string) - the public identity (IMPU) of + the user to drop authentication vectors for. + * private_identity (string) - the private identity (IMPI) of + the user to drop authentication vectors for. + * count (variable, optional) - a variable to return the + number of authentication vectors dropped. + + This function can be used from any route. + + Example 1.15. aka_av_drop_all usage + +... +aka_av_drop_all("sip:test@siphub.com", "test@siphub.com", $var(count)); +... + +1.6.8. aka_av_fail(public_identity, private_identity[, count]) + + Marks the engine that an authentication vector query for a user + has failed, unlocking the processing of the message. + + Note: this function is useful when you know that fetching a new + authentication vector is not possible (due to various reasons) + - calling it will resume the message procesing, using only the + available AVs fetched so far. + + Meaning of the parameters is as follows: + * public_identity (string) - the public identity (IMPU) of + the user to drop authentication vectors for. + * private_identity (string) - the private identity (IMPI) of + the user to drop authentication vectors for. + * count (integer, optional) - the number of authentication + vectors that failed. If missing, 1 is considered. + + This function can be used from any route. + + Example 1.16. aka_av_fail usage +... +aka_av_fail("sip:test@siphub.com", "test@siphub.com", 3); +... + +1.7. Exported MI Functions + +1.7.1. aka_av_add + + Adds an Authentication Vector through the MI interface. + + Parameters: + * public_identity (string) - the public identity (IMPU) of + the user to add authentication vector for. + * private_identity (string) - the private identity (IMPI) of + the user to add authentication vector for. + * authenticate (string) - the concatenation of the + authentication challenge RAND and the token AUTN, encoded + in hexa format. + * authorize (string) - the authorization string (XRES) used + for authorizing the user, encoded in hexa format. + * confidentiality_key (string) - the Confidentiality-Key used + in the AKA IPSec process, encoded in hexa format. + * integrity_key (string) - the Integrity-Key used in the AKA + IPSec process, encoded in hexa format. + * algorithms (string, optional) - AKA algorithms this AV + should be used for. If missing, the AV can be used for any + AKA algorithm. + + Example 1.17. aka_av_add usage +... +## adds an AKA AV +$ opensips-cli -x mi aka_av_add \ + sip:test@siphub.com + test@siphub.com + KFQ/MpR3cE3V9PxucEQS5KED8uUNYIAALFyk59sI +JI4= + 00000262c0000014000028af2d6398cbe26eea69 + db7f8c4a58e17083974bba3b936d34c4 + 6151667b9ef815c1dcb87473685f062a +... + +1.7.2. aka_av_drop + + Invalidates an Authentication Vector of an user identified by + its authenticate value. + + Parameters: + * public_identity (string) - the public identity (IMPU) of + the user to add authentication vector for. + * private_identity (string) - the private identity (IMPI) of + the user to add authentication vector for. + * authenticate (string) - the authenticate/nonce to indentify + the authentication vector. + + Example 1.18. aka_av_drop usage +... +## adds an AKA AV +$ opensips-cli -x mi aka_av_drop \ + sip:test@siphub.com + test@siphub.com + KFQ/MpR3cE3V9PxucEQS5KED8uUNYIAALFyk59sI +JI4= +... + +1.7.3. aka_av_drop_all + + Invalidates all Authentication Vectors of an user through the + MI interface. + + Parameters: + * public_identity (string) - the public identity (IMPU) of + the user to drop authentication vectors for. + * private_identity (string) - the private identity (IMPI) of + the user to drop authentication vectors for. + + Example 1.19. aka_av_drop_all usage +... +## adds an AKA AV +$ opensips-cli -x mi aka_av_drop_all \ + sip:test@siphub.com + test@siphub.com +... + +1.7.4. aka_av_fail + + Indicates the fact that the fetching of an authentication + vector has failed, unlocking the processing of the message. + + Note: this function is useful when you know that fetching a new + authentication vector is not possible (due to various reasons) + - calling it will resume the message procesing, using only the + available AVs fetched so far. + + Parameters: + * public_identity (string) - the public identity (IMPU) of + the user to add authentication vector for. + * private_identity (string) - the private identity (IMPI) of + the user to add authentication vector for. + * count (integer, optional) - the number of authentication + vectors failures. + + Example 1.20. aka_av_drop usage +... +## adds an AKA AV +$ opensips-cli -x mi aka_av_drop \ + sip:test@siphub.com + test@siphub.com + KFQ/MpR3cE3V9PxucEQS5KED8uUNYIAALFyk59sI +JI4= +... + +Chapter 2. Contributors + +2.1. By Commit Statistics + + Table 2.1. Top contributors by DevScore^(1), authored + commits^(2) and lines added/removed^(3) + Name DevScore Commits Lines ++ Lines -- + 1. Razvan Crainea (@razvancrainea) 45 14 3319 157 + + (1) DevScore = author_commits + author_lines_added / + (project_lines_added / project_commits) + author_lines_deleted + / (project_lines_deleted / project_commits) + + (2) including any documentation-related commits, excluding + merge commits. Regarding imported patches/code, we do our best + to count the work on behalf of the proper owner, as per the + "fix_authors" and "mod_renames" arrays in + opensips/doc/build-contrib.sh. If you identify any + patches/commits which do not get properly attributed to you, + please submit a pull request which extends "fix_authors" and/or + "mod_renames". + + (3) ignoring whitespace edits, renamed files and auto-generated + files + +2.2. By Commit Activity + + Table 2.2. Most recently active contributors^(1) to this module + Name Commit Activity + 1. Razvan Crainea (@razvancrainea) Feb 2024 - Mar 2024 + + (1) including any documentation-related commits, excluding + merge commits + +Chapter 3. Documentation + +3.1. Contributors + + Last edited by: Razvan Crainea (@razvancrainea). + + Documentation Copyrights: + + Copyright © 2024 OpenSIPS Solutions; diff --git a/modules/auth_db/README b/modules/auth_db/README index e8b519d5a7b..d4f41184546 100644 --- a/modules/auth_db/README +++ b/modules/auth_db/README @@ -473,8 +473,8 @@ Chapter 2. Contributors 3. Daniel-Constantin Mierla (@miconda) 29 20 130 382 4. Liviu Chircu (@liviuchircu) 22 17 161 135 5. Henning Westerholt (@henningw) 11 9 83 49 - 6. Maksym Sobolyev (@sobomax) 10 5 307 116 - 7. Razvan Crainea (@razvancrainea) 9 7 29 47 + 6. Razvan Crainea (@razvancrainea) 10 8 30 48 + 7. Maksym Sobolyev (@sobomax) 10 5 307 116 8. Vlad Patrascu (@rvlad-patrascu) 8 4 69 163 9. Sergio Gutierrez 7 5 13 13 10. Andrei Pelinescu-Onciul 6 4 81 33 @@ -506,11 +506,11 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Maksym Sobolyev (@sobomax) Oct 2004 - Feb 2023 - 2. Liviu Chircu (@liviuchircu) Mar 2014 - Sep 2022 - 3. Bogdan-Andrei Iancu (@bogdan-iancu) Jun 2005 - Jul 2021 - 4. Walter Doekes (@wdoekes) Apr 2021 - Apr 2021 - 5. Razvan Crainea (@razvancrainea) Jun 2011 - Sep 2019 + 1. Razvan Crainea (@razvancrainea) Jun 2011 - Jan 2024 + 2. Maksym Sobolyev (@sobomax) Oct 2004 - Feb 2023 + 3. Liviu Chircu (@liviuchircu) Mar 2014 - Sep 2022 + 4. Bogdan-Andrei Iancu (@bogdan-iancu) Jun 2005 - Jul 2021 + 5. Walter Doekes (@wdoekes) Apr 2021 - Apr 2021 6. Vlad Patrascu (@rvlad-patrascu) May 2017 - Jul 2019 7. Peter Lemenkov (@lemenkov) Jun 2018 - Jun 2018 8. Julián Moreno Patiño Feb 2016 - Feb 2016 diff --git a/modules/auth_db/doc/contributors.xml b/modules/auth_db/doc/contributors.xml index 125f4e8a9fa..0316c250428 100644 --- a/modules/auth_db/doc/contributors.xml +++ b/modules/auth_db/doc/contributors.xml @@ -60,20 +60,20 @@ 6. + Razvan Crainea (@razvancrainea) + 10 + 8 + 30 + 48 + + + 7. Maksym Sobolyev (@sobomax) 10 5 307 116 - - 7. - Razvan Crainea (@razvancrainea) - 9 - 7 - 29 - 47 - 8. Vlad Patrascu (@rvlad-patrascu) @@ -128,29 +128,29 @@ 1. + Razvan Crainea (@razvancrainea) + Jun 2011 - Jan 2024 + + + 2. Maksym Sobolyev (@sobomax) Oct 2004 - Feb 2023 - 2. + 3. Liviu Chircu (@liviuchircu) Mar 2014 - Sep 2022 - 3. + 4. Bogdan-Andrei Iancu (@bogdan-iancu) Jun 2005 - Jul 2021 - 4. + 5. Walter Doekes (@wdoekes) Apr 2021 - Apr 2021 - - 5. - Razvan Crainea (@razvancrainea) - Jun 2011 - Sep 2019 - 6. Vlad Patrascu (@rvlad-patrascu) diff --git a/modules/rtp_relay/README b/modules/rtp_relay/README index 8e47aea6ef2..6659c2743ca 100644 --- a/modules/rtp_relay/README +++ b/modules/rtp_relay/README @@ -332,7 +332,7 @@ Chapter 2. Contributors Table 2.1. Top contributors by DevScore^(1), authored commits^(2) and lines added/removed^(3) Name DevScore Commits Lines ++ Lines -- - 1. Razvan Crainea (@razvancrainea) 146 80 5771 1134 + 1. Razvan Crainea (@razvancrainea) 147 81 5772 1135 2. Maksym Sobolyev (@sobomax) 6 4 10 11 3. Vlad Patrascu (@rvlad-patrascu) 3 1 11 7 4. Norman Brandinger (@NormB) 3 1 1 1 @@ -358,8 +358,8 @@ Chapter 2. Contributors Table 2.2. Most recently active contributors^(1) to this module Name Commit Activity - 1. Norman Brandinger (@NormB) Mar 2024 - Mar 2024 - 2. Razvan Crainea (@razvancrainea) Apr 2021 - Feb 2024 + 1. Razvan Crainea (@razvancrainea) Apr 2021 - Mar 2024 + 2. Norman Brandinger (@NormB) Mar 2024 - Mar 2024 3. Maksym Sobolyev (@sobomax) Feb 2023 - Nov 2023 4. Vlad Patrascu (@rvlad-patrascu) Mar 2023 - Mar 2023 5. Vlad Paiu (@vladpaiu) Oct 2022 - Oct 2022 diff --git a/modules/rtp_relay/doc/contributors.xml b/modules/rtp_relay/doc/contributors.xml index ea9e3f00dd7..86acbf4db95 100644 --- a/modules/rtp_relay/doc/contributors.xml +++ b/modules/rtp_relay/doc/contributors.xml @@ -21,10 +21,10 @@ 1. Razvan Crainea (@razvancrainea) - 146 - 80 - 5771 - 1134 + 147 + 81 + 5772 + 1135 2. @@ -88,13 +88,13 @@ 1. - Norman Brandinger (@NormB) - Mar 2024 - Mar 2024 + Razvan Crainea (@razvancrainea) + Apr 2021 - Mar 2024 2. - Razvan Crainea (@razvancrainea) - Apr 2021 - Feb 2024 + Norman Brandinger (@NormB) + Mar 2024 - Mar 2024 3. diff --git a/modules/stir_shaken/README b/modules/stir_shaken/README index 3f3fcd3d693..91235c229c1 100644 --- a/modules/stir_shaken/README +++ b/modules/stir_shaken/README @@ -489,17 +489,19 @@ Chapter 2. Contributors Table 2.1. Top contributors by DevScore^(1), authored commits^(2) and lines added/removed^(3) - Name DevScore Commits Lines ++ Lines -- - 1. Vlad Patrascu (@rvlad-patrascu) 58 26 3129 324 - 2. Liviu Chircu (@liviuchircu) 22 17 246 120 - 3. MonkeyTester 12 7 375 8 - 4. Maksym Sobolyev (@sobomax) 6 4 22 22 - 5. Razvan Crainea (@razvancrainea) 4 2 8 9 - 6. kworm83 4 2 7 2 - 7. tcresson 3 1 4 18 - 8. Kevin 3 1 2 2 - 9. John Burke (@john08burke) 2 1 4 0 - 10. Andriy Pylypenko (@bambyster) 2 1 1 0 + Name DevScore Commits Lines ++ Lines -- + 1. Vlad Patrascu (@rvlad-patrascu) 58 26 3129 324 + 2. Liviu Chircu (@liviuchircu) 22 17 246 120 + 3. MonkeyTester 12 7 375 8 + 4. Maksym Sobolyev (@sobomax) 6 4 22 22 + 5. Razvan Crainea (@razvancrainea) 5 3 21 25 + 6. kworm83 4 2 7 2 + 7. Bogdan-Andrei Iancu (@bogdan-iancu) 3 1 47 56 + 8. tcresson 3 1 4 18 + 9. Kevin 3 1 2 2 + 10. John Burke (@john08burke) 2 1 4 0 + + All remaining contributors: Andriy Pylypenko (@bambyster). (1) DevScore = author_commits + author_lines_added / (project_lines_added / project_commits) + author_lines_deleted @@ -520,17 +522,19 @@ Chapter 2. Contributors 2.2. By Commit Activity Table 2.2. Most recently active contributors^(1) to this module - Name Commit Activity - 1. Maksym Sobolyev (@sobomax) Jan 2021 - Nov 2023 - 2. tcresson Oct 2023 - Oct 2023 - 3. Liviu Chircu (@liviuchircu) Nov 2019 - Sep 2023 - 4. MonkeyTester Aug 2023 - Aug 2023 - 5. Kevin Feb 2022 - Feb 2022 - 6. kworm83 Jan 2022 - Jan 2022 - 7. Vlad Patrascu (@rvlad-patrascu) Oct 2019 - Aug 2021 - 8. Razvan Crainea (@razvancrainea) Jan 2021 - Jul 2021 - 9. John Burke (@john08burke) Apr 2021 - Apr 2021 - 10. Andriy Pylypenko (@bambyster) Aug 2020 - Aug 2020 + Name Commit Activity + 1. Razvan Crainea (@razvancrainea) Jan 2021 - Mar 2024 + 2. Bogdan-Andrei Iancu (@bogdan-iancu) Mar 2024 - Mar 2024 + 3. Maksym Sobolyev (@sobomax) Jan 2021 - Nov 2023 + 4. tcresson Oct 2023 - Oct 2023 + 5. Liviu Chircu (@liviuchircu) Nov 2019 - Sep 2023 + 6. MonkeyTester Aug 2023 - Aug 2023 + 7. Kevin Feb 2022 - Feb 2022 + 8. kworm83 Jan 2022 - Jan 2022 + 9. Vlad Patrascu (@rvlad-patrascu) Oct 2019 - Aug 2021 + 10. John Burke (@john08burke) Apr 2021 - Apr 2021 + + All remaining contributors: Andriy Pylypenko (@bambyster). (1) including any documentation-related commits, excluding merge commits diff --git a/modules/stir_shaken/doc/contributors.xml b/modules/stir_shaken/doc/contributors.xml index 3743b734909..dc39e6342fa 100644 --- a/modules/stir_shaken/doc/contributors.xml +++ b/modules/stir_shaken/doc/contributors.xml @@ -53,10 +53,10 @@ 5. Razvan Crainea (@razvancrainea) - 4 - 2 - 8 - 9 + 5 + 3 + 21 + 25 6. @@ -68,6 +68,14 @@ 7. + Bogdan-Andrei Iancu (@bogdan-iancu) + 3 + 1 + 47 + 56 + + + 8. tcresson 3 1 @@ -75,7 +83,7 @@ 18 - 8. + 9. Kevin 3 1 @@ -83,25 +91,17 @@ 2 - 9. + 10. John Burke (@john08burke) 2 1 4 0 - - 10. - Andriy Pylypenko (@bambyster) - 2 - 1 - 1 - 0 - - +All remaining contributors: Andriy Pylypenko (@bambyster). (1) DevScore = author_commits + author_lines_added / (project_lines_added / project_commits) + author_lines_deleted / (project_lines_deleted / project_commits) @@ -128,58 +128,58 @@ 1. + Razvan Crainea (@razvancrainea) + Jan 2021 - Mar 2024 + + + 2. + Bogdan-Andrei Iancu (@bogdan-iancu) + Mar 2024 - Mar 2024 + + + 3. Maksym Sobolyev (@sobomax) Jan 2021 - Nov 2023 - 2. + 4. tcresson Oct 2023 - Oct 2023 - 3. + 5. Liviu Chircu (@liviuchircu) Nov 2019 - Sep 2023 - 4. + 6. MonkeyTester Aug 2023 - Aug 2023 - 5. + 7. Kevin Feb 2022 - Feb 2022 - 6. + 8. kworm83 Jan 2022 - Jan 2022 - 7. + 9. Vlad Patrascu (@rvlad-patrascu) Oct 2019 - Aug 2021 - 8. - Razvan Crainea (@razvancrainea) - Jan 2021 - Jul 2021 - - - 9. + 10. John Burke (@john08burke) Apr 2021 - Apr 2021 - - 10. - Andriy Pylypenko (@bambyster) - Aug 2020 - Aug 2020 - - +All remaining contributors: Andriy Pylypenko (@bambyster). (1) including any documentation-related commits, excluding merge commits From c740b1ab2e355f8a86eb32f8d769ac53105865e2 Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Thu, 21 Mar 2024 15:42:41 +0200 Subject: [PATCH 67/79] http2d: Initial version New "http2d" module, providing an RFC 7540/9113 HTTP/2 server implementation, based on "nghttp2" library (https://nghttp2.org/). --- modules/http2d/Makefile | 11 + modules/http2d/http2d.c | 112 ++++++ modules/http2d/server.c | 792 ++++++++++++++++++++++++++++++++++++++++ modules/http2d/server.h | 13 + 4 files changed, 928 insertions(+) create mode 100644 modules/http2d/Makefile create mode 100644 modules/http2d/http2d.c create mode 100644 modules/http2d/server.c create mode 100644 modules/http2d/server.h diff --git a/modules/http2d/Makefile b/modules/http2d/Makefile new file mode 100644 index 00000000000..52b64456059 --- /dev/null +++ b/modules/http2d/Makefile @@ -0,0 +1,11 @@ +# WARNING: do not run this directly, it should be run by the master Makefile + +include ../../Makefile.defs +auto_gen= +NAME=http2d.so + +#DEFS +=-I$(LOCALBASE)/include +LIBS +=-L$(LOCALBASE)/lib -lssl -lcrypto -levent -lnghttp2 -levent_openssl + +include ../../Makefile.modules + diff --git a/modules/http2d/http2d.c b/modules/http2d/http2d.c new file mode 100644 index 00000000000..bc9b7876609 --- /dev/null +++ b/modules/http2d/http2d.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 OpenSIPS Solutions + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include + +#include "../../globals.h" +#include "../../sr_module.h" +#include "../../str.h" +#include "../../ut.h" +#include "../../mem/mem.h" + +#include "server.h" + +/* module functions */ +static int mod_init(); +static void mod_destroy(void); + +unsigned int h2_port = 9111; +char *h2_ip; +str h2_tls_cert = STR_NULL; +str h2_tls_key = STR_NULL; + + +static const proc_export_t procs[] = { + {"HTTP2D", 0, 0, http2_server, 1, PROC_FLAG_INITCHILD }, + {NULL, 0, 0, NULL, 0, 0} +}; + +/* Module parameters */ +static const param_export_t params[] = { + {"port", INT_PARAM, &h2_port}, + {"ip", STR_PARAM, &h2_ip}, + {"tls_cert_file", STR_PARAM, &h2_tls_cert.s}, + {"tls_key_file", STR_PARAM, &h2_tls_key.s}, + {NULL, 0, NULL} +}; + +/* MI commands */ +static const mi_export_t mi_cmds[] = { + {EMPTY_MI_EXPORT}, +}; + +/* Module exports */ +struct module_exports exports = { + "http2d", /* module name */ + MOD_TYPE_DEFAULT, /* class of this module */ + MODULE_VERSION, + DEFAULT_DLFLAGS, /* dlopen flags */ + 0, /* load function */ + NULL, /* OpenSIPS module dependencies */ + NULL, /* exported functions */ + 0, /* exported async functions */ + params, /* exported parameters */ + NULL, /* exported statistics */ + mi_cmds, /* exported MI functions */ + NULL, /* exported PV */ + NULL, /* exported transformations */ + procs, /* extra processes */ + 0, /* module pre-initialization function */ + mod_init, /* module initialization function */ + (response_function) NULL, /* response handling function */ + (destroy_function) mod_destroy, /* destroy function */ + NULL, /* per-child init function */ + NULL /* reload confirm function */ +}; + + +static int mod_init(void) +{ + if (!h2_tls_cert.s) { + LM_ERR("no TLS cert filepath provided (mandatory)\n"); + return -1; + } + + if (!h2_tls_key.s) { + LM_ERR("no TLS key filepath provided (mandatory)\n"); + return -1; + } + + if (!h2_ip) + h2_ip = "127.0.0.1"; + + h2_tls_cert.len = strlen(h2_tls_cert.s); + h2_tls_key.len = strlen(h2_tls_key.s); + + return 0; +} + + +static void mod_destroy(void) +{ + return; +} diff --git a/modules/http2d/server.c b/modules/http2d/server.c new file mode 100644 index 00000000000..5934f1dbbbf --- /dev/null +++ b/modules/http2d/server.c @@ -0,0 +1,792 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "../../ut.h" +#include "server.h" + +#ifdef __sgi +# define errx(exitcode, format, args...) \ + { \ + warnx(format, ##args); \ + exit(exitcode); \ + } +# define warn(format, args...) warnx(format ": %s", ##args, strerror(errno)) +# define warnx(format, args...) fprintf(stderr, format "\n", ##args) +#endif + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif /* HAVE_SYS_SOCKET_H */ +#ifdef HAVE_NETDB_H +# include +#endif /* HAVE_NETDB_H */ +#include +#include +#include +#include +#include +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ +#include +#ifndef __sgi +# include +#endif +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#define NGHTTP2_NO_SSIZE_T +#include + +#define OUTPUT_WOULDBLOCK_THRESHOLD (1 << 16) + +#define ARRLEN(x) (sizeof(x) / sizeof(x[0])) + +#define MAKE_NV(NAME, VALUE) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +struct app_context; +typedef struct app_context app_context; + +typedef struct http2_stream_data { + struct http2_stream_data *prev, *next; + char *request_path; + int32_t stream_id; + int fd; +} http2_stream_data; + +typedef struct http2_session_data { + struct http2_stream_data root; + struct bufferevent *bev; + app_context *app_ctx; + nghttp2_session *session; + char *client_addr; +} http2_session_data; + +struct app_context { + SSL_CTX *ssl_ctx; + struct event_base *evbase; +}; + +static int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + int rv; + (void)ssl; + (void)arg; + + rv = nghttp2_select_alpn(out, outlen, in, inlen); + + if (rv != 1) { + return SSL_TLSEXT_ERR_NOACK; + } + + return SSL_TLSEXT_ERR_OK; +} + +/* Create SSL_CTX. */ +static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) { + SSL_CTX *ssl_ctx; + + ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + errx(1, "Could not create SSL/TLS context: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + SSL_CTX_set_options(ssl_ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) { + errx(1, "SSL_CTX_set1_curves_list failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } +#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + { + EC_KEY *ecdh; + ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (!ecdh) { + errx(1, "EC_KEY_new_by_curv_name failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); + EC_KEY_free(ecdh); + } +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) { + errx(1, "Could not read private key file %s", key_file); + } + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { + errx(1, "Could not read certificate file %s", cert_file); + } + + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL); + + return ssl_ctx; +} + +/* Create SSL object */ +static SSL *create_ssl(SSL_CTX *ssl_ctx) { + SSL *ssl; + ssl = SSL_new(ssl_ctx); + if (!ssl) { + errx(1, "Could not create SSL/TLS session object: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + return ssl; +} + +static void add_stream(http2_session_data *session_data, + http2_stream_data *stream_data) { + stream_data->next = session_data->root.next; + session_data->root.next = stream_data; + stream_data->prev = &session_data->root; + if (stream_data->next) { + stream_data->next->prev = stream_data; + } +} + +static void remove_stream(http2_session_data *session_data, + http2_stream_data *stream_data) { + (void)session_data; + + stream_data->prev->next = stream_data->next; + if (stream_data->next) { + stream_data->next->prev = stream_data->prev; + } +} + +static http2_stream_data * +create_http2_stream_data(http2_session_data *session_data, int32_t stream_id) { + http2_stream_data *stream_data; + stream_data = malloc(sizeof(http2_stream_data)); + memset(stream_data, 0, sizeof(http2_stream_data)); + stream_data->stream_id = stream_id; + stream_data->fd = -1; + + add_stream(session_data, stream_data); + return stream_data; +} + +static void delete_http2_stream_data(http2_stream_data *stream_data) { + if (stream_data->fd != -1) { + close(stream_data->fd); + } + free(stream_data->request_path); + free(stream_data); +} + +static http2_session_data *create_http2_session_data(app_context *app_ctx, + int fd, + struct sockaddr *addr, + int addrlen) { + int rv; + http2_session_data *session_data; + SSL *ssl; + char host[NI_MAXHOST]; + int val = 1; + + ssl = create_ssl(app_ctx->ssl_ctx); + session_data = malloc(sizeof(http2_session_data)); + memset(session_data, 0, sizeof(http2_session_data)); + session_data->app_ctx = app_ctx; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); + session_data->bev = bufferevent_openssl_socket_new( + app_ctx->evbase, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, + BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); + bufferevent_enable(session_data->bev, EV_READ | EV_WRITE); + rv = getnameinfo(addr, (socklen_t)addrlen, host, sizeof(host), NULL, 0, + NI_NUMERICHOST); + if (rv != 0) { + session_data->client_addr = strdup("(unknown)"); + } else { + session_data->client_addr = strdup(host); + } + + return session_data; +} + +static void delete_http2_session_data(http2_session_data *session_data) { + http2_stream_data *stream_data; + SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev); + fprintf(stderr, "%s disconnected\n", session_data->client_addr); + if (ssl) { + SSL_shutdown(ssl); + } + bufferevent_free(session_data->bev); + nghttp2_session_del(session_data->session); + for (stream_data = session_data->root.next; stream_data;) { + http2_stream_data *next = stream_data->next; + delete_http2_stream_data(stream_data); + stream_data = next; + } + free(session_data->client_addr); + free(session_data); +} + +/* Serialize the frame and send (or buffer) the data to + bufferevent. */ +static int session_send(http2_session_data *session_data) { + int rv; + rv = nghttp2_session_send(session_data->session); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; +} + +/* Read the data in the bufferevent and feed them into nghttp2 library + function. Invocation of nghttp2_session_mem_recv2() may make + additional pending frames, so call session_send() at the end of the + function. */ +static int session_recv(http2_session_data *session_data) { + nghttp2_ssize readlen; + struct evbuffer *input = bufferevent_get_input(session_data->bev); + size_t datalen = evbuffer_get_length(input); + unsigned char *data = evbuffer_pullup(input, -1); + + readlen = nghttp2_session_mem_recv2(session_data->session, data, datalen); + if (readlen < 0) { + warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); + return -1; + } + if (evbuffer_drain(input, (size_t)readlen) != 0) { + warnx("Fatal error: evbuffer_drain failed"); + return -1; + } + if (session_send(session_data) != 0) { + return -1; + } + return 0; +} + +static nghttp2_ssize send_callback(nghttp2_session *session, + const uint8_t *data, size_t length, + int flags, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + struct bufferevent *bev = session_data->bev; + (void)session; + (void)flags; + + /* Avoid excessive buffering in server side. */ + if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >= + OUTPUT_WOULDBLOCK_THRESHOLD) { + return NGHTTP2_ERR_WOULDBLOCK; + } + bufferevent_write(bev, data, length); + return (nghttp2_ssize)length; +} + +/* Returns nonzero if the string |s| ends with the substring |sub| */ +static int ends_with(const char *s, const char *sub) { + size_t slen = strlen(s); + size_t sublen = strlen(sub); + if (slen < sublen) { + return 0; + } + return memcmp(s + slen - sublen, sub, sublen) == 0; +} + +/* Returns int value of hex string character |c| */ +static uint8_t hex_to_uint(uint8_t c) { + if ('0' <= c && c <= '9') { + return (uint8_t)(c - '0'); + } + if ('A' <= c && c <= 'F') { + return (uint8_t)(c - 'A' + 10); + } + if ('a' <= c && c <= 'f') { + return (uint8_t)(c - 'a' + 10); + } + return 0; +} + +/* Decodes percent-encoded byte string |value| with length |valuelen| + and returns the decoded byte string in allocated buffer. The return + value is NULL terminated. The caller must free the returned + string. */ +static char *percent_decode(const uint8_t *value, size_t valuelen) { + char *res; + + res = malloc(valuelen + 1); + if (valuelen > 3) { + size_t i, j; + for (i = 0, j = 0; i < valuelen - 2;) { + if (value[i] != '%' || !isxdigit(value[i + 1]) || + !isxdigit(value[i + 2])) { + res[j++] = (char)value[i++]; + continue; + } + res[j++] = + (char)((hex_to_uint(value[i + 1]) << 4) + hex_to_uint(value[i + 2])); + i += 3; + } + memcpy(&res[j], &value[i], 2); + res[j + 2] = '\0'; + } else { + memcpy(res, value, valuelen); + res[valuelen] = '\0'; + } + return res; +} + +/* a callback of type "nghttp2_data_source_read_callback2" */ +static nghttp2_ssize file_read_callback(nghttp2_session *session, + int32_t stream_id, uint8_t *buf, + size_t length, uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data) { + int fd = source->fd; + ssize_t r; + (void)session; + (void)stream_id; + (void)user_data; + + while ((r = read(fd, buf, length)) == -1 && errno == EINTR) + ; + if (r == -1) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + if (r == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + return (nghttp2_ssize)r; +} + +static int send_response(nghttp2_session *session, int32_t stream_id, + nghttp2_nv *nva, size_t nvlen, int fd) { + int rv; + nghttp2_data_provider2 data_prd; + data_prd.source.fd = fd; + data_prd.read_callback = file_read_callback; + + rv = nghttp2_submit_response2(session, stream_id, nva, nvlen, &data_prd); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; +} + +static const char ERROR_HTML[] = "404" + "

404 Not Found

"; + +static int error_reply(nghttp2_session *session, + http2_stream_data *stream_data) { + int rv; + ssize_t writelen; + int pipefd[2]; + nghttp2_nv hdrs[] = {MAKE_NV(":status", "404")}; + + rv = pipe(pipefd); + if (rv != 0) { + warn("Could not create pipe"); + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, + stream_data->stream_id, + NGHTTP2_INTERNAL_ERROR); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; + } + + writelen = write(pipefd[1], ERROR_HTML, sizeof(ERROR_HTML) - 1); + close(pipefd[1]); + + if (writelen != sizeof(ERROR_HTML) - 1) { + close(pipefd[0]); + return -1; + } + + stream_data->fd = pipefd[0]; + + if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), + pipefd[0]) != 0) { + close(pipefd[0]); + return -1; + } + return 0; +} + +/* nghttp2_on_header_callback: Called when nghttp2 library emits + single header name/value pair. */ +static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, + size_t valuelen, uint8_t flags, void *user_data) { + http2_stream_data *stream_data; + const char PATH[] = ":path"; + (void)flags; + (void)user_data; + + LM_INFO("yay, header(%d)!!! %.*s == %.*s\n", frame->hd.type, (int)namelen, name, (int)valuelen, value); + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + break; + } + LM_INFO("A\n"); + stream_data = + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + if (!stream_data || stream_data->request_path) { + break; + } + LM_INFO("B\n"); + if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { + size_t j; + for (j = 0; j < valuelen && value[j] != '?'; ++j) + ; + stream_data->request_path = percent_decode(value, j); + } + + break; + } + return 0; +} + +static int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); + nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, + stream_data); + return 0; +} + +/* Minimum check for directory traversal. Returns nonzero if it is + safe. */ +static int check_path(const char *path) { + /* We don't like '\' in url. */ + return path[0] && path[0] == '/' && strchr(path, '\\') == NULL && + strstr(path, "/../") == NULL && strstr(path, "/./") == NULL && + !ends_with(path, "/..") && !ends_with(path, "/."); +} + +static int on_request_recv(nghttp2_session *session, + http2_session_data *session_data, + http2_stream_data *stream_data) { + int fd; + nghttp2_nv hdrs[] = {MAKE_NV(":status", "200")}; + char *rel_path; + + if (!stream_data->request_path) { + if (error_reply(session, stream_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; + } + LM_INFO("%s GET %s\n", session_data->client_addr, + stream_data->request_path); + if (!check_path(stream_data->request_path)) { + if (error_reply(session, stream_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; + } + for (rel_path = stream_data->request_path; *rel_path == '/'; ++rel_path) + ; + fd = open(rel_path, O_RDONLY); + if (fd == -1) { + if (error_reply(session, stream_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; + } + stream_data->fd = fd; + + if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), fd) != + 0) { + close(fd); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +static int on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; + switch (frame->hd.type) { + case NGHTTP2_DATA: + case NGHTTP2_HEADERS: + /* Check that the client request has finished */ + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + stream_data = + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + /* For DATA and HEADERS frame, this callback may be called after + on_stream_close_callback. Check that stream still alive. */ + if (!stream_data) { + return 0; + } + return on_request_recv(session, session_data, stream_data); + } + break; + default: + break; + } + return 0; +} + +static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; + (void)error_code; + + stream_data = nghttp2_session_get_stream_user_data(session, stream_id); + if (!stream_data) { + return 0; + } + remove_stream(session_data, stream_data); + delete_http2_stream_data(stream_data); + return 0; +} + +static void initialize_nghttp2_session(http2_session_data *session_data) { + nghttp2_session_callbacks *callbacks; + + nghttp2_session_callbacks_new(&callbacks); + + nghttp2_session_callbacks_set_send_callback2(callbacks, send_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, + data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + + nghttp2_session_server_new(&session_data->session, callbacks, session_data); + + nghttp2_session_callbacks_del(callbacks); +} + +/* Send HTTP/2 client connection header, which includes 24 bytes + magic octets and SETTINGS frame */ +static int send_server_connection_header(http2_session_data *session_data) { + nghttp2_settings_entry iv[1] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; + int rv; + + rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv, + ARRLEN(iv)); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; +} + +/* readcb for bufferevent after client connection header was + checked. */ +static void readcb(struct bufferevent *bev, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + (void)bev; + + if (session_recv(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } +} + +/* writecb for bufferevent. To greaceful shutdown after sending or + receiving GOAWAY, we check the some conditions on the nghttp2 + library and output buffer of bufferevent. If it indicates we have + no business to this session, tear down the connection. If the + connection is not going to shutdown, we call session_send() to + process pending data in the output buffer. This is necessary + because we have a threshold on the buffer size to avoid too much + buffering. See send_callback(). */ +static void writecb(struct bufferevent *bev, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) { + return; + } + if (nghttp2_session_want_read(session_data->session) == 0 && + nghttp2_session_want_write(session_data->session) == 0) { + delete_http2_session_data(session_data); + return; + } + if (session_send(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } +} + +/* eventcb for bufferevent */ +static void eventcb(struct bufferevent *bev, short events, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + if (events & BEV_EVENT_CONNECTED) { + const unsigned char *alpn = NULL; + unsigned int alpnlen = 0; + SSL *ssl; + (void)bev; + + fprintf(stderr, "%s connected\n", session_data->client_addr); + + ssl = bufferevent_openssl_get_ssl(session_data->bev); + + SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); + + if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { + fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr); + delete_http2_session_data(session_data); + return; + } + + initialize_nghttp2_session(session_data); + + if (send_server_connection_header(session_data) != 0 || + session_send(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } + + return; + } + if (events & BEV_EVENT_EOF) { + fprintf(stderr, "%s EOF\n", session_data->client_addr); + } else if (events & BEV_EVENT_ERROR) { + fprintf(stderr, "%s network error\n", session_data->client_addr); + } else if (events & BEV_EVENT_TIMEOUT) { + fprintf(stderr, "%s timeout\n", session_data->client_addr); + } + delete_http2_session_data(session_data); +} + +/* callback for evconnlistener */ +static void acceptcb(struct evconnlistener *listener, int fd, + struct sockaddr *addr, int addrlen, void *arg) { + app_context *app_ctx = (app_context *)arg; + http2_session_data *session_data; + (void)listener; + + session_data = create_http2_session_data(app_ctx, fd, addr, addrlen); + + bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data); +} + +static void start_listen(struct event_base *evbase, const char *service, + app_context *app_ctx) { + int rv; + struct addrinfo hints; + struct addrinfo *res, *rp; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif /* AI_ADDRCONFIG */ + + rv = getaddrinfo(h2_ip, service, &hints, &res); + if (rv != 0) { + errx(1, "Could not resolve server address"); + } + for (rp = res; rp; rp = rp->ai_next) { + struct evconnlistener *listener; + listener = evconnlistener_new_bind( + evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, + 16, rp->ai_addr, (int)rp->ai_addrlen); + if (listener) { + freeaddrinfo(res); + + return; + } + } + errx(1, "Could not start listener"); +} + +static void initialize_app_context(app_context *app_ctx, SSL_CTX *ssl_ctx, + struct event_base *evbase) { + memset(app_ctx, 0, sizeof(app_context)); + app_ctx->ssl_ctx = ssl_ctx; + app_ctx->evbase = evbase; +} + +static void run(const char *service, const char *key_file, + const char *cert_file) { + SSL_CTX *ssl_ctx; + app_context app_ctx; + struct event_base *evbase; + + ssl_ctx = create_ssl_ctx(key_file, cert_file); + evbase = event_base_new(); + initialize_app_context(&app_ctx, ssl_ctx, evbase); + start_listen(evbase, service, &app_ctx); + + LM_DBG("event loop start\n"); + event_base_loop(evbase, 0); + + event_base_free(evbase); + SSL_CTX_free(ssl_ctx); +} + +void http2_server(int rank) +{ + struct sigaction act; + + memset(&act, 0, sizeof act); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, NULL); + + LM_INFO("HTTP2 server starting\n"); + run(int2str(h2_port, NULL), h2_tls_key.s, h2_tls_cert.s); + LM_ERR("HTTP2 server exiting!\n"); +} + diff --git a/modules/http2d/server.h b/modules/http2d/server.h new file mode 100644 index 00000000000..b9acc7a9c26 --- /dev/null +++ b/modules/http2d/server.h @@ -0,0 +1,13 @@ +#ifndef HTTP2_SERVER +#define HTTP2_SERVER + +#include "../../str.h" + +extern unsigned int h2_port; +extern char *h2_ip; +extern str h2_tls_cert; +extern str h2_tls_key; + +void http2_server(int rank); + +#endif /* HTTP2_SERVER */ From e69f3250464e275dc91f0780532d1a01b2dea25f Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Thu, 21 Mar 2024 18:08:35 +0200 Subject: [PATCH 68/79] http2d: Collect headers & body using cbs; Raise event --- modules/http2d/h2_evi.c | 120 +++++++++++++++++ modules/http2d/h2_evi.h | 33 +++++ modules/http2d/http2d.c | 10 +- modules/http2d/server.c | 278 ++++++++++++++++++++++++++++------------ 4 files changed, 358 insertions(+), 83 deletions(-) create mode 100644 modules/http2d/h2_evi.c create mode 100644 modules/http2d/h2_evi.h diff --git a/modules/http2d/h2_evi.c b/modules/http2d/h2_evi.c new file mode 100644 index 00000000000..8cdf89569db --- /dev/null +++ b/modules/http2d/h2_evi.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2024 OpenSIPS Solutions + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA + */ + +#include "h2_evi.h" + +#include "../../dprint.h" +#include "../../ut.h" + +static event_id_t h2ev_req_id = EVI_ERROR; /* E_HTTP2_REQUEST */ +static evi_params_p h2ev_req_params; + +static evi_param_p h2ev_req_param_method; +static evi_param_p h2ev_req_param_path; +static evi_param_p h2ev_req_param_headers; +static evi_param_p h2ev_req_param_body; +static evi_param_p h2ev_req_param_msg; + +str h2ev_req_pname_method = str_init("method"); +str h2ev_req_pname_path = str_init("path"); +str h2ev_req_pname_headers = str_init("headers"); +str h2ev_req_pname_body = str_init("body"); +str h2ev_req_pname_msg = str_init("_h2msg_"); + + +int h2_init_evi(void) +{ + /* First publish the events */ + h2ev_req_id = evi_publish_event(str_init(H2EV_REQ_NAME)); + if (h2ev_req_id == EVI_ERROR) { + LM_ERR("cannot register 'request' event\n"); + return -1; + } + + h2ev_req_params = pkg_malloc(sizeof *h2ev_req_params); + if (!h2ev_req_params) { + LM_ERR("oom\n"); + return -1; + } + memset(h2ev_req_params, 0, sizeof *h2ev_req_params); + + h2ev_req_param_method = evi_param_create(h2ev_req_params, &h2ev_req_pname_method); + h2ev_req_param_path = evi_param_create(h2ev_req_params, &h2ev_req_pname_path); + h2ev_req_param_headers = evi_param_create(h2ev_req_params, &h2ev_req_pname_headers); + h2ev_req_param_body = evi_param_create(h2ev_req_params, &h2ev_req_pname_body); + h2ev_req_param_msg = evi_param_create(h2ev_req_params, &h2ev_req_pname_msg); + if (!h2ev_req_param_method || !h2ev_req_param_path + || !h2ev_req_param_headers || !h2ev_req_param_body + || !h2ev_req_param_msg) { + LM_ERR("failed to create EVI params\n"); + return -1; + } + + return 0; +} + + +/** + * The purpose of this dispatched job is for the logic to be ran by a + * process other than the Diameter peer, since PROC_MODULE workers have NULL + * @sroutes, causing a crash when attempting to raise a script event + */ +void h2_raise_event_request(const char *method, const char *path, + const char *headers_json, const str *body, void *msg) +{ + char buf[sizeof(long)*2 + 1], *p = buf; + int sz = sizeof(buf); + str st; + + init_str(&st, method); + if (evi_param_set_str(h2ev_req_param_method, &st) < 0) { + LM_ERR("failed to set 'method'\n"); + return; + } + + init_str(&st, path); + if (evi_param_set_str(h2ev_req_param_path, &st) < 0) { + LM_ERR("failed to set 'path'\n"); + return; + } + + init_str(&st, headers_json); + if (evi_param_set_str(h2ev_req_param_headers, &st) < 0) { + LM_ERR("failed to set 'headers_json'\n"); + return; + } + + if (evi_param_set_str(h2ev_req_param_body, body) < 0) { + LM_ERR("failed to set 'body'\n"); + return; + } + + int64_2reverse_hex(&p, &sz, (unsigned long)msg); + *p = '\0'; + init_str(&st, buf); + + if (evi_param_set_str(h2ev_req_param_msg, &st) < 0) { + LM_ERR("failed to set '_h2msg_'\n"); + return; + } + + if (evi_raise_event(h2ev_req_id, h2ev_req_params) < 0) + LM_ERR("failed to raise '"H2EV_REQ_NAME"' event\n"); +} diff --git a/modules/http2d/h2_evi.h b/modules/http2d/h2_evi.h new file mode 100644 index 00000000000..ef48689489f --- /dev/null +++ b/modules/http2d/h2_evi.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 OpenSIPS Solutions + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA + */ + +#ifndef __H2_EVI__ +#define __H2_EVI__ + +#include "../../str.h" + +#define H2EV_REQ_NAME "E_HTTP2_REQUEST" +extern str h2ev_req_pname_msg; + +int h2_init_evi(void); +void h2_raise_event_request(const char *method, const char *path, + const char *headers_json, const str *body, void *msg); + +#endif /* __H2_EVI__ */ diff --git a/modules/http2d/http2d.c b/modules/http2d/http2d.c index bc9b7876609..e9559c0262b 100644 --- a/modules/http2d/http2d.c +++ b/modules/http2d/http2d.c @@ -29,6 +29,7 @@ #include "../../mem/mem.h" #include "server.h" +#include "h2_evi.h" /* module functions */ static int mod_init(); @@ -38,10 +39,11 @@ unsigned int h2_port = 9111; char *h2_ip; str h2_tls_cert = STR_NULL; str h2_tls_key = STR_NULL; +unsigned int max_headers_size = 8192; /* B */ static const proc_export_t procs[] = { - {"HTTP2D", 0, 0, http2_server, 1, PROC_FLAG_INITCHILD }, + {"HTTP2D", 0, 0, http2_server, 1, PROC_FLAG_INITCHILD|PROC_FLAG_NEEDS_SCRIPT }, {NULL, 0, 0, NULL, 0, 0} }; @@ -51,6 +53,7 @@ static const param_export_t params[] = { {"ip", STR_PARAM, &h2_ip}, {"tls_cert_file", STR_PARAM, &h2_tls_cert.s}, {"tls_key_file", STR_PARAM, &h2_tls_key.s}, + {"max_headers_size", INT_PARAM, &max_headers_size}, {NULL, 0, NULL} }; @@ -102,6 +105,11 @@ static int mod_init(void) h2_tls_cert.len = strlen(h2_tls_cert.s); h2_tls_key.len = strlen(h2_tls_key.s); + if (h2_init_evi() != 0) { + LM_ERR("failed to init EVI structures\n"); + return -1; + } + return 0; } diff --git a/modules/http2d/server.c b/modules/http2d/server.c index 5934f1dbbbf..08acb3fc6f2 100644 --- a/modules/http2d/server.c +++ b/modules/http2d/server.c @@ -24,7 +24,13 @@ */ #include "../../ut.h" +#include "../../lib/list.h" +#include "../../lib/cJSON.h" + #include "server.h" +#include "h2_evi.h" + +extern unsigned int max_headers_size; #ifdef __sgi # define errx(exitcode, format, args...) \ @@ -88,14 +94,20 @@ struct app_context; typedef struct app_context app_context; typedef struct http2_stream_data { - struct http2_stream_data *prev, *next; - char *request_path; int32_t stream_id; + + char *method; + char *path; + cJSON *hdrs; + unsigned hdrs_len; + str data; int fd; + + struct list_head list; } http2_stream_data; typedef struct http2_session_data { - struct http2_stream_data root; + struct list_head root; struct bufferevent *bev; app_context *app_ctx; nghttp2_session *session; @@ -179,30 +191,34 @@ static SSL *create_ssl(SSL_CTX *ssl_ctx) { static void add_stream(http2_session_data *session_data, http2_stream_data *stream_data) { - stream_data->next = session_data->root.next; - session_data->root.next = stream_data; - stream_data->prev = &session_data->root; - if (stream_data->next) { - stream_data->next->prev = stream_data; - } + list_add(&stream_data->list, &session_data->root); } static void remove_stream(http2_session_data *session_data, http2_stream_data *stream_data) { (void)session_data; - - stream_data->prev->next = stream_data->next; - if (stream_data->next) { - stream_data->next->prev = stream_data->prev; - } + list_del(&stream_data->list); } static http2_stream_data * create_http2_stream_data(http2_session_data *session_data, int32_t stream_id) { http2_stream_data *stream_data; - stream_data = malloc(sizeof(http2_stream_data)); - memset(stream_data, 0, sizeof(http2_stream_data)); + + stream_data = pkg_malloc(sizeof *stream_data); + if (!stream_data) { + LM_ERR("oom\n"); + return NULL; + } + + memset(stream_data, 0, sizeof *stream_data); stream_data->stream_id = stream_id; + stream_data->hdrs = cJSON_CreateObject(); + if (!stream_data->hdrs) { + pkg_free(stream_data); + LM_ERR("oom\n"); + return NULL; + } + stream_data->fd = -1; add_stream(session_data, stream_data); @@ -213,8 +229,11 @@ static void delete_http2_stream_data(http2_stream_data *stream_data) { if (stream_data->fd != -1) { close(stream_data->fd); } - free(stream_data->request_path); - free(stream_data); + + free(stream_data->path); + cJSON_Delete(stream_data->hdrs); + pkg_free(stream_data->data.s); + pkg_free(stream_data); } static http2_session_data *create_http2_session_data(app_context *app_ctx, @@ -228,8 +247,10 @@ static http2_session_data *create_http2_session_data(app_context *app_ctx, int val = 1; ssl = create_ssl(app_ctx->ssl_ctx); - session_data = malloc(sizeof(http2_session_data)); - memset(session_data, 0, sizeof(http2_session_data)); + session_data = malloc(sizeof *session_data); + memset(session_data, 0, sizeof *session_data); + INIT_LIST_HEAD(&session_data->root); + session_data->app_ctx = app_ctx; setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); session_data->bev = bufferevent_openssl_socket_new( @@ -250,17 +271,22 @@ static http2_session_data *create_http2_session_data(app_context *app_ctx, static void delete_http2_session_data(http2_session_data *session_data) { http2_stream_data *stream_data; SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev); + struct list_head *it, *aux; + fprintf(stderr, "%s disconnected\n", session_data->client_addr); if (ssl) { SSL_shutdown(ssl); } bufferevent_free(session_data->bev); nghttp2_session_del(session_data->session); - for (stream_data = session_data->root.next; stream_data;) { - http2_stream_data *next = stream_data->next; + + list_for_each_safe (it, aux, &session_data->root) { + stream_data = list_entry(it, http2_stream_data, list); + + list_del(&stream_data->list); delete_http2_stream_data(stream_data); - stream_data = next; } + free(session_data->client_addr); free(session_data); } @@ -451,58 +477,6 @@ static int error_reply(nghttp2_session *session, return 0; } -/* nghttp2_on_header_callback: Called when nghttp2 library emits - single header name/value pair. */ -static int on_header_callback(nghttp2_session *session, - const nghttp2_frame *frame, const uint8_t *name, - size_t namelen, const uint8_t *value, - size_t valuelen, uint8_t flags, void *user_data) { - http2_stream_data *stream_data; - const char PATH[] = ":path"; - (void)flags; - (void)user_data; - - LM_INFO("yay, header(%d)!!! %.*s == %.*s\n", frame->hd.type, (int)namelen, name, (int)valuelen, value); - - switch (frame->hd.type) { - case NGHTTP2_HEADERS: - if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { - break; - } - LM_INFO("A\n"); - stream_data = - nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); - if (!stream_data || stream_data->request_path) { - break; - } - LM_INFO("B\n"); - if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { - size_t j; - for (j = 0; j < valuelen && value[j] != '?'; ++j) - ; - stream_data->request_path = percent_decode(value, j); - } - - break; - } - return 0; -} - -static int on_begin_headers_callback(nghttp2_session *session, - const nghttp2_frame *frame, - void *user_data) { - http2_session_data *session_data = (http2_session_data *)user_data; - http2_stream_data *stream_data; - - if (frame->hd.type != NGHTTP2_HEADERS || - frame->headers.cat != NGHTTP2_HCAT_REQUEST) { - return 0; - } - stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); - nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, - stream_data); - return 0; -} /* Minimum check for directory traversal. Returns nonzero if it is safe. */ @@ -518,23 +492,31 @@ static int on_request_recv(nghttp2_session *session, http2_stream_data *stream_data) { int fd; nghttp2_nv hdrs[] = {MAKE_NV(":status", "200")}; - char *rel_path; + char *rel_path, *H; - if (!stream_data->request_path) { + if (!stream_data->path) { if (error_reply(session, stream_data) != 0) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } - LM_INFO("%s GET %s\n", session_data->client_addr, - stream_data->request_path); - if (!check_path(stream_data->request_path)) { + LM_INFO("%s GET %s (stream_id: %d)\n", session_data->client_addr, + stream_data->path, stream_data->stream_id); + LM_INFO("body: %.*s %d\n", stream_data->data.len, stream_data->data.s, stream_data->data.len); + + if (!check_path(stream_data->path)) { if (error_reply(session, stream_data) != 0) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } - for (rel_path = stream_data->request_path; *rel_path == '/'; ++rel_path) + LM_INFO("A\n"); + + H = cJSON_PrintUnformatted(stream_data->hdrs); + h2_raise_event_request(stream_data->method, stream_data->path, H, &stream_data->data, NULL); + cJSON_PurgeString(H); + + for (rel_path = stream_data->path; *rel_path == '/'; ++rel_path) ; fd = open(rel_path, O_RDONLY); if (fd == -1) { @@ -545,6 +527,8 @@ static int on_request_recv(nghttp2_session *session, } stream_data->fd = fd; + LM_INFO("body: %.*s\n", stream_data->data.len, stream_data->data.s); + if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), fd) != 0) { close(fd); @@ -553,6 +537,7 @@ static int on_request_recv(nghttp2_session *session, return 0; } + static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { http2_session_data *session_data = (http2_session_data *)user_data; @@ -560,10 +545,12 @@ static int on_frame_recv_callback(nghttp2_session *session, switch (frame->hd.type) { case NGHTTP2_DATA: case NGHTTP2_HEADERS: + LM_DBG("h2 header [%d], %p %ld\n", frame->hd.type, frame->headers.nva, frame->headers.nvlen); /* Check that the client request has finished */ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { stream_data = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + LM_DBG("END STREAM, data: %p\n", stream_data); /* For DATA and HEADERS frame, this callback may be called after on_stream_close_callback. Check that stream still alive. */ if (!stream_data) { @@ -578,6 +565,133 @@ static int on_frame_recv_callback(nghttp2_session *session, return 0; } + +int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, size_t len, void *user_data) +{ + http2_stream_data *stream_data; + str *body; + int prevsz; + + stream_data = + nghttp2_session_get_stream_user_data(session, stream_id); + + body = &stream_data->data; + prevsz = body->len; + + if (pkg_str_extend(body, body->len + len) != 0) { + LM_ERR("out of PKG memory\n"); + return -1; + } + + memcpy(body->s+prevsz, data, len); + LM_DBG("stored %zu bytes\n", len); + + return 0; +} + + +/* nghttp2_on_header_callback: Called when nghttp2 library emits + single header name/value pair. */ +static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *_value, + size_t valuelen, uint8_t flags, void *user_data) { + http2_stream_data *stream_data; + const char PATH[] = ":path", METHOD[] = ":method"; + char *value = (char *)_value; + (void)flags; + (void)user_data; + + if (frame->hd.type != NGHTTP2_HEADERS + || frame->headers.cat != NGHTTP2_HCAT_REQUEST) + return 0; + + stream_data = + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + if (!stream_data) { + LM_ERR("failed to fetch data for stream %d\n", frame->hd.stream_id); + return 0; + } + + LM_DBG("received hdr(%d) on stream %d: '%.*s' = '%.*s' (%p)\n", frame->hd.type, + stream_data->stream_id, (int)namelen, name, (int)valuelen, value, stream_data); + + if (stream_data->path) + goto store_hdr; + + if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { + size_t j; + for (j = 0; j < valuelen && _value[j] != '?'; ++j) + ; + stream_data->path = percent_decode(_value, j); + + LM_DBG("detected ':path' header, decoded value: '%s'\n", stream_data->path); + + value = stream_data->path; + valuelen = strlen(value); + } + +store_hdr: + if (stream_data->hdrs_len + namelen + valuelen > max_headers_size) { + LM_ERR("max_headers_size exceeded (%d), skipping header: %s\n", + max_headers_size, name); + return 0; + } + + { + cJSON *val = cJSON_CreateStr(value, valuelen); + str key = {(char *)name, namelen}; + + if (!val) { + LM_ERR("oom\n"); + return 0; + } + + _cJSON_AddItemToObject(stream_data->hdrs, &key, val); + if (!val->string) { + LM_ERR("oom\n"); + cJSON_Delete(val); + return 0; + } + + stream_data->hdrs_len += namelen + valuelen; + + if (!stream_data->method && namelen == sizeof(METHOD) - 1 + && memcmp(METHOD, name, namelen) == 0) + stream_data->method = val->valuestring; + } + + return 0; +} + +static int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); + if (!stream_data) { + LM_ERR("failed to allocate stream data\n"); + return -1; + } + + LM_DBG("------------ BEGIN HEADERS (data: %p, stream_id: %d) ----------\n", stream_data, frame->hd.stream_id); + if (nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, + stream_data) < 0) { + LM_ERR("failed to set user data\n"); + return -1; + } + + return 0; +} + static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data) { http2_session_data *session_data = (http2_session_data *)user_data; @@ -606,7 +720,7 @@ static void initialize_nghttp2_session(http2_session_data *session_data) { nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback); nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, - data_chunk_recv_callback); + on_data_chunk_recv_callback); nghttp2_session_callbacks_set_on_begin_headers_callback( callbacks, on_begin_headers_callback); From accdcc7f3cda6c6f9b5e25478d71bd237b4602d3 Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Fri, 22 Mar 2024 14:41:06 +0200 Subject: [PATCH 69/79] http2d: Add support for HTTP/2 responses in opensips.cfg ... via the new http2_send_response(code, [hdrs], [body]) function. --- lib/cJSON.h | 4 +- modules/aaa_diameter/aaa_diameter.c | 2 +- modules/aaa_diameter/dm_impl.c | 2 +- modules/http2d/h2_evi.c | 27 ++-- modules/http2d/h2_evi.h | 3 +- modules/http2d/http2d.c | 192 ++++++++++++++++++++++- modules/http2d/server.c | 232 ++++++++++++++++++++++------ modules/http2d/server.h | 17 ++ sr_module.c | 1 - 9 files changed, 410 insertions(+), 70 deletions(-) diff --git a/lib/cJSON.h b/lib/cJSON.h index a65e1106437..627bc794d7b 100644 --- a/lib/cJSON.h +++ b/lib/cJSON.h @@ -153,7 +153,9 @@ extern cJSON_Hooks sys_mem_hooks; extern cJSON_Hooks shm_mem_hooks; -/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. + * The input @value can be safely freed immediately after the parsing. + * Call cJSON_Delete when finished. */ extern cJSON *cJSON_Parse(const char *value); /* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */ extern char *cJSON_Print(const cJSON *item); diff --git a/modules/aaa_diameter/aaa_diameter.c b/modules/aaa_diameter/aaa_diameter.c index 02a47a683e7..4888be2ff5d 100644 --- a/modules/aaa_diameter/aaa_diameter.c +++ b/modules/aaa_diameter/aaa_diameter.c @@ -454,7 +454,7 @@ static int dm_send_answer(struct sip_msg *msg, str *avp_json, int *is_error) } cJSON_Delete(avps); - return 0; + return 1; error: _dm_destroy_message(dmsg); diff --git a/modules/aaa_diameter/dm_impl.c b/modules/aaa_diameter/dm_impl.c index 6387c58d775..4bf8470c3ff 100644 --- a/modules/aaa_diameter/dm_impl.c +++ b/modules/aaa_diameter/dm_impl.c @@ -2028,7 +2028,7 @@ int _dm_send_message(aaa_conn *_, aaa_message *msg, struct dm_cond **reply_cond) gettimeofday(&now, NULL); wait_time.tv_sec = dm_answer_timeout / 1000; wait_time.tv_usec = dm_answer_timeout % 1000 * 1000UL; - LM_DBG("awaiting auth reply (%ld s, %ld us)...\n", wait_time.tv_sec, wait_time.tv_usec); + LM_DBG("awaiting reply (%ld s, %ld us)...\n", wait_time.tv_sec, wait_time.tv_usec); timeradd(&now, &wait_time, &res); diff --git a/modules/http2d/h2_evi.c b/modules/http2d/h2_evi.c index 8cdf89569db..6d30a983992 100644 --- a/modules/http2d/h2_evi.c +++ b/modules/http2d/h2_evi.c @@ -18,6 +18,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA */ +#include "server.h" #include "h2_evi.h" #include "../../dprint.h" @@ -30,13 +31,11 @@ static evi_param_p h2ev_req_param_method; static evi_param_p h2ev_req_param_path; static evi_param_p h2ev_req_param_headers; static evi_param_p h2ev_req_param_body; -static evi_param_p h2ev_req_param_msg; str h2ev_req_pname_method = str_init("method"); str h2ev_req_pname_path = str_init("path"); str h2ev_req_pname_headers = str_init("headers"); str h2ev_req_pname_body = str_init("body"); -str h2ev_req_pname_msg = str_init("_h2msg_"); int h2_init_evi(void) @@ -55,14 +54,19 @@ int h2_init_evi(void) } memset(h2ev_req_params, 0, sizeof *h2ev_req_params); + h2_response = shm_malloc(sizeof *h2_response); + if (!h2_response) { + LM_ERR("oom SHM\n"); + return -1; + } + *h2_response = NULL; + h2ev_req_param_method = evi_param_create(h2ev_req_params, &h2ev_req_pname_method); h2ev_req_param_path = evi_param_create(h2ev_req_params, &h2ev_req_pname_path); h2ev_req_param_headers = evi_param_create(h2ev_req_params, &h2ev_req_pname_headers); h2ev_req_param_body = evi_param_create(h2ev_req_params, &h2ev_req_pname_body); - h2ev_req_param_msg = evi_param_create(h2ev_req_params, &h2ev_req_pname_msg); if (!h2ev_req_param_method || !h2ev_req_param_path - || !h2ev_req_param_headers || !h2ev_req_param_body - || !h2ev_req_param_msg) { + || !h2ev_req_param_headers || !h2ev_req_param_body) { LM_ERR("failed to create EVI params\n"); return -1; } @@ -77,10 +81,8 @@ int h2_init_evi(void) * @sroutes, causing a crash when attempting to raise a script event */ void h2_raise_event_request(const char *method, const char *path, - const char *headers_json, const str *body, void *msg) + const char *headers_json, const str *body) { - char buf[sizeof(long)*2 + 1], *p = buf; - int sz = sizeof(buf); str st; init_str(&st, method); @@ -106,15 +108,6 @@ void h2_raise_event_request(const char *method, const char *path, return; } - int64_2reverse_hex(&p, &sz, (unsigned long)msg); - *p = '\0'; - init_str(&st, buf); - - if (evi_param_set_str(h2ev_req_param_msg, &st) < 0) { - LM_ERR("failed to set '_h2msg_'\n"); - return; - } - if (evi_raise_event(h2ev_req_id, h2ev_req_params) < 0) LM_ERR("failed to raise '"H2EV_REQ_NAME"' event\n"); } diff --git a/modules/http2d/h2_evi.h b/modules/http2d/h2_evi.h index ef48689489f..1acae98082a 100644 --- a/modules/http2d/h2_evi.h +++ b/modules/http2d/h2_evi.h @@ -24,10 +24,9 @@ #include "../../str.h" #define H2EV_REQ_NAME "E_HTTP2_REQUEST" -extern str h2ev_req_pname_msg; int h2_init_evi(void); void h2_raise_event_request(const char *method, const char *path, - const char *headers_json, const str *body, void *msg); + const char *headers_json, const str *body); #endif /* __H2_EVI__ */ diff --git a/modules/http2d/http2d.c b/modules/http2d/http2d.c index e9559c0262b..4745909632c 100644 --- a/modules/http2d/http2d.c +++ b/modules/http2d/http2d.c @@ -39,8 +39,24 @@ unsigned int h2_port = 9111; char *h2_ip; str h2_tls_cert = STR_NULL; str h2_tls_key = STR_NULL; + +int h2_response_timeout = 2000; /* ms */ unsigned int max_headers_size = 8192; /* B */ +struct h2_response **h2_response, *ng_h2_response; + +static int h2_send_response(struct sip_msg *msg, int *code, + str *headers_json, str *body); + +static const cmd_export_t cmds[]= { + {"http2_send_response", (cmd_function)h2_send_response, { + {CMD_PARAM_INT,0,0}, + {CMD_PARAM_STR|CMD_PARAM_OPT,0,0}, + {CMD_PARAM_STR|CMD_PARAM_OPT,0,0}, {0,0,0}}, + EVENT_ROUTE}, + + {0,0,{{0,0,0}},0} +}; static const proc_export_t procs[] = { {"HTTP2D", 0, 0, http2_server, 1, PROC_FLAG_INITCHILD|PROC_FLAG_NEEDS_SCRIPT }, @@ -70,7 +86,7 @@ struct module_exports exports = { DEFAULT_DLFLAGS, /* dlopen flags */ 0, /* load function */ NULL, /* OpenSIPS module dependencies */ - NULL, /* exported functions */ + cmds, /* exported functions */ 0, /* exported async functions */ params, /* exported parameters */ NULL, /* exported statistics */ @@ -114,6 +130,180 @@ static int mod_init(void) } +static int h2_send_response(struct sip_msg *msg, int *code, + str *headers_json, str *body) +{ +#define H_STATUS ":status" + cJSON *hdrs, *it; + struct h2_response *r; + int nh = 1; + + if (!h2_response) + return -1; + r = *h2_response; + r->code = -1; + + if (*code < 100 || *code > 599) { + LM_ERR("invalid HTTP/2 response code: %d, must be 100-599\n", *code); + goto error; + } + + if (headers_json) { + char *hp; + str h; + + /* safe to dereference outside buff (still within PKG block) */ + if (headers_json->s[headers_json->len] != '\0') { + if (pkg_nt_str_dup(&h, headers_json) != 0) { + LM_ERR("oom\n"); + goto error; + } + hp = h.s; + } else { + hp = headers_json->s; + } + + hdrs = cJSON_Parse(hp); + if (hp != headers_json->s) + pkg_free(hp); + + if (!hdrs) { + LM_ERR("failed to parse 'headers_json' (bad JSON syntax)\n"); + LM_ERR("first %d characters: %.*s ...\n", + headers_json->len > 20 ? 20 : headers_json->len, + headers_json->len > 20 ? 20 : headers_json->len, headers_json->s); + cJSON_Delete(hdrs); + goto error; + } + + if (hdrs->type != cJSON_Array) { + LM_ERR("bad 'headers_json' value (must be a List of name/value pairs)\n"); + LM_ERR("first %d characters: %.*s ...\n", + headers_json->len > 20 ? 20 : headers_json->len, + headers_json->len > 20 ? 20 : headers_json->len, headers_json->s); + cJSON_Delete(hdrs); + goto error; + } + + int pseudo_headers_done = 0; + + for (it = hdrs->child; it; it = it->next, nh++) { + if (it->type != cJSON_Object) { + LM_ERR("bad 'headers_json' value (must be a List of Objects, but " + "detected cJSON type %d as element)\n", it->type); + LM_ERR("first %d characters: %.*s ...\n", + headers_json->len > 20 ? 20 : headers_json->len, + headers_json->len > 20 ? 20 : headers_json->len, headers_json->s); + cJSON_Delete(hdrs); + goto error; + } + + if (it->child->type != cJSON_String) { + LM_ERR("bad 'headers_json' value (header values must be Strings, but " + "detected cJSON type %d as value)\n", it->child->type); + LM_ERR("first %d characters: %.*s ...\n", + headers_json->len > 20 ? 20 : headers_json->len, + headers_json->len > 20 ? 20 : headers_json->len, headers_json->s); + cJSON_Delete(hdrs); + goto error; + } + + if (!strlen(it->child->string)) { + LM_ERR("bad 'headers_json' value (empty-string header found)\n"); + LM_ERR("first %d characters: %.*s ...\n", + headers_json->len > 20 ? 20 : headers_json->len, + headers_json->len > 20 ? 20 : headers_json->len, headers_json->s); + cJSON_Delete(hdrs); + goto error; + } + + if (!strcmp(it->child->string, H_STATUS)) { + LM_ERR("bad 'headers_json' value (':status' header/code " + "already given as 1st argument)\n"); + LM_ERR("first %d characters: %.*s ...\n", + headers_json->len > 20 ? 20 : headers_json->len, + headers_json->len > 20 ? 20 : headers_json->len, headers_json->s); + cJSON_Delete(hdrs); + goto error; + } + + if (it->child->string[0] != ':') { + pseudo_headers_done = 1; + } else if (pseudo_headers_done) { + LM_ERR("bad response headers ordering: pseudo-header '%s' follows a literal header\n", + it->child->string); + cJSON_Delete(hdrs); + goto error; + } + } + } + + r->hdrs = shm_malloc(nh * sizeof *r->hdrs); + if (!r->hdrs) { + LM_ERR("oom\n"); + cJSON_Delete(hdrs); + goto error; + } + r->hdrs_len = 1; + nh = 0; + + r->hdrs[nh].name = (uint8_t *)shm_strdup(H_STATUS); + r->hdrs[nh].value = (uint8_t *)shm_malloc(4); + if (!r->hdrs[nh].name || !r->hdrs[nh].value) { + LM_ERR("oom (SHM)\n"); + cJSON_Delete(hdrs); + h2_response_clean(); + goto error; + } + + r->hdrs[nh].namelen = strlen((const char *)r->hdrs[nh].name); + sprintf((char *)r->hdrs[nh].value, "%d", *code); + r->hdrs[nh].valuelen = 3; + r->hdrs[nh].flags = NGHTTP2_NV_FLAG_NONE; + nh++; + + if (headers_json) { + for (it = hdrs->child; it; it = it->next, nh++, r->hdrs_len++) { + r->hdrs[nh].name = (uint8_t *)shm_strdup(it->child->string); + r->hdrs[nh].value = (uint8_t *)shm_strdup(it->child->valuestring); + + if (!r->hdrs[nh].name || !r->hdrs[nh].value) { + LM_ERR("oom (SHM)\n"); + cJSON_Delete(hdrs); + h2_response_clean(); + goto error; + } + + r->hdrs[nh].namelen = strlen((const char *)r->hdrs[nh].name); + r->hdrs[nh].valuelen = strlen((const char *)r->hdrs[nh].value); + r->hdrs[nh].flags = NGHTTP2_NV_FLAG_NONE; + } + + cJSON_Delete(hdrs); + } + + if (body) { + if (shm_str_dup(&r->body, body) != 0) { + LM_ERR("oom (SHM)\n"); + h2_response_clean(); + goto error; + } + } + + r->code = *code; + pthread_mutex_lock(&r->mutex); + pthread_cond_signal(&r->cond); + pthread_mutex_unlock(&r->mutex); + return 1; + +error: + pthread_mutex_lock(&r->mutex); + pthread_cond_signal(&r->cond); + pthread_mutex_unlock(&r->mutex); + return -1; +} + + static void mod_destroy(void) { return; diff --git a/modules/http2d/server.c b/modules/http2d/server.c index 08acb3fc6f2..692d16ad98c 100644 --- a/modules/http2d/server.c +++ b/modules/http2d/server.c @@ -78,7 +78,6 @@ extern unsigned int max_headers_size; #include #define NGHTTP2_NO_SSIZE_T -#include #define OUTPUT_WOULDBLOCK_THRESHOLD (1 << 16) @@ -421,14 +420,16 @@ static nghttp2_ssize file_read_callback(nghttp2_session *session, return (nghttp2_ssize)r; } -static int send_response(nghttp2_session *session, int32_t stream_id, +static int send_response_fd(nghttp2_session *session, int32_t stream_id, nghttp2_nv *nva, size_t nvlen, int fd) { int rv; nghttp2_data_provider2 data_prd; + data_prd.source.fd = fd; data_prd.read_callback = file_read_callback; - rv = nghttp2_submit_response2(session, stream_id, nva, nvlen, &data_prd); + rv = nghttp2_submit_response2(session, stream_id, nva, nvlen, + fd > 0 ? &data_prd : NULL); if (rv != 0) { warnx("Fatal error: %s", nghttp2_strerror(rv)); return -1; @@ -436,15 +437,27 @@ static int send_response(nghttp2_session *session, int32_t stream_id, return 0; } -static const char ERROR_HTML[] = "404" - "

404 Not Found

"; +static int send_response_empty(nghttp2_session *session, int32_t stream_id, + nghttp2_nv *nva, size_t nvlen) { + int rv; + + rv = nghttp2_submit_response2(session, stream_id, nva, nvlen, NULL); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; +} + +static const char ERROR_HTML[] = "500" + "

500 Internal Server Error

"; static int error_reply(nghttp2_session *session, http2_stream_data *stream_data) { int rv; ssize_t writelen; int pipefd[2]; - nghttp2_nv hdrs[] = {MAKE_NV(":status", "404")}; + nghttp2_nv hdrs[] = {MAKE_NV(":status", "500")}; rv = pipe(pipefd); if (rv != 0) { @@ -469,7 +482,7 @@ static int error_reply(nghttp2_session *session, stream_data->fd = pipefd[0]; - if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), + if (send_response_fd(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), pipefd[0]) != 0) { close(pipefd[0]); return -1; @@ -478,6 +491,17 @@ static int error_reply(nghttp2_session *session, } +static int timeout_reply(nghttp2_session *session, + http2_stream_data *stream_data) { + nghttp2_nv hdrs[] = {MAKE_NV(":status", "408")}; + + if (send_response_empty(session, stream_data->stream_id, hdrs, ARRLEN(hdrs)) != 0) + return -1; + + return 0; +} + + /* Minimum check for directory traversal. Returns nonzero if it is safe. */ static int check_path(const char *path) { @@ -487,54 +511,120 @@ static int check_path(const char *path) { !ends_with(path, "/..") && !ends_with(path, "/."); } + +static int h2_fdpack(str *data) +{ + int rv; + ssize_t writelen; + int pipefd[2]; + + if (!data->s || data->len == 0) + return 0; + + rv = pipe(pipefd); + if (rv != 0) { + LM_ERR("failed to create pipe %d (%s)\n", errno, strerror(errno)); + return -1; + } + + writelen = write(pipefd[1], data->s, data->len); + close(pipefd[1]); + + if (writelen != data->len) { + close(pipefd[0]); + return -1; + } + + return pipefd[0]; +} + + static int on_request_recv(nghttp2_session *session, http2_session_data *session_data, http2_stream_data *stream_data) { - int fd; - nghttp2_nv hdrs[] = {MAKE_NV(":status", "200")}; - char *rel_path, *H; + int rc; + struct timespec wait_until; + struct timeval now, wait_time, res; + char *H; + + if (!stream_data->path) { + if (error_reply(session, stream_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; + } - if (!stream_data->path) { - if (error_reply(session, stream_data) != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - return 0; - } - LM_INFO("%s GET %s (stream_id: %d)\n", session_data->client_addr, - stream_data->path, stream_data->stream_id); - LM_INFO("body: %.*s %d\n", stream_data->data.len, stream_data->data.s, stream_data->data.len); + LM_INFO("%s GET %s (stream_id: %d)\n", session_data->client_addr, + stream_data->path, stream_data->stream_id); + LM_INFO("body: %.*s %d\n", stream_data->data.len, stream_data->data.s, stream_data->data.len); - if (!check_path(stream_data->path)) { - if (error_reply(session, stream_data) != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - return 0; - } - LM_INFO("A\n"); + if (!check_path(stream_data->path)) { + if (error_reply(session, stream_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; + } + LM_INFO("A\n"); - H = cJSON_PrintUnformatted(stream_data->hdrs); - h2_raise_event_request(stream_data->method, stream_data->path, H, &stream_data->data, NULL); - cJSON_PurgeString(H); + pthread_mutex_lock(&ng_h2_response->mutex); - for (rel_path = stream_data->path; *rel_path == '/'; ++rel_path) - ; - fd = open(rel_path, O_RDONLY); - if (fd == -1) { - if (error_reply(session, stream_data) != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - return 0; - } - stream_data->fd = fd; + H = cJSON_PrintUnformatted(stream_data->hdrs); + h2_raise_event_request(stream_data->method, stream_data->path, + H, &stream_data->data); + cJSON_PurgeString(H); - LM_INFO("body: %.*s\n", stream_data->data.len, stream_data->data.s); + gettimeofday(&now, NULL); + wait_time.tv_sec = h2_response_timeout / 1000; + wait_time.tv_usec = h2_response_timeout % 1000 * 1000UL; + LM_DBG("awaiting HTTP2 reply (%ld s, %ld us)...\n", wait_time.tv_sec, wait_time.tv_usec); - if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), fd) != - 0) { - close(fd); - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - return 0; + timeradd(&now, &wait_time, &res); + + wait_until.tv_sec = res.tv_sec; + wait_until.tv_nsec = res.tv_usec * 1000UL; + + rc = pthread_cond_timedwait(&ng_h2_response->cond, + &ng_h2_response->mutex, &wait_until); + if (rc != 0) { + pthread_mutex_unlock(&ng_h2_response->mutex); + + LM_ERR("timeout (errno: %d '%s') while awaiting " + "HTTP2 reply from opensips.cfg\n", rc, strerror(rc)); + if (timeout_reply(session, stream_data) != 0) + return NGHTTP2_ERR_CALLBACK_FAILURE; + + return 0; + } + + pthread_mutex_unlock(&ng_h2_response->mutex); + + /* we failed to build a reply in the SIP worker, so reply with a 500 */ + if (ng_h2_response->code <= 0) { + if (error_reply(session, stream_data) != 0) + return NGHTTP2_ERR_CALLBACK_FAILURE; + return 0; + } + + LM_DBG("rpl code: %d\n", ng_h2_response->code); + LM_DBG("rpl # headers: %d\n", ng_h2_response->hdrs_len); + LM_DBG("body: %.*s\n", stream_data->data.len, stream_data->data.s); + LM_DBG("rpl body: %.*s\n", ng_h2_response->body.len, ng_h2_response->body.s); + + int fd = h2_fdpack(&ng_h2_response->body); + if (fd < 0) { + LM_ERR("failed to pack data\n"); + if (error_reply(session, stream_data) != 0) + return NGHTTP2_ERR_CALLBACK_FAILURE; + return 0; + } + + if (send_response_fd(session, stream_data->stream_id, + ng_h2_response->hdrs, ng_h2_response->hdrs_len, fd) != 0) { + close(fd); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return 0; } @@ -704,6 +794,7 @@ static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, } remove_stream(session_data, stream_data); delete_http2_stream_data(stream_data); + h2_response_clean(); return 0; } @@ -886,15 +977,64 @@ static void run(const char *service, const char *key_file, LM_DBG("event loop start\n"); event_base_loop(evbase, 0); + LM_DBG("event loop end\n"); event_base_free(evbase); SSL_CTX_free(ssl_ctx); } +static void init_mutex_cond(pthread_mutex_t *mutex, pthread_cond_t *cond) +{ + pthread_mutexattr_t mattr; + pthread_mutexattr_init(&mattr); + pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); + pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST); + pthread_mutex_init(mutex, &mattr); + pthread_mutexattr_destroy(&mattr); + + pthread_condattr_t cattr; + pthread_condattr_init(&cattr); + pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); + pthread_cond_init(cond, &cattr); + pthread_condattr_destroy(&cattr); +} + +void h2_response_clean(void) +{ + int i; + + if (ng_h2_response->hdrs) { + for (i = 0; i < ng_h2_response->hdrs_len; i++) { + shm_free(ng_h2_response->hdrs[i].name); + shm_free(ng_h2_response->hdrs[i].value); + } + + shm_free(ng_h2_response->hdrs); + ng_h2_response->hdrs = NULL; + ng_h2_response->hdrs_len = 0; + } + + if (ng_h2_response->body.s) { + shm_free(ng_h2_response->body.s); + memset(&ng_h2_response->body, 0, sizeof ng_h2_response->body); + } + + ng_h2_response->code = 0; +} + void http2_server(int rank) { struct sigaction act; + ng_h2_response = shm_malloc(sizeof *ng_h2_response); + if (!ng_h2_response) { + LM_ERR("oom SHM\n"); + return; + } + memset(ng_h2_response, 0, sizeof *ng_h2_response); + init_mutex_cond(&ng_h2_response->mutex, &ng_h2_response->cond); + *h2_response = ng_h2_response; + memset(&act, 0, sizeof act); act.sa_handler = SIG_IGN; sigaction(SIGPIPE, &act, NULL); diff --git a/modules/http2d/server.h b/modules/http2d/server.h index b9acc7a9c26..1f33afb781c 100644 --- a/modules/http2d/server.h +++ b/modules/http2d/server.h @@ -1,13 +1,30 @@ #ifndef HTTP2_SERVER #define HTTP2_SERVER +#include +#include #include "../../str.h" +struct h2_response { + pthread_mutex_t mutex; + pthread_cond_t cond; + + int code; + nghttp2_nv *hdrs; + int hdrs_len; + str body; +}; + +extern int h2_response_timeout; + +extern struct h2_response **h2_response, *ng_h2_response; + extern unsigned int h2_port; extern char *h2_ip; extern str h2_tls_cert; extern str h2_tls_key; void http2_server(int rank); +void h2_response_clean(void); #endif /* HTTP2_SERVER */ diff --git a/sr_module.c b/sr_module.c index 3f6ef2257d6..8d82286c163 100644 --- a/sr_module.c +++ b/sr_module.c @@ -868,7 +868,6 @@ int start_module_procs(void) flags = OSS_PROC_IS_EXTRA; if (m->exports->procs[n].flags&PROC_FLAG_NEEDS_SCRIPT) flags |= OSS_PROC_NEEDS_SCRIPT; - else if ( (m->exports->procs[n].flags&PROC_FLAG_HAS_IPC)==0) flags |= OSS_PROC_NO_IPC; struct internal_fork_params ifp = { From f2d17683c4339f14f4cd14062c255d2433d9f6ce Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Fri, 22 Mar 2024 19:12:51 +0200 Subject: [PATCH 70/79] http2d: Add documentation, contributors; Adjust modparams --- modules/http2d/doc/contributors.xml | 79 ++++++++ modules/http2d/doc/http2d.xml | 27 +++ modules/http2d/doc/http2d_admin.xml | 299 ++++++++++++++++++++++++++++ modules/http2d/h2_evi.c | 19 +- modules/http2d/http2d.c | 9 +- 5 files changed, 417 insertions(+), 16 deletions(-) create mode 100644 modules/http2d/doc/contributors.xml create mode 100644 modules/http2d/doc/http2d.xml create mode 100644 modules/http2d/doc/http2d_admin.xml diff --git a/modules/http2d/doc/contributors.xml b/modules/http2d/doc/contributors.xml new file mode 100644 index 00000000000..eaf19dca598 --- /dev/null +++ b/modules/http2d/doc/contributors.xml @@ -0,0 +1,79 @@ + + + &contributors; + +
+ By Commit Statistics + + Top contributors by DevScore<superscript>(1)</superscript>, authored commits<superscript>(2)</superscript> and lines added/removed<superscript>(3)</superscript> + + + + + Name + DevScore + Commits + Lines ++ + Lines -- + + + + + 1. + Liviu Chircu (@liviuchircu) + 20 + 3 + 1665 + 123 + + + +
+ + + (1) DevScore = author_commits + author_lines_added / (project_lines_added / project_commits) + author_lines_deleted / (project_lines_deleted / project_commits) + + + (2) including any documentation-related commits, excluding merge commits. Regarding imported patches/code, we do our best to count the work on behalf of the proper owner, as per the "fix_authors" and "mod_renames" arrays in opensips/doc/build-contrib.sh. If you identify any patches/commits which do not get properly attributed to you, please submit a pull request which extends "fix_authors" and/or "mod_renames". + + + (3) ignoring whitespace edits, renamed files and auto-generated files + +
+ +
+ By Commit Activity + + Most recently active contributors<superscript>(1)</superscript> to this module + + + + + Name + Commit Activity + + + + + 1. + Liviu Chircu (@liviuchircu) + Mar 2024 - Mar 2024 + + + +
+ + + (1) including any documentation-related commits, excluding merge commits + +
+ +
+ + Documentation +
+ Contributors + +
+ +
diff --git a/modules/http2d/doc/http2d.xml b/modules/http2d/doc/http2d.xml new file mode 100644 index 00000000000..0da060b9c72 --- /dev/null +++ b/modules/http2d/doc/http2d.xml @@ -0,0 +1,27 @@ + + + + + + +%docentities; + +]> + + + + HTTP2D MODULE + &osipsname; + + + + &admin; + &contrib; + + &docCopyrights; + ©right; 2024 &osipssol; + diff --git a/modules/http2d/doc/http2d_admin.xml b/modules/http2d/doc/http2d_admin.xml new file mode 100644 index 00000000000..06c341758fe --- /dev/null +++ b/modules/http2d/doc/http2d_admin.xml @@ -0,0 +1,299 @@ + + + + + &adminguide; + +
+ Overview + + This module provides an RFC 7540/9113 HTTP/2 server implementation with "h2" ALPN support, + based on the nghttp2 library (). + + + + HTTP/2, introduced in 2015, is a binary protocol with added transactional layers (SESSION, FRAME), + which allow identifying and managing multiple, concurrent transfers over the same TCP/TLS connection. + Thus, the revised protocol primarily aims to reduce resource usage for both clients and servers, by + reducing the amount of TCP and/or TLS handshakes performed when loading a given web page. + + + + The OpenSIPS http2d server includes support for both "h2" (TLS secured) + and "h2c" (cleartext) HTTP/2 connections. The requests arrive at + opensips.cfg level using the event, + where script writers may process the data and respond accordingly. + +
+ +
+ Dependencies +
+ &osips; Modules + + None. + +
+ +
+ External Libraries or Applications + + The HTTP/2 server is provided by the nghttp2 library, + which runs on top of the libevent server framework. + + + Overall, the following libraries must be installed before running + &osips; with this module loaded: + + + + libnghttp2 + + + + libevent, libevent_openssl + + + + libssl, libcrypto + + + +
+
+ +
+ Exported Parameters +
+ <varname>ip (string)</varname> + + The listening IPv4 address. + + + Default value is "127.0.0.1". + + + Setting the <varname>ip</varname> parameter + + +modparam("http2d", "ip", "127.0.0.2") + + + +
+ +
+ <varname>port (integer)</varname> + + The listening port. + + + Default value is 443. + + + Setting the <varname>port</varname> parameter + + +modparam("http2d", "port", 5000) + + + +
+ +
+ <varname>tls_cert_path (string)</varname> + + File path to the TLS certificate, in PEM format. + + + Default value is NULL (not set). + + + Setting the <varname>tls_cert_path</varname> parameter + + +modparam("http2d", "tls_cert_path", "/etc/pki/http2/cert.pem") + + + +
+ +
+ <varname>tls_cert_key (string)</varname> + + File path to the TLS private key, in PEM format. + + + Default value is NULL (not set). + + + Setting the <varname>tls_cert_key</varname> parameter + + +modparam("http2d", "tls_cert_key", "/etc/pki/http2/private/key.pem") + + + +
+ +
+ <varname>max_headers_size (integer)</varname> + + The maximum amount of bytes allowed for all header field names and values + combined in a single HTTP/2 request processed by the server. Once this + threshold is reached, extra headers will no longer be provided at script + level and will be reported as errors instead. + + + Default value is 8192 bytes. + + + Setting the <varname>max_headers_size</varname> parameter + + +modparam("http2d", "max_headers_size", 16384) + + + +
+ +
+ <varname>response_timeout (integer)</varname> + + The maximum amount of time, in milliseconds, that the library will + allow the opensips.cfg processing to take for a given HTTP/2 request. + + + Once this timeout is reached, the module will auto-generate a + 408 (request timeout) reply. + + + Default value is 2000 ms. + + + Setting the <varname>response_timeout</varname> parameter + + +modparam("http2d", "response_timeout", 5000) + + + +
+ +
+ +
+ Exported Functions +
+ + <function moreinfo="none">http2_send_response(code, [headers_json], [data])</function> + + + Sends a response for the HTTP/2 request being processed. The ":status" + header field will be automatically included by the module as 1st header, so it must not be + included in the headers_json array. + + Parameters + + + code (integer) - The HTTP/2 reply code + + + headers_json (string, default: NULL) + - Optional JSON Array containing {"header": "value"} elements, denoting HTTP/2 + headers and their values to be included in the response message. + + + data (string, default: NULL) + - Optional DATA payload to include in the response message. + + + + Return Codes + + + 1 - Success + + + + -1 - Internal Error + + + + + + This function can only be used from an EVENT_ROUTE. + + + <function moreinfo="none">http2_send_response()</function> usage + + +event_route [E_HTTP2_REQUEST] { + xlog(":: Method: $param(method)\n"); + xlog(":: Path: $param(path)\n"); + xlog(":: Headers: $param(headers)\n"); + xlog(":: Data: $param(data)\n"); + + $json(hdrs) := $param(headers); + xlog("content-type: $json(hdrs/content-type)\n"); + + $var(rpl_headers) = "[ + { \"content-type\": "application/json" }, + { \"server\": "OpenSIPS 3.5" }, + { \"x-current-time\": "1711457142" }, + { \"x-call-cost\": "0.355" } + ]"; + + $var(data) = "{\"status\": \"success\"}"; + + if (!http2_send_response(200, $var(rpl_headers), $var(data))) + xlog("ERROR - failed to send HTTP/2 response\n"); +} + + + +
+ +
+ +
+ Exported Events +
+ + <function moreinfo="none">E_HTTP2_REQUEST</function> + + + This event is raised whenever the http2d + module is loaded and OpenSIPS receives an HTTP/2 request on the configured + listening interface(s). + + Parameters: + + + method (string) - value of the ":method" HTTP/2 header + + + path (string) - value of the ":path" HTTP/2 header + + + headers (string) - JSON Array with all headers of the request, + including pseudo-headers + + + data (string, default: NULL) - If the request included a payload, + this parameter will hold its contents + + + + + Note that this event is currently designed to be mainly consumed by an event_route, + since that is the only way to gain access to the + function in order to build custom response messages. On the other hand, + if the application does not mind the answer being always a 200 with no payload, + this event can be successfully consumed through any other EVI-compatible delivery channel ☺️ + +
+ +
+ +
diff --git a/modules/http2d/h2_evi.c b/modules/http2d/h2_evi.c index 6d30a983992..68a372f7617 100644 --- a/modules/http2d/h2_evi.c +++ b/modules/http2d/h2_evi.c @@ -30,12 +30,7 @@ static evi_params_p h2ev_req_params; static evi_param_p h2ev_req_param_method; static evi_param_p h2ev_req_param_path; static evi_param_p h2ev_req_param_headers; -static evi_param_p h2ev_req_param_body; - -str h2ev_req_pname_method = str_init("method"); -str h2ev_req_pname_path = str_init("path"); -str h2ev_req_pname_headers = str_init("headers"); -str h2ev_req_pname_body = str_init("body"); +static evi_param_p h2ev_req_param_data; int h2_init_evi(void) @@ -61,12 +56,12 @@ int h2_init_evi(void) } *h2_response = NULL; - h2ev_req_param_method = evi_param_create(h2ev_req_params, &h2ev_req_pname_method); - h2ev_req_param_path = evi_param_create(h2ev_req_params, &h2ev_req_pname_path); - h2ev_req_param_headers = evi_param_create(h2ev_req_params, &h2ev_req_pname_headers); - h2ev_req_param_body = evi_param_create(h2ev_req_params, &h2ev_req_pname_body); + h2ev_req_param_method = evi_param_create(h2ev_req_params, &str_init("method")); + h2ev_req_param_path = evi_param_create(h2ev_req_params, &str_init("path")); + h2ev_req_param_headers = evi_param_create(h2ev_req_params, &str_init("headers")); + h2ev_req_param_data = evi_param_create(h2ev_req_params, &str_init("data")); if (!h2ev_req_param_method || !h2ev_req_param_path - || !h2ev_req_param_headers || !h2ev_req_param_body) { + || !h2ev_req_param_headers || !h2ev_req_param_data) { LM_ERR("failed to create EVI params\n"); return -1; } @@ -103,7 +98,7 @@ void h2_raise_event_request(const char *method, const char *path, return; } - if (evi_param_set_str(h2ev_req_param_body, body) < 0) { + if (evi_param_set_str(h2ev_req_param_data, body) < 0) { LM_ERR("failed to set 'body'\n"); return; } diff --git a/modules/http2d/http2d.c b/modules/http2d/http2d.c index 4745909632c..ee6002f4e7f 100644 --- a/modules/http2d/http2d.c +++ b/modules/http2d/http2d.c @@ -35,7 +35,7 @@ static int mod_init(); static void mod_destroy(void); -unsigned int h2_port = 9111; +unsigned int h2_port = 443; char *h2_ip; str h2_tls_cert = STR_NULL; str h2_tls_key = STR_NULL; @@ -65,11 +65,12 @@ static const proc_export_t procs[] = { /* Module parameters */ static const param_export_t params[] = { - {"port", INT_PARAM, &h2_port}, {"ip", STR_PARAM, &h2_ip}, - {"tls_cert_file", STR_PARAM, &h2_tls_cert.s}, - {"tls_key_file", STR_PARAM, &h2_tls_key.s}, + {"port", INT_PARAM, &h2_port}, + {"tls_cert_path", STR_PARAM, &h2_tls_cert.s}, + {"tls_key_path", STR_PARAM, &h2_tls_key.s}, {"max_headers_size", INT_PARAM, &max_headers_size}, + {"response_timeout", INT_PARAM, &h2_response_timeout}, {NULL, 0, NULL} }; From 2e57bb5b9043d2cfc2d3974d6874305099ad3bae Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Mon, 25 Mar 2024 16:08:22 +0200 Subject: [PATCH 71/79] http2d: Adjust indentation & coding style --- modules/http2d/server.c | 1017 ++++++++++++++++++++------------------- 1 file changed, 511 insertions(+), 506 deletions(-) diff --git a/modules/http2d/server.c b/modules/http2d/server.c index 692d16ad98c..cfbc67e3edb 100644 --- a/modules/http2d/server.c +++ b/modules/http2d/server.c @@ -2,6 +2,7 @@ * nghttp2 - HTTP/2 C Library * * Copyright (c) 2013 Tatsuhiro Tsujikawa + * Copyright (c) 2024 OpenSIPS Solutions * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -93,213 +94,210 @@ struct app_context; typedef struct app_context app_context; typedef struct http2_stream_data { - int32_t stream_id; + int32_t stream_id; - char *method; - char *path; - cJSON *hdrs; - unsigned hdrs_len; - str data; - int fd; + char *method; + char *path; + cJSON *hdrs; + unsigned hdrs_len; + str data; + int fd; - struct list_head list; + struct list_head list; } http2_stream_data; typedef struct http2_session_data { - struct list_head root; - struct bufferevent *bev; - app_context *app_ctx; - nghttp2_session *session; - char *client_addr; + struct list_head root; + struct bufferevent *bev; + app_context *app_ctx; + nghttp2_session *session; + char *client_addr; } http2_session_data; struct app_context { - SSL_CTX *ssl_ctx; - struct event_base *evbase; + SSL_CTX *ssl_ctx; + struct event_base *evbase; }; static int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { - int rv; - (void)ssl; - (void)arg; + int rv; + (void)ssl; + (void)arg; - rv = nghttp2_select_alpn(out, outlen, in, inlen); + rv = nghttp2_select_alpn(out, outlen, in, inlen); - if (rv != 1) { - return SSL_TLSEXT_ERR_NOACK; - } + if (rv != 1) + return SSL_TLSEXT_ERR_NOACK; return SSL_TLSEXT_ERR_OK; } /* Create SSL_CTX. */ static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) { - SSL_CTX *ssl_ctx; + SSL_CTX *ssl_ctx; - ssl_ctx = SSL_CTX_new(TLS_server_method()); - if (!ssl_ctx) { - errx(1, "Could not create SSL/TLS context: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - SSL_CTX_set_options(ssl_ctx, - SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | - SSL_OP_NO_COMPRESSION | - SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + errx(1, "Could not create SSL/TLS context: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + SSL_CTX_set_options(ssl_ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); #if OPENSSL_VERSION_NUMBER >= 0x30000000L - if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) { - errx(1, "SSL_CTX_set1_curves_list failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - } + if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) { + errx(1, "SSL_CTX_set1_curves_list failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } #else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ - { - EC_KEY *ecdh; - ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - if (!ecdh) { - errx(1, "EC_KEY_new_by_curv_name failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); - EC_KEY_free(ecdh); - } + { + EC_KEY *ecdh; + ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (!ecdh) { + errx(1, "EC_KEY_new_by_curv_name failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); + EC_KEY_free(ecdh); + } #endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ - if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) { - errx(1, "Could not read private key file %s", key_file); - } - if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { - errx(1, "Could not read certificate file %s", cert_file); - } + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) { + errx(1, "Could not read private key file %s", key_file); + } + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { + errx(1, "Could not read certificate file %s", cert_file); + } - SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL); + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL); - return ssl_ctx; + return ssl_ctx; } /* Create SSL object */ static SSL *create_ssl(SSL_CTX *ssl_ctx) { - SSL *ssl; - ssl = SSL_new(ssl_ctx); - if (!ssl) { - errx(1, "Could not create SSL/TLS session object: %s", - ERR_error_string(ERR_get_error(), NULL)); - } - return ssl; + SSL *ssl; + ssl = SSL_new(ssl_ctx); + if (!ssl) { + errx(1, "Could not create SSL/TLS session object: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + return ssl; } static void add_stream(http2_session_data *session_data, http2_stream_data *stream_data) { - list_add(&stream_data->list, &session_data->root); + list_add(&stream_data->list, &session_data->root); } static void remove_stream(http2_session_data *session_data, http2_stream_data *stream_data) { - (void)session_data; - list_del(&stream_data->list); + (void)session_data; + list_del(&stream_data->list); } static http2_stream_data * create_http2_stream_data(http2_session_data *session_data, int32_t stream_id) { - http2_stream_data *stream_data; + http2_stream_data *stream_data; - stream_data = pkg_malloc(sizeof *stream_data); - if (!stream_data) { - LM_ERR("oom\n"); - return NULL; - } + stream_data = pkg_malloc(sizeof *stream_data); + if (!stream_data) { + LM_ERR("oom\n"); + return NULL; + } - memset(stream_data, 0, sizeof *stream_data); - stream_data->stream_id = stream_id; - stream_data->hdrs = cJSON_CreateObject(); - if (!stream_data->hdrs) { - pkg_free(stream_data); - LM_ERR("oom\n"); - return NULL; - } + memset(stream_data, 0, sizeof *stream_data); + stream_data->stream_id = stream_id; + stream_data->hdrs = cJSON_CreateObject(); + if (!stream_data->hdrs) { + pkg_free(stream_data); + LM_ERR("oom\n"); + return NULL; + } - stream_data->fd = -1; + stream_data->fd = -1; - add_stream(session_data, stream_data); - return stream_data; + add_stream(session_data, stream_data); + return stream_data; } static void delete_http2_stream_data(http2_stream_data *stream_data) { - if (stream_data->fd != -1) { - close(stream_data->fd); - } + if (stream_data->fd != -1) + close(stream_data->fd); - free(stream_data->path); - cJSON_Delete(stream_data->hdrs); - pkg_free(stream_data->data.s); - pkg_free(stream_data); + free(stream_data->path); + cJSON_Delete(stream_data->hdrs); + pkg_free(stream_data->data.s); + pkg_free(stream_data); } static http2_session_data *create_http2_session_data(app_context *app_ctx, int fd, struct sockaddr *addr, int addrlen) { - int rv; - http2_session_data *session_data; - SSL *ssl; - char host[NI_MAXHOST]; - int val = 1; - - ssl = create_ssl(app_ctx->ssl_ctx); - session_data = malloc(sizeof *session_data); - memset(session_data, 0, sizeof *session_data); - INIT_LIST_HEAD(&session_data->root); - - session_data->app_ctx = app_ctx; - setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); - session_data->bev = bufferevent_openssl_socket_new( - app_ctx->evbase, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, - BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); - bufferevent_enable(session_data->bev, EV_READ | EV_WRITE); - rv = getnameinfo(addr, (socklen_t)addrlen, host, sizeof(host), NULL, 0, - NI_NUMERICHOST); - if (rv != 0) { - session_data->client_addr = strdup("(unknown)"); - } else { - session_data->client_addr = strdup(host); - } + int rv; + http2_session_data *session_data; + SSL *ssl; + char host[NI_MAXHOST]; + int val = 1; + + ssl = create_ssl(app_ctx->ssl_ctx); + session_data = malloc(sizeof *session_data); + memset(session_data, 0, sizeof *session_data); + INIT_LIST_HEAD(&session_data->root); + + session_data->app_ctx = app_ctx; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); + session_data->bev = bufferevent_openssl_socket_new( + app_ctx->evbase, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, + BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); + bufferevent_enable(session_data->bev, EV_READ | EV_WRITE); + rv = getnameinfo(addr, (socklen_t)addrlen, host, sizeof(host), NULL, 0, + NI_NUMERICHOST); + if (rv != 0) { + session_data->client_addr = strdup("(unknown)"); + } else { + session_data->client_addr = strdup(host); + } - return session_data; + return session_data; } static void delete_http2_session_data(http2_session_data *session_data) { - http2_stream_data *stream_data; - SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev); - struct list_head *it, *aux; + http2_stream_data *stream_data; + SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev); + struct list_head *it, *aux; - fprintf(stderr, "%s disconnected\n", session_data->client_addr); - if (ssl) { - SSL_shutdown(ssl); - } - bufferevent_free(session_data->bev); - nghttp2_session_del(session_data->session); + fprintf(stderr, "%s disconnected\n", session_data->client_addr); + if (ssl) + SSL_shutdown(ssl); + bufferevent_free(session_data->bev); + nghttp2_session_del(session_data->session); - list_for_each_safe (it, aux, &session_data->root) { - stream_data = list_entry(it, http2_stream_data, list); + list_for_each_safe (it, aux, &session_data->root) { + stream_data = list_entry(it, http2_stream_data, list); - list_del(&stream_data->list); - delete_http2_stream_data(stream_data); - } + list_del(&stream_data->list); + delete_http2_stream_data(stream_data); + } - free(session_data->client_addr); - free(session_data); + free(session_data->client_addr); + free(session_data); } /* Serialize the frame and send (or buffer) the data to bufferevent. */ static int session_send(http2_session_data *session_data) { - int rv; - rv = nghttp2_session_send(session_data->session); - if (rv != 0) { - warnx("Fatal error: %s", nghttp2_strerror(rv)); - return -1; - } - return 0; + int rv; + rv = nghttp2_session_send(session_data->session); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; } /* Read the data in the bufferevent and feed them into nghttp2 library @@ -307,65 +305,64 @@ static int session_send(http2_session_data *session_data) { additional pending frames, so call session_send() at the end of the function. */ static int session_recv(http2_session_data *session_data) { - nghttp2_ssize readlen; - struct evbuffer *input = bufferevent_get_input(session_data->bev); - size_t datalen = evbuffer_get_length(input); - unsigned char *data = evbuffer_pullup(input, -1); - - readlen = nghttp2_session_mem_recv2(session_data->session, data, datalen); - if (readlen < 0) { - warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); - return -1; - } - if (evbuffer_drain(input, (size_t)readlen) != 0) { - warnx("Fatal error: evbuffer_drain failed"); - return -1; - } - if (session_send(session_data) != 0) { - return -1; - } - return 0; + nghttp2_ssize readlen; + struct evbuffer *input = bufferevent_get_input(session_data->bev); + size_t datalen = evbuffer_get_length(input); + unsigned char *data = evbuffer_pullup(input, -1); + + readlen = nghttp2_session_mem_recv2(session_data->session, data, datalen); + if (readlen < 0) { + warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); + return -1; + } + if (evbuffer_drain(input, (size_t)readlen) != 0) { + warnx("Fatal error: evbuffer_drain failed"); + return -1; + } + if (session_send(session_data) != 0) + return -1; + return 0; } static nghttp2_ssize send_callback(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data) { - http2_session_data *session_data = (http2_session_data *)user_data; - struct bufferevent *bev = session_data->bev; - (void)session; - (void)flags; - - /* Avoid excessive buffering in server side. */ - if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >= - OUTPUT_WOULDBLOCK_THRESHOLD) { - return NGHTTP2_ERR_WOULDBLOCK; - } - bufferevent_write(bev, data, length); - return (nghttp2_ssize)length; + http2_session_data *session_data = (http2_session_data *)user_data; + struct bufferevent *bev = session_data->bev; + (void)session; + (void)flags; + + /* Avoid excessive buffering in server side. */ + if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >= + OUTPUT_WOULDBLOCK_THRESHOLD) { + return NGHTTP2_ERR_WOULDBLOCK; + } + + bufferevent_write(bev, data, length); + return (nghttp2_ssize)length; } /* Returns nonzero if the string |s| ends with the substring |sub| */ static int ends_with(const char *s, const char *sub) { - size_t slen = strlen(s); - size_t sublen = strlen(sub); - if (slen < sublen) { - return 0; - } - return memcmp(s + slen - sublen, sub, sublen) == 0; + size_t slen = strlen(s); + size_t sublen = strlen(sub); + if (slen < sublen) + return 0; + return memcmp(s + slen - sublen, sub, sublen) == 0; } /* Returns int value of hex string character |c| */ static uint8_t hex_to_uint(uint8_t c) { - if ('0' <= c && c <= '9') { - return (uint8_t)(c - '0'); - } - if ('A' <= c && c <= 'F') { - return (uint8_t)(c - 'A' + 10); - } - if ('a' <= c && c <= 'f') { - return (uint8_t)(c - 'a' + 10); - } - return 0; + if ('0' <= c && c <= '9') { + return (uint8_t)(c - '0'); + } + if ('A' <= c && c <= 'F') { + return (uint8_t)(c - 'A' + 10); + } + if ('a' <= c && c <= 'f') { + return (uint8_t)(c - 'a' + 10); + } + return 0; } /* Decodes percent-encoded byte string |value| with length |valuelen| @@ -373,28 +370,28 @@ static uint8_t hex_to_uint(uint8_t c) { value is NULL terminated. The caller must free the returned string. */ static char *percent_decode(const uint8_t *value, size_t valuelen) { - char *res; - - res = malloc(valuelen + 1); - if (valuelen > 3) { - size_t i, j; - for (i = 0, j = 0; i < valuelen - 2;) { - if (value[i] != '%' || !isxdigit(value[i + 1]) || - !isxdigit(value[i + 2])) { - res[j++] = (char)value[i++]; - continue; - } - res[j++] = - (char)((hex_to_uint(value[i + 1]) << 4) + hex_to_uint(value[i + 2])); - i += 3; - } - memcpy(&res[j], &value[i], 2); - res[j + 2] = '\0'; - } else { - memcpy(res, value, valuelen); - res[valuelen] = '\0'; - } - return res; + char *res; + + res = malloc(valuelen + 1); + if (valuelen > 3) { + size_t i, j; + for (i = 0, j = 0; i < valuelen - 2;) { + if (value[i] != '%' || !isxdigit(value[i + 1]) || + !isxdigit(value[i + 2])) { + res[j++] = (char)value[i++]; + continue; + } + res[j++] = + (char)((hex_to_uint(value[i + 1]) << 4) + hex_to_uint(value[i + 2])); + i += 3; + } + memcpy(&res[j], &value[i], 2); + res[j + 2] = '\0'; + } else { + memcpy(res, value, valuelen); + res[valuelen] = '\0'; + } + return res; } /* a callback of type "nghttp2_data_source_read_callback2" */ @@ -403,38 +400,39 @@ static nghttp2_ssize file_read_callback(nghttp2_session *session, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { - int fd = source->fd; - ssize_t r; - (void)session; - (void)stream_id; - (void)user_data; - - while ((r = read(fd, buf, length)) == -1 && errno == EINTR) - ; - if (r == -1) { - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - if (r == 0) { - *data_flags |= NGHTTP2_DATA_FLAG_EOF; - } - return (nghttp2_ssize)r; + int fd = source->fd; + ssize_t r; + (void)session; + (void)stream_id; + (void)user_data; + + while ((r = read(fd, buf, length)) == -1 && errno == EINTR) + ; + if (r == -1) + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + + if (r == 0) + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + + return (nghttp2_ssize)r; } static int send_response_fd(nghttp2_session *session, int32_t stream_id, nghttp2_nv *nva, size_t nvlen, int fd) { - int rv; - nghttp2_data_provider2 data_prd; + int rv; + nghttp2_data_provider2 data_prd; - data_prd.source.fd = fd; - data_prd.read_callback = file_read_callback; + data_prd.source.fd = fd; + data_prd.read_callback = file_read_callback; - rv = nghttp2_submit_response2(session, stream_id, nva, nvlen, + rv = nghttp2_submit_response2(session, stream_id, nva, nvlen, fd > 0 ? &data_prd : NULL); - if (rv != 0) { - warnx("Fatal error: %s", nghttp2_strerror(rv)); - return -1; - } - return 0; + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + + return 0; } static int send_response_empty(nghttp2_session *session, int32_t stream_id, @@ -454,88 +452,90 @@ static const char ERROR_HTML[] = "500" static int error_reply(nghttp2_session *session, http2_stream_data *stream_data) { - int rv; - ssize_t writelen; - int pipefd[2]; - nghttp2_nv hdrs[] = {MAKE_NV(":status", "500")}; - - rv = pipe(pipefd); - if (rv != 0) { - warn("Could not create pipe"); - rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, - stream_data->stream_id, - NGHTTP2_INTERNAL_ERROR); - if (rv != 0) { - warnx("Fatal error: %s", nghttp2_strerror(rv)); - return -1; - } - return 0; - } + int rv; + ssize_t writelen; + int pipefd[2]; + nghttp2_nv hdrs[] = {MAKE_NV(":status", "500")}; - writelen = write(pipefd[1], ERROR_HTML, sizeof(ERROR_HTML) - 1); - close(pipefd[1]); + rv = pipe(pipefd); + if (rv != 0) { + warn("Could not create pipe"); + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, + stream_data->stream_id, + NGHTTP2_INTERNAL_ERROR); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } - if (writelen != sizeof(ERROR_HTML) - 1) { - close(pipefd[0]); - return -1; - } + return 0; + } - stream_data->fd = pipefd[0]; + writelen = write(pipefd[1], ERROR_HTML, sizeof(ERROR_HTML) - 1); + close(pipefd[1]); - if (send_response_fd(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), - pipefd[0]) != 0) { - close(pipefd[0]); - return -1; - } - return 0; + if (writelen != sizeof(ERROR_HTML) - 1) { + close(pipefd[0]); + return -1; + } + + stream_data->fd = pipefd[0]; + + if (send_response_fd(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), + pipefd[0]) != 0) { + close(pipefd[0]); + return -1; + } + + return 0; } static int timeout_reply(nghttp2_session *session, http2_stream_data *stream_data) { - nghttp2_nv hdrs[] = {MAKE_NV(":status", "408")}; + nghttp2_nv hdrs[] = {MAKE_NV(":status", "408")}; - if (send_response_empty(session, stream_data->stream_id, hdrs, ARRLEN(hdrs)) != 0) - return -1; + if (send_response_empty(session, stream_data->stream_id, hdrs, ARRLEN(hdrs)) != 0) + return -1; - return 0; + return 0; } /* Minimum check for directory traversal. Returns nonzero if it is safe. */ static int check_path(const char *path) { - /* We don't like '\' in url. */ - return path[0] && path[0] == '/' && strchr(path, '\\') == NULL && - strstr(path, "/../") == NULL && strstr(path, "/./") == NULL && - !ends_with(path, "/..") && !ends_with(path, "/."); + /* We don't like '\' in url. */ + return path[0] && path[0] == '/' && strchr(path, '\\') == NULL && + strstr(path, "/../") == NULL && strstr(path, "/./") == NULL && + !ends_with(path, "/..") && !ends_with(path, "/."); } static int h2_fdpack(str *data) { - int rv; - ssize_t writelen; - int pipefd[2]; + int rv; + ssize_t writelen; + int pipefd[2]; - if (!data->s || data->len == 0) - return 0; + if (!data->s || data->len == 0) + return 0; - rv = pipe(pipefd); - if (rv != 0) { - LM_ERR("failed to create pipe %d (%s)\n", errno, strerror(errno)); - return -1; - } + rv = pipe(pipefd); + if (rv != 0) { + LM_ERR("failed to create pipe %d (%s)\n", errno, strerror(errno)); + return -1; + } - writelen = write(pipefd[1], data->s, data->len); - close(pipefd[1]); + writelen = write(pipefd[1], data->s, data->len); + close(pipefd[1]); - if (writelen != data->len) { - close(pipefd[0]); - return -1; - } + if (writelen != data->len) { + close(pipefd[0]); + return -1; + } - return pipefd[0]; + return pipefd[0]; } @@ -630,29 +630,32 @@ static int on_request_recv(nghttp2_session *session, static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { - http2_session_data *session_data = (http2_session_data *)user_data; - http2_stream_data *stream_data; - switch (frame->hd.type) { - case NGHTTP2_DATA: - case NGHTTP2_HEADERS: - LM_DBG("h2 header [%d], %p %ld\n", frame->hd.type, frame->headers.nva, frame->headers.nvlen); - /* Check that the client request has finished */ - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - stream_data = - nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); - LM_DBG("END STREAM, data: %p\n", stream_data); - /* For DATA and HEADERS frame, this callback may be called after - on_stream_close_callback. Check that stream still alive. */ - if (!stream_data) { - return 0; - } - return on_request_recv(session, session_data, stream_data); - } - break; - default: - break; - } - return 0; + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; + + switch (frame->hd.type) { + case NGHTTP2_DATA: + case NGHTTP2_HEADERS: + LM_DBG("h2 header [%d], %p %ld\n", frame->hd.type, frame->headers.nva, frame->headers.nvlen); + /* Check that the client request has finished */ + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + stream_data = + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + LM_DBG("END STREAM, data: %p\n", stream_data); + /* For DATA and HEADERS frame, this callback may be called after + on_stream_close_callback. Check that stream still alive. */ + if (!stream_data) { + return 0; + } + return on_request_recv(session, session_data, stream_data); + } + break; + + default: + break; + } + + return 0; } @@ -687,165 +690,165 @@ static int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *_value, size_t valuelen, uint8_t flags, void *user_data) { - http2_stream_data *stream_data; - const char PATH[] = ":path", METHOD[] = ":method"; - char *value = (char *)_value; - (void)flags; - (void)user_data; - - if (frame->hd.type != NGHTTP2_HEADERS - || frame->headers.cat != NGHTTP2_HCAT_REQUEST) - return 0; - - stream_data = - nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); - if (!stream_data) { - LM_ERR("failed to fetch data for stream %d\n", frame->hd.stream_id); - return 0; - } + http2_stream_data *stream_data; + const char PATH[] = ":path", METHOD[] = ":method"; + char *value = (char *)_value; + (void)flags; + (void)user_data; - LM_DBG("received hdr(%d) on stream %d: '%.*s' = '%.*s' (%p)\n", frame->hd.type, - stream_data->stream_id, (int)namelen, name, (int)valuelen, value, stream_data); + if (frame->hd.type != NGHTTP2_HEADERS + || frame->headers.cat != NGHTTP2_HCAT_REQUEST) + return 0; - if (stream_data->path) - goto store_hdr; + stream_data = + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + if (!stream_data) { + LM_ERR("failed to fetch data for stream %d\n", frame->hd.stream_id); + return 0; + } - if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { - size_t j; - for (j = 0; j < valuelen && _value[j] != '?'; ++j) - ; - stream_data->path = percent_decode(_value, j); + LM_DBG("received hdr(%d) on stream %d: '%.*s' = '%.*s' (%p)\n", frame->hd.type, + stream_data->stream_id, (int)namelen, name, (int)valuelen, value, stream_data); - LM_DBG("detected ':path' header, decoded value: '%s'\n", stream_data->path); + if (stream_data->path) + goto store_hdr; - value = stream_data->path; - valuelen = strlen(value); - } + if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { + size_t j; + for (j = 0; j < valuelen && _value[j] != '?'; ++j) + ; + stream_data->path = percent_decode(_value, j); -store_hdr: - if (stream_data->hdrs_len + namelen + valuelen > max_headers_size) { - LM_ERR("max_headers_size exceeded (%d), skipping header: %s\n", - max_headers_size, name); - return 0; - } + LM_DBG("detected ':path' header, decoded value: '%s'\n", stream_data->path); - { - cJSON *val = cJSON_CreateStr(value, valuelen); - str key = {(char *)name, namelen}; - - if (!val) { - LM_ERR("oom\n"); - return 0; + value = stream_data->path; + valuelen = strlen(value); } - _cJSON_AddItemToObject(stream_data->hdrs, &key, val); - if (!val->string) { - LM_ERR("oom\n"); - cJSON_Delete(val); +store_hdr: + if (stream_data->hdrs_len + namelen + valuelen > max_headers_size) { + LM_ERR("max_headers_size exceeded (%d), skipping header: %s\n", + max_headers_size, name); return 0; } - stream_data->hdrs_len += namelen + valuelen; + { + cJSON *val = cJSON_CreateStr(value, valuelen); + str key = {(char *)name, namelen}; - if (!stream_data->method && namelen == sizeof(METHOD) - 1 - && memcmp(METHOD, name, namelen) == 0) - stream_data->method = val->valuestring; - } + if (!val) { + LM_ERR("oom\n"); + return 0; + } + + _cJSON_AddItemToObject(stream_data->hdrs, &key, val); + if (!val->string) { + LM_ERR("oom\n"); + cJSON_Delete(val); + return 0; + } - return 0; + stream_data->hdrs_len += namelen + valuelen; + + if (!stream_data->method && namelen == sizeof(METHOD) - 1 + && memcmp(METHOD, name, namelen) == 0) + stream_data->method = val->valuestring; + } + + return 0; } static int on_begin_headers_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { - http2_session_data *session_data = (http2_session_data *)user_data; - http2_stream_data *stream_data; + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; - if (frame->hd.type != NGHTTP2_HEADERS || - frame->headers.cat != NGHTTP2_HCAT_REQUEST) { - return 0; - } + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } - stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); - if (!stream_data) { - LM_ERR("failed to allocate stream data\n"); - return -1; - } + stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); + if (!stream_data) { + LM_ERR("failed to allocate stream data\n"); + return -1; + } - LM_DBG("------------ BEGIN HEADERS (data: %p, stream_id: %d) ----------\n", stream_data, frame->hd.stream_id); - if (nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, - stream_data) < 0) { - LM_ERR("failed to set user data\n"); - return -1; - } + LM_DBG("------------ BEGIN HEADERS (data: %p, stream_id: %d) ----------\n", stream_data, frame->hd.stream_id); + if (nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, + stream_data) < 0) { + LM_ERR("failed to set user data\n"); + return -1; + } - return 0; + return 0; } static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data) { - http2_session_data *session_data = (http2_session_data *)user_data; - http2_stream_data *stream_data; - (void)error_code; + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; + (void)error_code; - stream_data = nghttp2_session_get_stream_user_data(session, stream_id); - if (!stream_data) { - return 0; - } - remove_stream(session_data, stream_data); - delete_http2_stream_data(stream_data); - h2_response_clean(); - return 0; + stream_data = nghttp2_session_get_stream_user_data(session, stream_id); + if (!stream_data) + return 0; + + remove_stream(session_data, stream_data); + delete_http2_stream_data(stream_data); + h2_response_clean(); + return 0; } static void initialize_nghttp2_session(http2_session_data *session_data) { - nghttp2_session_callbacks *callbacks; + nghttp2_session_callbacks *callbacks; - nghttp2_session_callbacks_new(&callbacks); + nghttp2_session_callbacks_new(&callbacks); - nghttp2_session_callbacks_set_send_callback2(callbacks, send_callback); - nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, - on_frame_recv_callback); - nghttp2_session_callbacks_set_on_stream_close_callback( - callbacks, on_stream_close_callback); - nghttp2_session_callbacks_set_on_header_callback(callbacks, - on_header_callback); - nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, - on_data_chunk_recv_callback); - nghttp2_session_callbacks_set_on_begin_headers_callback( - callbacks, on_begin_headers_callback); + nghttp2_session_callbacks_set_send_callback2(callbacks, send_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, + on_data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); - nghttp2_session_server_new(&session_data->session, callbacks, session_data); + nghttp2_session_server_new(&session_data->session, callbacks, session_data); - nghttp2_session_callbacks_del(callbacks); + nghttp2_session_callbacks_del(callbacks); } /* Send HTTP/2 client connection header, which includes 24 bytes magic octets and SETTINGS frame */ static int send_server_connection_header(http2_session_data *session_data) { - nghttp2_settings_entry iv[1] = { - {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; - int rv; - - rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv, - ARRLEN(iv)); - if (rv != 0) { - warnx("Fatal error: %s", nghttp2_strerror(rv)); - return -1; - } - return 0; + nghttp2_settings_entry iv[1] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; + int rv; + + rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv, + ARRLEN(iv)); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; } /* readcb for bufferevent after client connection header was checked. */ static void readcb(struct bufferevent *bev, void *ptr) { http2_session_data *session_data = (http2_session_data *)ptr; - (void)bev; + (void)bev; - if (session_recv(session_data) != 0) { - delete_http2_session_data(session_data); - return; - } + if (session_recv(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } } /* writecb for bufferevent. To greaceful shutdown after sending or @@ -857,130 +860,132 @@ static void readcb(struct bufferevent *bev, void *ptr) { because we have a threshold on the buffer size to avoid too much buffering. See send_callback(). */ static void writecb(struct bufferevent *bev, void *ptr) { - http2_session_data *session_data = (http2_session_data *)ptr; - if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) { - return; - } - if (nghttp2_session_want_read(session_data->session) == 0 && - nghttp2_session_want_write(session_data->session) == 0) { - delete_http2_session_data(session_data); - return; - } - if (session_send(session_data) != 0) { - delete_http2_session_data(session_data); - return; - } + http2_session_data *session_data = (http2_session_data *)ptr; + if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) + return; + + if (nghttp2_session_want_read(session_data->session) == 0 && + nghttp2_session_want_write(session_data->session) == 0) { + delete_http2_session_data(session_data); + return; + } + if (session_send(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } } /* eventcb for bufferevent */ static void eventcb(struct bufferevent *bev, short events, void *ptr) { http2_session_data *session_data = (http2_session_data *)ptr; - if (events & BEV_EVENT_CONNECTED) { - const unsigned char *alpn = NULL; - unsigned int alpnlen = 0; - SSL *ssl; - (void)bev; + if (events & BEV_EVENT_CONNECTED) { + const unsigned char *alpn = NULL; + unsigned int alpnlen = 0; + SSL *ssl; + (void)bev; - fprintf(stderr, "%s connected\n", session_data->client_addr); + fprintf(stderr, "%s connected\n", session_data->client_addr); - ssl = bufferevent_openssl_get_ssl(session_data->bev); + ssl = bufferevent_openssl_get_ssl(session_data->bev); - SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); + SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); - if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { - fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr); - delete_http2_session_data(session_data); - return; - } + if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { + fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr); + delete_http2_session_data(session_data); + return; + } - initialize_nghttp2_session(session_data); + initialize_nghttp2_session(session_data); - if (send_server_connection_header(session_data) != 0 || - session_send(session_data) != 0) { - delete_http2_session_data(session_data); - return; - } + if (send_server_connection_header(session_data) != 0 || + session_send(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } - return; - } - if (events & BEV_EVENT_EOF) { - fprintf(stderr, "%s EOF\n", session_data->client_addr); - } else if (events & BEV_EVENT_ERROR) { - fprintf(stderr, "%s network error\n", session_data->client_addr); - } else if (events & BEV_EVENT_TIMEOUT) { - fprintf(stderr, "%s timeout\n", session_data->client_addr); - } - delete_http2_session_data(session_data); + return; + } + + if (events & BEV_EVENT_EOF) + fprintf(stderr, "%s EOF\n", session_data->client_addr); + else if (events & BEV_EVENT_ERROR) + fprintf(stderr, "%s network error\n", session_data->client_addr); + else if (events & BEV_EVENT_TIMEOUT) + fprintf(stderr, "%s timeout\n", session_data->client_addr); + + delete_http2_session_data(session_data); } /* callback for evconnlistener */ static void acceptcb(struct evconnlistener *listener, int fd, struct sockaddr *addr, int addrlen, void *arg) { - app_context *app_ctx = (app_context *)arg; - http2_session_data *session_data; - (void)listener; + app_context *app_ctx = (app_context *)arg; + http2_session_data *session_data; + (void)listener; - session_data = create_http2_session_data(app_ctx, fd, addr, addrlen); + session_data = create_http2_session_data(app_ctx, fd, addr, addrlen); - bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data); + bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data); } static void start_listen(struct event_base *evbase, const char *service, app_context *app_ctx) { - int rv; - struct addrinfo hints; - struct addrinfo *res, *rp; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; + int rv; + struct addrinfo hints; + struct addrinfo *res, *rp; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; #ifdef AI_ADDRCONFIG - hints.ai_flags |= AI_ADDRCONFIG; + hints.ai_flags |= AI_ADDRCONFIG; #endif /* AI_ADDRCONFIG */ - rv = getaddrinfo(h2_ip, service, &hints, &res); - if (rv != 0) { - errx(1, "Could not resolve server address"); - } - for (rp = res; rp; rp = rp->ai_next) { - struct evconnlistener *listener; - listener = evconnlistener_new_bind( - evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, - 16, rp->ai_addr, (int)rp->ai_addrlen); - if (listener) { - freeaddrinfo(res); - - return; - } - } - errx(1, "Could not start listener"); + rv = getaddrinfo(h2_ip, service, &hints, &res); + if (rv != 0) + errx(1, "Could not resolve server address"); + + for (rp = res; rp; rp = rp->ai_next) { + struct evconnlistener *listener; + listener = evconnlistener_new_bind( + evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, + 16, rp->ai_addr, (int)rp->ai_addrlen); + + if (listener) { + freeaddrinfo(res); + return; + } + } + + errx(1, "Could not start listener"); } static void initialize_app_context(app_context *app_ctx, SSL_CTX *ssl_ctx, struct event_base *evbase) { - memset(app_ctx, 0, sizeof(app_context)); - app_ctx->ssl_ctx = ssl_ctx; - app_ctx->evbase = evbase; + memset(app_ctx, 0, sizeof(app_context)); + app_ctx->ssl_ctx = ssl_ctx; + app_ctx->evbase = evbase; } static void run(const char *service, const char *key_file, const char *cert_file) { - SSL_CTX *ssl_ctx; - app_context app_ctx; - struct event_base *evbase; + SSL_CTX *ssl_ctx; + app_context app_ctx; + struct event_base *evbase; - ssl_ctx = create_ssl_ctx(key_file, cert_file); - evbase = event_base_new(); - initialize_app_context(&app_ctx, ssl_ctx, evbase); - start_listen(evbase, service, &app_ctx); + ssl_ctx = create_ssl_ctx(key_file, cert_file); + evbase = event_base_new(); + initialize_app_context(&app_ctx, ssl_ctx, evbase); + start_listen(evbase, service, &app_ctx); - LM_DBG("event loop start\n"); - event_base_loop(evbase, 0); - LM_DBG("event loop end\n"); + LM_DBG("event loop start\n"); + event_base_loop(evbase, 0); + LM_DBG("event loop end\n"); - event_base_free(evbase); - SSL_CTX_free(ssl_ctx); + event_base_free(evbase); + SSL_CTX_free(ssl_ctx); } static void init_mutex_cond(pthread_mutex_t *mutex, pthread_cond_t *cond) From 61561c763d4bfe42c08098d3b92b031eb9da358d Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Tue, 26 Mar 2024 18:04:47 +0200 Subject: [PATCH 72/79] http2d: Convert errx/warnx to LM_ functions --- modules/http2d/server.c | 55 +++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/modules/http2d/server.c b/modules/http2d/server.c index cfbc67e3edb..c9d3bc6da1a 100644 --- a/modules/http2d/server.c +++ b/modules/http2d/server.c @@ -33,16 +33,6 @@ extern unsigned int max_headers_size; -#ifdef __sgi -# define errx(exitcode, format, args...) \ - { \ - warnx(format, ##args); \ - exit(exitcode); \ - } -# define warn(format, args...) warnx(format ": %s", ##args, strerror(errno)) -# define warnx(format, args...) fprintf(stderr, format "\n", ##args) -#endif - #ifdef HAVE_CONFIG_H # include #endif /* HAVE_CONFIG_H */ @@ -63,9 +53,6 @@ extern unsigned int max_headers_size; # include #endif /* HAVE_NETINET_IN_H */ #include -#ifndef __sgi -# include -#endif #include #include @@ -140,8 +127,9 @@ static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) { ssl_ctx = SSL_CTX_new(TLS_server_method()); if (!ssl_ctx) { - errx(1, "Could not create SSL/TLS context: %s", + LM_ERR("Could not create SSL/TLS context: %s", ERR_error_string(ERR_get_error(), NULL)); + return NULL; } SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | @@ -149,16 +137,18 @@ static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) { SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); #if OPENSSL_VERSION_NUMBER >= 0x30000000L if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) { - errx(1, "SSL_CTX_set1_curves_list failed: %s", + LM_ERR("SSL_CTX_set1_curves_list failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return NULL; } #else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ { EC_KEY *ecdh; ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); if (!ecdh) { - errx(1, "EC_KEY_new_by_curv_name failed: %s", + LM_ERR("EC_KEY_new_by_curv_name failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return NULL; } SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); EC_KEY_free(ecdh); @@ -166,10 +156,12 @@ static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) { #endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) { - errx(1, "Could not read private key file %s", key_file); + LM_ERR("Could not read private key file %s", key_file); + return NULL; } if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { - errx(1, "Could not read certificate file %s", cert_file); + LM_ERR("Could not read certificate file %s", cert_file); + return NULL; } SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL); @@ -182,8 +174,9 @@ static SSL *create_ssl(SSL_CTX *ssl_ctx) { SSL *ssl; ssl = SSL_new(ssl_ctx); if (!ssl) { - errx(1, "Could not create SSL/TLS session object: %s", + LM_ERR("Could not create SSL/TLS session object: %s", ERR_error_string(ERR_get_error(), NULL)); + return NULL; } return ssl; } @@ -294,7 +287,7 @@ static int session_send(http2_session_data *session_data) { int rv; rv = nghttp2_session_send(session_data->session); if (rv != 0) { - warnx("Fatal error: %s", nghttp2_strerror(rv)); + LM_WARN("Fatal error: %s", nghttp2_strerror(rv)); return -1; } return 0; @@ -312,11 +305,11 @@ static int session_recv(http2_session_data *session_data) { readlen = nghttp2_session_mem_recv2(session_data->session, data, datalen); if (readlen < 0) { - warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); + LM_WARN("Fatal error: %s", nghttp2_strerror((int)readlen)); return -1; } if (evbuffer_drain(input, (size_t)readlen) != 0) { - warnx("Fatal error: evbuffer_drain failed"); + LM_WARN("Fatal error: evbuffer_drain failed"); return -1; } if (session_send(session_data) != 0) @@ -428,7 +421,7 @@ static int send_response_fd(nghttp2_session *session, int32_t stream_id, rv = nghttp2_submit_response2(session, stream_id, nva, nvlen, fd > 0 ? &data_prd : NULL); if (rv != 0) { - warnx("Fatal error: %s", nghttp2_strerror(rv)); + LM_WARN("Fatal error: %s", nghttp2_strerror(rv)); return -1; } @@ -441,7 +434,7 @@ static int send_response_empty(nghttp2_session *session, int32_t stream_id, rv = nghttp2_submit_response2(session, stream_id, nva, nvlen, NULL); if (rv != 0) { - warnx("Fatal error: %s", nghttp2_strerror(rv)); + LM_WARN("Fatal error: %s", nghttp2_strerror(rv)); return -1; } return 0; @@ -459,12 +452,12 @@ static int error_reply(nghttp2_session *session, rv = pipe(pipefd); if (rv != 0) { - warn("Could not create pipe"); + LM_WARN("Could not create pipe"); rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_data->stream_id, NGHTTP2_INTERNAL_ERROR); if (rv != 0) { - warnx("Fatal error: %s", nghttp2_strerror(rv)); + LM_WARN("Fatal error: %s", nghttp2_strerror(rv)); return -1; } @@ -833,7 +826,7 @@ static int send_server_connection_header(http2_session_data *session_data) { rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv, ARRLEN(iv)); if (rv != 0) { - warnx("Fatal error: %s", nghttp2_strerror(rv)); + LM_WARN("Fatal error: %s", nghttp2_strerror(rv)); return -1; } return 0; @@ -944,8 +937,10 @@ static void start_listen(struct event_base *evbase, const char *service, #endif /* AI_ADDRCONFIG */ rv = getaddrinfo(h2_ip, service, &hints, &res); - if (rv != 0) - errx(1, "Could not resolve server address"); + if (rv != 0) { + LM_ERR("Could not resolve server address"); + return; + } for (rp = res; rp; rp = rp->ai_next) { struct evconnlistener *listener; @@ -959,7 +954,7 @@ static void start_listen(struct event_base *evbase, const char *service, } } - errx(1, "Could not start listener"); + LM_ERR("Could not start listener"); } static void initialize_app_context(app_context *app_ctx, SSL_CTX *ssl_ctx, From 72728e9c76106271999dacb31c064151d97114b8 Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Wed, 27 Mar 2024 11:03:57 +0200 Subject: [PATCH 73/79] http2d: Convert fprintf() logs; Measure IPC wait time --- modules/http2d/server.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/modules/http2d/server.c b/modules/http2d/server.c index c9d3bc6da1a..01a14869fee 100644 --- a/modules/http2d/server.c +++ b/modules/http2d/server.c @@ -264,7 +264,7 @@ static void delete_http2_session_data(http2_session_data *session_data) { SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev); struct list_head *it, *aux; - fprintf(stderr, "%s disconnected\n", session_data->client_addr); + LM_INFO("%s disconnected\n", session_data->client_addr); if (ssl) SSL_shutdown(ssl); bufferevent_free(session_data->bev); @@ -538,6 +538,8 @@ static int on_request_recv(nghttp2_session *session, int rc; struct timespec wait_until; struct timeval now, wait_time, res; + struct timespec begin; + unsigned long long diff_ns; char *H; if (!stream_data->path) { @@ -549,7 +551,10 @@ static int on_request_recv(nghttp2_session *session, LM_INFO("%s GET %s (stream_id: %d)\n", session_data->client_addr, stream_data->path, stream_data->stream_id); - LM_INFO("body: %.*s %d\n", stream_data->data.len, stream_data->data.s, stream_data->data.len); + if (stream_data->data.len) + LM_INFO("body: (%d) %.*s\n", stream_data->data.len, stream_data->data.len, stream_data->data.s); + else + LM_INFO("body: (none)\n"); if (!check_path(stream_data->path)) { if (error_reply(session, stream_data) != 0) { @@ -557,7 +562,6 @@ static int on_request_recv(nghttp2_session *session, } return 0; } - LM_INFO("A\n"); pthread_mutex_lock(&ng_h2_response->mutex); @@ -576,8 +580,11 @@ static int on_request_recv(nghttp2_session *session, wait_until.tv_sec = res.tv_sec; wait_until.tv_nsec = res.tv_usec * 1000UL; + clock_gettime(CLOCK_REALTIME, &begin); rc = pthread_cond_timedwait(&ng_h2_response->cond, &ng_h2_response->mutex, &wait_until); + diff_ns = get_clock_diff(&begin); + LM_DBG("waited %lld ns in total\n", diff_ns); if (rc != 0) { pthread_mutex_unlock(&ng_h2_response->mutex); @@ -600,7 +607,6 @@ static int on_request_recv(nghttp2_session *session, LM_DBG("rpl code: %d\n", ng_h2_response->code); LM_DBG("rpl # headers: %d\n", ng_h2_response->hdrs_len); - LM_DBG("body: %.*s\n", stream_data->data.len, stream_data->data.s); LM_DBG("rpl body: %.*s\n", ng_h2_response->body.len, ng_h2_response->body.s); int fd = h2_fdpack(&ng_h2_response->body); @@ -877,14 +883,14 @@ static void eventcb(struct bufferevent *bev, short events, void *ptr) { SSL *ssl; (void)bev; - fprintf(stderr, "%s connected\n", session_data->client_addr); + LM_INFO("%s connected\n", session_data->client_addr); ssl = bufferevent_openssl_get_ssl(session_data->bev); SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { - fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr); + LM_ERR("%s h2 is not negotiated\n", session_data->client_addr); delete_http2_session_data(session_data); return; } @@ -901,11 +907,11 @@ static void eventcb(struct bufferevent *bev, short events, void *ptr) { } if (events & BEV_EVENT_EOF) - fprintf(stderr, "%s EOF\n", session_data->client_addr); + LM_INFO("%s EOF\n", session_data->client_addr); else if (events & BEV_EVENT_ERROR) - fprintf(stderr, "%s network error\n", session_data->client_addr); + LM_INFO("%s network error\n", session_data->client_addr); else if (events & BEV_EVENT_TIMEOUT) - fprintf(stderr, "%s timeout\n", session_data->client_addr); + LM_INFO("%s timeout\n", session_data->client_addr); delete_http2_session_data(session_data); } From e939f09b611f61a459df7c910a509820bbbfd9d2 Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Wed, 27 Mar 2024 11:25:14 +0200 Subject: [PATCH 74/79] http2d: Add to "exclude_modules" --- Makefile.conf.template | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile.conf.template b/Makefile.conf.template index bcc9d42f2c6..7633aeb719f 100644 --- a/Makefile.conf.template +++ b/Makefile.conf.template @@ -28,10 +28,11 @@ #identity= Adds support for SIP Identity (see RFC 4474). | SSL library, typically libssl #jabber= Integrates XODE XML parser for parsing Jabber messages | Expat library. #json= Introduces a new type of variable that provides both serialization and de-serialization from JSON format. | JSON library, libjson -#launch_darkly= Implements an interface to the Launch Darkly feature management cloud +#launch_darkly= Implements an interface to the Launch Darkly feature management cloud | Launch Darkly C++ server-side SDK (libldserverapi) #ldap= Implements an LDAP search interface for OpenSIPS | OpenLDAP library & development files, typically libldap and libldap-dev #lua= Easily implement your own OpenSIPS extensions in Lua | liblua5.1-0-dev, libmemcache-dev and libmysqlclient-dev #httpd= Provides an HTTP transport layer implementation for OpenSIPS. | libmicrohttpd +#http2d= Provides HTTP/2 server-side support, using nghttp2 | nghttp2 library (libnghttp2), libevent, libssl and libevent-openssl #mi_xmlrpc_ng= New version of the xmlrpc server that handles xmlrpc requests and generates xmlrpc responses. | parsing/building XML library, typically libxml #mmgeoip= Lightweight wrapper for the MaxMind GeoIP API | libGeoIP #osp= Enables OpenSIPS to support secure, multi-lateral peering using the OSP standard | OSP development kit, typically osptoolkit @@ -68,7 +69,7 @@ #xmpp= Gateway between OpenSIPS and a jabber server. It enables the exchange of IMs between SIP clients and XMPP(jabber) clients. | parsing/building XML files, typically libexpat1-devel #uuid= UUID generator | uuid-dev -exclude_modules?= aaa_diameter aaa_radius auth_jwt b2b_logic_xml cachedb_cassandra cachedb_couchbase cachedb_memcached cachedb_mongodb cachedb_redis carrierroute cgrates compression cpl_c db_berkeley db_http db_mysql db_oracle db_perlvdb db_postgres db_sqlite db_unixodbc dialplan emergency event_rabbitmq event_kafka h350 httpd identity jabber json launch_darkly ldap lua mi_xmlrpc_ng mmgeoip osp perl pi_http presence presence_dialoginfo presence_mwi presence_xml presence_dfks proto_sctp proto_tls proto_wss pua pua_bla pua_dialoginfo pua_mi pua_usrloc pua_xmpp python regex rabbitmq rabbitmq_consumer rest_client rls siprec sngtc snmpstats stir_shaken tls_mgm tls_openssl tls_wolfssl uuid xcap xcap_client xml xmpp +exclude_modules?= aaa_diameter aaa_radius auth_jwt b2b_logic_xml cachedb_cassandra cachedb_couchbase cachedb_memcached cachedb_mongodb cachedb_redis carrierroute cgrates compression cpl_c db_berkeley db_http db_mysql db_oracle db_perlvdb db_postgres db_sqlite db_unixodbc dialplan emergency event_rabbitmq event_kafka h350 httpd http2d identity jabber json launch_darkly ldap lua mi_xmlrpc_ng mmgeoip osp perl pi_http presence presence_dialoginfo presence_mwi presence_xml presence_dfks proto_sctp proto_tls proto_wss pua pua_bla pua_dialoginfo pua_mi pua_usrloc pua_xmpp python regex rabbitmq rabbitmq_consumer rest_client rls siprec sngtc snmpstats stir_shaken tls_mgm tls_openssl tls_wolfssl uuid xcap xcap_client xml xmpp include_modules?= From 37555f8c9aa47d9bf67c3ddfecf06c330ef8a054 Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Wed, 27 Mar 2024 11:37:00 +0200 Subject: [PATCH 75/79] http2d: Skip on Unit Tests build and DEB/RPM builds --- packaging/debian/rules | 2 +- packaging/redhat_fedora/opensips.spec | 2 +- scripts/build/do_build.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packaging/debian/rules b/packaging/debian/rules index db2680c4ecc..60b88b5e281 100755 --- a/packaging/debian/rules +++ b/packaging/debian/rules @@ -278,7 +278,7 @@ BUILD_MODULE_PATHS = $(foreach pkg,$(BUILD_MODPKG_LIST),$($(pkg)_MOD_PATH)) ## modules not in the "main" package or unstable modules that we never want to build ## Everything we don't specifically exclude here will get built into the primary package -NONCORE_MODULES = $(ALL_MODULES) osp launch_darkly +NONCORE_MODULES = $(ALL_MODULES) osp launch_darkly http2d ifeq (cc, $(CC)) CC = gcc diff --git a/packaging/redhat_fedora/opensips.spec b/packaging/redhat_fedora/opensips.spec index 4d8efe99303..060f502b2f2 100644 --- a/packaging/redhat_fedora/opensips.spec +++ b/packaging/redhat_fedora/opensips.spec @@ -41,7 +41,7 @@ %global _with_wolfssl 1 %endif -%global EXCLUDE_MODULES %{!?_with_auth_jwt:auth_jwt} %{!?_with_cachedb_cassandra:cachedb_cassandra} %{!?_with_cachedb_couchbase:cachedb_couchbase} %{!?_with_cachedb_mongodb:cachedb_mongodb} %{!?_with_cachedb_redis:cachedb_redis} %{!?_with_db_oracle:db_oracle} %{!?_with_osp:osp} %{!?_with_sngtc:sngtc} %{!?_with_aaa_diameter:aaa_diameter} %{?_without_aaa_radius:aaa_radius} %{?_without_db_perlvdb:db_perlvdb} %{?_without_snmpstats:snmpstats} %{!?_with_wolfssl:tls_wolfssl} launch_darkly +%global EXCLUDE_MODULES %{!?_with_auth_jwt:auth_jwt} %{!?_with_cachedb_cassandra:cachedb_cassandra} %{!?_with_cachedb_couchbase:cachedb_couchbase} %{!?_with_cachedb_mongodb:cachedb_mongodb} %{!?_with_cachedb_redis:cachedb_redis} %{!?_with_db_oracle:db_oracle} %{!?_with_osp:osp} %{!?_with_sngtc:sngtc} %{!?_with_aaa_diameter:aaa_diameter} %{?_without_aaa_radius:aaa_radius} %{?_without_db_perlvdb:db_perlvdb} %{?_without_snmpstats:snmpstats} %{!?_with_wolfssl:tls_wolfssl} launch_darkly http2d Summary: Very fast and configurable SIP server Name: opensips diff --git a/scripts/build/do_build.sh b/scripts/build/do_build.sh index acca06c1a69..8e7e4037d72 100755 --- a/scripts/build/do_build.sh +++ b/scripts/build/do_build.sh @@ -7,7 +7,7 @@ set -e . $(dirname $0)/build.conf.sub EXCLUDE_MODULES="db_oracle osp sngtc cachedb_cassandra cachedb_couchbase \ - cachedb_mongodb auth_jwt event_kafka aaa_diameter launch_darkly" + cachedb_mongodb auth_jwt event_kafka aaa_diameter launch_darkly http2d" if [ ! -z "${EXCLUDE_MODULES_ADD}" ] then EXCLUDE_MODULES="${EXCLUDE_MODULES} ${EXCLUDE_MODULES_ADD}" From 9357cd43860f77057e8a0c17dded94b12357d840 Mon Sep 17 00:00:00 2001 From: Liviu Chircu Date: Wed, 27 Mar 2024 19:43:54 +0200 Subject: [PATCH 76/79] registrar docs: Clarify that save/lookup flags are CSVs --- lib/reg/doc/lookup_flags.xml | 3 ++- modules/mid_registrar/doc/mid_registrar_admin.xml | 4 ++-- modules/registrar/doc/registrar_admin.xml | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/reg/doc/lookup_flags.xml b/lib/reg/doc/lookup_flags.xml index f0efdcefe6d..d1d739ba656 100644 --- a/lib/reg/doc/lookup_flags.xml +++ b/lib/reg/doc/lookup_flags.xml @@ -1,4 +1,5 @@ -flags (string, optional) +flags (string, optional) - string composed of one or more of + the following flags, comma-separated: diff --git a/modules/mid_registrar/doc/mid_registrar_admin.xml b/modules/mid_registrar/doc/mid_registrar_admin.xml index 85aa061acf8..d27ea5761bb 100644 --- a/modules/mid_registrar/doc/mid_registrar_admin.xml +++ b/modules/mid_registrar/doc/mid_registrar_admin.xml @@ -859,8 +859,8 @@ modparam("mid_registrar", "gruu_secret", "my_secret") - flags (string, optional) - string of - the following flags: + flags (string, optional) - string composed of + one or more of the following flags, comma-separated: &save_common_flags; diff --git a/modules/registrar/doc/registrar_admin.xml b/modules/registrar/doc/registrar_admin.xml index ab04ae9fb13..b7ca87d3f04 100644 --- a/modules/registrar/doc/registrar_admin.xml +++ b/modules/registrar/doc/registrar_admin.xml @@ -481,8 +481,8 @@ modparam("registrar", "disable_gruu", 0) - flags (string, optional) - string of - the following flags: + flags (string, optional) - string composed of + one or more of the following flags, comma-separated: &save_common_flags; From 16fb12e0610bb88eccebe261d9990aca9a4f010c Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 28 Mar 2024 11:31:49 +0200 Subject: [PATCH 77/79] event_rabbitmq: add timeout support Add RPC timeout support for any command sent to the rabbitmq server --- .../doc/event_rabbitmq_admin.xml | 27 +++++++++++++++++++ modules/event_rabbitmq/event_rabbitmq.c | 17 ++++++++++++ modules/event_rabbitmq/rabbitmq_send.c | 5 ++++ modules/event_rabbitmq/rabbitmq_send.h | 3 +++ 4 files changed, 52 insertions(+) diff --git a/modules/event_rabbitmq/doc/event_rabbitmq_admin.xml b/modules/event_rabbitmq/doc/event_rabbitmq_admin.xml index 5400e6b6bf1..eab2c376b35 100644 --- a/modules/event_rabbitmq/doc/event_rabbitmq_admin.xml +++ b/modules/event_rabbitmq/doc/event_rabbitmq_admin.xml @@ -206,6 +206,33 @@ modparam("event_rabbitmq", "use_tls", 1)
+
+ <varname>timeout</varname> (integer) + + Indicates the timeout (in milliseconds) of any command (i.e. publish) + sent to the RabbitMQ server. + + + NOTE that this parameter is available only starting with + RabbitMQ library version 0.9.0; setting it when using an + earlier version will have no effect, and the publish command will run in + blocking mode. + + + + Default value is 0 (no timeout - blocking mode) + + + + Set the <varname>timeout</varname> parameter + +... +modparam("event_rabbitmq", "timeout", 1000) # timeout after 1s +... + + +
+
diff --git a/modules/event_rabbitmq/event_rabbitmq.c b/modules/event_rabbitmq/event_rabbitmq.c index 2c6bb85f32a..9027e07ad13 100644 --- a/modules/event_rabbitmq/event_rabbitmq.c +++ b/modules/event_rabbitmq/event_rabbitmq.c @@ -45,7 +45,11 @@ static void destroy(void); */ static unsigned int heartbeat = 0; static int rmq_connect_timeout = RMQ_DEFAULT_CONNECT_TIMEOUT; +static int rmq_timeout = 0; struct timeval conn_timeout_tv; +#if defined AMQP_VERSION && AMQP_VERSION >= 0x00090000 +struct timeval rpc_timeout_tv; +#endif int use_tls; struct tls_mgm_binds tls_api; @@ -70,6 +74,7 @@ static const proc_export_t procs[] = { static const param_export_t mod_params[] = { {"heartbeat", INT_PARAM, &heartbeat}, {"connect_timeout", INT_PARAM, &rmq_connect_timeout}, + {"timeout", INT_PARAM, &rmq_timeout}, {"use_tls", INT_PARAM, &use_tls}, {0,0,0} }; @@ -160,6 +165,18 @@ static int mod_init(void) conn_timeout_tv.tv_sec = rmq_connect_timeout/1000; conn_timeout_tv.tv_usec = (rmq_connect_timeout%1000)*1000; +#if defined AMQP_VERSION && AMQP_VERSION >= 0x00090000 + if (rmq_timeout < 0) { + LM_WARN("invalid value for 'timeout' %d; fallback to blocking mode\n", rmq_timeout); + rmq_timeout = 0; + } + rpc_timeout_tv.tv_sec = rmq_timeout/1000; + rpc_timeout_tv.tv_usec = (rmq_timeout%1000)*1000; +#else + if (rmq_timeout != 0) + LM_WARN("setting the timeout without support for it; fallback to blocking mode\n"); +#endif + if (use_tls) { #ifndef AMQP_VERSION_v04 LM_ERR("TLS not supported for librabbitmq version lower than 0.4.0\n"); diff --git a/modules/event_rabbitmq/rabbitmq_send.c b/modules/event_rabbitmq/rabbitmq_send.c index aac90a02057..0afd77e39cf 100644 --- a/modules/event_rabbitmq/rabbitmq_send.c +++ b/modules/event_rabbitmq/rabbitmq_send.c @@ -357,6 +357,11 @@ static int rmq_reconnect(evi_reply_sock *sock) LM_ERR("cannot open AMQP socket: %d\n", socket); goto destroy_rmqp; } +#if defined AMQP_VERSION && AMQP_VERSION >= 0x00090000 + if (rpc_timeout_tv.tv_sec > 0 && + amqp_set_rpc_timeout(rmqp->conn, &rpc_timeout_tv) < 0) + LM_ERR("setting RPC timeout - going blocking\n"); +#endif #else socket = amqp_open_socket_noblock(sock->address.s, sock->port, &conn_timeout_tv); diff --git a/modules/event_rabbitmq/rabbitmq_send.h b/modules/event_rabbitmq/rabbitmq_send.h index db6d0b9c32f..99ac08400fb 100644 --- a/modules/event_rabbitmq/rabbitmq_send.h +++ b/modules/event_rabbitmq/rabbitmq_send.h @@ -51,6 +51,9 @@ void rmq_free_param(rmq_params_t *rmqp); void rmq_destroy(evi_reply_sock *sock); extern struct timeval conn_timeout_tv; +#if defined AMQP_VERSION && AMQP_VERSION >= 0x00090000 +extern struct timeval rpc_timeout_tv; +#endif extern str rmq_static_holder; #endif From a889ac9152fd629444d1a52596a23030eee9b02b Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 28 Mar 2024 11:41:11 +0200 Subject: [PATCH 78/79] rabbitmq: don't block indefinitely on connect ported from fec5b51 --- modules/rabbitmq/doc/rabbitmq_admin.xml | 21 +++++++++++++++++++++ modules/rabbitmq/rabbitmq.c | 7 +++++++ modules/rabbitmq/rmq_servers.c | 6 ++++-- modules/rabbitmq/rmq_servers.h | 3 +++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/modules/rabbitmq/doc/rabbitmq_admin.xml b/modules/rabbitmq/doc/rabbitmq_admin.xml index df2b268df43..6e776d0af47 100644 --- a/modules/rabbitmq/doc/rabbitmq_admin.xml +++ b/modules/rabbitmq/doc/rabbitmq_admin.xml @@ -189,6 +189,27 @@ modparam("tls_mgm", "ca_list", "[rmq]/etc/pki/tls/certs/ca.pem") ... modparam("rabbitmq", "use_tls", 1) ... + + +
+ +
+ <varname>connect_timeout</varname> (integer) + + The maximally allowed duration (in milliseconds) for the establishment + of a TCP connection with a RabbitMQ server. + + + + Default value is 500 (milliseconds). + + + + Setting the <varname>connect_timeout</varname> parameter + + +aram("rabbitmq", "connect_timeout", 1000) +
diff --git a/modules/rabbitmq/rabbitmq.c b/modules/rabbitmq/rabbitmq.c index 364f532dbe8..100c2741dc1 100644 --- a/modules/rabbitmq/rabbitmq.c +++ b/modules/rabbitmq/rabbitmq.c @@ -44,6 +44,9 @@ static int rmq_publish(struct sip_msg *msg, struct rmq_server *srv, str *srkey, int use_tls; struct openssl_binds openssl_api; struct tls_mgm_binds tls_api; +static int rmq_connect_timeout = RMQ_DEFAULT_CONNECT_TIMEOUT; + +struct timeval conn_timeout_tv; #if AMQP_VERSION < AMQP_VERSION_CODE(0, 10, 0, 0) gen_lock_t *ssl_lock; @@ -53,6 +56,7 @@ static const param_export_t params[]={ { "server_id", STR_PARAM|USE_FUNC_PARAM, (void *)rmq_server_add}, {"use_tls", INT_PARAM, &use_tls}, + {"connect_timeout", INT_PARAM, &rmq_connect_timeout}, {0,0,0} }; @@ -158,6 +162,9 @@ static int mod_init(void) amqp_set_initialize_ssl_library(0); } + conn_timeout_tv.tv_sec = rmq_connect_timeout/1000; + conn_timeout_tv.tv_usec = (rmq_connect_timeout%1000)*1000; + return 0; } diff --git a/modules/rabbitmq/rmq_servers.c b/modules/rabbitmq/rmq_servers.c index ade97571d8e..ba3eec0ee78 100644 --- a/modules/rabbitmq/rmq_servers.c +++ b/modules/rabbitmq/rmq_servers.c @@ -335,14 +335,16 @@ int rmq_reconnect(struct rmq_server *srv) } } - socket = amqp_socket_open(amqp_sock, srv->uri.host, srv->uri.port); + socket = amqp_socket_open_noblock(amqp_sock, srv->uri.host, + srv->uri.port, &conn_timeout_tv); if (socket < 0) { LM_ERR("cannot open AMQP socket\n"); goto clean_rmq_conn; } #else - socket = amqp_open_socket(srv->uri.host, srv->uri.port); + socket = amqp_open_socket_noblock(srv->uri.host, srv->uri.port, + &conn_timeout_tv); if (socket < 0) { LM_ERR("cannot open AMQP socket\n"); goto clean_rmq_conn; diff --git a/modules/rabbitmq/rmq_servers.h b/modules/rabbitmq/rmq_servers.h index 9a6788ecb38..bdbf17dbe0a 100644 --- a/modules/rabbitmq/rmq_servers.h +++ b/modules/rabbitmq/rmq_servers.h @@ -31,6 +31,8 @@ #define RMQ_MIN_FRAMES 4096 #define RMQ_DEFAULT_FRAMES 131072 +#define RMQ_DEFAULT_CONNECT_TIMEOUT 500 /* ms */ + #ifndef AMQP_VERSION_CODE #define AMQP_VERSION_CODE(major, minor, patch, release) \ ((major << 24) | (minor << 16) | (patch << 8) | (release)) @@ -105,5 +107,6 @@ int rmq_send(struct rmq_server *srv, str *rkey, str *body, str *ctype, extern int use_tls; extern struct openssl_binds openssl_api; extern struct tls_mgm_binds tls_api; +extern struct timeval conn_timeout_tv; #endif /* _RMQ_SERVERS_H_ */ From 4b23a80bd14dcf509ebe8de22f26906d34e0b079 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 28 Mar 2024 11:46:18 +0200 Subject: [PATCH 79/79] event_rabbitmq: add timeout support Add RPC timeout support for any command sent to the rabbitmq server --- modules/rabbitmq/doc/rabbitmq_admin.xml | 27 +++++++++++++++++++++++++ modules/rabbitmq/rabbitmq.c | 17 ++++++++++++++++ modules/rabbitmq/rmq_servers.c | 5 +++++ modules/rabbitmq/rmq_servers.h | 3 +++ 4 files changed, 52 insertions(+) diff --git a/modules/rabbitmq/doc/rabbitmq_admin.xml b/modules/rabbitmq/doc/rabbitmq_admin.xml index 6e776d0af47..506ad2e8354 100644 --- a/modules/rabbitmq/doc/rabbitmq_admin.xml +++ b/modules/rabbitmq/doc/rabbitmq_admin.xml @@ -210,6 +210,33 @@ modparam("rabbitmq", "use_tls", 1) aram("rabbitmq", "connect_timeout", 1000) + + +
+ +
+ <varname>timeout</varname> (integer) + + Indicates the timeout (in milliseconds) of any command (i.e. publish) + sent to the RabbitMQ server. + + + NOTE that this parameter is available only starting with + RabbitMQ library version 0.9.0; setting it when using an + earlier version will have no effect, and the publish command will run in + blocking mode. + + + + Default value is 0 (no timeout - blocking mode) + + + + Set the <varname>timeout</varname> parameter + +... +modparam("rabbitmq", "timeout", 1000) # timeout after 1s +...
diff --git a/modules/rabbitmq/rabbitmq.c b/modules/rabbitmq/rabbitmq.c index 100c2741dc1..f67b633ad59 100644 --- a/modules/rabbitmq/rabbitmq.c +++ b/modules/rabbitmq/rabbitmq.c @@ -45,8 +45,12 @@ int use_tls; struct openssl_binds openssl_api; struct tls_mgm_binds tls_api; static int rmq_connect_timeout = RMQ_DEFAULT_CONNECT_TIMEOUT; +static int rmq_timeout = 0; struct timeval conn_timeout_tv; +#if defined AMQP_VERSION && AMQP_VERSION >= 0x00090000 +struct timeval rpc_timeout_tv; +#endif #if AMQP_VERSION < AMQP_VERSION_CODE(0, 10, 0, 0) gen_lock_t *ssl_lock; @@ -57,6 +61,7 @@ static const param_export_t params[]={ (void *)rmq_server_add}, {"use_tls", INT_PARAM, &use_tls}, {"connect_timeout", INT_PARAM, &rmq_connect_timeout}, + {"timeout", INT_PARAM, &rmq_timeout}, {0,0,0} }; @@ -165,6 +170,18 @@ static int mod_init(void) conn_timeout_tv.tv_sec = rmq_connect_timeout/1000; conn_timeout_tv.tv_usec = (rmq_connect_timeout%1000)*1000; +#if defined AMQP_VERSION && AMQP_VERSION >= 0x00090000 + if (rmq_timeout < 0) { + LM_WARN("invalid value for 'timeout' %d; fallback to blocking mode\n", rmq_timeout); + rmq_timeout = 0; + } + rpc_timeout_tv.tv_sec = rmq_timeout/1000; + rpc_timeout_tv.tv_usec = (rmq_timeout%1000)*1000; +#else + if (rmq_timeout != 0) + LM_WARN("setting the timeout without support for it; fallback to blocking mode\n"); +#endif + return 0; } diff --git a/modules/rabbitmq/rmq_servers.c b/modules/rabbitmq/rmq_servers.c index ba3eec0ee78..8e1b1c0d100 100644 --- a/modules/rabbitmq/rmq_servers.c +++ b/modules/rabbitmq/rmq_servers.c @@ -341,6 +341,11 @@ int rmq_reconnect(struct rmq_server *srv) LM_ERR("cannot open AMQP socket\n"); goto clean_rmq_conn; } +#if defined AMQP_VERSION && AMQP_VERSION >= 0x00090000 + if (rpc_timeout_tv.tv_sec > 0 && + amqp_set_rpc_timeout(srv->conn, &rpc_timeout_tv) < 0) + LM_ERR("setting RPC timeout - going blocking\n"); +#endif #else socket = amqp_open_socket_noblock(srv->uri.host, srv->uri.port, diff --git a/modules/rabbitmq/rmq_servers.h b/modules/rabbitmq/rmq_servers.h index bdbf17dbe0a..bc3fb2c51ee 100644 --- a/modules/rabbitmq/rmq_servers.h +++ b/modules/rabbitmq/rmq_servers.h @@ -108,5 +108,8 @@ extern int use_tls; extern struct openssl_binds openssl_api; extern struct tls_mgm_binds tls_api; extern struct timeval conn_timeout_tv; +#if defined AMQP_VERSION && AMQP_VERSION >= 0x00090000 +extern struct timeval rpc_timeout_tv; +#endif #endif /* _RMQ_SERVERS_H_ */