summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES11
-rw-r--r--configs/samples/pjsip.conf.sample1
-rw-r--r--contrib/ast-db-manage/config/versions/8fce4c573e15_add_pjsip_allow_overlap.py31
-rw-r--r--funcs/func_channel.c14
-rw-r--r--include/asterisk/manager.h2
-rw-r--r--include/asterisk/res_pjsip.h2
-rw-r--r--main/audiohook.c2
-rw-r--r--makeopts.in2
-rw-r--r--res/res_pjsip.c6
-rw-r--r--res/res_pjsip/pjsip_configuration.c1
-rw-r--r--res/res_pjsip_session.c24
-rw-r--r--res/res_xmpp.c135
-rw-r--r--third-party/pjproject/Makefile4
13 files changed, 166 insertions, 69 deletions
diff --git a/CHANGES b/CHANGES
index 00f318be8..5d14c97d1 100644
--- a/CHANGES
+++ b/CHANGES
@@ -123,6 +123,13 @@ app_voicemail
* Added 'fromstring' field to the voicemail boxes. If set, it will override
the global 'fromstring' field on a per-mailbox basis.
+func_channel
+------------------
+ * Added CHANNEL(callid) to retrieve the call log tag associated with the
+ channel. e.g., [C-00000000] Dialplan now has access to the call log
+ search key associated with the channel so it can be saved in case there
+ is a problem with the call.
+
res_pjsip
------------------
* A new transport parameter 'symmetric_transport' has been added.
@@ -139,6 +146,10 @@ res_pjsip
added to both transport and subscription_persistence, an alembic upgrade
should be run to bring the database tables up to date.
+ * A new option, allow_overlap, has been added to endpoints which allows
+ overlap dialing functionality to be enabled or disabled. The option defaults
+ to enabled.
+
res_pjsip_transport_websocket
------------------
* Removed non-secure websocket support. Firefox and Chrome have not allowed
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index 120a7ef1c..bb80768f5 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -595,6 +595,7 @@
; "yes")
;aggregate_mwi=yes ; (default: "yes")
;allow= ; Media Codec s to allow (default: "")
+;allow_overlap=yes ; Enable RFC3578 overlap dialing support. (default: "yes")
;aors= ; AoR s to be used with the endpoint (default: "")
;auth= ; Authentication Object s associated with the endpoint (default: "")
;callerid= ; CallerID information for the endpoint (default: "")
diff --git a/contrib/ast-db-manage/config/versions/8fce4c573e15_add_pjsip_allow_overlap.py b/contrib/ast-db-manage/config/versions/8fce4c573e15_add_pjsip_allow_overlap.py
new file mode 100644
index 000000000..24057ecc8
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/8fce4c573e15_add_pjsip_allow_overlap.py
@@ -0,0 +1,31 @@
+"""add pjsip allow_overlap
+
+Revision ID: 8fce4c573e15
+Revises: f638dbe2eb23
+Create Date: 2017-03-21 15:14:27.612945
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '8fce4c573e15'
+down_revision = 'f638dbe2eb23'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+YESNO_NAME = 'yesno_values'
+YESNO_VALUES = ['yes', 'no']
+
+def upgrade():
+ ############################# Enums ##############################
+
+ # yesno_values have already been created, so use postgres enum object
+ # type to get around "already created" issue - works okay with mysql
+ yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
+
+ op.add_column('ps_endpoints', sa.Column('allow_overlap', yesno_values))
+
+
+def downgrade():
+ op.drop_column('ps_endpoints', 'allow_overlap')
diff --git a/funcs/func_channel.c b/funcs/func_channel.c
index 27e9f41bf..eb3ceddb4 100644
--- a/funcs/func_channel.c
+++ b/funcs/func_channel.c
@@ -233,6 +233,10 @@
<enum name="max_forwards">
<para>R/W The maximum number of forwards allowed.</para>
</enum>
+ <enum name="callid">
+ <para>R/O Call identifier log tag associated with the channel
+ e.g., <literal>[C-00000000]</literal>.</para>
+ </enum>
</enumlist>
<xi:include xpointer="xpointer(/docs/info[@name='CHANNEL'])" />
</parameter>
@@ -450,6 +454,16 @@ static int func_channel_read(struct ast_channel *chan, const char *function,
ast_channel_lock(chan);
snprintf(buf, len, "%d", ast_max_forwards_get(chan));
ast_channel_unlock(chan);
+ } else if (!strcasecmp(data, "callid")) {
+ ast_callid callid;
+
+ buf[0] = '\0';
+ ast_channel_lock(chan);
+ callid = ast_channel_callid(chan);
+ if (callid) {
+ ast_callid_strnprint(buf, len, callid);
+ }
+ ast_channel_unlock(chan);
} else if (!ast_channel_tech(chan) || !ast_channel_tech(chan)->func_channel_read || ast_channel_tech(chan)->func_channel_read(chan, function, data, buf, len)) {
ast_log(LOG_WARNING, "Unknown or unavailable item requested: '%s'\n", data);
ret = -1;
diff --git a/include/asterisk/manager.h b/include/asterisk/manager.h
index 60c51de85..3de7b1d17 100644
--- a/include/asterisk/manager.h
+++ b/include/asterisk/manager.h
@@ -54,7 +54,7 @@
- \ref manager.c Main manager code file
*/
-#define AMI_VERSION "3.1.0"
+#define AMI_VERSION "3.2.0"
#define DEFAULT_MANAGER_PORT 5038 /* Default port for Asterisk management via TCP */
#define DEFAULT_MANAGER_TLS_PORT 5039 /* Default port for Asterisk management via TCP */
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index c6c308bee..6f44852b1 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -765,6 +765,8 @@ struct ast_sip_endpoint {
unsigned int preferred_codec_only;
/*! Do we allow an asymmetric RTP codec? */
unsigned int asymmetric_rtp_codec;
+ /*! Do we allow overlap dialling? */
+ unsigned int allow_overlap;
};
/*! URI parameter for symmetric transport */
diff --git a/main/audiohook.c b/main/audiohook.c
index 836ae0496..986f11f84 100644
--- a/main/audiohook.c
+++ b/main/audiohook.c
@@ -185,7 +185,7 @@ int ast_audiohook_write_frame(struct ast_audiohook *audiohook, enum ast_audiohoo
other_factory_samples = ast_slinfactory_available(other_factory);
other_factory_ms = other_factory_samples / (audiohook->hook_internal_samp_rate / 1000);
- if (ast_test_flag(audiohook, AST_AUDIOHOOK_TRIGGER_SYNC) && other_factory_samples && (our_factory_ms - other_factory_ms > AST_AUDIOHOOK_SYNC_TOLERANCE)) {
+ if (ast_test_flag(audiohook, AST_AUDIOHOOK_TRIGGER_SYNC) && (our_factory_ms - other_factory_ms > AST_AUDIOHOOK_SYNC_TOLERANCE)) {
ast_debug(1, "Flushing audiohook %p so it remains in sync\n", audiohook);
ast_slinfactory_flush(factory);
ast_slinfactory_flush(other_factory);
diff --git a/makeopts.in b/makeopts.in
index 6a1164c32..5bc5258da 100644
--- a/makeopts.in
+++ b/makeopts.in
@@ -28,7 +28,7 @@ WGET=@WGET@
FETCH=@FETCH@
DOWNLOAD=@DOWNLOAD@
DOWNLOAD_TO_STDOUT=@DOWNLOAD_TO_STDOUT@
-DOWNLOAD_MAX_TIMEOUT=@DOWNLOAD_MAX_TIMEOUT@
+DOWNLOAD_TIMEOUT=@DOWNLOAD_TIMEOUT@
SOUNDS_CACHE_DIR=@SOUNDS_CACHE_DIR@
EXTERNALS_CACHE_DIR=@EXTERNALS_CACHE_DIR@
RUBBER=@RUBBER@
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 962c4be4f..e4bcb7038 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -100,6 +100,9 @@
<configOption name="allow">
<synopsis>Media Codec(s) to allow</synopsis>
</configOption>
+ <configOption name="allow_overlap" default="yes">
+ <synopsis>Enable RFC3578 overlap dialing support.</synopsis>
+ </configOption>
<configOption name="aors">
<synopsis>AoR(s) to be used with the endpoint</synopsis>
<description><para>
@@ -2134,6 +2137,9 @@
<parameter name="SubscribeContext">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='subscribe_context']/synopsis/node())"/></para>
</parameter>
+ <parameter name="Allowoverlap">
+ <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='allow_overlap']/synopsis/node())"/></para>
+ </parameter>
</syntax>
</managerEventInstance>
</managerEvent>
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index c8ff42708..02562e782 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -1938,6 +1938,7 @@ int ast_res_pjsip_initialize_configuration(void)
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "preferred_codec_only", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, preferred_codec_only));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "asymmetric_rtp_codec", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, asymmetric_rtp_codec));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtcp_mux", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtcp_mux));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_overlap", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_overlap));
if (ast_sip_initialize_sorcery_transport()) {
ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index de073d304..5f42dab9f 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -1986,10 +1986,17 @@ static enum sip_get_destination_result get_destination(struct ast_sip_session *s
return SIP_GET_DEST_EXTEN_FOUND;
}
- /* XXX In reality, we'll likely have further options so that partial matches
- * can be indicated here, but for getting something up and running, we're going
- * to return a "not exists" error here.
+
+ /*
+ * Check for partial match via overlap dialling (if enabled)
*/
+ if (session->endpoint->allow_overlap && (
+ !strncmp(session->exten, pickupexten, strlen(session->exten)) ||
+ ast_canmatch_extension(NULL, session->endpoint->context, session->exten, 1, NULL))) {
+ /* Overlap partial match */
+ return SIP_GET_DEST_EXTEN_PARTIAL;
+ }
+
return SIP_GET_DEST_EXTEN_NOT_FOUND;
}
@@ -2106,8 +2113,17 @@ static int new_invite(void *data)
pjsip_inv_terminate(invite->session->inv_session, 416, PJ_TRUE);
}
goto end;
- case SIP_GET_DEST_EXTEN_NOT_FOUND:
case SIP_GET_DEST_EXTEN_PARTIAL:
+ ast_debug(1, "Call from '%s' (%s:%s:%d) to extension '%s' - partial match\n", ast_sorcery_object_get_id(invite->session->endpoint),
+ invite->rdata->tp_info.transport->type_name, invite->rdata->pkt_info.src_name, invite->rdata->pkt_info.src_port, invite->session->exten);
+
+ if (pjsip_inv_initial_answer(invite->session->inv_session, invite->rdata, 484, NULL, NULL, &tdata) == PJ_SUCCESS) {
+ ast_sip_session_send_response(invite->session, tdata);
+ } else {
+ pjsip_inv_terminate(invite->session->inv_session, 484, PJ_TRUE);
+ }
+ goto end;
+ case SIP_GET_DEST_EXTEN_NOT_FOUND:
default:
ast_log(LOG_NOTICE, "Call from '%s' (%s:%s:%d) to extension '%s' rejected because extension not found in context '%s'.\n",
ast_sorcery_object_get_id(invite->session->endpoint), invite->rdata->tp_info.transport->type_name, invite->rdata->pkt_info.src_name,
diff --git a/res/res_xmpp.c b/res/res_xmpp.c
index 1aa865cd6..f4a5d8e05 100644
--- a/res/res_xmpp.c
+++ b/res/res_xmpp.c
@@ -1630,6 +1630,35 @@ static int xmpp_resource_immediate(void *obj, void *arg, int flags)
return CMP_MATCH | CMP_STOP;
}
+#define BUDDY_OFFLINE 6
+#define BUDDY_NOT_IN_ROSTER 7
+
+static int get_buddy_status(struct ast_xmpp_client_config *clientcfg, char *screenname, char *resource)
+{
+ int status = BUDDY_OFFLINE;
+ struct ast_xmpp_resource *res;
+ struct ast_xmpp_buddy *buddy = ao2_find(clientcfg->client->buddies, screenname, OBJ_KEY);
+
+ if (!buddy) {
+ return BUDDY_NOT_IN_ROSTER;
+ }
+
+ res = ao2_callback(
+ buddy->resources,
+ 0,
+ ast_strlen_zero(resource) ? xmpp_resource_immediate : xmpp_resource_cmp,
+ resource);
+
+ if (res) {
+ status = res->status;
+ }
+
+ ao2_cleanup(res);
+ ao2_cleanup(buddy);
+
+ return status;
+}
+
/*
* \internal
* \brief Dial plan function status(). puts the status of watched user
@@ -1643,10 +1672,7 @@ static int xmpp_status_exec(struct ast_channel *chan, const char *data)
{
RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup);
- struct ast_xmpp_buddy *buddy;
- struct ast_xmpp_resource *resource;
char *s = NULL, status[2];
- int stat = 7;
static int deprecation_warning = 0;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(sender);
@@ -1685,25 +1711,7 @@ static int xmpp_status_exec(struct ast_channel *chan, const char *data)
return -1;
}
- if (!(buddy = ao2_find(clientcfg->client->buddies, jid.screenname, OBJ_KEY))) {
- ast_log(LOG_WARNING, "Could not find buddy in list: '%s'\n", jid.screenname);
- return -1;
- }
-
- if (ast_strlen_zero(jid.resource) || !(resource = ao2_callback(buddy->resources, 0, xmpp_resource_cmp, jid.resource))) {
- resource = ao2_callback(buddy->resources, OBJ_NODATA, xmpp_resource_immediate, NULL);
- }
-
- ao2_ref(buddy, -1);
-
- if (resource) {
- stat = resource->status;
- ao2_ref(resource, -1);
- } else {
- ast_log(LOG_NOTICE, "Resource '%s' of buddy '%s' was not found\n", jid.resource, jid.screenname);
- }
-
- snprintf(status, sizeof(status), "%d", stat);
+ snprintf(status, sizeof(status), "%d", get_buddy_status(clientcfg, jid.screenname, jid.resource));
pbx_builtin_setvar_helper(chan, args.variable, status);
return 0;
@@ -1722,9 +1730,6 @@ static int acf_jabberstatus_read(struct ast_channel *chan, const char *name, cha
{
RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup);
- struct ast_xmpp_buddy *buddy;
- struct ast_xmpp_resource *resource;
- int stat = 7;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(sender);
AST_APP_ARG(jid);
@@ -1756,25 +1761,7 @@ static int acf_jabberstatus_read(struct ast_channel *chan, const char *name, cha
return -1;
}
- if (!(buddy = ao2_find(clientcfg->client->buddies, jid.screenname, OBJ_KEY))) {
- ast_log(LOG_WARNING, "Could not find buddy in list: '%s'\n", jid.screenname);
- return -1;
- }
-
- if (ast_strlen_zero(jid.resource) || !(resource = ao2_callback(buddy->resources, 0, xmpp_resource_cmp, jid.resource))) {
- resource = ao2_callback(buddy->resources, OBJ_NODATA, xmpp_resource_immediate, NULL);
- }
-
- ao2_ref(buddy, -1);
-
- if (resource) {
- stat = resource->status;
- ao2_ref(resource, -1);
- } else {
- ast_log(LOG_NOTICE, "Resource %s of buddy %s was not found.\n", jid.resource, jid.screenname);
- }
-
- snprintf(buf, buflen, "%d", stat);
+ snprintf(buf, buflen, "%d", get_buddy_status(clientcfg, jid.screenname, jid.resource));
return 0;
}
@@ -2562,10 +2549,16 @@ static void xmpp_log_hook(void *data, const char *xmpp, size_t size, int incomin
static int xmpp_client_send_raw_message(struct ast_xmpp_client *client, const char *message)
{
int ret;
-#ifdef HAVE_OPENSSL
- int len = strlen(message);
+ if (client->state == XMPP_STATE_DISCONNECTED) {
+ /* iks_send_raw will crash without a connection */
+ return IKS_NET_NOCONN;
+ }
+
+#ifdef HAVE_OPENSSL
if (xmpp_is_secure(client)) {
+ int len = strlen(message);
+
ret = SSL_write(client->ssl_session, message, len);
if (ret) {
/* Log the message here, because iksemel's logHook is
@@ -2629,12 +2622,31 @@ static int xmpp_client_request_tls(struct ast_xmpp_client *client, struct ast_xm
#endif
}
+#ifdef HAVE_OPENSSL
+static char *openssl_error_string(void)
+{
+ char *buf = NULL, *ret;
+ size_t len;
+ BIO *bio = BIO_new(BIO_s_mem());
+
+ ERR_print_errors(bio);
+ len = BIO_get_mem_data(bio, &buf);
+ ret = ast_calloc(1, len + 1);
+ if (ret) {
+ memcpy(ret, buf, len);
+ }
+ BIO_free(bio);
+ return ret;
+}
+#endif
+
/*! \brief Internal function called when we receive a response to our TLS initiation request */
static int xmpp_client_requested_tls(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node)
{
#ifdef HAVE_OPENSSL
int sock;
long ssl_opts;
+ char *err;
#endif
if (!strcmp(iks_name(node), "success")) {
@@ -2670,7 +2682,7 @@ static int xmpp_client_requested_tls(struct ast_xmpp_client *client, struct ast_
goto failure;
}
- if (!SSL_connect(client->ssl_session)) {
+ if (SSL_connect(client->ssl_session) <= 0) {
goto failure;
}
@@ -2690,7 +2702,10 @@ static int xmpp_client_requested_tls(struct ast_xmpp_client *client, struct ast_
return 0;
failure:
- ast_log(LOG_ERROR, "TLS connection for client '%s' cannot be established. OpenSSL initialization failed.\n", client->name);
+ err = openssl_error_string();
+ ast_log(LOG_ERROR, "TLS connection for client '%s' cannot be established. "
+ "OpenSSL initialization failed: %s\n", client->name, err);
+ ast_free(err);
return -1;
#endif
}
@@ -3752,12 +3767,12 @@ static void *xmpp_client_thread(void *data)
do {
if (client->state == XMPP_STATE_DISCONNECTING) {
- ast_debug(1, "JABBER: Disconnecting client '%s'\n", client->name);
+ ast_debug(1, "[%s] Disconnecting\n", client->name);
break;
}
if (res == IKS_NET_RWERR || client->timeout == 0) {
- ast_debug(3, "Connecting client '%s'\n", client->name);
+ ast_debug(3, "[%s] Connecting\n", client->name);
if ((res = xmpp_client_reconnect(client)) != IKS_OK) {
sleep(4);
res = IKS_NET_RWERR;
@@ -3774,9 +3789,9 @@ static void *xmpp_client_thread(void *data)
}
if (res == IKS_HOOK) {
- ast_debug(2, "JABBER: Got hook event.\n");
+ ast_debug(2, "[%s] Got hook event\n", client->name);
} else if (res == IKS_NET_TLSFAIL) {
- ast_log(LOG_ERROR, "JABBER: Failure in TLS.\n");
+ ast_log(LOG_ERROR, "[%s] TLS failure\n", client->name);
} else if (!client->timeout && client->state == XMPP_STATE_CONNECTED) {
RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup);
@@ -3794,22 +3809,22 @@ static void *xmpp_client_thread(void *data)
if (res == IKS_OK) {
client->timeout = 50;
} else {
- ast_log(LOG_WARNING, "JABBER: Network Timeout\n");
+ ast_log(LOG_WARNING, "[%s] Network timeout\n", client->name);
}
} else if (res == IKS_NET_RWERR) {
- ast_log(LOG_WARNING, "JABBER: socket read error\n");
+ ast_log(LOG_WARNING, "[%s] Socket read error\n", client->name);
} else if (res == IKS_NET_NOSOCK) {
- ast_log(LOG_WARNING, "JABBER: No Socket\n");
+ ast_log(LOG_WARNING, "[%s] No socket\n", client->name);
} else if (res == IKS_NET_NOCONN) {
- ast_log(LOG_WARNING, "JABBER: No Connection\n");
+ ast_log(LOG_WARNING, "[%s] No connection\n", client->name);
} else if (res == IKS_NET_NODNS) {
- ast_log(LOG_WARNING, "JABBER: No DNS\n");
+ ast_log(LOG_WARNING, "[%s] No DNS\n", client->name);
} else if (res == IKS_NET_NOTSUPP) {
- ast_log(LOG_WARNING, "JABBER: Not Supported\n");
+ ast_log(LOG_WARNING, "[%s] Not supported\n", client->name);
} else if (res == IKS_NET_DROPPED) {
- ast_log(LOG_WARNING, "JABBER: Dropped?\n");
+ ast_log(LOG_WARNING, "[%s] Dropped?\n", client->name);
} else if (res == IKS_NET_UNKNOWN) {
- ast_debug(5, "JABBER: Unknown\n");
+ ast_debug(5, "[%s] Unknown\n", client->name);
}
} while (1);
diff --git a/third-party/pjproject/Makefile b/third-party/pjproject/Makefile
index 99c22fa8b..e691f2242 100644
--- a/third-party/pjproject/Makefile
+++ b/third-party/pjproject/Makefile
@@ -96,9 +96,9 @@ endef
define download_from_pjproject
($(SHELL_ECHO_PREFIX) Downloading $(TARBALL_URL) to $(TARBALL) ;\
- $(DOWNLOAD_TO_STDOUT) $(call DOWNLOAD_TIMEOUT,5,10) $(TARBALL_URL) > $(TARBALL) &&\
+ $(DOWNLOAD_TO_STDOUT) $(call DOWNLOAD_TIMEOUT,5,60) $(TARBALL_URL) > $(TARBALL) &&\
$(SHELL_ECHO_PREFIX) Downloading $(PJPROJECT_URL)/MD5SUM to $(PJMD5SUM) &&\
- $(DOWNLOAD_TO_STDOUT) $(call DOWNLOAD_TIMEOUT,5,10) $(PJPROJECT_URL)/MD5SUM.TXT > $(PJMD5SUM) &&\
+ $(DOWNLOAD_TO_STDOUT) $(call DOWNLOAD_TIMEOUT,5,60) $(PJPROJECT_URL)/MD5SUM.TXT > $(PJMD5SUM) &&\
$(verify_tarball))
endef