merge darcs tree
git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@5715 d0543943-73ff-0310-b7d9-9358b9ac24b2
This commit is contained in:
parent
038a60d5d4
commit
9d495ea568
|
@ -160,7 +160,7 @@ int ca_challenge(auth_client_t *ca,
|
|||
if (!ca || !ch)
|
||||
return -1;
|
||||
|
||||
if (strcmp(ca->ca_scheme, scheme))
|
||||
if (strcasecmp(ca->ca_scheme, scheme))
|
||||
return 0;
|
||||
if (strcmp(ca->ca_realm, realm))
|
||||
return 0;
|
||||
|
@ -433,7 +433,7 @@ int auc_copy_credentials(auth_client_t **dst,
|
|||
continue;
|
||||
if (AUTH_CLIENT_IS_EXTENDED(ca) && ca->ca_clear)
|
||||
continue;
|
||||
if (!ca->ca_scheme[0] || strcmp(ca->ca_scheme, d->ca_scheme))
|
||||
if (!ca->ca_scheme[0] || strcasecmp(ca->ca_scheme, d->ca_scheme))
|
||||
continue;
|
||||
if (!ca->ca_realm[0] || strcmp(ca->ca_realm, d->ca_realm))
|
||||
continue;
|
||||
|
@ -1012,6 +1012,8 @@ auth_client_t *ca_create(su_home_t *home,
|
|||
break;
|
||||
}
|
||||
|
||||
/* XXX - should report error if the auth scheme is not known? */
|
||||
|
||||
aucsize = auc ? (size_t)auc->auc_size : (sizeof *ca);
|
||||
size = aucsize + realmlen;
|
||||
if (!auc)
|
||||
|
|
|
@ -484,7 +484,7 @@ int test_digest_client()
|
|||
"From:surf3.ims3.so.noklab.net <sip:surf3@ims3.so.noklab.net>;tag=I8hFdg0H3OK\r\n"
|
||||
"To:<sip:surf3@ims3.so.noklab.net>\r\n"
|
||||
"Via:SIP/2.0/UDP 10.21.36.70:23800;branch=z9hG4bKJjKGu9vIHqf;received=10.21.36.70;rport\r\n"
|
||||
"WWW-Authenticate:Digest algorithm=MD5,nonce=\"h7wIpP+atU+/+Zau5UwLMA==\",realm=\"ims3.so.noklab.net\"\r\n"
|
||||
"WWW-Authenticate:DIGEST algorithm=MD5,nonce=\"h7wIpP+atU+/+Zau5UwLMA==\",realm=\"ims3.so.noklab.net\"\r\n"
|
||||
"Content-Length:0\r\n"
|
||||
"Security-Server:digest\r\n"
|
||||
"r\n";
|
||||
|
@ -526,11 +526,13 @@ int test_digest_client()
|
|||
TEST_1(m1 = read_message(MSG_DO_EXTRACT_COPY, challenge));
|
||||
TEST_1(sip = sip_object(m1));
|
||||
|
||||
TEST_1(aucs == NULL);
|
||||
TEST(auc_challenge(&aucs, home, sip->sip_www_authenticate,
|
||||
sip_authorization_class), 1);
|
||||
TEST_1(aucs != NULL);
|
||||
msg_destroy(m1);
|
||||
|
||||
TEST(auc_all_credentials(&aucs, "Digest", "\"ims3.so.noklab.net\"",
|
||||
TEST(auc_all_credentials(&aucs, "DIGEST", "\"ims3.so.noklab.net\"",
|
||||
"surf3.private@ims3.so.noklab.net", "1234"), 1);
|
||||
|
||||
TEST_1(m2 = read_message(MSG_DO_EXTRACT_COPY, request));
|
||||
|
|
|
@ -112,10 +112,13 @@ issize_t msg_numeric_d(su_home_t *home,
|
|||
char *s,
|
||||
isize_t slen)
|
||||
{
|
||||
msg_numeric_t *x = (msg_numeric_t *)h;
|
||||
uint32_t value = 0;
|
||||
issize_t retval = msg_uint32_d(&s, &value);
|
||||
|
||||
h->sh_numeric->x_value = value;
|
||||
assert(x->x_common->h_class->hc_size >= sizeof *x);
|
||||
|
||||
x->x_value = value;
|
||||
|
||||
if (*s)
|
||||
return -1;
|
||||
|
@ -125,12 +128,14 @@ issize_t msg_numeric_d(su_home_t *home,
|
|||
|
||||
issize_t msg_numeric_e(char b[], isize_t bsiz, msg_header_t const *h, int flags)
|
||||
{
|
||||
uint32_t value = h->sh_numeric->x_value;
|
||||
msg_numeric_t *x = (msg_numeric_t *)h;
|
||||
|
||||
if (h->sh_numeric->x_value > 0xffffffff)
|
||||
assert(x->x_common->h_class->hc_size >= sizeof *x);
|
||||
|
||||
if (x->x_value > 0xffffffffU)
|
||||
return -1;
|
||||
|
||||
return snprintf(b, bsiz, "%lu", (unsigned long)value);
|
||||
return snprintf(b, bsiz, "%lu", x->x_value);
|
||||
}
|
||||
|
||||
/* ====================================================================== */
|
||||
|
|
|
@ -134,7 +134,8 @@ struct msg_pub_s {
|
|||
|
||||
/** Numeric header.
|
||||
*
|
||||
* A numeric header has a 32-bit integer as its value.
|
||||
* A numeric header has value range of a 32-bit, 0..4294967295. The @a
|
||||
* x_value field is unsigned long, however.
|
||||
*/
|
||||
struct msg_numeric_s {
|
||||
msg_common_t x_common[1]; /**< Common fragment info */
|
||||
|
|
|
@ -193,6 +193,14 @@ char *msg_status_dup_one(msg_header_t *dst, msg_header_t const *src,
|
|||
return b;
|
||||
}
|
||||
|
||||
msg_hclass_t test_numeric_class[] =
|
||||
MSG_HEADER_CLASS(msg_, numeric, "Numeric", "", x_common,
|
||||
single, msg_generic, msg_generic);
|
||||
|
||||
msg_hclass_t test_auth_class[] =
|
||||
MSG_HEADER_CLASS(msg_, auth, "Auth", "", au_params,
|
||||
append, msg_auth, msg_generic);
|
||||
|
||||
/** Extract the message body, including separator line.
|
||||
*
|
||||
* @param[in,out] msg message object
|
||||
|
@ -411,3 +419,4 @@ int tst_add_tl(msg_t *msg, msg_test_t *tst,
|
|||
|
||||
return t ? -1 : 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,9 @@ typedef struct msg_test_s {
|
|||
msg_content_encoding_t *msg_content_encoding;
|
||||
/**< Content-Encoding */
|
||||
msg_content_length_t *msg_content_length; /**< Content-Length */
|
||||
|
||||
msg_auth_t *msg_auth; /**< Auth (testing) */
|
||||
msg_numeric_t *msg_numeric; /**< Numeric (testing) */
|
||||
/* === Headers end here */
|
||||
|
||||
msg_unknown_t *msg_unknown;
|
||||
|
@ -160,6 +163,17 @@ msg_test_t *msg_test_public(msg_t *msg)
|
|||
return (msg_test_t *)msg_public(msg, MSG_TEST_PROTOCOL_TAG);
|
||||
}
|
||||
|
||||
#define msg_auth_class test_auth_class
|
||||
|
||||
#define msg_numeric_class test_numeric_class
|
||||
|
||||
enum {
|
||||
msg_auth_hash = 22894,
|
||||
msg_numeric_hash = 24435
|
||||
};
|
||||
|
||||
SOFIAPUBVAR msg_hclass_t test_auth_class[1], test_numeric_class[1];
|
||||
|
||||
SOFIA_END_DECLS
|
||||
|
||||
#endif /* !defined(TEST_CLASS_H) */
|
||||
|
|
|
@ -127,7 +127,7 @@ static int msg_time_test(void)
|
|||
char *s;
|
||||
msg_numeric_t x[1];
|
||||
|
||||
memset(x, 0, sizeof (x));
|
||||
memset(x, 0, sizeof (x)); x->x_common->h_class = test_numeric_class;
|
||||
|
||||
TEST_1(msg_numeric_d(NULL, (msg_header_t *)x, s = error1, strlen(error1)) < 0);
|
||||
}
|
||||
|
|
|
@ -287,6 +287,8 @@ int nua_stack_init_instance(nua_handle_t *nh, tagi_t const *tags)
|
|||
* NUTAG_ENABLEINVITE() \n
|
||||
* NUTAG_ENABLEMESSAGE() \n
|
||||
* NUTAG_ENABLEMESSENGER() \n
|
||||
* NUTAG_INITIAL_ROUTE() \n
|
||||
* NUTAG_INITIAL_ROUTE_STR() \n
|
||||
* NUTAG_INSTANCE() \n
|
||||
* NUTAG_INVITE_TIMER() \n
|
||||
* NUTAG_KEEPALIVE() \n
|
||||
|
@ -404,6 +406,8 @@ int nua_stack_init_instance(nua_handle_t *nh, tagi_t const *tags)
|
|||
* NUTAG_ENABLEINVITE() \n
|
||||
* NUTAG_ENABLEMESSAGE() \n
|
||||
* NUTAG_ENABLEMESSENGER() \n
|
||||
* NUTAG_INITIAL_ROUTE() \n
|
||||
* NUTAG_INITIAL_ROUTE_STR() \n
|
||||
* NUTAG_INSTANCE() \n
|
||||
* NUTAG_INVITE_TIMER() \n
|
||||
* NUTAG_KEEPALIVE() \n
|
||||
|
@ -635,10 +639,10 @@ static int nhp_set_tags(su_home_t *home,
|
|||
}
|
||||
|
||||
/* Set copy of header to handle pref structure */
|
||||
#define NHP_SET_HEADER(nhp, name, v) \
|
||||
#define NHP_SET_HEADER(nhp, name, hdr, v) \
|
||||
if ((v) != 0) { \
|
||||
sip_##name##_t const *_value = (sip_##name##_t const *)(v); \
|
||||
sip_##name##_t *_new = NULL; \
|
||||
sip_##hdr##_t const *_value = (sip_##hdr##_t const *)(v); \
|
||||
sip_##hdr##_t *_new = NULL; \
|
||||
if (_value != SIP_NONE) \
|
||||
_new = sip_##name##_dup(home, _value); \
|
||||
if (NHP_ISSET(nhp, name)) \
|
||||
|
@ -649,10 +653,10 @@ static int nhp_set_tags(su_home_t *home,
|
|||
}
|
||||
|
||||
/* Set header made of string to handle pref structure */
|
||||
#define NHP_SET_HEADER_STR(nhp, name, v) \
|
||||
#define NHP_SET_HEADER_STR(nhp, name, hdr, v) \
|
||||
if ((v) != 0) { \
|
||||
char const *_value = (char const *)(v); \
|
||||
sip_##name##_t *_new = NULL; \
|
||||
sip_##hdr##_t *_new = NULL; \
|
||||
if (_value != SIP_NONE) \
|
||||
_new = sip_##name##_make(home, _value); \
|
||||
if (NHP_ISSET(nhp, name)) \
|
||||
|
@ -662,6 +666,26 @@ static int nhp_set_tags(su_home_t *home,
|
|||
return -1; \
|
||||
}
|
||||
|
||||
/* Append copy of header to handle pref structure */
|
||||
#define NHP_APPEND_HEADER(nhp, name, hdr, is_str, next, v) \
|
||||
{ \
|
||||
sip_##hdr##_t const *_value = (sip_##hdr##_t const *)(v); \
|
||||
char const *_str = (char const *)(v); \
|
||||
sip_##hdr##_t *_new = NULL; \
|
||||
sip_##hdr##_t **_end = &nhp->nhp_##name; \
|
||||
if (_value != SIP_NONE && _value != NULL) { \
|
||||
_new = (is_str) \
|
||||
? sip_##hdr##_make(home, _str) \
|
||||
: sip_##hdr##_dup(home, _value); \
|
||||
if (_new == NULL) return -1; \
|
||||
} \
|
||||
if (NHP_ISSET(nhp, name)) \
|
||||
while(*_end) \
|
||||
_end = next(*_end); \
|
||||
nhp->nhp_set.nhb_##name = 1; \
|
||||
*_end = _new; \
|
||||
}
|
||||
|
||||
/* Set copy of string from header to handle pref structure */
|
||||
#define NHP_SET_STR_BY_HEADER(nhp, name, v) \
|
||||
if ((v) != 0) { \
|
||||
|
@ -894,6 +918,15 @@ static int nhp_set_tags(su_home_t *home,
|
|||
NHP_SET(nhp, appl_method, (sip_allow_t *)appl_method);
|
||||
}
|
||||
}
|
||||
else if (tag == nutag_initial_route ||
|
||||
tag == nutag_initial_route_str) {
|
||||
#define next_route(r) (&(r)->r_next)
|
||||
NHP_APPEND_HEADER(nhp, initial_route, route,
|
||||
(tag == nutag_initial_route_str),
|
||||
next_route,
|
||||
t->t_value);
|
||||
sip_route_fix(nhp->nhp_initial_route);
|
||||
}
|
||||
/* SIPTAG_USER_AGENT(user_agent) */
|
||||
else if (tag == siptag_user_agent) {
|
||||
NHP_SET_STR_BY_HEADER(nhp, user_agent, value);
|
||||
|
@ -1079,6 +1112,14 @@ nua_handle_preferences_t *nhp_move_params(su_home_t *home,
|
|||
NHP_ZAP_OVERRIDEN(tbf, dst, m_features);
|
||||
NHP_ZAP_OVERRIDEN(tbf, dst, outbound);
|
||||
|
||||
#define NHP_ZAP_OVERRIDEN_HDR(tbf, nhp, pref) \
|
||||
(((tbf)->nhp_set.nhb_##pref \
|
||||
&& (tbf)->nhp_##pref != (nhp)->nhp_##pref \
|
||||
? msg_header_free(home, (void *)(tbf)->nhp_##pref) : (void)0), \
|
||||
(void)(!(nhp)->nhp_set.nhb_##pref ? (nhp)->nhp_##pref = NULL : NULL))
|
||||
|
||||
NHP_ZAP_OVERRIDEN_HDR(tbf, dst, initial_route);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
@ -1399,6 +1440,8 @@ int nua_stack_set_smime_params(nua_t *nua, tagi_t const *tags)
|
|||
* NUTAG_ENABLEINVITE() \n
|
||||
* NUTAG_ENABLEMESSAGE() \n
|
||||
* NUTAG_ENABLEMESSENGER() \n
|
||||
* NUTAG_INITIAL_ROUTE() \n
|
||||
* NUTAG_INITIAL_ROUTE_STR() \n
|
||||
* NUTAG_INSTANCE() \n
|
||||
* NUTAG_INVITE_TIMER() \n
|
||||
* NUTAG_KEEPALIVE() \n
|
||||
|
@ -1523,7 +1566,8 @@ int nua_stack_get_params(nua_t *nua, nua_handle_t *nh, nua_event_t e,
|
|||
TAG_IF(nhp->nhp_set.nhb_##pref, TAG(nhp->nhp_##pref))
|
||||
|
||||
/* Include tag in the list returned to user
|
||||
* if it has been earlier set (by user) returning default parameters */
|
||||
* if it has been earlier set (by user)
|
||||
* but always include in the default parameters */
|
||||
#define TIFD(TAG, pref) \
|
||||
TAG_IF(nh == dnh || nhp->nhp_set.nhb_##pref, TAG(nhp->nhp_##pref))
|
||||
|
||||
|
@ -1597,6 +1641,9 @@ int nua_stack_get_params(nua_t *nua, nua_handle_t *nh, nua_event_t e,
|
|||
TIF_SIP(SIPTAG_ORGANIZATION, organization),
|
||||
TIF(SIPTAG_ORGANIZATION_STR, organization),
|
||||
|
||||
TIF(NUTAG_INITIAL_ROUTE, initial_route),
|
||||
TIF_STR(NUTAG_INITIAL_ROUTE_STR, initial_route),
|
||||
|
||||
TIF(NUTAG_REGISTRAR, registrar),
|
||||
TIF(NUTAG_KEEPALIVE, keepalive),
|
||||
TIF(NUTAG_KEEPALIVE_STREAM, keepalive_stream),
|
||||
|
|
|
@ -108,7 +108,7 @@ typedef struct nua_handle_preferences
|
|||
/* Subscriber state, i.e. nua_substate_pending */
|
||||
unsigned nhp_substate;
|
||||
|
||||
/* REGISTER Keepalive intervals */
|
||||
/* REGISTER keepalive intervals */
|
||||
unsigned nhp_keepalive, nhp_keepalive_stream;
|
||||
char const *nhp_registrar;
|
||||
|
||||
|
@ -124,14 +124,17 @@ typedef struct nua_handle_preferences
|
|||
char const *nhp_m_features;
|
||||
char const *nhp_instance;
|
||||
|
||||
/**< Outbound OPTIONS */
|
||||
/** Outbound OPTIONS */
|
||||
char const *nhp_outbound;
|
||||
|
||||
/**< Network detection: NONE, INFORMAL, TRY_FULL */
|
||||
/** Network detection: NONE, INFORMAL, TRY_FULL */
|
||||
int nhp_detect_network_updates;
|
||||
|
||||
sip_allow_t *nhp_appl_method;
|
||||
|
||||
/** Initial route set */
|
||||
sip_route_t *nhp_initial_route;
|
||||
|
||||
union { struct {
|
||||
/* A bit for each feature set by application */
|
||||
unsigned nhb_retry_count:1;
|
||||
|
@ -182,6 +185,7 @@ typedef struct nua_handle_preferences
|
|||
unsigned nhb_outbound:1;
|
||||
unsigned nhb_detect_network_updates:1;
|
||||
unsigned nhb_appl_method:1;
|
||||
unsigned nhb_initial_route:1;
|
||||
unsigned :0;
|
||||
} set_bits;
|
||||
unsigned set_unsigned[2];
|
||||
|
|
|
@ -245,7 +245,6 @@ static void nua_register_connection_closed(tp_stack_t *sip_stack,
|
|||
sip_contact_t *nua_handle_contact_by_via(nua_handle_t *nh,
|
||||
su_home_t *home,
|
||||
int in_dialog,
|
||||
char const *extra_username,
|
||||
sip_via_t const *v,
|
||||
char const *transport,
|
||||
char const *m_param,
|
||||
|
@ -268,12 +267,12 @@ static int nua_stack_outbound_credentials(nua_handle_t *, auth_client_t **auc);
|
|||
|
||||
outbound_owner_vtable nua_stack_outbound_callbacks = {
|
||||
sizeof nua_stack_outbound_callbacks,
|
||||
nua_handle_contact_by_via,
|
||||
nua_stack_outbound_refresh,
|
||||
nua_stack_outbound_status,
|
||||
nua_stack_outbound_failed,
|
||||
nua_stack_outbound_failed,
|
||||
nua_stack_outbound_credentials
|
||||
/* oo_contact */ nua_handle_contact_by_via,
|
||||
/* oo_refresh */ nua_stack_outbound_refresh,
|
||||
/* oo_status */ nua_stack_outbound_status,
|
||||
/* oo_probe_error */ nua_stack_outbound_failed,
|
||||
/* oo_keepalive_error */ nua_stack_outbound_failed,
|
||||
/* oo_credentials */ nua_stack_outbound_credentials
|
||||
};
|
||||
|
||||
/**@fn void nua_register(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...);
|
||||
|
@ -1364,7 +1363,7 @@ int nua_registration_from_via(nua_registration_t **list,
|
|||
|
||||
v2[1].v_next = NULL;
|
||||
|
||||
contact = nua_handle_contact_by_via(nh, home, 0, NULL, v2, protocol, NULL);
|
||||
contact = nua_handle_contact_by_via(nh, home, 0, v2, protocol, NULL);
|
||||
|
||||
v = sip_via_dup(home, v2);
|
||||
|
||||
|
@ -1780,7 +1779,7 @@ int nua_registration_set_contact(nua_handle_t *nh,
|
|||
if (nr0 && nr0->nr_via) {
|
||||
char const *tport = nr0->nr_via->v_next ? NULL : nr0->nr_via->v_protocol;
|
||||
m = nua_handle_contact_by_via(nh, nh->nh_home, 0,
|
||||
NULL, nr0->nr_via, tport, NULL);
|
||||
nr0->nr_via, tport, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1918,7 +1917,6 @@ static int nua_stack_outbound_credentials(nua_handle_t *nh,
|
|||
sip_contact_t *nua_handle_contact_by_via(nua_handle_t *nh,
|
||||
su_home_t *home,
|
||||
int in_dialog,
|
||||
char const *extra_username,
|
||||
sip_via_t const *v,
|
||||
char const *transport,
|
||||
char const *m_param,
|
||||
|
@ -1926,11 +1924,12 @@ sip_contact_t *nua_handle_contact_by_via(nua_handle_t *nh,
|
|||
{
|
||||
su_strlst_t *l;
|
||||
char const *s;
|
||||
char const *scheme = "sip:", *host, *port, *maddr, *comp;
|
||||
char const *host, *port, *maddr, *comp;
|
||||
int one = 1;
|
||||
char _transport[16];
|
||||
va_list va;
|
||||
sip_contact_t *m;
|
||||
url_t url = URL_INIT_AS(sip);
|
||||
|
||||
if (!v) return NULL;
|
||||
|
||||
|
@ -1946,7 +1945,7 @@ sip_contact_t *nua_handle_contact_by_via(nua_handle_t *nh,
|
|||
|
||||
if (sip_transport_has_tls(v->v_protocol) ||
|
||||
sip_transport_has_tls(transport)) {
|
||||
scheme = "sips:";
|
||||
url.url_type = url_sips;
|
||||
if (port && strcmp(port, SIPS_DEFAULT_SERV) == 0)
|
||||
port = NULL;
|
||||
if (port || host_is_ip_address(host))
|
||||
|
@ -1974,6 +1973,25 @@ sip_contact_t *nua_handle_contact_by_via(nua_handle_t *nh,
|
|||
}
|
||||
}
|
||||
|
||||
s = NH_PGET(nh, m_username);
|
||||
if (s)
|
||||
url.url_user = s;
|
||||
url.url_host = host;
|
||||
url.url_port = port;
|
||||
url.url_params = su_strdup(home, NH_PGET(nh, m_params));
|
||||
if (transport) {
|
||||
url.url_params = url_strip_param_string((char*)url.url_params, "transport");
|
||||
url_param_add(home, &url, su_sprintf(home, "transport=%s", transport));
|
||||
}
|
||||
if (maddr) {
|
||||
url.url_params = url_strip_param_string((char*)url.url_params, "maddr");
|
||||
url_param_add(home, &url, su_sprintf(home, "maddr=%s", maddr));
|
||||
}
|
||||
if (comp) {
|
||||
url.url_params = url_strip_param_string((char*)url.url_params, "comp");
|
||||
url_param_add(home, &url, su_sprintf(home, "comp=%s", comp));
|
||||
}
|
||||
|
||||
l = su_strlst_create(NULL);
|
||||
|
||||
s = NH_PGET(nh, m_display);
|
||||
|
@ -1984,25 +2002,9 @@ sip_contact_t *nua_handle_contact_by_via(nua_handle_t *nh,
|
|||
su_strlst_append(l, s);
|
||||
su_strlst_append(l, quote ? "\" " : " ");
|
||||
}
|
||||
|
||||
su_strlst_append(l, "<");
|
||||
su_strlst_append(l, scheme);
|
||||
s = NH_PGET(nh, m_username);
|
||||
if (s) su_strlst_append(l, s);
|
||||
if (extra_username) su_strlst_append(l, s);
|
||||
if (s || extra_username)
|
||||
su_strlst_append(l, "@");
|
||||
su_strlst_append(l, host);
|
||||
if (port)
|
||||
su_strlst_append(l, ":"), su_strlst_append(l, port);
|
||||
if (transport)
|
||||
su_strlst_append(l, ";transport="), su_strlst_append(l, transport);
|
||||
if (maddr)
|
||||
su_strlst_append(l, ";maddr="), su_strlst_append(l, maddr);
|
||||
if (comp)
|
||||
su_strlst_append(l, ";comp="), su_strlst_append(l, comp);
|
||||
s = NH_PGET(nh, m_params);
|
||||
if (s)
|
||||
s[0] == ';' ? "" : su_strlst_append(l, ";"), su_strlst_append(l, s);
|
||||
su_strlst_append(l, url_as_string(home, &url));
|
||||
su_strlst_append(l, ">");
|
||||
|
||||
va_start(va, m_param);
|
||||
|
|
|
@ -1997,6 +1997,12 @@ int nua_client_init_request(nua_client_request_t *cr)
|
|||
t = nh->nh_tags, sip_add_tagis(msg, sip, &t);
|
||||
}
|
||||
|
||||
if (!ds->ds_route) {
|
||||
sip_route_t *initial_route = NH_PGET(nh, initial_route);
|
||||
if (initial_route)
|
||||
sip_add_dup(msg, sip, (sip_header_t *)initial_route);
|
||||
}
|
||||
|
||||
for (t = cr->cr_tags; t; t = t_next(t)) {
|
||||
if (t->t_tag == siptag_contact ||
|
||||
t->t_tag == siptag_contact_str)
|
||||
|
|
|
@ -103,6 +103,10 @@ tag_typedef_t nutag_sips_url = URLTAG_TYPEDEF(sips_url);
|
|||
tag_typedef_t nutag_certificate_dir = STRTAG_TYPEDEF(certificate_dir);
|
||||
tag_typedef_t nutag_certificate_phrase = STRTAG_TYPEDEF(certificate_phrase);
|
||||
|
||||
extern msg_hclass_t sip_route_class[];
|
||||
tag_typedef_t nutag_initial_route = SIPEXTHDRTAG_TYPEDEF(initial_route, route);
|
||||
tag_typedef_t nutag_initial_route_str = STRTAG_TYPEDEF(inital_route_str);
|
||||
|
||||
tag_typedef_t nutag_registrar = URLTAG_TYPEDEF(registrar);
|
||||
tag_typedef_t nutag_identity = PTRTAG_TYPEDEF(identity);
|
||||
tag_typedef_t nutag_m_display = STRTAG_TYPEDEF(m_display);
|
||||
|
|
|
@ -581,6 +581,9 @@ int outbound_gruuize(outbound_t *ob, sip_t const *sip)
|
|||
return 0;
|
||||
}
|
||||
|
||||
gruu = (char *)msg_header_find_param(m->m_common, "pub-gruu=");
|
||||
|
||||
if (gruu == NULL || gruu[0] == '\0')
|
||||
gruu = (char *)msg_header_find_param(m->m_common, "gruu=");
|
||||
|
||||
if (gruu == NULL || gruu[0] == '\0')
|
||||
|
@ -1005,13 +1008,13 @@ int outbound_contacts_from_via(outbound_t *ob, sip_via_t const *via)
|
|||
v = v0; *v0 = *via; v0->v_next = NULL;
|
||||
|
||||
dcontact = ob->ob_oo->oo_contact(ob->ob_owner, home, 1,
|
||||
NULL, v, v->v_protocol, NULL);
|
||||
v, v->v_protocol, NULL);
|
||||
|
||||
if (ob->ob_instance && ob->ob_reg_id != 0)
|
||||
snprintf(reg_id_param, sizeof reg_id_param, ";reg-id=%u", ob->ob_reg_id);
|
||||
|
||||
rcontact = ob->ob_oo->oo_contact(ob->ob_owner, home, 0,
|
||||
NULL, v, v->v_protocol,
|
||||
v, v->v_protocol,
|
||||
ob->ob_instance, reg_id_param, NULL);
|
||||
|
||||
v = sip_via_dup(home, v);
|
||||
|
@ -1112,7 +1115,7 @@ int outbound_set_contact(outbound_t *ob,
|
|||
char reg_id_param[20];
|
||||
|
||||
dcontact = ob->ob_oo->oo_contact(ob->ob_owner, home, 1,
|
||||
NULL, v, tport, NULL);
|
||||
v, tport, NULL);
|
||||
if (!dcontact)
|
||||
return -1;
|
||||
|
||||
|
@ -1120,7 +1123,7 @@ int outbound_set_contact(outbound_t *ob,
|
|||
snprintf(reg_id_param, sizeof reg_id_param, ";reg-id=%u", ob->ob_reg_id);
|
||||
|
||||
rcontact = ob->ob_oo->oo_contact(ob->ob_owner, home, 0,
|
||||
NULL, v, v->v_protocol,
|
||||
v, v->v_protocol,
|
||||
ob->ob_instance, reg_id_param, NULL);
|
||||
if (!rcontact)
|
||||
return -1;
|
||||
|
|
|
@ -119,7 +119,6 @@ struct outbound_owner_vtable
|
|||
sip_contact_t *(*oo_contact)(outbound_owner_t *,
|
||||
su_home_t *home,
|
||||
int used_in_dialog,
|
||||
char const *extra_username,
|
||||
sip_via_t const *v,
|
||||
char const *transport,
|
||||
char const *m_param,
|
||||
|
|
|
@ -1086,6 +1086,85 @@ SOFIAPUBVAR tag_typedef_t nutag_sips_url_ref;
|
|||
#define NUTAG_PROXY_REF(x) NTATAG_DEFAULT_PROXY_REF(x)
|
||||
#define nutag_proxy ntatag_default_proxy
|
||||
|
||||
|
||||
/** Specify initial route set.
|
||||
*
|
||||
* The initial route set is used instead or or in addition to the outbound
|
||||
* proxy URL given by NUTAG_PROXY(). The NUTAG_INITIAL_ROUTE() accepts a
|
||||
* list of parsed @Route header structures, NUTAG_INITIAL_ROUTE_STR() an
|
||||
* unparsed string.
|
||||
*
|
||||
* If a tag list contains multiple NUTAG_INITIAL_ROUTE() or
|
||||
* NUTAG_INITIAL_ROUTE_STR() tags, the route set is constructed from them
|
||||
* all.
|
||||
*
|
||||
* @par Used with
|
||||
* nua_set_params() \n
|
||||
* nua_set_hparams() \n
|
||||
* any handle-specific nua call
|
||||
*
|
||||
* @par Parameter type
|
||||
* sip_route_t const *
|
||||
*
|
||||
* @par Values
|
||||
* Linked list of #sip_route_t structures
|
||||
*
|
||||
* Corresponding tag taking reference parameter is NUTAG_INITIAL_ROUTE_REF().
|
||||
*
|
||||
* @since @NEW_1_12_7.
|
||||
*/
|
||||
#define NUTAG_INITIAL_ROUTE(x) nutag_initial_route, siptag_route_v(x)
|
||||
SOFIAPUBVAR tag_typedef_t nutag_initial_route;
|
||||
|
||||
#define NUTAG_INITIAL_ROUTE_REF(x) nutag_initial_route_ref, siptag_route_vr(&(x))
|
||||
SOFIAPUBVAR tag_typedef_t nutag_initial_route_ref;
|
||||
|
||||
|
||||
/** Specify initial route set.
|
||||
*
|
||||
* The initial route set is used instead or or in addition to the outbound
|
||||
* proxy URL given by NUTAG_PROXY(). The NUTAG_INITIAL_ROUTE() accepts a
|
||||
* list of parsed @Route header structures, NUTAG_INITIAL_ROUTE_STR() a
|
||||
* unparsed string containing route URIs, quoted with <> and separated by
|
||||
* commas.
|
||||
*
|
||||
* Please note that the syntax requires <> around the @Route URIs if they
|
||||
* contain parameters, e.g., "lr".
|
||||
*
|
||||
* If a tag list contains multiple NUTAG_INITIAL_ROUTE() or
|
||||
* NUTAG_INITIAL_ROUTE_STR() tags, the route set is constructed from them
|
||||
* all.
|
||||
*
|
||||
* The initial route set can be reset with NUTAG_INITIAL_ROUTE(NULL).
|
||||
*
|
||||
* If a tag list of a request contains SIPTAG_ROUTE() or
|
||||
* SIPTAG_ROUTE_STR() tags, the resulting route set will contain first the
|
||||
* initial route entries followed by the route URIs given with the
|
||||
* SIPTAG_ROUTE()/SIPTAG_ROUTE_STR() tags.
|
||||
*
|
||||
* @par Used with
|
||||
* nua_set_params() \n
|
||||
* nua_set_hparams() \n
|
||||
* any handle-specific nua call
|
||||
*
|
||||
* @par Parameter type
|
||||
* sip_route_t const *
|
||||
*
|
||||
* @par Values
|
||||
* Linked list of #sip_route_t structures
|
||||
*
|
||||
* Corresponding tag taking reference parameter is NUTAG_INITIAL_ROUTE_STR_REF().
|
||||
*
|
||||
* @since @NEW_1_12_7.
|
||||
*/
|
||||
#define NUTAG_INITIAL_ROUTE_STR(x) nutag_initial_route_str, tag_str_v(x)
|
||||
SOFIAPUBVAR tag_typedef_t nutag_initial_route_str;
|
||||
|
||||
#define NUTAG_INITIAL_ROUTE_STR_REF(x) nutag_initial_route_str_ref, tag_str_vr(&(x))
|
||||
SOFIAPUBVAR tag_typedef_t nutag_initial_route_str_ref;
|
||||
|
||||
|
||||
|
||||
/** Registrar URL
|
||||
*
|
||||
* @par Used with
|
||||
|
@ -1461,7 +1540,7 @@ SOFIAPUBVAR tag_typedef_t nutag_m_username_ref;
|
|||
|
||||
/**URL parameters for @Contact.
|
||||
*
|
||||
* Specify URL parameters for the Contact header URI generated for
|
||||
* Specify URL parameters for the @Contact header URI generated for
|
||||
* registration request and dialog-creating requests/responses.
|
||||
*
|
||||
* Please note that some proxies may remove even the non-transport
|
||||
|
|
|
@ -1436,12 +1436,13 @@ int test_basic_call_6(struct context *ctx)
|
|||
struct endpoint *a = &ctx->a, *b = &ctx->b;
|
||||
struct call *a_call = a->call, *b_call = b->call;
|
||||
struct event *e;
|
||||
sip_route_t *r, rb[1];
|
||||
sip_t *sip;
|
||||
|
||||
sip_contact_t ma[1], mb[1];
|
||||
|
||||
if (print_headings)
|
||||
printf("TEST NUA-3.1: Basic call\n");
|
||||
printf("TEST NUA-3.6: Basic call\n");
|
||||
|
||||
a_call->sdp = "m=audio 5008 RTP/AVP 8";
|
||||
b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
|
||||
|
@ -1461,8 +1462,16 @@ int test_basic_call_6(struct context *ctx)
|
|||
|
||||
contact_for_b = mb;
|
||||
|
||||
sip_route_init(rb)->r_url[0] = b->contact->m_url[0];
|
||||
rb->r_url->url_user = "bob+0";
|
||||
url_param_add(nua_handle_home(a_call->nh), rb->r_url, "lr");
|
||||
|
||||
INVITE(a, a_call, a_call->nh,
|
||||
TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
|
||||
NUTAG_URL("sip:bob@example.org"), /* Expanded by proxy */
|
||||
SIPTAG_ROUTE_STR("B2 <sip:bob+2@example.org>;bar=foo"), /* Last in list */
|
||||
NUTAG_INITIAL_ROUTE(ctx->lr), /* Removed by proxy (if any) */
|
||||
NUTAG_INITIAL_ROUTE(rb), /* Used to route request to b (not removed) */
|
||||
NUTAG_INITIAL_ROUTE_STR("B1 <sip:bob+1@example.org;lr>;foo=bar"), /* Next in list */
|
||||
SOATAG_USER_SDP_STR(a_call->sdp),
|
||||
SIPTAG_CONTACT(ma),
|
||||
TAG_END());
|
||||
|
@ -1517,6 +1526,14 @@ int test_basic_call_6(struct context *ctx)
|
|||
TEST_1(sip->sip_contact);
|
||||
TEST_S(sip->sip_contact->m_display, "Alice B.");
|
||||
TEST_S(sip->sip_contact->m_url->url_user, "a++a");
|
||||
TEST_1(r = sip->sip_route);
|
||||
TEST_S(r->r_url->url_user, "bob+0");
|
||||
TEST_1(r = r->r_next);
|
||||
TEST_S(r->r_url->url_user, "bob+1");
|
||||
TEST_1(r = r->r_next);
|
||||
TEST_S(r->r_url->url_user, "bob+2");
|
||||
TEST_1(!r->r_next);
|
||||
|
||||
TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
|
||||
TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
|
||||
TEST_1(is_offer_recv(e->data->e_tags));
|
||||
|
@ -1598,7 +1615,7 @@ int test_basic_call_6(struct context *ctx)
|
|||
nua_handle_destroy(b_call->nh), b_call->nh = NULL;
|
||||
|
||||
if (print_headings)
|
||||
printf("TEST NUA-3.1: PASSED\n");
|
||||
printf("TEST NUA-3.6: PASSED\n");
|
||||
|
||||
END();
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ int test_nua_init(struct context *ctx,
|
|||
sip_supported_t const *supported = NULL;
|
||||
char const *appl_method = NULL;
|
||||
url_t const *p_uri, *a_uri, *b_uri; /* Proxy URI */
|
||||
char const *initial_route = NULL; /* Initial route towards internal proxy */
|
||||
char const *a_bind, *a_bind2;
|
||||
int err = -1;
|
||||
url_t b_proxy[1];
|
||||
|
@ -164,6 +165,9 @@ int test_nua_init(struct context *ctx,
|
|||
if (start_nat && p_uri == NULL)
|
||||
p_uri = url_hdup(ctx->home, (void *)o_proxy);
|
||||
|
||||
if (ctx->p)
|
||||
initial_route = test_proxy_route_uri(ctx->p, &ctx->lr);
|
||||
|
||||
if (start_nat && p_uri != NULL) {
|
||||
int family = 0;
|
||||
su_sockaddr_t su[1];
|
||||
|
@ -259,6 +263,7 @@ int test_nua_init(struct context *ctx,
|
|||
|
||||
ctx->a.nua = nua_create(ctx->root, a_callback, ctx,
|
||||
NUTAG_PROXY(a_uri ? a_uri : o_proxy),
|
||||
NUTAG_INITIAL_ROUTE_STR(initial_route),
|
||||
SIPTAG_FROM_STR("sip:alice@example.com"),
|
||||
NUTAG_URL(a_bind),
|
||||
TAG_IF(a_bind != a_bind2, NUTAG_SIPS_URL(a_bind2)),
|
||||
|
|
|
@ -180,6 +180,7 @@ struct context
|
|||
} a, b, c;
|
||||
|
||||
struct proxy *p;
|
||||
sip_route_t const *lr;
|
||||
struct nat *nat;
|
||||
};
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@
|
|||
#define __func__ "test_nua_params"
|
||||
#endif
|
||||
|
||||
sip_route_t *GLOBAL_ROUTE;
|
||||
|
||||
int test_tag_filter(void)
|
||||
{
|
||||
BEGIN();
|
||||
|
@ -104,7 +106,6 @@ int test_nua_params(struct context *ctx)
|
|||
ctx->root = su_root_create(NULL);
|
||||
TEST_1(ctx->root);
|
||||
|
||||
/* Disable threading by command line switch? */
|
||||
su_root_threading(ctx->root, ctx->threading);
|
||||
|
||||
ctx->a.nua = nua_create(ctx->root, a_callback, ctx,
|
||||
|
@ -153,7 +154,6 @@ int test_nua_params(struct context *ctx)
|
|||
SIPTAG_ALLOW_EVENTS_STR("reg"),
|
||||
SIPTAG_USER_AGENT_STR("test_nua/1.0"),
|
||||
|
||||
|
||||
SIPTAG_ORGANIZATION_STR("Open Laboratory"),
|
||||
|
||||
NUTAG_M_DISPLAY("XXX"),
|
||||
|
@ -163,6 +163,12 @@ int test_nua_params(struct context *ctx)
|
|||
NUTAG_INSTANCE("urn:uuid:3eb007b1-6d7f-472e-8b64-29e482795da8"),
|
||||
NUTAG_OUTBOUND("bar"),
|
||||
|
||||
NUTAG_INITIAL_ROUTE(NULL),
|
||||
NUTAG_INITIAL_ROUTE(sip_route_make(tmphome, "<sip:tst@example.net;lr>")),
|
||||
NUTAG_INITIAL_ROUTE_STR("<sip:str1@example.net;lr>"),
|
||||
NUTAG_INITIAL_ROUTE_STR("sip:str2@example.net;lr=foo"),
|
||||
NUTAG_INITIAL_ROUTE_STR(NULL),
|
||||
|
||||
TAG_END());
|
||||
|
||||
run_a_until(ctx, nua_r_set_params, until_final_response);
|
||||
|
@ -221,6 +227,12 @@ int test_nua_params(struct context *ctx)
|
|||
SIPTAG_ALLOW_EVENTS(sip_allow_events_make(tmphome, "presence")),
|
||||
NUTAG_ALLOW_EVENTS("presence.winfo"),
|
||||
|
||||
NUTAG_INITIAL_ROUTE(NULL),
|
||||
NUTAG_INITIAL_ROUTE(sip_route_make(nua_handle_home(nh), "<sip:1@example.com;lr>")),
|
||||
NUTAG_INITIAL_ROUTE_STR("<sip:2@example.com;lr>"),
|
||||
/* Check for sip_route_fix() */
|
||||
NUTAG_INITIAL_ROUTE_STR("sip:3@example.com;lr=foo"),
|
||||
NUTAG_INITIAL_ROUTE_STR(NULL),
|
||||
|
||||
SIPTAG_USER_AGENT(sip_user_agent_make(tmphome, "test_nua")),
|
||||
|
||||
|
@ -287,6 +299,9 @@ int test_nua_params(struct context *ctx)
|
|||
sip_organization_t const *organization = NONE;
|
||||
char const *organization_str = "NONE";
|
||||
|
||||
sip_route_t *initial_route = NONE;
|
||||
char const *initial_route_str = NONE;
|
||||
|
||||
char const *outbound = "NONE";
|
||||
char const *m_display = "NONE";
|
||||
char const *m_username = "NONE";
|
||||
|
@ -352,6 +367,9 @@ int test_nua_params(struct context *ctx)
|
|||
SIPTAG_ORGANIZATION_REF(organization),
|
||||
SIPTAG_ORGANIZATION_STR_REF(organization_str),
|
||||
|
||||
NUTAG_INITIAL_ROUTE_REF(initial_route),
|
||||
NUTAG_INITIAL_ROUTE_STR_REF(initial_route_str),
|
||||
|
||||
NUTAG_REGISTRAR_REF(registrar),
|
||||
NUTAG_KEEPALIVE_REF(keepalive),
|
||||
NUTAG_KEEPALIVE_STREAM_REF(keepalive_stream),
|
||||
|
@ -364,7 +382,7 @@ int test_nua_params(struct context *ctx)
|
|||
NUTAG_INSTANCE_REF(instance),
|
||||
|
||||
TAG_END());
|
||||
TEST(n, 48);
|
||||
TEST(n, 50);
|
||||
|
||||
TEST_S(sip_header_as_string(tmphome, (void *)from), Alice);
|
||||
TEST_S(from_str, Alice);
|
||||
|
@ -414,6 +432,17 @@ int test_nua_params(struct context *ctx)
|
|||
"Pussy Galore's Flying Circus");
|
||||
TEST_S(organization_str, "Pussy Galore's Flying Circus");
|
||||
|
||||
TEST_1(initial_route); TEST_1(initial_route != (void *)-1);
|
||||
TEST_S(initial_route->r_url->url_user, "1");
|
||||
TEST_1(url_has_param(initial_route->r_url, "lr"));
|
||||
TEST_1(initial_route->r_next);
|
||||
TEST_S(initial_route->r_next->r_url->url_user, "2");
|
||||
TEST_1(url_has_param(initial_route->r_next->r_url, "lr"));
|
||||
TEST_1(initial_route->r_next->r_next);
|
||||
TEST_S(initial_route->r_next->r_next->r_url->url_user, "3");
|
||||
TEST_1(url_has_param(initial_route->r_next->r_next->r_url, "lr"));
|
||||
TEST_1(!initial_route->r_next->r_next->r_next);
|
||||
|
||||
TEST_S(url_as_string(tmphome, registrar->us_url),
|
||||
"sip:sip.wonderland.org");
|
||||
TEST(keepalive, 66);
|
||||
|
@ -473,6 +502,9 @@ int test_nua_params(struct context *ctx)
|
|||
sip_organization_t const *organization = NONE;
|
||||
char const *organization_str = "NONE";
|
||||
|
||||
sip_route_t *initial_route = NONE;
|
||||
char const *initial_route_str = "NONE";
|
||||
|
||||
url_string_t const *registrar = NONE;
|
||||
|
||||
char const *outbound = "NONE";
|
||||
|
@ -585,6 +617,9 @@ int test_nua_params(struct context *ctx)
|
|||
TEST_P(organization, NONE);
|
||||
TEST_S(organization_str, "NONE");
|
||||
|
||||
TEST_1(initial_route == (void *)-1);
|
||||
TEST_S(initial_route_str, "NONE");
|
||||
|
||||
TEST_S(outbound, "NONE");
|
||||
TEST_S(m_display, "NONE");
|
||||
TEST_S(m_username, "NONE");
|
||||
|
|
|
@ -100,6 +100,8 @@ struct proxy {
|
|||
|
||||
nta_agent_t *agent;
|
||||
url_t const *uri;
|
||||
sip_route_t *lr;
|
||||
char const *lr_str;
|
||||
url_t const *rr_uri;
|
||||
|
||||
nta_leg_t *defleg;
|
||||
|
@ -326,6 +328,11 @@ test_proxy_init(su_root_t *root, struct proxy *proxy)
|
|||
return -1;
|
||||
|
||||
proxy->uri = nta_agent_contact(proxy->agent)->m_url;
|
||||
proxy->lr_str = su_sprintf(proxy->home, "<" URL_PRINT_FORMAT ";lr>", URL_PRINT_ARGS(proxy->uri));
|
||||
proxy->lr = sip_route_make(proxy->home, proxy->lr_str);
|
||||
|
||||
if (!proxy->lr)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -391,6 +398,19 @@ url_t const *test_proxy_uri(struct proxy const *p)
|
|||
return p ? p->uri : NULL;
|
||||
}
|
||||
|
||||
/* Return the proxy route URI */
|
||||
char const *test_proxy_route_uri(struct proxy const *p,
|
||||
sip_route_t const **return_route)
|
||||
{
|
||||
if (p == NULL)
|
||||
return NULL;
|
||||
|
||||
if (return_route)
|
||||
*return_route = p->lr;
|
||||
|
||||
return p->lr_str;
|
||||
}
|
||||
|
||||
void test_proxy_domain_set_expiration(struct domain *d,
|
||||
sip_time_t min_expires,
|
||||
sip_time_t expires,
|
||||
|
@ -797,7 +817,8 @@ static int validate_transaction(struct proxy_tr *t)
|
|||
/* Remove our routes */
|
||||
while (t->sip->sip_route &&
|
||||
url_has_param(t->sip->sip_route->r_url, "lr") &&
|
||||
url_cmp(t->proxy->rr_uri, t->sip->sip_route->r_url) == 0) {
|
||||
(url_cmp(t->proxy->lr->r_url, t->sip->sip_route->r_url) == 0 ||
|
||||
url_cmp(t->proxy->rr_uri, t->sip->sip_route->r_url) == 0)) {
|
||||
sip_route_remove(t->msg, t->sip);
|
||||
/* add record-route also to the forwarded request */
|
||||
t->rr = 1;
|
||||
|
|
|
@ -39,6 +39,9 @@ void test_proxy_destroy(struct proxy *);
|
|||
|
||||
url_t const *test_proxy_uri(struct proxy const *);
|
||||
|
||||
char const *test_proxy_route_uri(struct proxy const *p,
|
||||
sip_route_t const **return_route);
|
||||
|
||||
struct domain *test_proxy_add_domain(struct proxy *,
|
||||
url_t const *domain,
|
||||
tag_type_t, tag_value_t, ...);
|
||||
|
|
|
@ -320,7 +320,7 @@ struct sdp_rtpmap_s {
|
|||
unsigned rm_predef : 1; /**< is this entry well-known? */
|
||||
unsigned rm_pt : 7; /**< Payload type */
|
||||
unsigned rm_any : 1; /**< Wildcard entry */
|
||||
int :0;
|
||||
unsigned :0;
|
||||
};
|
||||
|
||||
SOFIAPUBVAR sdp_rtpmap_t const * const sdp_rtpmap_well_known[128];
|
||||
|
|
|
@ -65,6 +65,8 @@ ALIASES += \
|
|||
"WWWAuthenticate=@ref sip_www_authenticate \"WWW-Authenticate\"" \
|
||||
"Warning=@ref sip_warning \"Warning\"" \
|
||||
"ReferSub=@ref sip_refer_sub \"Refer-Sub\"" \
|
||||
"AlertInfo=@ref sip_alert_info \"Alert-Info\"" \
|
||||
"ReplyTo=@ref sip_reply_to \"Reply-To\"" \
|
||||
"SuppressBodyIfMatch=@ref sip_suppress_body_if_match \"Suppress-Body-If-Match\"" \
|
||||
"SuppressNotifyIfMatch=@ref sip_suppress_notify_if_match \"Suppress-Notify-If-Match\"" \
|
||||
|
||||
|
|
|
@ -1484,13 +1484,18 @@ issize_t sip_content_length_d(su_home_t *home,
|
|||
char *s,
|
||||
isize_t slen)
|
||||
{
|
||||
return sip_numeric_d(home, h, s, slen);
|
||||
sip_content_length_t *l = (sip_content_length_t *)h;
|
||||
issize_t retval = msg_uint32_d(&s, &l->l_length);
|
||||
if (*s)
|
||||
retval = -1;
|
||||
return retval;
|
||||
}
|
||||
|
||||
issize_t sip_content_length_e(char b[], isize_t bsiz, sip_header_t const *h, int flags)
|
||||
{
|
||||
sip_content_length_t const *l = (sip_content_length_t const *)h;
|
||||
assert(sip_is_content_length(h));
|
||||
return sip_numeric_e(b, bsiz, h, flags);
|
||||
return snprintf(b, bsiz, "%lu", (unsigned long)l->l_length);
|
||||
}
|
||||
|
||||
/**@ingroup sip_content_length
|
||||
|
|
|
@ -48,6 +48,28 @@
|
|||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
/** Tag class for tags containing SIP headers. @HIDE
|
||||
*
|
||||
* Tags in this class are not automatically added to the message with
|
||||
* sip_add_tl() or sip_add_tagis().
|
||||
*/
|
||||
tag_class_t sipexthdrtag_class[1] =
|
||||
{{
|
||||
sizeof(siphdrtag_class),
|
||||
/* tc_next */ NULL,
|
||||
/* tc_len */ NULL,
|
||||
/* tc_move */ NULL,
|
||||
/* tc_xtra */ msghdrtag_xtra,
|
||||
/* tc_dup */ msghdrtag_dup,
|
||||
/* tc_free */ NULL,
|
||||
/* tc_find */ NULL,
|
||||
/* tc_snprintf */ msghdrtag_snprintf,
|
||||
/* tc_filter */ siptag_filter,
|
||||
/* tc_ref_set */ t_ptr_ref_set,
|
||||
/* tc_scan */ msghdrtag_scan,
|
||||
}};
|
||||
|
||||
|
||||
/** Tag class for SIP header tags. @HIDE */
|
||||
tag_class_t siphdrtag_class[1] =
|
||||
{{
|
||||
|
|
|
@ -65,6 +65,22 @@ SOFIA_BEGIN_DECLS
|
|||
{{ TAG_NAMESPACE, #t, sipmsgtag_class, \
|
||||
(tag_value_t)SIP_PROTOCOL_TAG }}
|
||||
|
||||
/** Tag class for SIP headers */
|
||||
SOFIAPUBVAR tag_class_t siphdrtag_class[1];
|
||||
/** Tag class for string values of SIP headers */
|
||||
SOFIAPUBVAR tag_class_t sipstrtag_class[1];
|
||||
/** Tag class for SIP message */
|
||||
SOFIAPUBVAR tag_class_t sipmsgtag_class[1];
|
||||
|
||||
/** Define a named tag type using structure of SIP header @a t. */
|
||||
#define SIPEXTHDRTAG_TYPEDEF(n, t) \
|
||||
{{ TAG_NAMESPACE, #n, sipexthdrtag_class, \
|
||||
(tag_value_t)sip_##t##_class }}
|
||||
|
||||
/** Tag class using SIP header structure */
|
||||
SOFIAPUBVAR tag_class_t sipexthdrtag_class[1];
|
||||
|
||||
|
||||
/**@internal Filter SIP header tag items. */
|
||||
SOFIAPUBFUN tagi_t *siptag_filter(tagi_t *dst, tagi_t const f[],
|
||||
tagi_t const *src,
|
||||
|
|
|
@ -293,6 +293,9 @@ SOFIAPUBFUN int su_setblocking(su_socket_t s, int blocking);
|
|||
SOFIAPUBFUN int su_setreuseaddr(su_socket_t s, int reuse);
|
||||
/** Get the error code associated with the socket. */
|
||||
SOFIAPUBFUN int su_soerror(su_socket_t s);
|
||||
/** Get the socket type. */
|
||||
SOFIAPUBFUN int su_getsocktype(su_socket_t s);
|
||||
|
||||
/** Get size of message available in socket. */
|
||||
SOFIAPUBFUN issize_t su_getmsgsize(su_socket_t s);
|
||||
|
||||
|
|
|
@ -337,6 +337,17 @@ int su_soerror(su_socket_t s)
|
|||
return error;
|
||||
}
|
||||
|
||||
int su_getsocktype(su_socket_t s)
|
||||
{
|
||||
int socktype = 0;
|
||||
socklen_t intlen = sizeof(socktype);
|
||||
|
||||
if (getsockopt(s, SOL_SOCKET, SO_TYPE, (void *)&socktype, &intlen) < 0)
|
||||
return -1;
|
||||
|
||||
return socktype;
|
||||
}
|
||||
|
||||
int su_setreuseaddr(su_socket_t s, int reuse)
|
||||
{
|
||||
return setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
|
||||
|
|
|
@ -1,35 +1,34 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDPjCCAqegAwIBAgIHJQIBAZACBjANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQG
|
||||
EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEBxMIU2FuIEpvc2UxDjAM
|
||||
BgNVBAoTBXNpcGl0MSkwJwYDVQQLEyBTaXBpdCBUZXN0IENlcnRpZmljYXRlIEF1
|
||||
dGhvcml0eTAeFw0wMzEyMDMxODMwMjJaFw0wNjEyMDIxODMwMjJaMGUxCzAJBgNV
|
||||
MIIDIjCCAougAwIBAgIJAPHSWjotpO2kMA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNV
|
||||
BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQHEwhTYW4gSm9zZTEO
|
||||
MAwGA1UEChMFc2lwaXQxHjAcBgNVBAMTFXBla2thLm5va2lhLnNpcGl0Lm5ldDCB
|
||||
nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsdJn/O6JoIC1I2iXOVJLQypmt9sN
|
||||
HmvB84853Qx9KS+xqP3U2nqNMnDQby6ZmTsRHNGAK5QuGugU11wocmYNP0/TQFaz
|
||||
KNLhNt0pMBOfpAV9vG6pCSkocObsUo2XFULPTEB/SzGcvE1G1em3XmwRfPA178y9
|
||||
L2+sVNT5Vtt5KfMCAwEAAaOB7DCB6TAgBgNVHREEGTAXghVwZWtrYS5ub2tpYS5z
|
||||
aXBpdC5uZXQwCQYDVR0TBAIwADAdBgNVHQ4EFgQU1OjdL9cdA0NNbxPDQ9xZUZG6
|
||||
NnIwgZoGA1UdIwSBkjCBj4AUa0YXFOqUdiWAVG4TVNqh41QUobahdKRyMHAxCzAJ
|
||||
BgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQHEwhTYW4gSm9z
|
||||
ZTEOMAwGA1UEChMFc2lwaXQxKTAnBgNVBAsTIFNpcGl0IFRlc3QgQ2VydGlmaWNh
|
||||
dGUgQXV0aG9yaXR5ggEAMA0GCSqGSIb3DQEBBQUAA4GBADCO35LJqgiK5OUR+DuT
|
||||
N4CfUhsn9T5kDSf2rikna4ZFbuS7smc/oVu4g26HHjt6DKs4UEx9OmyXFslSENZ+
|
||||
tFNeVClpHJrPsNwjk/uyjhfeW1NezhXMIi6q8DYcwE5I7r+Uz2XST+jWCTb4obPY
|
||||
10ysI7e7/rgCoITe+qO2U35D
|
||||
MAwGA1UEChMFc2lwaXQxKTAnBgNVBAsTIFNpcGl0IFRlc3QgQ2VydGlmaWNhdGUg
|
||||
QXV0aG9yaXR5MB4XDTA3MDkxNzIwMDMzOVoXDTA5MDkxNjIwMDMzOVowNzE1MDMG
|
||||
A1UEAxMsQz1GSSwgTD1IZWxzaW5raSwgTz1zb2ZpYS1zaXAsIENOPXRwb3J0IHRl
|
||||
c3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALUlg4LStE/8WEjUny7Edpl1
|
||||
M0HCdE9Tfu8bHfcbCBDhlSQfQkAl0NilfB8tkX8Uz8LnTTsmtz76tDEORNEQRVng
|
||||
FSyg2ugryDxNKURCsdq5WyMUh1XFnu0TLAdD8z5/hqAn6+jo5gcoT92uxNsXYA79
|
||||
6qctdS8WMGuaRPsUUbRBAgMBAAGjgfwwgfkwCQYDVR0TBAIwADAdBgNVHQ4EFgQU
|
||||
12vA8bvBkE0q8hrFrvmE033MLq4wgZoGA1UdIwSBkjCBj4AUa0YXFOqUdiWAVG4T
|
||||
VNqh41QUobahdKRyMHAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
|
||||
MREwDwYDVQQHEwhTYW4gSm9zZTEOMAwGA1UEChMFc2lwaXQxKTAnBgNVBAsTIFNp
|
||||
cGl0IFRlc3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5ggEAMCMGA1UdEQQcMBqCGHRw
|
||||
b3J0LXRlc3Quc29maWEtc2lwLm9yZzALBgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEF
|
||||
BQADgYEAiJ2FvB4RUOieuiqy3m3SbVtVeUJ9cLbPMF/MGKMGhxgCm61GTteQHW6y
|
||||
f7gg2UkDeFoWclzqJam677fy+1IzVhpna6611vNow71w344eFBzg4GRBPhzR+qNn
|
||||
X5CcWPWME2olV64dngtFsECdYnW58gGVuXhPW2CYiZ8nPq1gu18=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQCx0mf87omggLUjaJc5UktDKma32w0ea8HzjzndDH0pL7Go/dTa
|
||||
eo0ycNBvLpmZOxEc0YArlC4a6BTXXChyZg0/T9NAVrMo0uE23SkwE5+kBX28bqkJ
|
||||
KShw5uxSjZcVQs9MQH9LMZy8TUbV6bdebBF88DXvzL0vb6xU1PlW23kp8wIDAQAB
|
||||
AoGAVtICT64vqBvvVPB2FVimwo5rRI1BJH88XSyq9dBpM7jDp1z3lgyL7/rA6ef4
|
||||
uqXqPwXS7HQW5rA1rMikPuawxE5UG31OG3U0+H/OGl0xwAq57mEtRDR8464jSUPY
|
||||
l9bzkRpjnEgdUtkLnogm8F4mALexdc3KxIgg3uo/OOg0N5ECQQDZon1JBNEYWxEF
|
||||
GBNbEvQthPy7rRLmxontgcsfhRvm5lSbuC+VP1uRHibwwIrXOUZD8uuEVdVZNzfV
|
||||
bGPdh70HAkEA0Ss6HyAWczRBzrvC8eVvPmkI9XihdLqUFLTDL0R1sMCISwW/FEeH
|
||||
X9yFqOY+y6EJAitzhxtol+0k+UsIJl5ctQJAXU0L6QHnokloQobPxXuasukQcGUC
|
||||
dW0oNGowapLmI1cbbqbHv3QqDUyf5Rambx5ewUKjNViW3miNxzFwnshSgQJAINuQ
|
||||
gskwnaJM4CPgqM0o333yeVUcz9BraKFItAkmD8D+6AIcFRxzaJykpnac0LIYTy3y
|
||||
NPwaPxtynnKp8hUKrQJBAM1i5051UzJVFuRedwtPdGDrfkNwoJm27fwWozSQcBC6
|
||||
G5VnTrQ6V8VCJglNzVhy9b2WqlqfWV3D5BCgxyuH984=
|
||||
MIICXQIBAAKBgQC1JYOC0rRP/FhI1J8uxHaZdTNBwnRPU37vGx33GwgQ4ZUkH0JA
|
||||
JdDYpXwfLZF/FM/C5007Jrc++rQxDkTREEVZ4BUsoNroK8g8TSlEQrHauVsjFIdV
|
||||
xZ7tEywHQ/M+f4agJ+vo6OYHKE/drsTbF2AO/eqnLXUvFjBrmkT7FFG0QQIDAQAB
|
||||
AoGAU6lrv7QIyxhEvf5VNohOLjnO3oQsq6ZobY4cnM/DPeixtOkq8+2DM0vas5uc
|
||||
IxE9k0q4hKUw5MltB2sLpXLaVAz3d9GjouKhMyzIv1UcHZli6OStSIVl458usfAy
|
||||
SnJLvCP12fp4S4cSAc7Q+z5v83ztGHqPvl7GOXaiNL5BNfECQQDfo8ElGtBZtUKu
|
||||
MPw/+07dPoMX4DHZ6Q9+4g3gfMoxkM+1RrFItDtkZx7W5aiWzcRrHlSB+2k/33Q1
|
||||
XyxyDnaHAkEAz1uxknSlLn//WwD5kwqPVOscWoQMHWNPG34H4Ktx/8IQ3WZGaECh
|
||||
g/09XViIM4t5l1xDLZaWymcXBNqdRTXo9wJBAJDzBSO0fmg2eJKIQ2aAzvvNIZCf
|
||||
ChCP4zA8+fuPRknb7xQyToOt9XS89ZsZXSzpDy7SiLl6pxv0C9Dv79G5GA8CQQCi
|
||||
L5oIb2vznvHgADc1J4FMbkjTE41WPyLU4hLPS8nyvZvrT9+qE0NBtYWVyXVeu6zz
|
||||
EpsIwUxYK6H5jfSM+cmpAkBRoE3KPkKJzqMYW2jNNk1CrE9YbHPcdWxsqh8+86CG
|
||||
oK1ABCIX/PA4t1i0KWi0gJ3DQFr+c77XOhtChE0P2kA0
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
#! /bin/sh
|
||||
#
|
||||
# Generate agent.pem
|
||||
#
|
||||
# Copyright (C) 2007 Nokia Corporation
|
||||
#
|
||||
# Author: Pekka Pessi
|
||||
# Based on Mikko Haataja's perl script
|
||||
#
|
||||
|
||||
CN='C=FI, L=Helsinki, O=sofia-sip, CN=tport test'
|
||||
ALTNAME=DNS:tport-test.sofia-sip.org
|
||||
#ALTNAME=URI:sips:tport-test.sofia-sip.org
|
||||
DAYS=730
|
||||
|
||||
umask 077
|
||||
|
||||
T=${TMPDIR:-/tmp}/sofia-sip-certs-${USER}-$$-`date +%N`
|
||||
CA=${T}/CA
|
||||
|
||||
mkdir $T || exit 1
|
||||
mkdir $T/CA || exit 1
|
||||
|
||||
cat > $CA/cakey.pem <<EOF
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,4B47A0A73ADE342E
|
||||
|
||||
aHmlPa+ZrOV6v+Jk0SClxzpxoG3j0ZuyoVkF9rzq2bZkzVBKLU6xhWwjMDqwA8dH
|
||||
3fCRLhMGIUVnmymXYhTW9svI1gpFxMBQHJcKpV/SmgFn/fbYk98Smo2izHOniIiu
|
||||
NOu2zr+bMiaBphOAZ/OCtVUxUOoBDKN9lR39UCDOgkEQzp9Vbw7l736yu5H9GMHP
|
||||
JtGLJyx3RhS3TvLfLAJZhjm/wZ/9QM8GjyJEiDhMQRJVeIZGvv4Yr1u6yYHiHfjX
|
||||
tX2eds8Luc83HbSvjAyjnkLtJsAZ/8cFzrd7pjFzbogLdWuil+kpkkf5h1uzh7oa
|
||||
um0M1EXBE4tcDHsfg1iqEsDMIei/U+/rWfk1PrzYlklwZp8S03vulkDm1fT76W7d
|
||||
mRBg4+CrHA6qYn6EPWB37OBtfEqAfINnIcI1dWzso9A0bTPD4EJO0JA0PcZ/2JgT
|
||||
PaKySgooHQ8AHNQebelch6M5LFExpaOADJKrqauKcc2HeUxXaYIpac5/7drIl3io
|
||||
UloqUnMlGa3eLP7BZIMsZKCfHZ8oqwU4g6mmmJath2gODRDx3mfhH6yaimDL7v4i
|
||||
SAIIkrEHXfSyovrTJymfSfQtYxUraVZDqax6oj/eGllRxliGfMLYG9ceU+yU/8FN
|
||||
LE7P+Cs19H5tHHzx1LlieaK43u/XvbXHlB5mqL/fZdkUIBJsjbBVx0HR8eQl2CH9
|
||||
YJDMOPLADecwHoyKA0AY59oN9d41oF7yZtN9KwNdslROYH7mNJlqMMenhXCLN+Nz
|
||||
vVU5/7/ugZFhZqfS46c1WdmSvuqpDp7TBtMeaH/PXjysBr0iZffOxQ==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
EOF
|
||||
|
||||
cat > $CA/cacert.pem <<EOF
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDJDCCAo2gAwIBAgIBADANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJVUzET
|
||||
MBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEBxMIU2FuIEpvc2UxDjAMBgNVBAoT
|
||||
BXNpcGl0MSkwJwYDVQQLEyBTaXBpdCBUZXN0IENlcnRpZmljYXRlIEF1dGhvcml0
|
||||
eTAeFw0wMzA3MTgxMjIxNTJaFw0xMzA3MTUxMjIxNTJaMHAxCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQHEwhTYW4gSm9zZTEOMAwGA1UE
|
||||
ChMFc2lwaXQxKTAnBgNVBAsTIFNpcGl0IFRlc3QgQ2VydGlmaWNhdGUgQXV0aG9y
|
||||
aXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDIh6DkcUDLDyK9BEUxkud
|
||||
+nJ4xrCVGKfgjHm6XaSuHiEtnfELHM+9WymzkBNzZpJu30yzsxwfKoIKugdNUrD4
|
||||
N3viCicwcN35LgP/KnbN34cavXHr4ZlqxH+OdKB3hQTpQa38A7YXdaoz6goW2ft5
|
||||
Mi74z03GNKP/G9BoKOGd5QIDAQABo4HNMIHKMB0GA1UdDgQWBBRrRhcU6pR2JYBU
|
||||
bhNU2qHjVBShtjCBmgYDVR0jBIGSMIGPgBRrRhcU6pR2JYBUbhNU2qHjVBShtqF0
|
||||
pHIwcDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAPBgNVBAcT
|
||||
CFNhbiBKb3NlMQ4wDAYDVQQKEwVzaXBpdDEpMCcGA1UECxMgU2lwaXQgVGVzdCBD
|
||||
ZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B
|
||||
AQUFAAOBgQCWbRvv1ZGTRXxbH8/EqkdSCzSoUPrs+rQqR0xdQac9wNY/nlZbkR3O
|
||||
qAezG6Sfmklvf+DOg5RxQq/+Y6I03LRepc7KeVDpaplMFGnpfKsibETMipwzayNQ
|
||||
QgUf4cKBiF+65Ue7hZuDJa2EMv8qW4twEhGDYclpFU9YozyS1OhvUg==
|
||||
-----END CERTIFICATE-----
|
||||
EOF
|
||||
|
||||
cat > $T/a.cnf <<EOF
|
||||
[ req ]
|
||||
default_bits = 1024
|
||||
prompt = no
|
||||
distinguished_name = req_dn
|
||||
|
||||
[ req_dn ]
|
||||
commonName = $CN
|
||||
|
||||
[ ext ]
|
||||
basicConstraints=CA:FALSE
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid,issuer:always
|
||||
subjectAltName=$ALTNAME
|
||||
#keyUsage=digitalSignature:TRUE,keyEncipherment:TRUE
|
||||
|
||||
EOF
|
||||
|
||||
cat $T/a.cnf
|
||||
|
||||
openssl req -new -out $T/a_req.pem -newkey rsa:1024 -keyout $T/a_key.pem \
|
||||
-sha1 -config $T/a.cnf -days $DAYS -nodes
|
||||
|
||||
openssl x509 -req -in $T/a_req.pem -sha1 \
|
||||
-extensions ext -extfile $T/a.cnf \
|
||||
-CA $CA/cacert.pem -CAkey $CA/cakey.pem \
|
||||
-passin pass:password \
|
||||
-CAcreateserial \
|
||||
-days $DAYS \
|
||||
-out $T/a_cert.pem
|
||||
|
||||
cat $T/a_cert.pem $T/a_key.pem
|
||||
|
||||
rm $CA/* && rmdir $CA && rm $T/* && rmdir $T
|
|
@ -313,6 +313,7 @@ int tport_recv_stream(tport_t *self)
|
|||
if (i + self->tp_ping >= 4)
|
||||
tport_tcp_pong(self);
|
||||
else
|
||||
|
||||
self->tp_ping += (unsigned short)i;
|
||||
|
||||
if (i == iovec->siv_len && veclen == 1) {
|
||||
|
|
|
@ -337,8 +337,8 @@ char *url_canonize(char *d, char const *s, size_t n,
|
|||
return url_canonize2(d, s, n, syn33, mask32, mask64, mask96);
|
||||
}
|
||||
|
||||
#define SYN33(c) (1 << (c - 33))
|
||||
#define IS_SYN33(syn33, c) ((syn33 & (1 << (c - 33))) != 0)
|
||||
#define SYN33(c) (1U << (c - 33))
|
||||
#define IS_SYN33(syn33, c) ((syn33 & (1U << (c - 33))) != 0)
|
||||
|
||||
/** Canonize a URL component (with precomputed mask) */
|
||||
static
|
||||
|
|
|
@ -61,6 +61,10 @@
|
|||
* <dt>--method=s</dt>
|
||||
* <dd>Specify the request method (defaults to OPTIONS).
|
||||
* </dd>
|
||||
* <dt>--extra | -x/dt>
|
||||
* <dd>Read extra headers (and optionally a message body) from the standard
|
||||
* input
|
||||
* </dd>
|
||||
* </dl>
|
||||
*
|
||||
* @section return Return Codes
|
||||
|
|
Loading…
Reference in New Issue