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?= 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/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/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/doc/build-contrib.sh b/doc/build-contrib.sh index 9c3c756d4c7..d28a9d25bd2 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" @@ -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() { 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/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/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__ */ 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__ */ 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); diff --git a/lib/digest_auth/digest_auth.h b/lib/digest_auth/digest_auth.h index 651abf46e9d..b0905429a2f 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; @@ -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 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/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/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/aaa_diameter.c b/modules/aaa_diameter/aaa_diameter.c index 9e494c3e4be..4888be2ff5d 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); @@ -429,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); @@ -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,15 +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); - if (ret > 0 && amsg->ret && 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 && 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; } @@ -550,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) { @@ -560,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..1071b3d9c26 --- /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, 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, void *reply_param); + +/* + 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..f4f756bc5f2 --- /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, 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); + +#endif /* DIAMETER_API_IMPL_H */ diff --git a/modules/aaa_diameter/dm_impl.c b/modules/aaa_diameter/dm_impl.c index 3630421cc17..4bf8470c3ff 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, void *param) { 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,16 @@ 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.f = cb; + cond->sync.cb.p = param; + break; } return cond; @@ -130,7 +140,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, NULL); return my_reply_cond?0:-1; } @@ -266,19 +276,27 @@ 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: + if (cond->sync.cb.f) + cond->sync.cb.f(NULL, &cond->rpl, cond->sync.cb.p); + shm_free(cond); + break; } } @@ -292,6 +310,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,25 +330,30 @@ 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; } rpl_cond = *prpl_cond; - rpl_cond->rc = rc; + rpl_cond->rpl.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) { - 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); @@ -596,6 +620,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,8 +668,11 @@ 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; @@ -656,15 +684,14 @@ static int dm_receive_msg(struct msg **_msg, struct avp * avp, struct session * goto out; } - if (rpl_cond->rpl_avps_json) - shm_free(rpl_cond->rpl_avps_json); - rpl_cond->rpl_avps_json = cJSON_PrintUnformatted(avps); + 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; @@ -673,12 +700,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)); @@ -1652,6 +1678,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) @@ -1880,93 +1912,119 @@ int dm_build_avps(struct list_head *out_avps, cJSON *array) return -1; } +static void dm_push_queue(aaa_message *msg, struct dm_cond *cond) +{ + struct dm_message *dm = (struct dm_message *)(msg->avpair); + dm->reply_cond = cond; + msg->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, 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, void *param) +{ + struct dm_cond *cond; - pthread_mutex_unlock(msg_send_lk); + if (!req) + return -1; + + cond = dm_get_cond(DM_TYPE_CB, cb, param); + 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 1; + memcpy(rpl, &cond->rpl, sizeof *rpl); + cond->rpl.json = NULL; /* also detach the data */ + return (cond->rpl.is_error?-1:0); } +int _dm_get_message_response(struct dm_cond *cond, char **rpl_avps) +{ + cJSON *obj; + diameter_reply rpl; + int rc = _dm_get_message_reply(cond, &rpl); -int _dm_send_message(aaa_conn *_, aaa_message *msg, aaa_message **reply, - char **rpl_avps) + if (rpl_avps) { + obj = dm_api_get_reply(&rpl); + *rpl_avps = cJSON_PrintUnformatted(obj); + LM_DBG("AVPs: %s\n", *rpl_avps); + } + return rc; +} + +int _dm_send_message(aaa_conn *_, aaa_message *msg, struct dm_cond **reply_cond) { - struct dm_message *dm; - int await_reply = 0; + struct timespec wait_until; + struct timeval now, wait_time, res; + int rc, await_reply = 0; if (!msg || !my_reply_cond) return -1; - dm = (struct dm_message *)(msg->avpair); - dm->reply_cond = my_reply_cond; - - /* never provide the reply, just grab the result code, if any */ - if (reply) - *reply = NULL; - - msg->last_found = DM_MSG_SENT; if (msg->type == AAA_AUTH || msg->type == AAA_CUSTOM_REQ) await_reply = 1; - pthread_mutex_lock(msg_send_lk); - - list_add_tail(&dm->list, msg_send_queue); - pthread_cond_signal(msg_send_cond); + LM_DBG("queue message for sending, type %d\n", msg->type); pthread_mutex_lock(&my_reply_cond->sync.cond.mutex); - pthread_mutex_unlock(msg_send_lk); - - LM_DBG("message queued for sending, await_reply: %d\n", await_reply); + dm_push_queue(msg, my_reply_cond); + /* WARNING: @msg *cannot* be read anymore here! (dangling pointer) */ if (!await_reply) { pthread_mutex_unlock(&my_reply_cond->sync.cond.mutex); - return 1; + return 0; } - 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; @@ -1984,20 +2042,115 @@ int _dm_send_message(aaa_conn *_, aaa_message *msg, aaa_message **reply, "reply\n", rc, strerror(rc)); pthread_mutex_unlock(&my_reply_cond->sync.cond.mutex); - if (rpl_avps) - *rpl_avps = NULL; return -2; } - pthread_mutex_unlock(&my_reply_cond->sync.cond.mutex); + if (reply_cond) + *reply_cond = my_reply_cond; + + 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); +} + +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; + + if (!req) { + LM_ERR("no request provided\n"); + return -1; + } + + if (req->type != cJSON_Array) { + LM_ERR("request must be an array\n"); + return -2; + } + + dmsg = _dm_create_message(NULL, AAA_CUSTOM_REQ, app_id, cmd_code, NULL); + if (!dmsg) { + LM_ERR("oom\n"); + return -1; + } + + 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 (_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; +} + +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; + + if (!req) { + LM_ERR("no request provided\n"); + return -1; + } + + if (req->type != cJSON_Array) { + LM_ERR("request must be an array\n"); + return -2; + } + + dmsg = _dm_create_message(NULL, AAA_CUSTOM_REQ, app_id, cmd_code, NULL); + if (!dmsg) { + LM_ERR("oom\n"); + return -1; + } + + 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 (_dm_send_message_callback(NULL, dmsg, reply_cb, reply_param) != 0) { + LM_ERR("could not send Diameter callback message\n"); + return -1; + } + return 0; - 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); } @@ -2196,8 +2349,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 +2360,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/modules/aaa_diameter/dm_impl.h b/modules/aaa_diameter/dm_impl.h index c8bb28db03c..2de25d5fae8 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,13 @@ struct dm_cond { int fd; int pid; } event; + struct { + diameter_reply_cb *f; + void *p; + } 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 +168,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); 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/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/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/aka_av_diameter/aka_av_diameter.c b/modules/aka_av_diameter/aka_av_diameter.c new file mode 100644 index 00000000000..391c175bfe8 --- /dev/null +++ b/modules/aka_av_diameter/aka_av_diameter.c @@ -0,0 +1,597 @@ +/* + * 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_AAA, "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 = -1; + + 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) + 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/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/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 +} 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/api.c b/modules/auth/api.c index bd16f767d8b..a1160618d93 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"); @@ -545,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 2235bdb9869..34cc0414ce5 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); /* @@ -103,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 */ @@ -121,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/auth_mod.c b/modules/auth/auth_mod.c index 46983e1cb33..ca9daa90400 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" @@ -447,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/challenge.c b/modules/auth/challenge.c index 5a1a84b69e4..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); @@ -276,40 +254,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/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); } 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/qop.h b/modules/auth/qop.h new file mode 100644 index 00000000000..41f8bf468bc --- /dev/null +++ b/modules/auth/qop.h @@ -0,0 +1,99 @@ +/* + * 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; +} + +#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 */ 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/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_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/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/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_aka/aka_av_mgm.c b/modules/auth_aka/aka_av_mgm.c new file mode 100644 index 00000000000..22945f3adab --- /dev/null +++ b/modules/auth_aka/aka_av_mgm.c @@ -0,0 +1,619 @@ +/* + * 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_binds *binds); + +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->binds) < 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 (user->ref != 0 || !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--; + 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)) { + av = NULL; + 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; +} + +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); + av->ts = get_ticks(); +} + +int aka_av_get_new_wait(struct aka_user *user, int algmask, + long milliseconds, struct aka_av **av) +{ + int ret = -1; + struct timespec spec, end, begin; + + cond_lock(&user->cond); + 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); + 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); + 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) { + /* 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; + } + } + if (*av) { + aka_av_mark_using(*av, algmask); + ret = 1; + } else { + ret = 0; + } +end: + cond_unlock(&user->cond); + return ret; +} + +int aka_av_get_new(struct aka_user *user, int algmask, struct aka_av **av) +{ + int ret; + cond_lock(&user->cond); + 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 ret; +} + +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; +} + +void aka_av_free(struct aka_av *av) +{ + list_del(&av->list); + shm_free(av); +} + +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); + av->ts = av->new_ts = get_ticks(); + ret = 1; + LM_DBG("adding av %p\n", av); +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); +} + +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) +{ + cond_lock(&user->cond); + av->state = AKA_AV_NEW; + av->ts = av->new_ts; /* restore the new timestamp */ + 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_unsafe_async(struct aka_user *user, struct list_head *subs) +{ + 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, *usafe; + unsigned int ticks = *(unsigned int*)param; + struct aka_user *user; + struct aka_user_pub *pub = (struct aka_user_pub *)value; + + 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) { + 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); + aka_user_try_free(user); + } + 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/aka_av_mgm.h b/modules/auth_aka/aka_av_mgm.h new file mode 100644 index 00000000000..6826cc71374 --- /dev/null +++ b/modules/auth_aka/aka_av_mgm.h @@ -0,0 +1,104 @@ +/* + * 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_" + +/* + * 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 { + aka_av_fetch_f fetch; +}; + +/* + * 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); + +/* + * 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); + +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 new file mode 100644 index 00000000000..2c001645074 --- /dev/null +++ b/modules/auth_aka/auth_aka.c @@ -0,0 +1,1425 @@ +/* + * 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 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 */ + +/* + * 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 */ +static int aka_unused_timeout = 60; /* s */ +static int aka_pending_timeout = 30; /* s */ + +/* + * 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}, + {"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} +}; + +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}}, + {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}}}, + { "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} +}; + +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_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) { + 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; + } + + 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; +} + +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)aka_default_qop; + } + 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 _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 inline int aka_avs_new_wait(struct aka_user *user, int *algmask, + struct aka_av **avs, int count) +{ + 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 (%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, int *err_count) +{ + int c; + *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; + } + } +end: + LM_DBG("got %d AVs out of %d (%d error)\n", c, count, *err_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) +{ + 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, err_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 = aka_avs_get_new(user, &algmask, avs, count, &err_count); + + /* if we need more, fetch them remotely */ + 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_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); + } + + ret = aka_send_resp(_msg, &realm, user, avs, new_count, qop, + _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, avs_error; + int process_no; + unsigned int ticks; + struct list_head list; + async_ctx *async; + char buf[0]; +}; + +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 */ + 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, 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); + 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_error) != param->avs_count) { + 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", + param->avs_fetched, param->avs_count, param->avs_error); + else + 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"); + } + param->replied = 1; + } + 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; + } + 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, err_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) { + avs = &av; + 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 + + 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 */ + + 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); + + /* now that we finished preparing, go fetch the vectors */ + if (mgm->binds.fetch(&realm, &user->public->impu, &user->impi, + (sync.len?&sync:NULL), algmask, 1, 1) != 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->ref = 1; + 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; + 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; + + aka_push_async(user, ¶m->list); + return 1; + +synchronous: + async_status = ASYNC_NO_IO; + ret = aka_send_resp(_msg, &realm, user, avs, count, qop, + _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); +} + +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) +{ + param->ref++; + if (ipc_send_rpc(param->process_no, func, param) < 0) { + LM_ERR("could not resume aka challenge\n"); + aka_async_param_remove(param); + aka_async_param_unref(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); +} + +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) +{ + 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 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) +{ + 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)); +} + +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 new file mode 100644 index 00000000000..fce35e017a1 --- /dev/null +++ b/modules/auth_aka/auth_aka.h @@ -0,0 +1,119 @@ +/* + * 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 */ + time_t ts, new_ts; + 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; + int error_count; + struct aka_user_pub *public; + struct list_head avs; + struct list_head list; + struct list_head async; + gen_cond_t cond; + 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); + +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); +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); + +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_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); + +#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..d17081c4355 --- /dev/null +++ b/modules/auth_aka/doc/auth_aka_admin.xml @@ -0,0 +1,858 @@ + + + + + &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> (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. + + + <varname>sync_timeout</varname> parameter usage + + +modparam("auth_aka", "sync_timeout", 200) + + +
+
+ <varname>async_timeout</varname> (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. + + + <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) + + +
+
+ +
+ 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)); +... + + + +
+
+ <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); +... + + + +
+
+ +
+ 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 +... + + +
+
+ <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= +... + + +
+
+
+ diff --git a/modules/dbops/doc/contributors.xml b/modules/auth_aka/doc/contributors.xml similarity index 83% rename from modules/dbops/doc/contributors.xml rename to modules/auth_aka/doc/contributors.xml index d9ed441cdbf..81277aa2580 100644 --- a/modules/dbops/doc/contributors.xml +++ b/modules/auth_aka/doc/contributors.xml @@ -20,11 +20,11 @@ 1. - Bogdan-Andrei Iancu (@bogdan-iancu) - 33 - 3 - 3495 - 18 + Razvan Crainea (@razvancrainea) + 45 + 14 + 3319 + 157 @@ -56,7 +56,7 @@ 1. - Bogdan-Andrei Iancu (@bogdan-iancu) + Razvan Crainea (@razvancrainea) Feb 2024 - Mar 2024 @@ -73,7 +73,7 @@ Documentation
Contributors - Last edited by: Bogdan-Andrei Iancu (@bogdan-iancu). + Last edited by: Razvan Crainea (@razvancrainea).
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/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; 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/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/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; } 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/dbops/README b/modules/dbops/README deleted file mode 100644 index 45737a6cacc..00000000000 --- a/modules/dbops/README +++ /dev/null @@ -1,517 +0,0 @@ -DBops Module - __________________________________________________________ - - Table of Contents - - 1. Admin Guide - - 1.1. Overview - 1.2. Dependencies - - 1.2.1. OpenSIPS Modules - 1.2.2. External Libraries or Applications - - 1.3. Exported Parameters - - 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.4. Exported Functions - - 1.4.1. db_avp_load(source, name, [db_id], [prefix]]) - - 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.5. Exported Asynchronous Functions - - 1.5.1. db_query(query, [dest], [db_id]) - 1.5.2. db_query_one(query, [dest], [db_id]) - - 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. 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 - -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). - -1.2. Dependencies - -1.2.1. OpenSIPS Modules - - The following modules must be loaded before this module: - * a database module - -1.2.2. External Libraries or Applications - - The following libraries or applications must be installed - before running OpenSIPS with this module loaded: - * None - -1.3. Exported Parameters - -1.3.1. db_url (string) - - DB URL for database connection. As the module allows the usage - of multiple DBs (DB URLs), the actual DB URL may be preceded by - an reference number. This reference number is to be passed to - AVPOPS function that what to explicitly use this DB connection. - If no reference number is given, 0 is assumed - this is the - default DB URL. - - This parameter is optional, it's default value being NULL. - - Example 1.1. Set db_url parameter -... -# default URL -modparam("dbops","db_url","mysql://user:passwd@host/database") -# an additional DB URL -modparam("dbops","db_url","1 postgres://user:passwd@host2/opensips") -... - -1.3.2. usr_table (string) - - DB table to be used for user preferences (AVPs) - - This parameter is optional, it's default value being - “usr_preferences”. - - Example 1.2. Set usr_table parameter -... -modparam("dbops","usr_table","avptable") -... - -1.3.3. 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 -... -modparam("dbops","use_domain",1) -... - -1.3.4. uuid_column (string) - - Name of column containing the uuid (unique user id). - - Default value is “uuid”. - - Example 1.4. Set uuid_column parameter -... -modparam("dbops","uuid_column","uuid") -... - -1.3.5. username_column (string) - - Name of column containing the username. - - Default value is “username”. - - Example 1.5. Set username_column parameter -... -modparam("dbops","username_column","username") -... - -1.3.6. domain_column (string) - - Name of column containing the domain name. - - Default value is “domain”. - - Example 1.6. Set domain_column parameter -... -modparam("dbops","domain_column","domain") -... - -1.3.7. attribute_column (string) - - Name of column containing the attribute name (AVP name). - - Default value is “attribute”. - - Example 1.7. Set attribute_column parameter -... -modparam("dbops","attribute_column","attribute") -... - -1.3.8. value_column (string) - - Name of column containing the AVP value. - - Default value is “value”. - - Example 1.8. Set value_column parameter -... -modparam("dbops","value_column","value") -... - -1.3.9. type_column (string) - - Name of column containing the AVP type. - - Default value is “type”. - - Example 1.9. Set type_column parameter -... -modparam("dbops","type_column","type") -... - -1.3.10. 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.10. Set db_scheme parameter -... -modparam("dbops","db_scheme", -"scheme1:table=subscriber;uuid_col=uuid;value_col=first_name") -... - -1.4. Exported Functions - -1.4.1. 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 - returns true if it loaded some values in AVPs, false otherwise - (db error, no avp loaded ...). - - AVPs may be preceded by an optional prefix, in order to avoid - some conflicts. - - Meaning of the parameters is as follows: - * source (string, no expand) - what info is used for - identifying the AVPs. Parameter syntax: - + source = (pvar|str_value) - ['/'('username'|'domain'|'uri'|'uuid')]) - + pvar = any pseudo variable defined in OpenSIPS. If the - pvar is $ru (request uri), $fu (from uri), $tu (to - uri) or $ou (original uri), then the implicit flag is - 'uri'. Otherwise, the implicit flag is 'uuid'. - * 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 - precede the names of the AVPs populated by this function. - - This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, - BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - - Example 1.11. db_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"); - -# use DB URL id 3 -db_avp_load("$ru", "$avp(1)", 3); - -# precede all loaded AVPs by the "caller_" prefix -db_avp_load("$ru", "$avp(100)", , "caller_"); -xlog("Loaded: $avp(caller_100)\n"); - -... - -1.4.2. db_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 - description. - - This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, - BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - - Example 1.12. db_avp_store usage -... -db_avp_store("$tu", "$avp(678)"); -db_avp_store("$ru/username", "$avp(email)"); -# use DB URL id 3 -db_avp_store("$ru", "$avp(1)", 3); -... - -1.4.3. db_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 - description. - - This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, - BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - - Example 1.13. db_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"); -# use DB URL id 3 -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]) - - This function takes the same parameters and behaves identically - to db_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). - - 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 of a slow MySQL query - it should take around 5 seconds */ -async( - db_query( - "SELECT table_name, table_version, SLEEP(0.1) from versi -on", - "$avp(tb_name); $avp(tb_ver); $avp(retcode)"), - my_resume_route); -/* script execution is halted right after the async() call */ -} - -/* We will be called when data is ready - meanwhile, the worker is free -*/ -route [my_resume_route] -{ - xlog("Results: \n$(avp(tb_name)[*])\n --------------------\n$(avp(tb_ver)[*])\n --------------------\n$(avp(retcode)[*])\n"); -} -... - -1.5.2. db_query_one(query, [dest], [db_id]) - - This function takes the same parameters and behaves identically - to db_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.17. async db_query_one usage -... -{ -... -/* Example of a slow MySQL query - it should take around 5 seconds */ -async( - db_query_one( - "SELECT table_name, table_version, SLEEP(0.1) from versi -on", - "$var(tb_name); $var(tb_ver); $var(retcode)"), - my_resume_route); -/* script execution is halted right after the async() call */ -} - -/* We will be called when data is ready - meanwhile, the worker is free -*/ -route [my_resume_route] -{ - xlog("Result: $var(tb_name) | $var(tb_ver) | $(var(retcode)\n"); -} -... - -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. Bogdan-Andrei Iancu (@bogdan-iancu) 33 3 3495 18 - - (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. Bogdan-Andrei Iancu (@bogdan-iancu) Feb 2024 - Mar 2024 - - (1) including any documentation-related commits, excluding - merge commits - -Chapter 3. Documentation - -3.1. Contributors - - Last edited by: Bogdan-Andrei Iancu (@bogdan-iancu). - - Documentation Copyrights: - - Copyright © 2009-2012 www.opensips-solutions.com - - Copyright © 2004-2008 Voice Sistem SRL diff --git a/modules/dbops/doc/dbops_admin.xml b/modules/dbops/doc/dbops_admin.xml deleted file mode 100644 index 040767dc4ec..00000000000 --- a/modules/dbops/doc/dbops_admin.xml +++ /dev/null @@ -1,636 +0,0 @@ - - - - - &adminguide; - - -
- Overview - - DBops (DB-operations) modules implements a set of script - functions which allow DB manipulation (loading/storing/removing) - of user AVPs (preferences). - -
-
- Dependencies -
- &osips; Modules - - The following modules must be loaded before this module: - - - - a database module - - - - -
-
- External Libraries or Applications - - The following libraries or applications must be installed - before running &osips; with this module loaded: - - - - None - - - - -
-
- -
- Exported Parameters -
- <varname>db_url</varname> (string) - - DB URL for database connection. As the module allows the usage - of multiple DBs (DB URLs), the actual DB URL may be preceded by - an reference number. This reference number is to be passed to - AVPOPS function that what to explicitly use this DB connection. - If no reference number is given, 0 is assumed - this is the default - DB URL. - - - - This parameter is optional, it's default value being NULL. - - - - Set <varname>db_url</varname> parameter - -... -# default URL -modparam("dbops","db_url","mysql://user:passwd@host/database") -# an additional DB URL -modparam("dbops","db_url","1 postgres://user:passwd@host2/opensips") -... - - -
-
- <varname>usr_table</varname> (string) - - DB table to be used for user preferences (AVPs) - - - - This parameter is optional, it's default value being - usr_preferences. - - - - Set <varname>usr_table</varname> parameter - -... -modparam("dbops","usr_table","avptable") -... - - -
-
- <varname>use_domain</varname> (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). - - - - Set <varname>use_domain</varname> parameter - - -... -modparam("dbops","use_domain",1) -... - - -
-
- <varname>uuid_column</varname> (string) - - Name of column containing the uuid (unique user id). - - - Default value is uuid. - - - - Set <varname>uuid_column</varname> parameter - -... -modparam("dbops","uuid_column","uuid") -... - - -
-
- <varname>username_column</varname> (string) - - Name of column containing the username. - - - Default value is username. - - - - Set <varname>username_column</varname> parameter - -... -modparam("dbops","username_column","username") -... - - -
-
- <varname>domain_column</varname> (string) - - Name of column containing the domain name. - - - Default value is domain. - - - - Set <varname>domain_column</varname> parameter - -... -modparam("dbops","domain_column","domain") -... - - -
-
- <varname>attribute_column</varname> (string) - - Name of column containing the attribute name (AVP name). - - - Default value is attribute. - - - - Set <varname>attribute_column</varname> parameter - - -... -modparam("dbops","attribute_column","attribute") -... - - -
-
- <varname>value_column</varname> (string) - - Name of column containing the AVP value. - - - Default value is value. - - - - Set <varname>value_column</varname> parameter - - -... -modparam("dbops","value_column","value") -... - - -
-
- <varname>type_column</varname> (string) - - Name of column containing the AVP type. - - - Default value is type. - - - - Set <varname>type_column</varname> parameter - - -... -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") -... - - -
-
- -
- Exported Functions -
- - <function moreinfo="none">db_avp_load(source, name, [db_id], [prefix]]) - </function> - - - Loads from DB into memory the AVPs corresponding to the given - source. If given, it sets the script flags - for loaded AVPs. It returns true if it loaded some values - in AVPs, false otherwise (db error, no avp loaded ...). - - - AVPs may be preceded by an optional prefix, in - order to avoid some conflicts. - - Meaning of the parameters is as follows: - - - source (string, no expand) - what info is used for - identifying the AVPs. Parameter syntax: - - - source = (pvar|str_value) - ['/'('username'|'domain'|'uri'|'uuid')]) - - - pvar = any pseudo variable defined in &osips;. If - the pvar is $ru (request uri), $fu (from uri), $tu (to uri) - or $ou (original uri), then the implicit flag is 'uri'. - Otherwise, the implicit flag is 'uuid'. - - - - - - 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 - precede the names of the AVPs populated by this function. - - - - - This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, - BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - - - - <function>db_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"); - -# use DB URL id 3 -db_avp_load("$ru", "$avp(1)", 3); - -# precede all loaded AVPs by the "caller_" prefix -db_avp_load("$ru", "$avp(100)", , "caller_"); -xlog("Loaded: $avp(caller_100)\n"); - -... - - -
-
- - <function moreinfo="none">db_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) - function. Please refer to its description. - - - This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, - BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - - - - <function>db_avp_store</function> usage - -... -db_avp_store("$tu", "$avp(678)"); -db_avp_store("$ru/username", "$avp(email)"); -# use DB URL id 3 -db_avp_store("$ru", "$avp(1)", 3); -... - - -
-
- - <function moreinfo="none">db_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) - function. Please refer to its description. - - - This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, - BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - - - <function>db_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"); -# use DB URL id 3 -db_avp_delete("$ru", "$avp(1)", 3); -... - - -
-
- - <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 -... - - -
-
- - -
- Exported Asynchronous Functions -
- - <function moreinfo="none">db_query(query, [dest], [db_id])</function> - - - This function takes the same parameters and behaves identically - 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). - - - This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, - BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. - - - <function>async db_query</function> usage - -... -{ -... -/* Example of a slow MySQL query - it should take around 5 seconds */ -async( - db_query( - "SELECT table_name, table_version, SLEEP(0.1) from version", - "$avp(tb_name); $avp(tb_ver); $avp(retcode)"), - my_resume_route); -/* script execution is halted right after the async() call */ -} - -/* We will be called when data is ready - meanwhile, the worker is free */ -route [my_resume_route] -{ - xlog("Results: \n$(avp(tb_name)[*])\n --------------------\n$(avp(tb_ver)[*])\n --------------------\n$(avp(retcode)[*])\n"); -} -... - - -
- -
- - <function moreinfo="none">db_query_one(query, [dest], [db_id])</function> - - - This function takes the same parameters and behaves identically - 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). - - - This function can be used from any route. - - - <function>async db_query_one</function> usage - -... -{ -... -/* Example of a slow MySQL query - it should take around 5 seconds */ -async( - db_query_one( - "SELECT table_name, table_version, SLEEP(0.1) from version", - "$var(tb_name); $var(tb_ver); $var(retcode)"), - my_resume_route); -/* script execution is halted right after the async() call */ -} - -/* We will be called when data is ready - meanwhile, the worker is free */ -route [my_resume_route] -{ - xlog("Result: $var(tb_name) | $var(tb_ver) | $(var(retcode)\n"); -} -... - - -
- -
- - -
- 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/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); 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/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 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/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 new file mode 100644 index 00000000000..68a372f7617 --- /dev/null +++ b/modules/http2d/h2_evi.c @@ -0,0 +1,108 @@ +/* + * 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 "server.h" +#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_data; + + +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); + + 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, &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_data) { + 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) +{ + 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_data, body) < 0) { + LM_ERR("failed to set 'body'\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..1acae98082a --- /dev/null +++ b/modules/http2d/h2_evi.h @@ -0,0 +1,32 @@ +/* + * 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" + +int h2_init_evi(void); +void h2_raise_event_request(const char *method, const char *path, + const char *headers_json, const str *body); + +#endif /* __H2_EVI__ */ diff --git a/modules/http2d/http2d.c b/modules/http2d/http2d.c new file mode 100644 index 00000000000..ee6002f4e7f --- /dev/null +++ b/modules/http2d/http2d.c @@ -0,0 +1,311 @@ +/* + * 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" +#include "h2_evi.h" + +/* module functions */ +static int mod_init(); +static void mod_destroy(void); + +unsigned int h2_port = 443; +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 }, + {NULL, 0, 0, NULL, 0, 0} +}; + +/* Module parameters */ +static const param_export_t params[] = { + {"ip", STR_PARAM, &h2_ip}, + {"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} +}; + +/* 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 */ + cmds, /* 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); + + if (h2_init_evi() != 0) { + LM_ERR("failed to init EVI structures\n"); + return -1; + } + + return 0; +} + + +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 new file mode 100644 index 00000000000..01a14869fee --- /dev/null +++ b/modules/http2d/server.c @@ -0,0 +1,1052 @@ +/* + * 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 + * "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 "../../lib/list.h" +#include "../../lib/cJSON.h" + +#include "server.h" +#include "h2_evi.h" + +extern unsigned int max_headers_size; + +#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 +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#define NGHTTP2_NO_SSIZE_T + +#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 { + 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 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; +}; + +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) { + 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 | + 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) { + 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) { + 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); + } +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) { + 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) { + 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); + + return ssl_ctx; +} + +/* Create SSL object */ +static SSL *create_ssl(SSL_CTX *ssl_ctx) { + SSL *ssl; + ssl = SSL_new(ssl_ctx); + if (!ssl) { + LM_ERR("Could not create SSL/TLS session object: %s", + ERR_error_string(ERR_get_error(), NULL)); + return 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); +} + +static void remove_stream(http2_session_data *session_data, + http2_stream_data *stream_data) { + (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; + + 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); + 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->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); + } + + 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; + + LM_INFO("%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_del(&stream_data->list); + delete_http2_stream_data(stream_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) { + LM_WARN("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) { + LM_WARN("Fatal error: %s", nghttp2_strerror((int)readlen)); + return -1; + } + if (evbuffer_drain(input, (size_t)readlen) != 0) { + LM_WARN("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_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, + fd > 0 ? &data_prd : NULL); + if (rv != 0) { + LM_WARN("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + + return 0; +} + +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) { + LM_WARN("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", "500")}; + + rv = pipe(pipefd); + if (rv != 0) { + 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) { + LM_WARN("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_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")}; + + 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) { + /* 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]; + + 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 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) { + 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); + 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) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; + } + + pthread_mutex_lock(&ng_h2_response->mutex); + + H = cJSON_PrintUnformatted(stream_data->hdrs); + h2_raise_event_request(stream_data->method, stream_data->path, + H, &stream_data->data); + cJSON_PurgeString(H); + + 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); + + timeradd(&now, &wait_time, &res); + + 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); + + 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("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; +} + + +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; +} + + +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; + 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; +} + +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, + 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_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) { + LM_WARN("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; + + 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) { + LM_ERR("%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) + LM_INFO("%s EOF\n", session_data->client_addr); + else if (events & BEV_EVENT_ERROR) + LM_INFO("%s network error\n", session_data->client_addr); + else if (events & BEV_EVENT_TIMEOUT) + LM_INFO("%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) { + LM_ERR("Could not resolve server address"); + return; + } + + 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; + } + } + + LM_ERR("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); + 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); + + 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..1f33afb781c --- /dev/null +++ b/modules/http2d/server.h @@ -0,0 +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/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/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/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_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/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_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/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_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/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_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/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/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/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/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/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/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/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/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/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; } diff --git a/modules/rabbitmq/doc/rabbitmq_admin.xml b/modules/rabbitmq/doc/rabbitmq_admin.xml index df2b268df43..506ad2e8354 100644 --- a/modules/rabbitmq/doc/rabbitmq_admin.xml +++ b/modules/rabbitmq/doc/rabbitmq_admin.xml @@ -193,6 +193,54 @@ 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) + + + +
+ +
+ <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 364f532dbe8..f67b633ad59 100644 --- a/modules/rabbitmq/rabbitmq.c +++ b/modules/rabbitmq/rabbitmq.c @@ -44,6 +44,13 @@ 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; +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; @@ -53,6 +60,8 @@ 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}, + {"timeout", INT_PARAM, &rmq_timeout}, {0,0,0} }; @@ -158,6 +167,21 @@ 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; + +#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 ade97571d8e..8e1b1c0d100 100644 --- a/modules/rabbitmq/rmq_servers.c +++ b/modules/rabbitmq/rmq_servers.c @@ -335,14 +335,21 @@ 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; } +#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(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..bc3fb2c51ee 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,9 @@ 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; +#if defined AMQP_VERSION && AMQP_VERSION >= 0x00090000 +extern struct timeval rpc_timeout_tv; +#endif #endif /* _RMQ_SERVERS_H_ */ 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; 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/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) { diff --git a/modules/rtp_relay/README b/modules/rtp_relay/README index 0fce026413a..6659c2743ca 100644 --- a/modules/rtp_relay/README +++ b/modules/rtp_relay/README @@ -332,10 +332,11 @@ 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. 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. 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 (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..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. @@ -44,6 +44,14 @@ 4. + Norman Brandinger (@NormB) + 3 + 1 + 1 + 1 + + + 5. Vlad Paiu (@vladpaiu) 2 1 @@ -81,20 +89,25 @@ 1. Razvan Crainea (@razvancrainea) - Apr 2021 - Feb 2024 + Apr 2021 - Mar 2024 2. + Norman Brandinger (@NormB) + Mar 2024 - Mar 2024 + + + 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/rtp_relay/rtp_relay_ctx.c b/modules/rtp_relay/rtp_relay_ctx.c index 2a78be35836..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; @@ -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); 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. 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 diff --git a/modules/dbops/Makefile b/modules/sqlops/Makefile similarity index 78% rename from modules/dbops/Makefile rename to modules/sqlops/Makefile index 2d61b22130f..15a524a469a 100644 --- a/modules/dbops/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 new file mode 100644 index 00000000000..dc62096656a --- /dev/null +++ b/modules/sqlops/README @@ -0,0 +1,786 @@ +SQLops Module + __________________________________________________________ + + Table of Contents + + 1. Admin Guide + + 1.1. Overview + 1.2. Dependencies + + 1.2.1. OpenSIPS Modules + 1.2.2. External Libraries or Applications + + 1.3. Exported Parameters + + 1.3.1. db_url (string) + 1.3.2. usr_table (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. sql_query(query, [res_col_avps], [db_id]) + 1.4.2. sql_query_one(query, [res_col_vars], [db_id]) + + 1.4.3. + sql_select([columns],table,[filter],[order],[r + es_col_avps], [db_id]) + + 1.4.4. + sql_select_one([columns],table,[filter],[order + ],[res_col_vars], [db_id]) + + 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. 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. sql_query(query, [dest], [db_id]) + 1.5.2. sql_query_one(query, [dest], [db_id]) + + 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. Set db_url parameter + 1.2. Set usr_table parameter + 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. 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 + + 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). + +1.2. Dependencies + +1.2.1. OpenSIPS Modules + + The following modules must be loaded before this module: + * a database module + +1.2.2. External Libraries or Applications + + The following libraries or applications must be installed + before running OpenSIPS with this module loaded: + * None + +1.3. Exported Parameters + +1.3.1. db_url (string) + + DB URL for database connection. As the module allows the usage + of multiple DBs (DB URLs), the actual DB URL may be preceded by + an reference number. This reference number is to be passed to + AVPOPS function that what to explicitly use this DB connection. + If no reference number is given, 0 is assumed - this is the + default DB URL. + + This parameter is optional, it's default value being NULL. + + Example 1.1. Set db_url parameter +... +# default URL +modparam("sqlops","db_url","mysql://user:passwd@host/database") +# an additional DB URL +modparam("sqlops","db_url","1 postgres://user:passwd@host2/opensips") +... + +1.3.2. usr_table (string) + + DB table to be used for user preferences (AVPs) + + This parameter is optional, it's default value being + “usr_preferences”. + + Example 1.2. Set usr_table parameter +... +modparam("sqlops","usr_table","avptable") +... + +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("sqlops","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.4. Set use_domain parameter +... +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 "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 + PS support will be completly disabled. + + Default value is 1024. + + Example 1.5. Set ps_id_max_buf_len parameter +... +modparam("sqlops","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.6. Set uuid_column parameter +... +modparam("sqlops","uuid_column","uuid") +... + +1.3.7. username_column (string) + + Name of column containing the username. + + Default value is “username”. + + Example 1.7. Set username_column parameter +... +modparam("sqlops","username_column","username") +... + +1.3.8. domain_column (string) + + Name of column containing the domain name. + + Default value is “domain”. + + Example 1.8. Set domain_column parameter +... +modparam("sqlops","domain_column","domain") +... + +1.3.9. attribute_column (string) + + Name of column containing the attribute name (AVP name). + + Default value is “attribute”. + + Example 1.9. Set attribute_column parameter +... +modparam("sqlops","attribute_column","attribute") +... + +1.3.10. value_column (string) + + Name of column containing the AVP value. + + Default value is “value”. + + Example 1.10. Set value_column parameter +... +modparam("sqlops","value_column","value") +... + +1.3.11. type_column (string) + + Name of column containing the AVP type. + + Default value is “type”. + + Example 1.11. Set type_column parameter +... +modparam("sqlops","type_column","type") +... + +1.4. Exported Functions + +1.4.1. sql_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.12. sql_query usage +... +sql_query("SELECT password, ha1 FROM subscriber WHERE username='$tu'", + "$avp(pass);$avp(hash)"); +sql_query("DELETE FROM subscriber"); +sql_query("DELETE FROM subscriber", , 2); + +$avp(id) = 2; +sql_query("DELETE FROM subscriber", , $avp(id)); +... + +1.4.2. sql_query_one(query, [res_col_vars], [db_id]) + + 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. + * 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. sql_query_one usage +... +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 +... +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. sql_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. sql_select usage +... +sql_select('["password","ha1"]', 'subscriber', + '[ {"username", "$tu"}, {"domain",{"!=",null}}]', , + '$avp(pass);$avp(hash)'); +... + +1.4.4. +sql_select_one([columns],table,[filter],[order],[res_col_vars], +[db_id]) + + 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 + 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. sql_select_one usage +... +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` +# attributes for the user +... + +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 + sql_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. sql_update usage +... +sql_update( '[{"password":"my_secret"}]', 'subscriber', + '[{"username", "$tu"}]'); +... + +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 + sql_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. + + Example 1.17. sql_insert usage +... +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. 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 + sql_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. sql_delete usage +... +sql_delete( 'subscriber', '[{"username", "$tu"}]'); +... + +1.4.8. sql_replace(table,columns,[db_id]) + + 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. 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 + returns true if it loaded some values in AVPs, false otherwise + (db error, no avp loaded ...). + + AVPs may be preceded by an optional prefix, in order to avoid + some conflicts. + + Meaning of the parameters is as follows: + * source (string, no expand) - what info is used for + identifying the AVPs. Parameter syntax: + + source = (pvar|str_value) + ['/'('username'|'domain'|'uri'|'uuid')]) + + pvar = any pseudo variable defined in OpenSIPS. If the + pvar is $ru (request uri), $fu (from uri), $tu (to + uri) or $ou (original uri), then the implicit flag is + 'uri'. Otherwise, the implicit flag is 'uuid'. + * name (string, no expand) - which AVPs will be loaded from + DB into memory. Parameter syntax is: + + name = avp_spec['/'(table_name|'$'db_scheme)] + * 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 + precede the names of the AVPs populated by this function. + + This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, + BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. + + Example 1.19. sql_avp_load usage +... +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 +sql_avp_load("$ru", "$avp(1)", 3); + +# precede all loaded AVPs by the "caller_" prefix +sql_avp_load("$ru", "$avp(100)", , "caller_"); +xlog("Loaded: $avp(caller_100)\n"); + +... + +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 + 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. sql_avp_store usage +... +sql_avp_store("$tu", "$avp(678)"); +sql_avp_store("$ru/username", "$avp(email)"); +# use DB URL id 3 +sql_avp_store("$ru", "$avp(1)", 3); +... + +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 + 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. sql_avp_delete usage +... +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 +sql_avp_delete("$ru", "$avp(1)", 3); +... + +1.5. Exported Asynchronous Functions + +1.5.1. sql_query(query, [dest], [db_id]) + + This function takes the same parameters and behaves identically + 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). + + This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, + BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. + + Example 1.22. async sql_query usage +... +{ +... +/* Example of a slow MySQL query - it should take around 5 seconds */ +async( + sql_query( + "SELECT table_name, table_version, SLEEP(0.1) from versi +on", + "$avp(tb_name); $avp(tb_ver); $avp(retcode)"), + my_resume_route); +/* script execution is halted right after the async() call */ +} + +/* We will be called when data is ready - meanwhile, the worker is free +*/ +route [my_resume_route] +{ + xlog("Results: \n$(avp(tb_name)[*])\n +-------------------\n$(avp(tb_ver)[*])\n +-------------------\n$(avp(retcode)[*])\n"); +} +... + +1.5.2. sql_query_one(query, [dest], [db_id]) + + This function takes the same parameters and behaves identically + 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 sql_query_one usage +... +{ +... +/* Example of a slow MySQL query - it should take around 5 seconds */ +async( + sql_query_one( + "SELECT table_name, table_version, SLEEP(0.1) from versi +on", + "$var(tb_name); $var(tb_ver); $var(retcode)"), + my_resume_route); +/* script execution is halted right after the async() call */ +} + +/* We will be called when data is ready - meanwhile, the worker is free +*/ +route [my_resume_route] +{ + xlog("Result: $var(tb_name) | $var(tb_ver) | $(var(retcode)\n"); +} +... + +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. 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 + / (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. 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 + +Chapter 3. Documentation + +3.1. Contributors + + 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: + + Copyright © 2009-2024 www.opensips-solutions.com + + Copyright © 2004-2008 Voice Sistem SRL diff --git a/modules/sqlops/doc/contributors.xml b/modules/sqlops/doc/contributors.xml new file mode 100644 index 00000000000..2fd71546365 --- /dev/null +++ b/modules/sqlops/doc/contributors.xml @@ -0,0 +1,196 @@ + + + &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. + 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 / (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. + 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 + +
+ +
+ + Documentation +
+ Contributors + 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. +
+ +
diff --git a/modules/dbops/doc/dbops.xml b/modules/sqlops/doc/sqlops.xml similarity index 83% rename from modules/dbops/doc/dbops.xml rename to modules/sqlops/doc/sqlops.xml index a7c77d890ab..cbb06fc442b 100644 --- a/modules/dbops/doc/dbops.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; @@ -25,6 +25,6 @@ &contrib; &docCopyrights; - ©right; 2009-2012 &osipssol; + ©right; 2009-2024 &osipssol; ©right; 2004-2008 &voicesystem; diff --git a/modules/sqlops/doc/sqlops_admin.xml b/modules/sqlops/doc/sqlops_admin.xml new file mode 100644 index 00000000000..49031b5607b --- /dev/null +++ b/modules/sqlops/doc/sqlops_admin.xml @@ -0,0 +1,993 @@ + + + + + &adminguide; + + +
+ Overview + + 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). + +
+
+ Dependencies +
+ &osips; Modules + + The following modules must be loaded before this module: + + + + a database module + + + + +
+
+ External Libraries or Applications + + The following libraries or applications must be installed + before running &osips; with this module loaded: + + + + None + + + + +
+
+ +
+ Exported Parameters +
+ <varname>db_url</varname> (string) + + DB URL for database connection. As the module allows the usage + of multiple DBs (DB URLs), the actual DB URL may be preceded by + an reference number. This reference number is to be passed to + AVPOPS function that what to explicitly use this DB connection. + If no reference number is given, 0 is assumed - this is the default + DB URL. + + + + This parameter is optional, it's default value being NULL. + + + + Set <varname>db_url</varname> parameter + +... +# default URL +modparam("sqlops","db_url","mysql://user:passwd@host/database") +# an additional DB URL +modparam("sqlops","db_url","1 postgres://user:passwd@host2/opensips") +... + + +
+
+ <varname>usr_table</varname> (string) + + DB table to be used for user preferences (AVPs) + + + + This parameter is optional, it's default value being + usr_preferences. + + + + Set <varname>usr_table</varname> parameter + +... +modparam("sqlops","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("sqlops","db_scheme", +"scheme1:table=subscriber;uuid_col=uuid;value_col=first_name") +... + + +
+ +
+ <varname>use_domain</varname> (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). + + + + Set <varname>use_domain</varname> parameter + + +... +modparam("sqlops","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 + "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 PS + support will be completly disabled. + + + Default value is 1024. + + + + Set <varname>ps_id_max_buf_len</varname> parameter + + +... +modparam("sqlops","ps_id_max_buf_len", 2048) +... + + +
+ +
+ <varname>uuid_column</varname> (string) + + Name of column containing the uuid (unique user id). + + + Default value is uuid. + + + + Set <varname>uuid_column</varname> parameter + +... +modparam("sqlops","uuid_column","uuid") +... + + +
+
+ <varname>username_column</varname> (string) + + Name of column containing the username. + + + Default value is username. + + + + Set <varname>username_column</varname> parameter + +... +modparam("sqlops","username_column","username") +... + + +
+
+ <varname>domain_column</varname> (string) + + Name of column containing the domain name. + + + Default value is domain. + + + + Set <varname>domain_column</varname> parameter + +... +modparam("sqlops","domain_column","domain") +... + + +
+
+ <varname>attribute_column</varname> (string) + + Name of column containing the attribute name (AVP name). + + + Default value is attribute. + + + + Set <varname>attribute_column</varname> parameter + + +... +modparam("sqlops","attribute_column","attribute") +... + + +
+
+ <varname>value_column</varname> (string) + + Name of column containing the AVP value. + + + Default value is value. + + + + Set <varname>value_column</varname> parameter + + +... +modparam("sqlops","value_column","value") +... + + +
+
+ <varname>type_column</varname> (string) + + Name of column containing the AVP type. + + + Default value is type. + + + + Set <varname>type_column</varname> parameter + + +... +modparam("sqlops","type_column","type") +... + + +
+ +
+ +
+ Exported Functions + +
+ + <function moreinfo="none">sql_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>sql_query</function> usage + +... +sql_query("SELECT password, ha1 FROM subscriber WHERE username='$tu'", + "$avp(pass);$avp(hash)"); +sql_query("DELETE FROM subscriber"); +sql_query("DELETE FROM subscriber", , 2); + +$avp(id) = 2; +sql_query("DELETE FROM subscriber", , $avp(id)); +... + + +
+ +
+ + <function moreinfo="none">sql_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>sql_query_one</function> usage + +... +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 +... +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 +... + + +
+ +
+ + <function moreinfo="none">sql_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>sql_select</function> usage + +... +sql_select('["password","ha1"]', 'subscriber', + '[ {"username", "$tu"}, {"domain",{"!=",null}}]', , + '$avp(pass);$avp(hash)'); +... + + +
+ +
+ + <function moreinfo="none">sql_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>sql_select_one</function> usage + +... +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` +# attributes for the user +... + + +
+ +
+ + <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. + + + 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>sql_update</function> usage + +... +sql_update( '[{"password":"my_secret"}]', 'subscriber', + '[{"username", "$tu"}]'); +... + + +
+ +
+ + <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. + + + 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 + module parameter. It can + be either a constant, or a string/int variable. + + + + + This function can be used from any type of route. + + + <function>sql_insert</function> usage + +... +sql_insert( 'cc_agents', '[{"agentid":"agentX"},{"skills":"info"},{"location":null},{"msrp_location":"sip:agentX@opensips.com"},{"msrp_max_sessions":2}]' ); +... + + +
+ +
+ + <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. + + + 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>sql_delete</function> usage + +... +sql_delete( 'subscriber', '[{"username", "$tu"}]'); +... + + +
+ +
+ + <function moreinfo="none">sql_replace(table,columns,[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">sql_avp_load(source, name, [db_id], [prefix]]) + </function> + + + Loads from DB into memory the AVPs corresponding to the given + source. If given, it sets the script flags + for loaded AVPs. It returns true if it loaded some values + in AVPs, false otherwise (db error, no avp loaded ...). + + + AVPs may be preceded by an optional prefix, in + order to avoid some conflicts. + + Meaning of the parameters is as follows: + + + source (string, no expand) - what info is used for + identifying the AVPs. Parameter syntax: + + + source = (pvar|str_value) + ['/'('username'|'domain'|'uri'|'uuid')]) + + + pvar = any pseudo variable defined in &osips;. If + the pvar is $ru (request uri), $fu (from uri), $tu (to uri) + or $ou (original uri), then the implicit flag is 'uri'. + Otherwise, the implicit flag is 'uuid'. + + + + + + name (string, no expand) - which AVPs will be loaded + from DB into memory. Parameter syntax is: + + + name = avp_spec['/'(table_name|'$'db_scheme)] + + + + + + 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 + precede the names of the AVPs populated by this function. + + + + + This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, + BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. + + + + <function>sql_avp_load</function> usage + +... +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 +sql_avp_load("$ru", "$avp(1)", 3); + +# precede all loaded AVPs by the "caller_" prefix +sql_avp_load("$ru", "$avp(100)", , "caller_"); +xlog("Loaded: $avp(caller_100)\n"); + +... + + +
+
+ + <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 + 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. + + + + <function>sql_avp_store</function> usage + +... +sql_avp_store("$tu", "$avp(678)"); +sql_avp_store("$ru/username", "$avp(email)"); +# use DB URL id 3 +sql_avp_store("$ru", "$avp(1)", 3); +... + + +
+
+ + <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 + 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. + + + <function>sql_avp_delete</function> usage + +... +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 +sql_avp_delete("$ru", "$avp(1)", 3); +... + + +
+ +
+ + +
+ Exported Asynchronous Functions +
+ + <function moreinfo="none">sql_query(query, [dest], [db_id])</function> + + + This function takes the same parameters and behaves identically + 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). + + + This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, + BRANCH_ROUTE, LOCAL_ROUTE and ONREPLY_ROUTE. + + + <function>async sql_query</function> usage + +... +{ +... +/* Example of a slow MySQL query - it should take around 5 seconds */ +async( + sql_query( + "SELECT table_name, table_version, SLEEP(0.1) from version", + "$avp(tb_name); $avp(tb_ver); $avp(retcode)"), + my_resume_route); +/* script execution is halted right after the async() call */ +} + +/* We will be called when data is ready - meanwhile, the worker is free */ +route [my_resume_route] +{ + xlog("Results: \n$(avp(tb_name)[*])\n +-------------------\n$(avp(tb_ver)[*])\n +-------------------\n$(avp(retcode)[*])\n"); +} +... + + +
+ +
+ + <function moreinfo="none">sql_query_one(query, [dest], [db_id])</function> + + + This function takes the same parameters and behaves identically + 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). + + + This function can be used from any route. + + + <function>async sql_query_one</function> usage + +... +{ +... +/* Example of a slow MySQL query - it should take around 5 seconds */ +async( + sql_query_one( + "SELECT table_name, table_version, SLEEP(0.1) from version", + "$var(tb_name); $var(tb_ver); $var(retcode)"), + my_resume_route); +/* script execution is halted right after the async() call */ +} + +/* We will be called when data is ready - meanwhile, the worker is free */ +route [my_resume_route] +{ + xlog("Result: $var(tb_name) | $var(tb_ver) | $(var(retcode)\n"); +} +... + + +
+ +
+ + +
+ diff --git a/modules/dbops/dbops.c b/modules/sqlops/sqlops.c similarity index 64% rename from modules/dbops/dbops.c rename to modules/sqlops/sqlops.c index 9fc4b91374d..ffc0c29c07b 100644 --- a/modules/dbops/dbops.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,30 +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_sql_select(struct sip_msg* msg, str* cols, str *table, + str *filter, str *order, void* dest, void *url); +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_sql_update(struct sip_msg* msg, str* cols, str *table, + str *filter, void *url); +static int w_sql_insert(struct sip_msg* msg, str* table, str *cols, + void *url); +static int w_sql_delete(struct sip_msg* msg, str *table, str *filter, + void *url); +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}, @@ -120,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}, @@ -157,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}, @@ -166,6 +180,63 @@ static const cmd_export_t cmds[] = { {0, 0, 0}}, ALL_ROUTES}, + {"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 */ + {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}, + + {"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 */ + {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}, + + {"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 */ + {CMD_PARAM_INT|CMD_PARAM_OPT, + fixup_db_id_sync, fixup_free_pkg}, + {0, 0, 0}}, + ALL_ROUTES}, + + {"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, + fixup_db_id_sync, fixup_free_pkg}, + {0, 0, 0}}, + ALL_ROUTES}, + + {"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, + fixup_db_id_sync, fixup_free_pkg}, + {0, 0, 0}}, + ALL_ROUTES}, + + {"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, + fixup_db_id_sync, fixup_free_pkg}, + {0, 0, 0}}, + ALL_ROUTES}, + {0, 0, {{0, 0, 0}}, 0} }; @@ -184,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} }; @@ -198,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 */ @@ -213,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); @@ -254,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); @@ -265,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); } @@ -325,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) { @@ -348,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; @@ -384,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); @@ -400,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"); @@ -421,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; @@ -456,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) @@ -582,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; @@ -619,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; @@ -633,11 +705,97 @@ 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_sql_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_sql_api_select(parsed_url, msg, cols, table, filter, order, + (pvname_list_t*)dest, 0); +} + + +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; + + if (url) + parsed_url = ((struct db_url_container *)url)->u.url; + else + parsed_url = default_db_url; + + return ops_sql_api_select(parsed_url, msg, cols, table, filter, order, + (pvname_list_t*)dest, 1); +} + + +static int w_sql_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_sql_api_update(parsed_url, msg, cols, table, filter); +} + + +static int w_sql_insert(struct sip_msg* msg, str* table, str *cols, + 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_sql_api_insert(parsed_url, msg, table, cols); +} + + +static int w_sql_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_sql_api_delete(parsed_url, msg, table, filter); +} + + +static int w_sql_replace(struct sip_msg* msg, str* table, str *cols, + 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_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; @@ -647,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; @@ -661,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/dbops/dbops_db.c b/modules/sqlops/sqlops_db.c similarity index 52% rename from modules/dbops/dbops_db.c rename to modules/sqlops/sqlops_db.c index b3a0abe034b..d8612cabadc 100644 --- a/modules/dbops/dbops_db.c +++ b/modules/sqlops/sqlops_db.c @@ -30,8 +30,9 @@ #include "../../db/db_insertq.h" #include "../../dprint.h" #include "../../route.h" -#include "dbops_parse.h" -#include "dbops_db.h" +#include "../../map.h" +#include "sqlops_parse.h" +#include "sqlops_db.h" static str def_table; /* default DB table */ @@ -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; @@ -124,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; @@ -138,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; } @@ -146,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 */ @@ -170,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; @@ -333,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]; @@ -377,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; @@ -399,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; @@ -419,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; @@ -466,6 +469,551 @@ 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++ ) { + cols[i]->s = col->valuestring; + 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 objects\n"); + goto error; + } + if (filter->child->string==NULL) { + LM_ERR("invalid filter node %d type %d , without name\n", + nk, filter->child->type); + 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))) { + 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; +} + +static str _query_id = {NULL,0}; +static inline str* _query_id_start( str *table, str *order) +{ + 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; + + 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; +} + +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 + 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); + _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 + 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); + _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 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; + 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) { + 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; + + 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_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) { + 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 ) { + 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 sql_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); + 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; + + 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_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) { + 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 + ? 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 sql_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); + 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; + + /* set the PS to be used */ + 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_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) { + 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 + ? 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 sql_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) { + 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; + + /* set the PS to be used */ + 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_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) { + 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 + ? 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 sql_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); + 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; + + /* set the PS to be used */ + 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_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) { + 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 + ? 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/sqlops/sqlops_db.h similarity index 67% rename from modules/dbops/dbops_db.h rename to modules/sqlops/sqlops_db.h index 1bb566e4750..d936612307c 100644 --- a/modules/dbops/dbops_db.h +++ b/modules/sqlops/sqlops_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" @@ -33,6 +34,8 @@ extern struct db_url *default_db_url; +extern int query_id_max_len; + struct db_url { str url; @@ -61,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); @@ -89,4 +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 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 sql_api_update(struct db_url *url, struct sip_msg* msg, cJSON *Jcols, + str *table, cJSON *Jfilter); + +int sql_api_insert(struct db_url *url, struct sip_msg* msg, str *table, + cJSON *Jcols); + +int sql_api_delete(struct db_url *url, struct sip_msg* msg, + str *table, cJSON *Jfilter); + +int sql_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/sqlops/sqlops_impl.c similarity index 71% rename from modules/dbops/dbops_impl.c rename to modules/sqlops/sqlops_impl.c index 462295ebb6c..dc7caad5bdd 100644 --- a/modules/dbops/dbops_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) @@ -779,7 +779,189 @@ int ops_db_query(struct sip_msg* msg, str* query, struct db_url *url, return 1; } -int ops_async_db_query(struct sip_msg* msg, async_ctx *ctx, + +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_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; + 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 = sql_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_sql_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 = sql_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_sql_api_insert(struct db_url *url, struct sip_msg* msg, str *table, + str *cols) +{ + 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 = sql_api_insert( url, msg, table, Jcols); + 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_sql_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 = sql_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_sql_api_replace(struct db_url *url, struct sip_msg* msg, str *table, + str *cols) +{ + 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 = sql_api_replace( url, msg, table, Jcols); + 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_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; @@ -798,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; @@ -826,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; @@ -841,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; @@ -851,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/dbops/dbops_impl.h b/modules/sqlops/sqlops_impl.h similarity index 62% rename from modules/dbops/dbops_impl.h rename to modules/sqlops/sqlops_impl.h index d63873a1fd8..73e8a40d771 100644 --- a/modules/dbops/dbops_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,23 +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_async_db_query(struct sip_msg* msg, async_ctx *ctx, +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_sql_api_update(struct db_url *url, struct sip_msg* msg, str *cols, + str *table, str *filter); + +int ops_sql_api_insert(struct db_url *url, struct sip_msg* msg, str *table, + str *cols); + +int ops_sql_api_delete(struct db_url *url, struct sip_msg* msg, + str *table, str *filter); + +int ops_sql_api_replace(struct db_url *url, struct sip_msg* msg, str *table, + str *cols); + +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/dbops/dbops_parse.c b/modules/sqlops/sqlops_parse.c similarity index 95% rename from modules/dbops/dbops_parse.c rename to modules/sqlops/sqlops_parse.c index e275ecc4db1..fe979b723e1 100644 --- a/modules/dbops/dbops_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/dbops/dbops_parse.h b/modules/sqlops/sqlops_parse.h similarity index 91% rename from modules/dbops/dbops_parse.h rename to modules/sqlops/sqlops_parse.h index b4d8a8448fb..e062af18f47 100644 --- a/modules/dbops/dbops_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); 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 diff --git a/modules/stir_shaken/stir_shaken.c b/modules/stir_shaken/stir_shaken.c index c8c2f4eca36..a5fdb00befe 100644 --- a/modules/stir_shaken/stir_shaken.c +++ b/modules/stir_shaken/stir_shaken.c @@ -1842,17 +1842,36 @@ 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 -2; + } + + rc = -2; + + do { *parsed = pkg_malloc(sizeof **parsed); if (*parsed == NULL) { @@ -1862,11 +1881,27 @@ 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 - pkg_free(*parsed); - } + 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 */ + } + 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(rc < 0); + + if (rc >= 0) + parsed_ctx_set(*parsed); return rc; } @@ -1903,7 +1938,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 +1947,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 +2000,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 +2012,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 +2163,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 +2176,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 +2250,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/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/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/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..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 @@ -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/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_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 51cc52143cb..f012d92c534 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 @@ -182,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; } @@ -230,6 +243,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,12 +264,63 @@ 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; } 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 9c92ec3f43b..1992f3bf4a0 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 { @@ -115,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; @@ -148,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 */ 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; 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) */ 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}" 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/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/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/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/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/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/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, 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 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 = { 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); } 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 * */ 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 )