diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index fa16557dd5c4c00af815b41249537e4010ecba93..dc0679bc33af9fa8455edbeee4cfc7d37664b40d 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -127,7 +127,7 @@ ; ;[transport-udp] ;type=transport -;protocol=udp ;udp,tcp,tls,ws,wss +;protocol=udp ;udp,tcp,tls,ws,wss,flow ;bind=0.0.0.0 ; UDP transport behind NAT @@ -158,6 +158,18 @@ ;cipher=ADH-AES256-SHA,ADH-AES128-SHA ;method=tlsv1 +; Example flow transport +; +; A flow transport is used to reference a flow of signaling with a specific +; target. All endpoints or other objects that reference the transport will use +; the same underlying transport and can share runtime discovered transport +; configuration (such as service routes). The protocol in use will be determined +; based on the URI used to establish the connection. Currently only TCP and TLS +; are supported. +; +;[transport-flow] +;type=transport +;protocol=flow ;===============OUTBOUND REGISTRATION WITH OUTBOUND AUTHENTICATION============ ; diff --git a/configure b/configure index 6b71e19eaa8a2d488d15e4b4008800ea21e44a08..47cf7e7b81d5cc58898a567e4a459156c3c31b27 100755 --- a/configure +++ b/configure @@ -930,6 +930,14 @@ PBX_POPT POPT_DIR POPT_INCLUDE POPT_LIB +PBX_PJSIP_OAUTH_AUTHENTICATION +PJSIP_OAUTH_AUTHENTICATION_DIR +PJSIP_OAUTH_AUTHENTICATION_INCLUDE +PJSIP_OAUTH_AUTHENTICATION_LIB +PBX_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE +PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_DIR +PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_INCLUDE +PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_LIB PBX_PJSIP_ENDPOINT_COMPACT_FORM PJSIP_ENDPOINT_COMPACT_FORM_DIR PJSIP_ENDPOINT_COMPACT_FORM_INCLUDE @@ -9470,6 +9478,12 @@ $as_echo "#define HAVE_PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS 1" >>confdefs.h $as_echo "#define HAVE_PJSIP_ENDPOINT_COMPACT_FORM 1" >>confdefs.h +$as_echo "#define HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE 1" >>confdefs.h + + +$as_echo "#define HAVE_PJSIP_OAUTH_AUTHENTICATION 1" >>confdefs.h + + @@ -11624,6 +11638,30 @@ PBX_PJSIP_ENDPOINT_COMPACT_FORM=0 + +PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_DESCRIP="PJSIP Transport Connection Reuse Disabling" +PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_OPTION=pjsip +PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_DIR=${PJPROJECT_DIR} + +PBX_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE=0 + + + + + + + +PJSIP_OAUTH_AUTHENTICATION_DESCRIP="PJSIP OAuth Authentication Support" +PJSIP_OAUTH_AUTHENTICATION_OPTION=pjsip +PJSIP_OAUTH_AUTHENTICATION_DIR=${PJPROJECT_DIR} + +PBX_PJSIP_OAUTH_AUTHENTICATION=0 + + + + + + fi @@ -25568,6 +25606,86 @@ $as_echo "#define HAVE_PJSIP_ENDPOINT_COMPACT_FORM 1" >>confdefs.h +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CPPFLAGS="${saved_cppflags}" + fi + + + if test "x${PBX_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE}" != "x1" -a "${USE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE}" != "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if \"struct pjsip_tpselector sel; sel.disable_connection_reuse = PJ_TRUE;\" compiles using pjsip.h" >&5 +$as_echo_n "checking if \"struct pjsip_tpselector sel; sel.disable_connection_reuse = PJ_TRUE;\" compiles using pjsip.h... " >&6; } + saved_cppflags="${CPPFLAGS}" + if test "x${PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_DIR}" != "x"; then + PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_INCLUDE="-I${PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_DIR}/include" + fi + CPPFLAGS="${CPPFLAGS} ${PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE_INCLUDE}" + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + #include <pjsip.h> +int +main () +{ + struct pjsip_tpselector sel; sel.disable_connection_reuse = PJ_TRUE;; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + PBX_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE=1 + +$as_echo "#define HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE 1" >>confdefs.h + + + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CPPFLAGS="${saved_cppflags}" + fi + + + if test "x${PBX_PJSIP_OAUTH_AUTHENTICATION}" != "x1" -a "${USE_PJSIP_OAUTH_AUTHENTICATION}" != "no"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if \"struct pjsip_oauth_credential credential;\" compiles using pjsip.h" >&5 +$as_echo_n "checking if \"struct pjsip_oauth_credential credential;\" compiles using pjsip.h... " >&6; } + saved_cppflags="${CPPFLAGS}" + if test "x${PJSIP_OAUTH_AUTHENTICATION_DIR}" != "x"; then + PJSIP_OAUTH_AUTHENTICATION_INCLUDE="-I${PJSIP_OAUTH_AUTHENTICATION_DIR}/include" + fi + CPPFLAGS="${CPPFLAGS} ${PJSIP_OAUTH_AUTHENTICATION_INCLUDE}" + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + #include <pjsip.h> +int +main () +{ + struct pjsip_oauth_credential credential;; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + PBX_PJSIP_OAUTH_AUTHENTICATION=1 + +$as_echo "#define HAVE_PJSIP_OAUTH_AUTHENTICATION 1" >>confdefs.h + + + else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } diff --git a/configure.ac b/configure.ac index dd0c8edd13c2b8a5098f014333b76974191ddafa..73727b97a07196c1ca2e5ba6b5cb394f148c4a13 100644 --- a/configure.ac +++ b/configure.ac @@ -537,6 +537,8 @@ AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_AUTH_CLT_DEINIT], [pjsip_auth_clt_deinit suppo AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TSX_LAYER_FIND_TSX2], [pjsip_tsx_layer_find_tsx2 support], [PJPROJECT], [pjsip]) AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS], [PJSIP INVITE Accept Multiple SDP Answers], [PJPROJECT], [pjsip]) AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_ENDPOINT_COMPACT_FORM], [PJSIP Compact Form Support on Endpoint], [PJPROJECT], [pjsip]) +AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE], [PJSIP Transport Connection Reuse Disabling], [PJPROJECT], [pjsip]) +AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_OAUTH_AUTHENTICATION], [PJSIP OAuth Authentication Support], [PJPROJECT], [pjsip]) fi AST_EXT_LIB_SETUP([POPT], [popt], [popt]) @@ -2376,6 +2378,8 @@ if test "$USE_PJPROJECT" != "no" ; then AST_C_COMPILE_CHECK([PJSIP_TLS_TRANSPORT_PROTO], [struct pjsip_tls_setting setting; int proto; proto = setting.proto;], [pjsip.h]) AST_C_COMPILE_CHECK([PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS], [pjsip_cfg()->endpt.accept_multiple_sdp_answers = 0;], [pjsip.h]) AST_C_COMPILE_CHECK([PJSIP_ENDPOINT_COMPACT_FORM], [pjsip_cfg()->endpt.use_compact_form = PJ_TRUE;], [pjsip.h]) + AST_C_COMPILE_CHECK([PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE], [struct pjsip_tpselector sel; sel.disable_connection_reuse = PJ_TRUE;], [pjsip.h]) + AST_C_COMPILE_CHECK([PJSIP_OAUTH_AUTHENTICATION], [struct pjsip_oauth_credential credential;], [pjsip.h]) LIBS="${saved_libs}" CPPFLAGS="${saved_cppflags}" diff --git a/contrib/ast-db-manage/config/versions/465f47f880be_add_pjsip_google_voice_sip_options.py b/contrib/ast-db-manage/config/versions/465f47f880be_add_pjsip_google_voice_sip_options.py new file mode 100644 index 0000000000000000000000000000000000000000..0f9e8b99bcf90020c609bd6426d355cc8cf260a3 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/465f47f880be_add_pjsip_google_voice_sip_options.py @@ -0,0 +1,115 @@ +"""add pjsip google voice sip options + +Revision ID: 465f47f880be +Revises: 7f85dd44c775 +Create Date: 2018-09-25 17:26:12.892161 + +""" + +# revision identifiers, used by Alembic. +revision = '465f47f880be' +down_revision = '7f85dd44c775' + +from alembic import op +from sqlalchemy.dialects.postgresql import ENUM +import sqlalchemy as sa + +AST_BOOL_NAME = 'ast_bool_values' +# We'll just ignore the n/y and f/t abbreviations as Asterisk does not write +# those aliases. +AST_BOOL_VALUES = [ '0', '1', + 'off', 'on', + 'false', 'true', + 'no', 'yes' ] + +PJSIP_TRANSPORT_PROTOCOL_OLD_NAME = 'pjsip_transport_protocol_values' +PJSIP_TRANSPORT_PROTOCOL_NEW_NAME = 'pjsip_transport_protocol_values_v2' + +PJSIP_TRANSPORT_PROTOCOL_OLD_VALUES = ['udp', 'tcp', 'tls', 'ws', 'wss'] +PJSIP_TRANSPORT_PROTOCOL_NEW_VALUES = ['udp', 'tcp', 'tls', 'ws', 'wss', 'flow'] + +PJSIP_TRANSPORT_PROTOCOL_OLD_TYPE = sa.Enum(*PJSIP_TRANSPORT_PROTOCOL_OLD_VALUES, + name=PJSIP_TRANSPORT_PROTOCOL_OLD_NAME) +PJSIP_TRANSPORT_PROTOCOL_NEW_TYPE = sa.Enum(*PJSIP_TRANSPORT_PROTOCOL_NEW_VALUES, + name=PJSIP_TRANSPORT_PROTOCOL_NEW_NAME) + +PJSIP_AUTH_TYPE_OLD_NAME = 'pjsip_auth_type_values' +PJSIP_AUTH_TYPE_NEW_NAME = 'pjsip_auth_type_values_v2' + +PJSIP_AUTH_TYPE_OLD_VALUES = ['md5', 'userpass'] +PJSIP_AUTH_TYPE_NEW_VALUES = ['md5', 'userpass', 'google_oauth'] + +PJSIP_AUTH_TYPE_OLD_TYPE = sa.Enum(*PJSIP_AUTH_TYPE_OLD_VALUES, + name=PJSIP_AUTH_TYPE_OLD_NAME) +PJSIP_AUTH_TYPE_NEW_TYPE = sa.Enum(*PJSIP_AUTH_TYPE_NEW_VALUES, + name=PJSIP_AUTH_TYPE_NEW_NAME) + + +def upgrade(): + if op.get_context().bind.dialect.name == 'postgresql': + enum = PJSIP_TRANSPORT_PROTOCOL_NEW_TYPE + enum.create(op.get_bind(), checkfirst=False) + op.execute('ALTER TABLE ps_transports ALTER COLUMN protocol TYPE' + ' ' + PJSIP_TRANSPORT_PROTOCOL_NEW_NAME + ' USING' + ' protocol::text::' + PJSIP_TRANSPORT_PROTOCOL_NEW_NAME) + ENUM(name=PJSIP_TRANSPORT_PROTOCOL_OLD_NAME).drop(op.get_bind(), checkfirst=False) + + enum = PJSIP_AUTH_TYPE_NEW_TYPE + enum.create(op.get_bind(), checkfirst=False) + op.execute('ALTER TABLE ps_auths ALTER COLUMN auth_type TYPE' + ' ' + PJSIP_AUTH_TYPE_NEW_NAME + ' USING' + ' auth_type::text::' + PJSIP_AUTH_TYPE_NEW_NAME) + ENUM(name=PJSIP_AUTH_TYPE_OLD_NAME).drop(op.get_bind(), checkfirst=False) + else: + op.alter_column('ps_transports', 'protocol', + type_=PJSIP_TRANSPORT_PROTOCOL_NEW_TYPE, + existing_type=PJSIP_TRANSPORT_PROTOCOL_OLD_TYPE) + op.alter_column('ps_auths', 'auth_type', + type_=PJSIP_AUTH_TYPE_NEW_TYPE, + existing_type=PJSIP_AUTH_TYPE_OLD_TYPE) + + # ast_bool_values have already been created, so use postgres enum object + # type to get around "already created" issue - works okay with mysql + ast_bool_values = ENUM(*AST_BOOL_VALUES, name=AST_BOOL_NAME, create_type=False) + + op.add_column('ps_registrations', sa.Column('support_outbound', ast_bool_values)) + op.add_column('ps_registrations', sa.Column('contact_header_params', sa.String(255))) + + op.add_column('ps_auths', sa.Column('refresh_token', sa.String(255))) + op.add_column('ps_auths', sa.Column('oauth_clientid', sa.String(255))) + op.add_column('ps_auths', sa.Column('oauth_secret', sa.String(255))) + +def downgrade(): + # First we need to ensure that columns are not using the enum values + # that are going away. + op.execute("UPDATE ps_transports SET protocol='udp' WHERE protocol='flow'") + op.execute("UPDATE ps_auths SET auth_type='userpass' WHERE auth_type='google_oauth'") + + if op.get_context().bind.dialect.name == 'postgresql': + enum = PJSIP_TRANSPORT_PROTOCOL_OLD_TYPE + enum.create(op.get_bind(), checkfirst=False) + op.execute('ALTER TABLE ps_transports ALTER COLUMN protocol TYPE' + ' ' + PJSIP_TRANSPORT_PROTOCOL_OLD_NAME + ' USING' + ' protocol::text::' + PJSIP_TRANSPORT_PROTOCOL_OLD_NAME) + ENUM(name=PJSIP_TRANSPORT_PROTOCOL_NEW_NAME).drop(op.get_bind(), checkfirst=False) + + enum = PJSIP_AUTH_TYPE_OLD_TYPE + enum.create(op.get_bind(), checkfirst=False) + op.execute('ALTER TABLE ps_auths ALTER COLUMN auth_type TYPE' + ' ' + PJSIP_AUTH_TYPE_OLD_NAME + ' USING' + ' auth_type::text::' + PJSIP_AUTH_TYPE_OLD_NAME) + ENUM(name=PJSIP_AUTH_TYPE_NEW_NAME).drop(op.get_bind(), checkfirst=False) + else: + op.alter_column('ps_transports', 'protocol', + type_=PJSIP_TRANSPORT_PROTOCOL_OLD_TYPE, + existing_type=PJSIP_TRANSPORT_PROTOCOL_NEW_TYPE) + op.alter_column('ps_auths', 'auth_type', + type_=PJSIP_AUTH_TYPE_OLD_TYPE, + existing_type=PJSIP_AUTH_TYPE_NEW_TYPE) + + op.drop_column('ps_registrations', 'support_outbound') + op.drop_column('ps_registrations', 'contact_header_params') + + op.drop_column('ps_auths', 'refresh_token') + op.drop_column('ps_auths', 'oauth_clientid') + op.drop_column('ps_auths', 'oauth_secret') diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in index 30bb90749df7bf753ece1ccc027c969c422e47da..cce24eb8892b8fe68bbc161fa7d4b7a4a9b33805 100644 --- a/include/asterisk/autoconfig.h.in +++ b/include/asterisk/autoconfig.h.in @@ -640,12 +640,19 @@ support feature. */ #undef HAVE_PJSIP_INV_SESSION_REF +/* Define if your system has the PJSIP_OAUTH_AUTHENTICATION headers. */ +#undef HAVE_PJSIP_OAUTH_AUTHENTICATION + /* Define if your system has the PJSIP_REPLACE_MEDIA_STREAM headers. */ #undef HAVE_PJSIP_REPLACE_MEDIA_STREAM /* Define if your system has the PJSIP_TLS_TRANSPORT_PROTO headers. */ #undef HAVE_PJSIP_TLS_TRANSPORT_PROTO +/* Define if your system has the PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE + headers. */ +#undef HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE + /* Define to 1 if PJPROJECT has the pjsip_tsx_layer_find_tsx2 support feature. */ #undef HAVE_PJSIP_TSX_LAYER_FIND_TSX2 diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index b323bc15b7cab3429fddbbf789864cd32662fe12..20af039fd67fd53f0925ea0e7d7fc45614b9e342 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -63,6 +63,8 @@ struct pjsip_tpselector; /*! \brief Maximum number of ciphers supported for a TLS transport */ #define SIP_TLS_MAX_CIPHERS 64 +AST_VECTOR(ast_sip_service_route_vector, char *); + /*! * \brief Structure for SIP transport information */ @@ -124,6 +126,21 @@ struct ast_sip_transport_state { * \since 13.18.0 */ struct ast_sockaddr external_media_address; + /*! + * Set when this transport is a flow of signaling to a target + * \since 17.0.0 + */ + int flow; + /*! + * The P-Preferred-Identity to use on traffic using this transport + * \since 17.0.0 + */ + char *preferred_identity; + /*! + * The Service Routes to use on traffic using this transport + * \since 17.0.0 + */ + struct ast_sip_service_route_vector *service_routes; }; #define ast_sip_transport_is_nonlocal(transport_state, addr) \ @@ -214,6 +231,8 @@ struct ast_sip_transport { int allow_reload; /*! Automatically send requests out the same transport requests have come in on */ int symmetric_transport; + /*! This is a flow to another target */ + int flow; }; #define SIP_SORCERY_DOMAIN_ALIAS_TYPE "domain_alias" @@ -400,6 +419,8 @@ enum ast_sip_auth_type { AST_SIP_AUTH_TYPE_USER_PASS, /*! Credentials stored as an MD5 sum */ AST_SIP_AUTH_TYPE_MD5, + /*! Google Oauth */ + AST_SIP_AUTH_TYPE_GOOGLE_OAUTH, /*! Credentials not stored this is a fake auth */ AST_SIP_AUTH_TYPE_ARTIFICIAL }; @@ -418,6 +439,12 @@ struct ast_sip_auth { AST_STRING_FIELD(auth_pass); /*! Authentication credentials in MD5 format (hash of user:realm:pass) */ AST_STRING_FIELD(md5_creds); + /*! Refresh token to use for OAuth authentication */ + AST_STRING_FIELD(refresh_token); + /*! Client ID to use for OAuth authentication */ + AST_STRING_FIELD(oauth_clientid); + /*! Secret to use for OAuth authentication */ + AST_STRING_FIELD(oauth_secret); ); /*! The time period (in seconds) that a nonce may be reused */ unsigned int nonce_lifetime; @@ -2952,6 +2979,8 @@ struct ao2_container *ast_sip_get_transport_states(void); * \param selector The selector to be populated * \retval 0 success * \retval -1 failure + * + * \note The transport selector must be unreffed using ast_sip_tpselector_unref */ int ast_sip_set_tpselector_from_transport(const struct ast_sip_transport *transport, pjsip_tpselector *selector); @@ -2963,9 +2992,80 @@ int ast_sip_set_tpselector_from_transport(const struct ast_sip_transport *transp * \param selector The selector to be populated * \retval 0 success * \retval -1 failure + * + * \note The transport selector must be unreffed using ast_sip_tpselector_unref */ int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip_tpselector *selector); +/*! + * \brief Unreference a pjsip_tpselector + * \since 17.0.0 + * + * \param selector The selector to be unreffed + */ +void ast_sip_tpselector_unref(pjsip_tpselector *selector); + +/*! + * \brief Sets the PJSIP transport on a child transport + * \since 17.0.0 + * + * \param transport_name The name of the transport to be updated + * \param transport The PJSIP transport + * \retval 0 success + * \retval -1 failure + */ +int ast_sip_transport_state_set_transport(const char *transport_name, pjsip_transport *transport); + +/*! + * \brief Sets the P-Preferred-Identity on a child transport + * \since 17.0.0 + * + * \param transport_name The name of the transport to be set on + * \param identity The P-Preferred-Identity to use on requests on this transport + * \retval 0 success + * \retval -1 failure + */ +int ast_sip_transport_state_set_preferred_identity(const char *transport_name, const char *identity); + +/*! + * \brief Sets the service routes on a child transport + * \since 17.0.0 + * + * \param transport_name The name of the transport to be set on + * \param service_routes A vector of service routes + * \retval 0 success + * \retval -1 failure + * + * \note This assumes ownership of the service routes in both success and failure scenarios + */ +int ast_sip_transport_state_set_service_routes(const char *transport_name, struct ast_sip_service_route_vector *service_routes); + +/*! + * \brief Apply the configuration for a transport to an outgoing message + * \since 17.0.0 + * + * \param transport_name The name of the transport to apply configuration from + * \param tdata The SIP message + */ +void ast_sip_message_apply_transport(const char *transport_name, pjsip_tx_data *tdata); + +/*! + * \brief Allocate a vector of service routes + * \since 17.0.0 + * + * \retval non-NULL success + * \retval NULL failure + */ +struct ast_sip_service_route_vector *ast_sip_service_route_vector_alloc(void); + +/*! + * \brief Destroy a vector of service routes + * \since 17.0.0 + * + * \param service_routes A vector of service routes + */ +void ast_sip_service_route_vector_destroy(struct ast_sip_service_route_vector *service_routes); + /*! * \brief Set name and number information on an identity header. * @@ -3033,6 +3133,9 @@ int ast_sip_set_tpselector_from_ep_or_uri(const struct ast_sip_endpoint *endpoin * This API calls ast_sip_get_transport_name(endpoint, dlg->target) and if the result is * non-NULL, calls pjsip_dlg_set_transport. If 'selector' is non-NULL, it is updated with * the selector used. + * + * \note + * It is the responsibility of the caller to unref the passed in selector if one is provided. */ int ast_sip_dlg_set_transport(const struct ast_sip_endpoint *endpoint, pjsip_dialog *dlg, pjsip_tpselector *selector); diff --git a/res/res_pjsip.c b/res/res_pjsip.c index c2e77cba2f53e15d48d36feae2171effefd57739..a61208f38c89d57c4f1e4a3139b63179e2126a10 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -1136,11 +1136,12 @@ This option specifies which of the password style config options should be read when trying to authenticate an endpoint inbound request. If set to <literal>userpass</literal> then we'll read from the 'password' option. For <literal>md5</literal> we'll read - from 'md5_cred'. + from 'md5_cred'. If set to <literal>google_oauth</literal> then we'll read from the refresh_token/oauth_clientid/oauth_secret fields. </para> <enumlist> <enum name="md5"/> <enum name="userpass"/> + <enum name="google_oauth"/> </enumlist> </description> </configOption> @@ -1155,6 +1156,15 @@ <synopsis>Plain text password used for authentication.</synopsis> <description><para>Only used when auth_type is <literal>userpass</literal>.</para></description> </configOption> + <configOption name="refresh_token"> + <synopsis>OAuth 2.0 refresh token</synopsis> + </configOption> + <configOption name="oauth_clientid"> + <synopsis>OAuth 2.0 application's client id</synopsis> + </configOption> + <configOption name="oauth_secret"> + <synopsis>OAuth 2.0 application's secret</synopsis> + </configOption> <configOption name="realm"> <synopsis>SIP realm for endpoint</synopsis> <description><para> @@ -1307,6 +1317,7 @@ <enum name="tls" /> <enum name="ws" /> <enum name="wss" /> + <enum name="flow" /> </enumlist> </description> </configOption> @@ -3277,8 +3288,13 @@ int ast_sip_dlg_set_transport(const struct ast_sip_endpoint *endpoint, pjsip_dia } ast_sip_set_tpselector_from_ep_or_uri(endpoint, uri, selector); + pjsip_dlg_set_transport(dlg, selector); + if (selector == &sel) { + ast_sip_tpselector_unref(&sel); + } + return 0; } @@ -3367,7 +3383,8 @@ static int sip_dialog_create_from(pj_pool_t *pool, pj_str_t *from, const char *u int ast_sip_set_tpselector_from_transport(const struct ast_sip_transport *transport, pjsip_tpselector *selector) { - RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup); + int res = 0; + struct ast_sip_transport_state *transport_state; transport_state = ast_sip_get_transport_state(ast_sorcery_object_get_id(transport)); if (!transport_state) { @@ -3376,9 +3393,15 @@ int ast_sip_set_tpselector_from_transport(const struct ast_sip_transport *transp return -1; } + /* Only flows maintain dynamic state which needs protection */ + if (transport_state->flow) { + ao2_lock(transport_state); + } + if (transport_state->transport) { selector->type = PJSIP_TPSELECTOR_TRANSPORT; selector->u.transport = transport_state->transport; + pjsip_transport_add_ref(selector->u.transport); } else if (transport_state->factory) { selector->type = PJSIP_TPSELECTOR_LISTENER; selector->u.listener = transport_state->factory; @@ -3387,12 +3410,25 @@ int ast_sip_set_tpselector_from_transport(const struct ast_sip_transport *transp * even if an endpoint is locked to a WebSocket transport we let the PJSIP logic * find the existing connection if available and use it. */ - return 0; + } else if (transport->flow) { + /* This is a child of another transport, so we need to establish a new connection */ +#ifdef HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE + selector->disable_connection_reuse = PJ_TRUE; +#else + ast_log(LOG_WARNING, "Connection reuse could not be disabled on transport '%s' as support is not available\n", + ast_sorcery_object_get_id(transport)); +#endif } else { - return -1; + res = -1; } - return 0; + if (transport_state->flow) { + ao2_unlock(transport_state); + } + + ao2_ref(transport_state, -1); + + return res; } int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip_tpselector *selector) @@ -3425,6 +3461,13 @@ int ast_sip_set_tpselector_from_ep_or_uri(const struct ast_sip_endpoint *endpoin return ast_sip_set_tpselector_from_transport_name(transport_name, selector); } +void ast_sip_tpselector_unref(pjsip_tpselector *selector) +{ + if (selector->type == PJSIP_TPSELECTOR_TRANSPORT && selector->u.transport) { + pjsip_transport_dec_ref(selector->u.transport); + } +} + void ast_sip_add_usereqphone(const struct ast_sip_endpoint *endpoint, pj_pool_t *pool, pjsip_uri *uri) { pjsip_sip_uri *sip_uri; @@ -3493,9 +3536,12 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint, if (sip_dialog_create_from(dlg->pool, &local_uri, endpoint->fromuser, endpoint->fromdomain, &remote_uri, &selector)) { dlg->sess_count--; pjsip_dlg_terminate(dlg); + ast_sip_tpselector_unref(&selector); return NULL; } + ast_sip_tpselector_unref(&selector); + /* Update the dialog with the new local URI, we do it afterwards so we can use the dialog pool for construction */ pj_strdup_with_null(dlg->pool, &dlg->local.info_str, &local_uri); dlg->local.info->uri = pjsip_parse_uri(dlg->pool, dlg->local.info_str.ptr, dlg->local.info_str.slen, 0); @@ -3642,12 +3688,16 @@ pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint, pj_strerror(*status, err, sizeof(err)); ast_log(LOG_ERROR, "Could not create dialog with endpoint %s. %s\n", ast_sorcery_object_get_id(endpoint), err); + ast_sip_tpselector_unref(&selector); return NULL; } dlg->sess_count++; pjsip_dlg_set_transport(dlg, &selector); dlg->sess_count--; + + ast_sip_tpselector_unref(&selector); + #ifdef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK pjsip_dlg_dec_lock(dlg); #endif @@ -3817,6 +3867,7 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s (int) pj_strlen(&method->name), pj_strbuf(&method->name), endpoint ? ast_sorcery_object_get_id(endpoint) : "<none>"); pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); + ast_sip_tpselector_unref(&selector); return -1; } @@ -3826,11 +3877,14 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s (int) pj_strlen(&method->name), pj_strbuf(&method->name), endpoint ? ast_sorcery_object_get_id(endpoint) : "<none>"); pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); + ast_sip_tpselector_unref(&selector); return -1; } pjsip_tx_data_set_transport(*tdata, &selector); + ast_sip_tpselector_unref(&selector); + if (endpoint && !ast_strlen_zero(endpoint->contact_user)){ pjsip_contact_hdr *contact_hdr; pjsip_sip_uri *contact_uri; @@ -4385,6 +4439,10 @@ int ast_sip_send_out_of_dialog_request(pjsip_tx_data *tdata, return -1; } + if (endpoint) { + ast_sip_message_apply_transport(endpoint->transport, tdata); + } + contact = ast_sip_mod_data_get(tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT); AST_RWLIST_RDLOCK(&supplements); @@ -4828,6 +4886,10 @@ static void supplement_outgoing_response(pjsip_tx_data *tdata, struct ast_sip_en pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); struct ast_sip_contact *contact = ast_sip_mod_data_get(tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT); + if (sip_endpoint) { + ast_sip_message_apply_transport(sip_endpoint->transport, tdata); + } + AST_RWLIST_RDLOCK(&supplements); AST_LIST_TRAVERSE(&supplements, supplement, next) { if (supplement->outgoing_response && does_method_match(&cseq->method.name, supplement->method)) { diff --git a/res/res_pjsip/config_auth.c b/res/res_pjsip/config_auth.c index b1bf9c422f59535428907bbe4ec9dd46084c92fa..2350140f5339f26298f7d6c314a259d6c55ee8f9 100644 --- a/res/res_pjsip/config_auth.c +++ b/res/res_pjsip/config_auth.c @@ -56,6 +56,13 @@ static int auth_type_handler(const struct aco_option *opt, struct ast_variable * auth->type = AST_SIP_AUTH_TYPE_USER_PASS; } else if (!strcasecmp(var->value, "md5")) { auth->type = AST_SIP_AUTH_TYPE_MD5; + } else if (!strcasecmp(var->value, "google_oauth")) { +#ifdef HAVE_PJSIP_OAUTH_AUTHENTICATION + auth->type = AST_SIP_AUTH_TYPE_GOOGLE_OAUTH; +#else + ast_log(LOG_WARNING, "OAuth support is not available in the version of PJSIP in use\n"); + return -1; +#endif } else { ast_log(LOG_WARNING, "Unknown authentication storage type '%s' specified for %s\n", var->value, var->name); @@ -66,7 +73,8 @@ static int auth_type_handler(const struct aco_option *opt, struct ast_variable * static const char *auth_types_map[] = { [AST_SIP_AUTH_TYPE_USER_PASS] = "userpass", - [AST_SIP_AUTH_TYPE_MD5] = "md5" + [AST_SIP_AUTH_TYPE_MD5] = "md5", + [AST_SIP_AUTH_TYPE_GOOGLE_OAUTH] = "google_oauth" }; const char *ast_sip_auth_type_to_str(enum ast_sip_auth_type type) @@ -106,6 +114,16 @@ static int auth_apply(const struct ast_sorcery *sorcery, void *obj) res = -1; } break; + case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH: + if (ast_strlen_zero(auth->refresh_token) + || ast_strlen_zero(auth->oauth_clientid) + || ast_strlen_zero(auth->oauth_secret)) { + ast_log(LOG_ERROR, "'google_oauth' authentication specified but refresh_token," + " oauth_clientid, or oauth_secret not specified for auth '%s'\n", + ast_sorcery_object_get_id(auth)); + res = -1; + } + break; case AST_SIP_AUTH_TYPE_USER_PASS: case AST_SIP_AUTH_TYPE_ARTIFICIAL: break; @@ -365,6 +383,12 @@ int ast_sip_initialize_sorcery_auth(void) "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, auth_user)); ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "password", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, auth_pass)); + ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "refresh_token", + "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, refresh_token)); + ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "oauth_clientid", + "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, oauth_clientid)); + ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "oauth_secret", + "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, oauth_secret)); ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "md5_cred", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, md5_creds)); ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "realm", diff --git a/res/res_pjsip/config_transport.c b/res/res_pjsip/config_transport.c index 13a9ff88ae8ed2c0001900a7f9437a23030bd555..a84fee03a58ca1a72db6b9ccff3955bc5cd2d861 100644 --- a/res/res_pjsip/config_transport.c +++ b/res/res_pjsip/config_transport.c @@ -203,6 +203,176 @@ struct ast_sip_endpoint_formatter endpoint_transport_formatter = { .format_ami = format_ami_endpoint_transport }; +int ast_sip_transport_state_set_transport(const char *transport_name, pjsip_transport *transport) +{ + struct ast_sip_transport_state *transport_state; + + /* To make it easier on callers we allow an empty transport name */ + if (ast_strlen_zero(transport_name)) { + return 0; + } + + transport_state = ast_sip_get_transport_state(transport_name); + if (!transport_state) { + return -1; + } + + if (!transport_state->flow) { + ao2_ref(transport_state, -1); + return 0; + } + + ao2_lock(transport_state); + if (transport_state->transport != transport) { + if (transport_state->transport) { + pjsip_transport_dec_ref(transport_state->transport); + } + transport_state->transport = transport; + if (transport_state->transport) { + pjsip_transport_add_ref(transport_state->transport); + } + } + ao2_unlock(transport_state); + + ao2_ref(transport_state, -1); + + return 0; +} + +int ast_sip_transport_state_set_preferred_identity(const char *transport_name, const char *identity) +{ + struct ast_sip_transport_state *transport_state; + + if (ast_strlen_zero(transport_name)) { + return 0; + } + + transport_state = ast_sip_get_transport_state(transport_name); + if (!transport_state) { + return -1; + } + + if (!transport_state->flow) { + ao2_ref(transport_state, -1); + return 0; + } + + ao2_lock(transport_state); + ast_free(transport_state->preferred_identity); + transport_state->preferred_identity = ast_strdup(identity); + ao2_unlock(transport_state); + + ao2_ref(transport_state, -1); + + return 0; +} + +int ast_sip_transport_state_set_service_routes(const char *transport_name, struct ast_sip_service_route_vector *service_routes) +{ + struct ast_sip_transport_state *transport_state; + + if (ast_strlen_zero(transport_name)) { + ast_sip_service_route_vector_destroy(service_routes); + return 0; + } + + transport_state = ast_sip_get_transport_state(transport_name); + if (!transport_state) { + ast_sip_service_route_vector_destroy(service_routes); + return -1; + } + + if (!transport_state->flow) { + ao2_ref(transport_state, -1); + ast_sip_service_route_vector_destroy(service_routes); + return 0; + } + + ao2_lock(transport_state); + ast_sip_service_route_vector_destroy(transport_state->service_routes); + transport_state->service_routes = service_routes; + ao2_unlock(transport_state); + + ao2_ref(transport_state, -1); + + return 0; +} + +void ast_sip_message_apply_transport(const char *transport_name, pjsip_tx_data *tdata) +{ + struct ast_sip_transport_state *transport_state; + + if (ast_strlen_zero(transport_name)) { + return; + } + + /* We only currently care about requests that are of the INVITE, CANCEL, or OPTIONS + * type but in the future we could support other messages. + */ + if (tdata->msg->type != PJSIP_REQUEST_MSG || + (pjsip_method_cmp(&tdata->msg->line.req.method, &pjsip_invite_method) && + pjsip_method_cmp(&tdata->msg->line.req.method, &pjsip_cancel_method) && + pjsip_method_cmp(&tdata->msg->line.req.method, &pjsip_options_method))) { + return; + } + + transport_state = ast_sip_get_transport_state(transport_name); + if (!transport_state) { + return; + } + + if (!transport_state->flow) { + ao2_ref(transport_state, -1); + return; + } + + ao2_lock(transport_state); + + /* If a Preferred Identity has been set then add it to the request */ + if (transport_state->preferred_identity) { + ast_sip_add_header(tdata, "P-Preferred-Identity", transport_state->preferred_identity); + } + + /* If Service Routes have been set then add them to the request */ + if (transport_state->service_routes) { + int idx; + + for (idx = 0; idx < AST_VECTOR_SIZE(transport_state->service_routes); ++idx) { + char *service_route = AST_VECTOR_GET(transport_state->service_routes, idx); + + ast_sip_add_header(tdata, "Route", service_route); + } + } + + ao2_unlock(transport_state); + + ao2_ref(transport_state, -1); +} + +struct ast_sip_service_route_vector *ast_sip_service_route_vector_alloc(void) +{ + struct ast_sip_service_route_vector *service_routes; + + service_routes = ast_calloc(1, sizeof(*service_routes)); + if (!service_routes) { + return NULL; + } + + AST_VECTOR_INIT(service_routes, 0); + + return service_routes; +} + +void ast_sip_service_route_vector_destroy(struct ast_sip_service_route_vector *service_routes) +{ + if (!service_routes) { + return; + } + + AST_VECTOR_CALLBACK_VOID(service_routes, ast_free); + ast_free(service_routes); +} + static void set_qos(struct ast_sip_transport *transport, pj_qos_params *qos) { int tos_as_dscp = transport->tos >> 2; @@ -500,7 +670,7 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj) return 0; } - if (!transport->allow_reload) { + if (!transport->allow_reload && !transport->flow) { if (!perm_state->change_detected) { perm_state->change_detected = 1; ast_log(LOG_WARNING, "Transport '%s' is not reloadable, maintaining previous values\n", transport_id); @@ -513,14 +683,16 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj) } } - if (temp_state->state->host.addr.sa_family != PJ_AF_INET && temp_state->state->host.addr.sa_family != PJ_AF_INET6) { - ast_log(LOG_ERROR, "Transport '%s' could not be started as binding not specified\n", transport_id); - return -1; - } + if (!transport->flow) { + if (temp_state->state->host.addr.sa_family != PJ_AF_INET && temp_state->state->host.addr.sa_family != PJ_AF_INET6) { + ast_log(LOG_ERROR, "Transport '%s' could not be started as binding not specified\n", transport_id); + return -1; + } - /* Set default port if not present */ - if (!pj_sockaddr_get_port(&temp_state->state->host)) { - pj_sockaddr_set_port(&temp_state->state->host, (transport->type == AST_TRANSPORT_TLS) ? 5061 : 5060); + /* Set default port if not present */ + if (!pj_sockaddr_get_port(&temp_state->state->host)) { + pj_sockaddr_set_port(&temp_state->state->host, (transport->type == AST_TRANSPORT_TLS) ? 5061 : 5060); + } } /* Now that we know what address family we can set up a dnsmgr refresh for the external addresses if present */ @@ -558,7 +730,16 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj) } } - if (transport->type == AST_TRANSPORT_UDP) { + if (transport->flow) { + pj_str_t address; + + ast_debug(1, "Ignoring any bind configuration on transport '%s' as it is a child of another\n", + transport_id); + pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&address, "0.0.0.0"), &temp_state->state->host); + + temp_state->state->flow = 1; + res = PJ_SUCCESS; + } else if (transport->type == AST_TRANSPORT_UDP) { for (i = 0; i < BIND_TRIES && res != PJ_SUCCESS; i++) { if (perm_state && perm_state->state && perm_state->state->transport) { @@ -775,18 +956,23 @@ static int transport_protocol_handler(const struct aco_option *opt, struct ast_v return -1; } - if (!strcasecmp(var->value, "udp")) { - transport->type = AST_TRANSPORT_UDP; - } else if (!strcasecmp(var->value, "tcp")) { - transport->type = AST_TRANSPORT_TCP; - } else if (!strcasecmp(var->value, "tls")) { - transport->type = AST_TRANSPORT_TLS; - } else if (!strcasecmp(var->value, "ws")) { - transport->type = AST_TRANSPORT_WS; - } else if (!strcasecmp(var->value, "wss")) { - transport->type = AST_TRANSPORT_WSS; + if (!strcasecmp(var->value, "flow")) { + transport->flow = 1; } else { - return -1; + if (!strcasecmp(var->value, "udp")) { + transport->type = AST_TRANSPORT_UDP; + } else if (!strcasecmp(var->value, "tcp")) { + transport->type = AST_TRANSPORT_TCP; + } else if (!strcasecmp(var->value, "tls")) { + transport->type = AST_TRANSPORT_TLS; + } else if (!strcasecmp(var->value, "ws")) { + transport->type = AST_TRANSPORT_WS; + } else if (!strcasecmp(var->value, "wss")) { + transport->type = AST_TRANSPORT_WSS; + } else { + return -1; + } + transport->flow = 0; } state->type = transport->type; @@ -806,7 +992,9 @@ static int transport_protocol_to_str(const void *obj, const intptr_t *args, char { const struct ast_sip_transport *transport = obj; - if (ARRAY_IN_BOUNDS(transport->type, transport_types)) { + if (transport->flow) { + *buf = ast_strdup("flow"); + } else if (ARRAY_IN_BOUNDS(transport->type, transport_types)) { *buf = ast_strdup(transport_types[transport->type]); } @@ -1362,6 +1550,16 @@ struct ast_sip_transport_state *ast_sip_get_transport_state(const char *transpor trans_state = ao2_bump(state->state); ao2_ref(state, -1); + /* If this is a child transport see if the transport is actually dead */ + if (trans_state->flow) { + ao2_lock(trans_state); + if (trans_state->transport && trans_state->transport->is_shutdown == PJ_TRUE) { + pjsip_transport_dec_ref(trans_state->transport); + trans_state->transport = NULL; + } + ao2_unlock(trans_state); + } + return trans_state; } diff --git a/res/res_pjsip_outbound_authenticator_digest.c b/res/res_pjsip_outbound_authenticator_digest.c index 5f6d994faf5f9fce76078e5cee0ade67af304654..b1011b00a3b5888210a609d4930094baebbe9834 100644 --- a/res/res_pjsip_outbound_authenticator_digest.c +++ b/res/res_pjsip_outbound_authenticator_digest.c @@ -83,6 +83,9 @@ static int set_outbound_authentication_credentials(pjsip_auth_clt_sess *auth_ses pj_cstr(&auth_creds[i].data, auths[i]->md5_creds); auth_creds[i].data_type = PJSIP_CRED_DATA_DIGEST; break; + case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH: + /* nothing to do. handled seperately in res_pjsip_outbound_registration */ + break; case AST_SIP_AUTH_TYPE_ARTIFICIAL: ast_log(LOG_ERROR, "Trying to set artificial outbound auth credentials shouldn't happen.\n"); break; diff --git a/res/res_pjsip_outbound_publish.c b/res/res_pjsip_outbound_publish.c index e4e9fd4ac4c5a0cdba6e343d318f53da4a67eb76..d9a8b642e0dec51eb92d393742051fd4d9ae1ca5 100644 --- a/res/res_pjsip_outbound_publish.c +++ b/res/res_pjsip_outbound_publish.c @@ -428,9 +428,11 @@ static void set_transport(struct sip_outbound_publisher *publisher, pjsip_tx_dat { if (!ast_strlen_zero(publisher->owner->publish->transport)) { pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, }; + ast_sip_set_tpselector_from_transport_name( publisher->owner->publish->transport, &selector); pjsip_tx_data_set_transport(tdata, &selector); + ast_sip_tpselector_unref(&selector); } } diff --git a/res/res_pjsip_outbound_registration.c b/res/res_pjsip_outbound_registration.c index 0d815ad3966276afcd81b64ba7af8e78d0839864..648deee59615af24bcf3bb5b0e17f6dba49d3edf 100644 --- a/res/res_pjsip_outbound_registration.c +++ b/res/res_pjsip_outbound_registration.c @@ -38,6 +38,8 @@ #include "asterisk/threadpool.h" #include "asterisk/statsd.h" #include "res_pjsip/include/res_pjsip_private.h" +#include "asterisk/vector.h" +#include "asterisk/pbx.h" /*** DOCUMENTATION <configInfo name="res_pjsip_outbound_registration" language="en_US"> @@ -76,6 +78,9 @@ <configOption name="contact_user"> <synopsis>Contact User to use in request</synopsis> </configOption> + <configOption name="contact_header_params"> + <synopsis>Header parameters to place in the Contact header</synopsis> + </configOption> <configOption name="expiration" default="3600"> <synopsis>Expiration time for registrations in seconds</synopsis> </configOption> @@ -164,13 +169,16 @@ <synopsis>Must be of type 'registration'.</synopsis> </configOption> <configOption name="support_path"> - <synopsis>Enables Path support for outbound REGISTER requests.</synopsis> + <synopsis>Enables advertising SIP Path support for outbound REGISTER requests.</synopsis> <description><para> When this option is enabled, outbound REGISTER requests will advertise support for Path headers so that intervening proxies can add to the Path header as necessary. </para></description> </configOption> + <configOption name="support_outbound"> + <synopsis>Enables advertising SIP Outbound support (RFC5626) for outbound REGISTER requests.</synopsis> + </configOption> </configObject> </configFile> </configInfo> @@ -224,6 +232,10 @@ </manager> ***/ +/* forward declarations */ +static int set_outbound_initial_authentication_credentials(pjsip_regc *regc, + const struct ast_sip_auth_vector *auth_vector); + /*! \brief Some thread local storage used to determine if the running thread invoked the callback */ AST_THREADSTORAGE(register_callback_invoked); @@ -291,6 +303,8 @@ struct sip_outbound_registration { AST_STRING_FIELD(client_uri); /*! \brief Optional user for contact header */ AST_STRING_FIELD(contact_user); + /*! \bried Optional header parameters for contact */ + AST_STRING_FIELD(contact_header_params); /*! \brief Explicit transport to use for registration */ AST_STRING_FIELD(transport); /*! \brief Outbound proxy to use */ @@ -316,6 +330,8 @@ struct sip_outbound_registration { struct ast_sip_auth_vector outbound_auths; /*! \brief Whether Path support is enabled */ unsigned int support_path; + /*! \brief Whether Outbound support is enabled */ + unsigned int support_outbound; }; /*! \brief Outbound registration client state information (persists for lifetime of regc) */ @@ -347,6 +363,8 @@ struct sip_outbound_registration_client_state { unsigned int auth_rejection_permanent; /*! \brief Determines whether SIP Path support should be advertised */ unsigned int support_path; + /*! \brief Determines whether SIP Outbound support should be advertised */ + unsigned int support_outbound; /*! CSeq number of last sent auth request. */ unsigned int auth_cseq; /*! \brief Serializer for stuff and things */ @@ -520,6 +538,7 @@ static void cancel_registration(struct sip_outbound_registration_client_state *c } static pj_str_t PATH_NAME = { "path", 4 }; +static pj_str_t OUTBOUND_NAME = { "outbound", 8 }; /*! \brief Helper function which sends a message and cleans up, if needed, on failure */ static pj_status_t registration_client_send(struct sip_outbound_registration_client_state *client_state, @@ -545,6 +564,8 @@ static pj_status_t registration_client_send(struct sip_outbound_registration_cli */ ast_sip_set_tpselector_from_transport_name(client_state->transport_name, &selector); pjsip_regc_set_transport(client_state->client, &selector); + ast_sip_tpselector_unref(&selector); + status = pjsip_regc_send(client_state->client, tdata); /* If the attempt to send the message failed and the callback was not invoked we need to @@ -557,12 +578,58 @@ static pj_status_t registration_client_send(struct sip_outbound_registration_cli return status; } +/*! \brief Helper function to add string to Supported header */ +static int add_to_supported_header(pjsip_tx_data *tdata, pj_str_t *name) +{ + pjsip_supported_hdr *hdr; + + hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_SUPPORTED, NULL); + if (!hdr) { + /* insert a new Supported header */ + hdr = pjsip_supported_hdr_create(tdata->pool); + if (!hdr) { + pjsip_tx_data_dec_ref(tdata); + return 0; + } + + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr); + } + + /* add on to the existing Supported header */ + pj_strassign(&hdr->values[hdr->count++], name); + + return 1; +} + +/*! \brief Helper function to add configured supported headers */ +static int add_configured_supported_headers(struct sip_outbound_registration_client_state *client_state, pjsip_tx_data *tdata) +{ + if (client_state->support_path) { + if (!add_to_supported_header(tdata, &PATH_NAME)) { + return 0; + } + } + + if (client_state->support_outbound) { + if (!add_to_supported_header(tdata, &OUTBOUND_NAME)) { + return 0; + } + } + + return 1; +} + /*! \brief Callback function for registering */ static int handle_client_registration(void *data) { RAII_VAR(struct sip_outbound_registration_client_state *, client_state, data, ao2_cleanup); pjsip_tx_data *tdata; + if (set_outbound_initial_authentication_credentials(client_state->client, &client_state->outbound_auths)) { + ast_log(LOG_WARNING, "Failed to set initial authentication credentials\n"); + return -1; + } + if (client_state->status == SIP_REGISTRATION_STOPPED || pjsip_regc_register(client_state->client, PJ_FALSE, &tdata) != PJ_SUCCESS) { return 0; @@ -578,23 +645,9 @@ static int handle_client_registration(void *data) (int) info.client_uri.slen, info.client_uri.ptr); } - if (client_state->support_path) { - pjsip_supported_hdr *hdr; - - hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_SUPPORTED, NULL); - if (!hdr) { - /* insert a new Supported header */ - hdr = pjsip_supported_hdr_create(tdata->pool); - if (!hdr) { - pjsip_tx_data_dec_ref(tdata); - return -1; - } - - pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr); - } - - /* add on to the existing Supported header */ - pj_strassign(&hdr->values[hdr->count++], &PATH_NAME); + if (!add_configured_supported_headers(client_state, tdata)) { + ast_log(LOG_WARNING, "Failed to set supported headers\n"); + return -1; } registration_client_send(client_state, tdata); @@ -707,6 +760,7 @@ static int handle_client_state_destruction(void *data) update_client_state_status(client_state, SIP_REGISTRATION_STOPPING); client_state->destroy = 1; if (pjsip_regc_unregister(client_state->client, &tdata) == PJ_SUCCESS + && add_configured_supported_headers(client_state, tdata) && registration_client_send(client_state, tdata) == PJ_SUCCESS) { ao2_ref(client_state, -1); return 0; @@ -885,6 +939,75 @@ static void registration_transport_monitor_setup(pjsip_transport *transport, con ao2_ref(monitor, -1); } +static void save_response_fields_to_transport(struct registration_response *response) +{ + static const pj_str_t associated_uri_str = { "P-Associated-URI", 16 }; + static const pj_str_t service_route_str = { "Service-Route", 13 }; + pjsip_hdr *header = NULL; + pjsip_msg *msg = response->rdata->msg_info.msg; + struct ast_sip_service_route_vector *service_routes = NULL; + + /* If no transport is specified then we can't update any */ + if (ast_strlen_zero(response->client_state->transport_name)) { + return; + } + + ast_sip_transport_state_set_transport(response->client_state->transport_name, response->rdata->tp_info.transport); + + while ((header = pjsip_msg_find_hdr_by_name(msg, &service_route_str, header ? header->next : NULL))) { + char *service_route; + size_t size; + + /* The below code takes the approach that if we can't store all service routes then we + * store none at all. This gives a predictable failure condition instead of storing a + * partial list and having partial route headers. + */ + size = pj_strlen(&((pjsip_generic_string_hdr*)header)->hvalue) + 1; + service_route = ast_malloc(size); + if (!service_route) { + if (service_routes) { + ast_sip_service_route_vector_destroy(service_routes); + service_routes = NULL; + } + break; + } + + ast_copy_pj_str(service_route, &((pjsip_generic_string_hdr*)header)->hvalue, size); + + if (!service_routes) { + service_routes = ast_sip_service_route_vector_alloc(); + if (!service_routes) { + ast_free(service_route); + break; + } + } + + if (AST_VECTOR_APPEND(service_routes, service_route)) { + ast_free(service_route); + ast_sip_service_route_vector_destroy(service_routes); + service_routes = NULL; + break; + } + } + + /* If any service routes were handled then store them on the transport */ + if (service_routes) { + ast_sip_transport_state_set_service_routes(response->client_state->transport_name, service_routes); + } + + /* If an associated URI is present in the response we need to use it on any outgoing + * traffic on the transport. + */ + header = pjsip_msg_find_hdr_by_name(msg, &associated_uri_str, NULL); + if (header) { + char value[pj_strlen(&((pjsip_generic_string_hdr*)header)->hvalue) + 1]; + + ast_copy_pj_str(value, &((pjsip_generic_string_hdr*)header)->hvalue, sizeof(value)); + ast_sip_transport_state_set_preferred_identity(response->client_state->transport_name, value); + } +} + + /*! \brief Callback function for handling a response to a registration attempt */ static int handle_registration_response(void *data) { @@ -964,6 +1087,8 @@ static int handle_registration_response(void *data) registration_transport_shutdown_cb, response->client_state->registration_name, monitor_matcher); } + + save_response_fields_to_transport(response); } else if (response->client_state->destroy) { /* We need to deal with the pending destruction instead. */ } else if (response->retry_after) { @@ -1190,7 +1315,7 @@ static void *sip_outbound_registration_alloc(const char *name) /*! \brief Helper function which populates a pj_str_t with a contact header */ static int sip_dialog_create_contact(pj_pool_t *pool, pj_str_t *contact, const char *user, - const pj_str_t *target, pjsip_tpselector *selector, const char *line) + const pj_str_t *target, pjsip_tpselector *selector, const char *line, const char *header_params) { pj_str_t tmp, local_addr; pjsip_uri *uri; @@ -1234,7 +1359,7 @@ static int sip_dialog_create_contact(pj_pool_t *pool, pj_str_t *contact, const c contact->ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE); contact->slen = pj_ansi_snprintf(contact->ptr, PJSIP_MAX_URL_SIZE, - "<%s:%s@%s%.*s%s:%d%s%s%s%s>", + "<%s:%s@%s%.*s%s:%d%s%s%s%s>%s%s", ((pjsip_transport_get_flag_from_type(type) & PJSIP_TRANSPORT_SECURE) && PJSIP_URI_SCHEME_IS_SIPS(uri)) ? "sips" : "sip", user, (type & PJSIP_TRANSPORT_IPV6) ? "[" : "", @@ -1245,7 +1370,9 @@ static int sip_dialog_create_contact(pj_pool_t *pool, pj_str_t *contact, const c (type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? ";transport=" : "", (type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? pjsip_transport_get_type_name(type) : "", !ast_strlen_zero(line) ? ";line=" : "", - S_OR(line, "")); + S_OR(line, ""), + !ast_strlen_zero(header_params) ? ";" : "", + S_OR(header_params, "")); return 0; } @@ -1283,6 +1410,124 @@ static int can_reuse_registration(struct sip_outbound_registration *existing, return rc; } +/* \brief Get google oauth2 access token using refresh token */ +static const char *fetch_google_access_token(const struct ast_sip_auth *auth) +{ + char *cmd = NULL; + const char *token; + const char *url = "https://www.googleapis.com/oauth2/v3/token"; + char buf[4096]; + int res; + struct ast_json_error error; + struct ast_json *json; + + /* set timeout to be shorter than default 180s (also checks func_curl is available) */ + if (ast_func_write(NULL, "CURLOPT(conntimeout)", "10")) { + ast_log(LOG_ERROR, "CURL is unavailable. This is required for Google OAuth 2.0 authentication. Please ensure it is loaded.\n"); + return NULL; + } + + res = ast_asprintf(&cmd, + "CURL(%s,client_id=%s&client_secret=%s&refresh_token=%s&grant_type=refresh_token)", + url, auth->oauth_clientid, auth->oauth_secret, auth->refresh_token); + if (res < 0) { + return NULL; + } + + ast_debug(2, "Performing Google OAuth 2.0 authentication using command: %s\n", cmd); + + buf[0] = '\0'; + res = ast_func_read(NULL, cmd, buf, sizeof(buf)); + ast_free(cmd); + if (res) { + ast_log(LOG_ERROR, "Could not retrieve Google OAuth 2.0 authentication\n"); + return NULL; + } + + ast_debug(2, "Google OAuth 2.0 authentication returned: %s\n", buf); + + json = ast_json_load_string(buf, &error); + if (!json) { + ast_log(LOG_ERROR, "Could not parse Google OAuth 2.0 authentication: %d(%d) %s: '%s'\n", + error.line, error.column, error.text, buf); + return NULL; + } + + token = ast_json_string_get(ast_json_object_get(json, "access_token")); + if (!token) { + ast_log(LOG_ERROR, "Could not find Google OAuth 2.0 access_token in: '%s'\n", + buf); + } + token = ast_strdup(token); + ast_json_unref(json); + return token; +} + +/*! + * \internal + * \brief Set pjsip registration context with any authentication credentials that need to be + * sent in the initial registration request + * + * \param regc The pjsip registration context + * \param auth_vector The vector of configured authentication credentials + */ +static int set_outbound_initial_authentication_credentials(pjsip_regc *regc, + const struct ast_sip_auth_vector *auth_vector) +{ + size_t auth_size = AST_VECTOR_SIZE(auth_vector); + struct ast_sip_auth *auths[auth_size]; + const char *access_token; + pjsip_cred_info auth_creds[1]; + pjsip_auth_clt_pref prefs; + int res = 0; + int idx; + + memset(auths, 0, sizeof(auths)); + if (ast_sip_retrieve_auths(auth_vector, auths)) { + res = -1; + goto cleanup; + } + + for (idx = 0; idx < auth_size; ++idx) { + switch (auths[idx]->type) { + case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH: + pj_cstr(&auth_creds[0].username, auths[idx]->auth_user); + pj_cstr(&auth_creds[0].scheme, "Bearer"); + pj_cstr(&auth_creds[0].realm, auths[idx]->realm); + ast_debug(2, "Obtaining Google OAuth access token\n"); + access_token = fetch_google_access_token(auths[idx]); + if (!access_token) { + ast_log(LOG_WARNING, "Obtaining Google OAuth access token failed\n"); + access_token = auths[idx]->auth_pass; + res = -1; + } + ast_debug(2, "Setting data to '%s'\n", access_token); + + pj_cstr(&auth_creds[0].data, access_token); + auth_creds[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; + + pjsip_regc_set_credentials(regc, 1, auth_creds); + + /* for oauth, send auth without waiting for unauthorized response */ + prefs.initial_auth = PJ_TRUE; + pj_cstr(&prefs.algorithm, "oauth"); + pjsip_regc_set_prefs(regc, &prefs); + + if (access_token != auths[idx]->auth_pass) { + ast_free((char *) access_token); + } + break; + default: + /* other cases handled after receiving auth rejection */ + break; + } + } + +cleanup: + ast_sip_cleanup_auths(auths, auth_size); + return res; +} + /*! \brief Helper function that allocates a pjsip registration client and configures it */ static int sip_outbound_registration_regc_alloc(void *data) { @@ -1356,6 +1601,7 @@ static int sip_outbound_registration_regc_alloc(void *data) route = pjsip_parse_hdr(pjsip_regc_get_pool(state->client_state->client), &ROUTE_HNAME, tmp.ptr, tmp.slen, NULL); if (!route) { + ast_sip_tpselector_unref(&selector); return -1; } pj_list_insert_nodes_before(&route_set, route); @@ -1369,13 +1615,15 @@ static int sip_outbound_registration_regc_alloc(void *data) pj_cstr(&server_uri, registration->server_uri); - if (sip_dialog_create_contact(pjsip_regc_get_pool(state->client_state->client), &contact_uri, S_OR(registration->contact_user, "s"), &server_uri, &selector, - state->client_state->line)) { + state->client_state->line, registration->contact_header_params)) { + ast_sip_tpselector_unref(&selector); return -1; } + ast_sip_tpselector_unref(&selector); + pj_cstr(&client_uri, registration->client_uri); if (pjsip_regc_init(state->client_state->client, &server_uri, &client_uri, &client_uri, 1, &contact_uri, registration->expiration) != PJ_SUCCESS) { @@ -1409,6 +1657,7 @@ static int sip_outbound_registration_perform(void *data) state->client_state->max_retries = registration->max_retries; state->client_state->retries = 0; state->client_state->support_path = registration->support_path; + state->client_state->support_outbound = registration->support_outbound; state->client_state->auth_rejection_permanent = registration->auth_rejection_permanent; pjsip_regc_update_expires(state->client_state->client, registration->expiration); @@ -1550,7 +1799,8 @@ static int unregister_task(void *obj) cancel_registration(state->client_state); - if (pjsip_regc_unregister(client, &tdata) == PJ_SUCCESS) { + if (pjsip_regc_unregister(client, &tdata) == PJ_SUCCESS + && add_configured_supported_headers(state->client_state, tdata)) { registration_client_send(state->client_state, tdata); } @@ -2219,6 +2469,7 @@ static int load_module(void) ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "server_uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, server_uri)); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "client_uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, client_uri)); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "contact_user", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, contact_user)); + ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "contact_header_params", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, contact_header_params)); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "transport", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, transport)); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, outbound_proxy)); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "expiration", "3600", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, expiration)); @@ -2229,6 +2480,7 @@ static int load_module(void) ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "auth_rejection_permanent", "yes", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, auth_rejection_permanent)); ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, outbound_auths_to_var_list, 0, 0); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "support_path", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, support_path)); + ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "support_outbound", "no", OPT_YESNO_T, 1, FLDSET(struct sip_outbound_registration, support_outbound)); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "line", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, line)); ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, endpoint)); diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index e681dcb1a709a6fee52e27cba249ffec74e5cd0d..1dd8ce9b044399d589f0fe51fc19df3a8d22749a 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -3344,6 +3344,9 @@ static void handle_outgoing_request(struct ast_sip_session *session, pjsip_tx_da struct pjsip_request_line req = tdata->msg->line.req; ast_debug(3, "Method is %.*s\n", (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name)); + + ast_sip_message_apply_transport(session->endpoint->transport, tdata); + AST_LIST_TRAVERSE(&session->supplements, supplement, next) { if (supplement->outgoing_request && does_method_match(&req.method.name, supplement->method)) { supplement->outgoing_request(session, tdata); @@ -3366,6 +3369,8 @@ static void handle_outgoing_response(struct ast_sip_session *session, pjsip_tx_d pj_strbuf(&cseq->method.name), status.code, (int) pj_strlen(&status.reason), pj_strbuf(&status.reason)); + ast_sip_message_apply_transport(session->endpoint->transport, tdata); + AST_LIST_TRAVERSE(&session->supplements, supplement, next) { if (supplement->outgoing_response && does_method_match(&cseq->method.name, supplement->method)) { supplement->outgoing_response(session, tdata); diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4 index 94be9b83794e7468692aeef2aa811424b7d4a949..9e89098762b3930675206555850c5a84e622e184 100644 --- a/third-party/pjproject/configure.m4 +++ b/third-party/pjproject/configure.m4 @@ -106,6 +106,8 @@ AC_DEFUN([_PJPROJECT_CONFIGURE], AC_DEFINE([HAVE_PJSIP_TSX_LAYER_FIND_TSX2], 1, [Define if your system has pjsip_tsx_layer_find_tsx2 declared.]) AC_DEFINE([HAVE_PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS], 1, [Define if your system has HAVE_PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS declared.]) AC_DEFINE([HAVE_PJSIP_ENDPOINT_COMPACT_FORM], 1, [Define if your system has HAVE_PJSIP_ENDPOINT_COMPACT_FORM declared.]) + AC_DEFINE([HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE], 1, [Define if your system has HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE declared]) + AC_DEFINE([HAVE_PJSIP_OAUTH_AUTHENTICATION], 1, [Define if your system has HAVE_PJSIP_OAUTH_AUTHENTICATION declared]) AC_SUBST([PJPROJECT_BUNDLED]) AC_SUBST([PJPROJECT_DIR]) diff --git a/third-party/pjproject/patches/0020-oauth.patch b/third-party/pjproject/patches/0020-oauth.patch new file mode 100644 index 0000000000000000000000000000000000000000..927cb5cc08026d8e1193a1d419e5cc2f3f7e8254 --- /dev/null +++ b/third-party/pjproject/patches/0020-oauth.patch @@ -0,0 +1,129 @@ +diff -x '*.o' -x '*.a' -ru a/pjsip/include/pjsip/sip_auth_msg.h b/pjsip/include/pjsip/sip_auth_msg.h +--- a/pjsip/include/pjsip/sip_auth_msg.h 2011-05-05 02:14:19.000000000 -0400 ++++ b/pjsip/include/pjsip/sip_auth_msg.h 2018-09-14 16:42:03.986813665 -0400 +@@ -89,6 +89,23 @@ + typedef struct pjsip_pgp_credential pjsip_pgp_credential; + + /** ++ * This structure describe credential used in Authorization and ++ * Proxy-Authorization header for OAuth authentication scheme. ++ */ ++struct pjsip_oauth_credential ++{ ++ pj_str_t realm; /**< Realm of the credential */ ++ pjsip_param other_param; /**< Other parameters. */ ++ pj_str_t username; /**< Username parameter. */ ++ pj_str_t token; /**< Token parameter. */ ++}; ++ ++/** ++ * @see pjsip_oauth_credential ++ */ ++typedef struct pjsip_oauth_credential pjsip_oauth_credential; ++ ++/** + * This structure describes SIP Authorization header (and also SIP + * Proxy-Authorization header). + */ +@@ -106,6 +123,7 @@ + pjsip_common_credential common; /**< Common fields. */ + pjsip_digest_credential digest; /**< Digest credentials. */ + pjsip_pgp_credential pgp; /**< PGP credentials. */ ++ pjsip_oauth_credential oauth; /**< OAuth credentials. */ + } credential; + }; + +diff -x '*.o' -x '*.a' -ru a/pjsip/include/pjsip/sip_auth_parser.h b/pjsip/include/pjsip/sip_auth_parser.h +--- a/pjsip/include/pjsip/sip_auth_parser.h 2011-05-05 02:14:19.000000000 -0400 ++++ b/pjsip/include/pjsip/sip_auth_parser.h 2018-09-14 16:42:11.982807508 -0400 +@@ -64,6 +64,7 @@ + pjsip_FALSE_STR, /**< "false" string const. */ + pjsip_DIGEST_STR, /**< "digest" string const. */ + pjsip_PGP_STR, /**< "pgp" string const. */ ++ pjsip_BEARER_STR, /**< "bearer" string const. */ + pjsip_MD5_STR, /**< "md5" string const. */ + pjsip_AUTH_STR; /**< "auth" string const. */ + +diff -x '*.o' -x '*.a' -ru a/pjsip/src/pjsip/sip_auth_client.c b/pjsip/src/pjsip/sip_auth_client.c +--- a/pjsip/src/pjsip/sip_auth_client.c 2017-03-31 02:02:48.000000000 -0400 ++++ b/pjsip/src/pjsip/sip_auth_client.c 2018-09-14 16:42:28.138795061 -0400 +@@ -959,13 +959,22 @@ + + hs = pjsip_authorization_hdr_create(tdata->pool); + pj_strdup(tdata->pool, &hs->scheme, &c->scheme); +- pj_strdup(tdata->pool, &hs->credential.digest.username, +- &c->username); +- pj_strdup(tdata->pool, &hs->credential.digest.realm, +- &c->realm); +- pj_strdup(tdata->pool, &hs->credential.digest.uri, &uri); +- pj_strdup(tdata->pool, &hs->credential.digest.algorithm, +- &sess->pref.algorithm); ++ if (pj_stricmp(&c->scheme, &pjsip_BEARER_STR)==0) { ++ pj_strdup(tdata->pool, &hs->credential.oauth.username, ++ &c->username); ++ pj_strdup(tdata->pool, &hs->credential.oauth.realm, ++ &c->realm); ++ pj_strdup(tdata->pool, &hs->credential.oauth.token, ++ &c->data); ++ } else { //if (pj_stricmp(&c->scheme, &pjsip_DIGEST_STR)==0) ++ pj_strdup(tdata->pool, &hs->credential.digest.username, ++ &c->username); ++ pj_strdup(tdata->pool, &hs->credential.digest.realm, ++ &c->realm); ++ pj_strdup(tdata->pool,&hs->credential.digest.uri, &uri); ++ pj_strdup(tdata->pool, &hs->credential.digest.algorithm, ++ &sess->pref.algorithm); ++ } + + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hs); + } +diff -x '*.o' -x '*.a' -ru a/pjsip/src/pjsip/sip_auth_msg.c b/pjsip/src/pjsip/sip_auth_msg.c +--- a/pjsip/src/pjsip/sip_auth_msg.c 2016-01-27 00:42:20.000000000 -0500 ++++ b/pjsip/src/pjsip/sip_auth_msg.c 2018-09-14 16:42:15.882804502 -0400 +@@ -103,6 +103,23 @@ + return -1; + } + ++static int print_oauth_credential(pjsip_oauth_credential *cred, char *buf, ++ pj_size_t size) ++{ ++ pj_ssize_t printed; ++ char *startbuf = buf; ++ char *endbuf = buf + size; ++ ++ copy_advance_pair_quote_cond_always(buf, "token=", 6, cred->token, ++ '"', '"'); ++ copy_advance_pair_quote_cond_always(buf, ", username=", 11, cred->username, ++ '"', '"'); ++ copy_advance_pair_quote_cond_always(buf, ", realm=", 8, cred->realm, ++ '"', '"'); ++ ++ return (int) (buf-startbuf); ++} ++ + static int pjsip_authorization_hdr_print( pjsip_authorization_hdr *hdr, + char *buf, pj_size_t size) + { +@@ -125,6 +142,11 @@ + { + printed = print_pgp_credential(&hdr->credential.pgp, buf, endbuf - buf); + } ++ else if (pj_stricmp(&hdr->scheme, &pjsip_BEARER_STR) == 0) ++ { ++ printed = print_oauth_credential(&hdr->credential.oauth, buf, ++ endbuf - buf); ++ } + else { + pj_assert(0); + return -1; +diff -x '*.o' -x '*.a' -ru a/pjsip/src/pjsip/sip_auth_parser.c b/pjsip/src/pjsip/sip_auth_parser.c +--- a/pjsip/src/pjsip/sip_auth_parser.c 2014-06-09 22:56:56.000000000 -0400 ++++ b/pjsip/src/pjsip/sip_auth_parser.c 2018-09-14 16:42:21.418800238 -0400 +@@ -59,6 +59,7 @@ + pjsip_QUOTED_DIGEST_STR = { "\"Digest\"", 8}, + pjsip_PGP_STR = { "PGP", 3 }, + pjsip_QUOTED_PGP_STR = { "\"PGP\"", 5 }, ++ pjsip_BEARER_STR = { "Bearer", 6 }, + pjsip_MD5_STR = { "md5", 3 }, + pjsip_QUOTED_MD5_STR = { "\"md5\"", 5}, + pjsip_AUTH_STR = { "auth", 4}, diff --git a/third-party/pjproject/patches/0030-allow-disabling-of-connection-reuse.patch b/third-party/pjproject/patches/0030-allow-disabling-of-connection-reuse.patch new file mode 100644 index 0000000000000000000000000000000000000000..47270855f7955bbc77c68d478d3fe94e3a677ac7 --- /dev/null +++ b/third-party/pjproject/patches/0030-allow-disabling-of-connection-reuse.patch @@ -0,0 +1,102 @@ +diff -x '*.o' -x '*.a' -ru a/pjsip/include/pjsip/sip_transport.h b/pjsip/include/pjsip/sip_transport.h +--- a/pjsip/include/pjsip/sip_transport.h 2017-02-19 20:16:58.000000000 -0500 ++++ b/pjsip/include/pjsip/sip_transport.h 2018-09-14 16:47:25.145266710 -0400 +@@ -221,12 +221,26 @@ + * application specificly request that a particular transport/listener + * should be used to send request. This structure is used when calling + * pjsip_tsx_set_transport() and pjsip_dlg_set_transport(). ++ * ++ * If application disables connection reuse and wants to force creating ++ * a new transport, it needs to consider the following couple of things: ++ * - If it still wants to reuse an existing transport (if any), it ++ * needs to keep a reference to that transport and specifically set ++ * the transport to be used for sending requests. ++ * - Delete those existing transports manually when no longer needed. + */ + typedef struct pjsip_tpselector + { + /** The type of data in the union */ + pjsip_tpselector_type type; + ++ /** ++ * Whether to disable reuse of an existing connection. ++ * This setting will be ignored if (type == PJSIP_TPSELECTOR_TRANSPORT) ++ * and transport in the union below is set. ++ */ ++ pj_bool_t disable_connection_reuse; ++ + /** Union representing the transport/listener criteria to be used. */ + union { + pjsip_transport *transport; +diff -x '*.o' -x '*.a' -ru a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c +--- a/pjsip/src/pjsip/sip_transport.c 2017-11-07 21:58:18.000000000 -0500 ++++ b/pjsip/src/pjsip/sip_transport.c 2018-09-14 16:47:25.145266710 -0400 +@@ -2118,7 +2118,7 @@ + */ + pjsip_transport_key key; + int key_len; +- pjsip_transport *transport; ++ pjsip_transport *transport = NULL; + + /* If listener is specified, verify that the listener type matches + * the destination type. +@@ -2131,17 +2131,21 @@ + } + } + +- pj_bzero(&key, sizeof(key)); +- key_len = sizeof(key.type) + addr_len; ++ if (!sel || sel->disable_connection_reuse == PJ_FALSE) { ++ pj_bzero(&key, sizeof(key)); ++ key_len = sizeof(key.type) + addr_len; ++ ++ /* First try to get exact destination. */ ++ key.type = type; ++ pj_memcpy(&key.rem_addr, remote, addr_len); + +- /* First try to get exact destination. */ +- key.type = type; +- pj_memcpy(&key.rem_addr, remote, addr_len); +- +- transport = (pjsip_transport*) +- pj_hash_get(mgr->table, &key, key_len, NULL); ++ transport = (pjsip_transport*) ++ pj_hash_get(mgr->table, &key, key_len, NULL); ++ } + +- if (transport == NULL) { ++ if (transport == NULL && ++ (!sel || sel->disable_connection_reuse == PJ_FALSE)) ++ { + unsigned flag = pjsip_transport_get_flag_from_type(type); + const pj_sockaddr *remote_addr = (const pj_sockaddr*)remote; + +@@ -2179,9 +2183,7 @@ + transport = NULL; + /* This will cause a new transport to be created which will be a + * 'duplicate' of the existing transport (same type & remote addr, +- * but different factory). Any future hash lookup will return +- * the new one, and eventually the old one will still be freed +- * (by application or #1774). ++ * but different factory). + */ + } + +@@ -2199,9 +2201,14 @@ + + + /* +- * Transport not found! +- * So we need to create one, find factory that can create +- * such transport. ++ * Either transport not found, or we don't want to use the existing ++ * transport (such as in the case of different factory or ++ * if connection reuse is disabled). So we need to create one, ++ * find factory that can create such transport. ++ * ++ * If there's an existing transport, its place in the hash table ++ * will be replaced by this new one. And eventually the existing ++ * transport will still be freed (by application or #1774). + */ + if (sel && sel->type == PJSIP_TPSELECTOR_LISTENER && sel->u.listener) + {