From 1c156b82d16bf1fa2013cafe900f3081415aab9a Mon Sep 17 00:00:00 2001
From: Matteo Brancaleoni <mbrancaleoni@espia.it>
Date: Sun, 16 Feb 2003 06:00:12 +0000
Subject: [PATCH] Sun Feb 16 07:00:01 CET 2003

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@616 65c4cc65-6c06-0410-ace0-fbb531ad65f3
---
 CHANGES                   |   6 +
 Makefile                  |   2 +-
 apps/Makefile             |   3 +-
 apps/app_authenticate.c   | 154 ++++++++++++++++++++++++++
 channels/chan_mgcp.c      |  61 +++++++++-
 channels/chan_sip.c       | 227 +++++++++++++++++++++++++++++++-------
 configs/sip.conf.sample   |   2 +
 frame.c                   |  33 ++++++
 include/asterisk/rtp.h    |  15 +++
 rtp.c                     | 204 +++++++++++++++++++++++++++++++++-
 sounds.txt                |   2 +
 sounds/auth-incorrect.gsm | Bin 0 -> 7524 bytes
 12 files changed, 660 insertions(+), 49 deletions(-)
 create mode 100755 apps/app_authenticate.c
 create mode 100755 sounds/auth-incorrect.gsm

diff --git a/CHANGES b/CHANGES
index 55cd0d7d57..d190dd4bf6 100755
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,9 @@
+ -- Make HOLD on SIP make use of asterisk MOH
+ -- Add supervised transfer (tested with Pingtel only)
+ -- Allow maxexpirey and defaultexpirey to be runtime configurable for SIP
+ -- Preliminary codec 13 support (RFC3389)
+ -- Add app_authenticate for general purpose authentication
+ -- Optimize RTP and smoother
  -- Create special variable "EXTEN-n" where it is extension stripped by n MSD
  -- Fix uninitialized frame pointer in channel.c
  -- Add global variables support under [globals] of extensions.conf
diff --git a/Makefile b/Makefile
index 1d2bb9993c..d62c6a0793 100755
--- a/Makefile
+++ b/Makefile
@@ -144,7 +144,7 @@ datafiles: all
 			exit 1; \
 		fi; \
 	done
-	for x in sounds/vm-* sounds/transfer* sounds/pbx-* sounds/ss-* sounds/beep* sounds/dir-* sounds/conf-* sounds/agent-* sounds/invalid* sounds/tt-*; do \
+	for x in sounds/vm-* sounds/transfer* sounds/pbx-* sounds/ss-* sounds/beep* sounds/dir-* sounds/conf-* sounds/agent-* sounds/invalid* sounds/tt-* sounds/auth-*; do \
 		if grep -q "^%`basename $$x`%" sounds.txt; then \
 			install $$x $(ASTVARLIBDIR)/sounds ; \
 		else \
diff --git a/apps/Makefile b/apps/Makefile
index 95f7275319..1d3ad29ad4 100755
--- a/apps/Makefile
+++ b/apps/Makefile
@@ -17,7 +17,8 @@ APPS=app_dial.so app_playback.so app_voicemail.so app_directory.so app_intercom.
      app_agi.so app_qcall.so app_adsiprog.so app_getcpeid.so app_milliwatt.so \
      app_zapateller.so app_datetime.so app_setcallerid.so app_festival.so \
      app_queue.so app_senddtmf.so app_parkandannounce.so app_striplsd.so \
-     app_setcidname.so app_lookupcidname.so app_substring.so app_macro.so
+     app_setcidname.so app_lookupcidname.so app_substring.so app_macro.so \
+     app_authenticate.so
 
 #APPS+=app_sql_postgres.so
 #APPS+=app_sql_odbc.so
diff --git a/apps/app_authenticate.c b/apps/app_authenticate.c
new file mode 100755
index 0000000000..69d2443511
--- /dev/null
+++ b/apps/app_authenticate.c
@@ -0,0 +1,154 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Execute arbitrary authenticate commands
+ * 
+ * Copyright (C) 1999, Mark Spencer
+ *
+ * Mark Spencer <markster@linux-support.net>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#include <asterisk/lock.h>
+#include <asterisk/file.h>
+#include <asterisk/logger.h>
+#include <asterisk/channel.h>
+#include <asterisk/pbx.h>
+#include <asterisk/module.h>
+#include <asterisk/app.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include <pthread.h>
+
+
+static char *tdesc = "Authentication Application";
+
+static char *app = "Authenticate";
+
+static char *synopsis = "Authenticate a user";
+
+static char *descrip =
+"  Authenticate(password[|options]): Requires a user to enter a"
+"given password in order to continue execution.  If the\n"
+"password begins with the '/' character, it is interpreted as\n"
+"a file which contains a list of valid passwords (1 per line).\n"
+"an optional set of opions may be provided by concatenating any\n"
+"of the following letters:\n"
+"     a - Set account code to the password that is entered\n"
+"\n"
+"Returns 0 if the user enters a valid password within three\n"
+"tries, or -1 otherwise (or on hangup).\n";
+
+STANDARD_LOCAL_USER;
+
+LOCAL_USER_DECL;
+
+static int auth_exec(struct ast_channel *chan, void *data)
+{
+	int res=0;
+	int retries;
+	struct localuser *u;
+	char password[256]="";
+	char passwd[256];
+	char *opts;
+	char *prompt;
+	if (!data || !strlen(data)) {
+		ast_log(LOG_WARNING, "Authenticate requires an argument(password)\n");
+		return -1;
+	}
+	LOCAL_USER_ADD(u);
+	if (chan->_state != AST_STATE_UP) {
+		res = ast_answer(chan);
+		if (res) {
+			LOCAL_USER_REMOVE(u);
+			return -1;
+		}
+	}
+	strncpy(password, data, sizeof(password) - 1);
+	opts=strchr(password, '|');
+	if (opts) {
+		*opts = 0;
+		opts++;
+	} else
+		opts = "";
+	/* Start asking for password */
+	prompt = "agent-pass";
+	for (retries = 0; retries < 3; retries++) {
+		res = ast_app_getdata(chan, prompt, passwd, sizeof(passwd) - 2, 0);
+		if (res < 0)
+			break;
+		res = 0;
+		if (password[0] == '/') {
+			/* Compare against a file */
+			char tmp[80];
+			FILE *f;
+			f = fopen(password, "r");
+			if (f) {
+				char buf[256] = "";
+				while(!feof(f)) {
+					fgets(buf, sizeof(buf), f);
+					if (!feof(f) && strlen(buf)) {
+						buf[strlen(buf) - 1] = '\0';
+						if (strlen(buf) && !strcmp(passwd, buf))
+							break;
+					}
+				}
+				fclose(f);
+				if (strlen(buf) && !strcmp(passwd, buf))
+					break;
+			} else 
+				ast_log(LOG_WARNING, "Unable to open file '%s' for authentication: %s\n", password, strerror(errno));
+		} else {
+			/* Compare against a fixed password */
+			if (!strcmp(passwd, password)) 
+				break;
+		}
+		prompt="auth-incorrect";
+	}
+	if ((retries < 3) && !res) {
+		if (strchr(opts, 'a')) 
+			ast_cdr_setaccount(chan, passwd);
+	} else {
+		if (!res)
+			res = ast_streamfile(chan, "vm-goodbye", chan->language);
+		if (!res)
+			res = ast_waitstream(chan, "");
+		res = -1;
+	}
+	LOCAL_USER_REMOVE(u);
+	return res;
+}
+
+int unload_module(void)
+{
+	STANDARD_HANGUP_LOCALUSERS;
+	return ast_unregister_application(app);
+}
+
+int load_module(void)
+{
+	return ast_register_application(app, auth_exec, synopsis, descrip);
+}
+
+char *description(void)
+{
+	return tdesc;
+}
+
+int usecount(void)
+{
+	int res;
+	STANDARD_USECOUNT(res);
+	return res;
+}
+
+char *key()
+{
+	return ASTERISK_GPL_KEY;
+}
diff --git a/channels/chan_mgcp.c b/channels/chan_mgcp.c
index 88a0d2cf01..08ef81499b 100755
--- a/channels/chan_mgcp.c
+++ b/channels/chan_mgcp.c
@@ -131,6 +131,7 @@ struct mgcp_endpoint {
 	int outgoing;
 	struct ast_channel *owner;
 	struct ast_rtp *rtp;
+	struct sockaddr_in tmpdest;
 	struct mgcp_endpoint *next;
 	struct mgcp_gateway *parent;
 };
@@ -317,6 +318,7 @@ static int mgcp_hangup(struct ast_channel *ast)
 	p->owner = NULL;
 	if (strlen(p->cxident))
 		transmit_connection_del(p);
+	strcpy(p->cxident, "");
 	if (!p->alreadygone && (!p->outgoing || (ast->_state == AST_STATE_UP)))
 		transmit_notify_request(p, "ro", 1);
 	else
@@ -324,8 +326,9 @@ static int mgcp_hangup(struct ast_channel *ast)
 	ast->pvt->pvt = NULL;
 	p->alreadygone = 0;
 	p->outgoing = 0;
-	strcpy(p->cxident, "");
 	strcpy(p->callid, "");
+	/* Reset temporary destination */
+	memset(&p->tmpdest, 0, sizeof(p->tmpdest));
 	if (p->rtp) {
 		ast_rtp_destroy(p->rtp);
 		p->rtp = NULL;
@@ -515,6 +518,7 @@ static struct ast_channel *mgcp_new(struct mgcp_endpoint *i, int state)
 		tmp->pvt->indicate = mgcp_indicate;
 		tmp->pvt->fixup = mgcp_fixup;
 		tmp->pvt->send_digit = mgcp_senddigit;
+		tmp->pvt->bridge = ast_rtp_bridge;
 		if (strlen(i->language))
 			strncpy(tmp->language, i->language, sizeof(tmp->language)-1);
 		i->owner = tmp;
@@ -951,8 +955,15 @@ static int add_sdp(struct mgcp_request *resp, struct mgcp_endpoint *p, struct as
 	if (rtp) {
 		ast_rtp_get_peer(rtp, &dest);
 	} else {
-		dest.sin_addr = p->parent->ourip;
-		dest.sin_port = sin.sin_port;
+		if (p->tmpdest.sin_addr.s_addr) {
+			dest.sin_addr = p->tmpdest.sin_addr;
+			dest.sin_port = p->tmpdest.sin_port;
+			/* Reset temporary destination */
+			memset(&p->tmpdest, 0, sizeof(p->tmpdest));
+		} else {
+			dest.sin_addr = p->parent->ourip;
+			dest.sin_port = sin.sin_port;
+		}
 	}
 	printf("We're at %s port %d\n", inet_ntoa(p->parent->ourip), ntohs(sin.sin_port));	
 	snprintf(v, sizeof(v), "v=0\r\n");
@@ -991,6 +1002,12 @@ static int transmit_modify_with_sdp(struct mgcp_endpoint *p, struct ast_rtp *rtp
 	char local[256];
 	char tmp[80];
 	int x;
+	if (!strlen(p->cxident) && rtp) {
+		/* We don't have a CXident yet, store the destination and
+		   wait a bit */
+		ast_rtp_get_peer(rtp, &p->tmpdest);
+		return 0;
+	}
 	snprintf(local, sizeof(local), "p:20");
 	for (x=1;x<= AST_FORMAT_MAX_AUDIO; x <<= 1) {
 		if (p->capability & x) {
@@ -1003,6 +1020,7 @@ static int transmit_modify_with_sdp(struct mgcp_endpoint *p, struct ast_rtp *rtp
 	add_header(&resp, "L", local);
 	add_header(&resp, "M", "sendrecv");
 	add_header(&resp, "X", p->txident);
+	add_header(&resp, "I", p->cxident);
 	add_header(&resp, "S", "");
 	add_sdp(&resp, p, rtp);
 	p->lastout = oseq;
@@ -1278,8 +1296,14 @@ static int mgcpsock_read(int *id, int fd, short events, void *ignore)
 		p = find_endpoint(NULL, ident, &sin);
 		if (p) {
 			handle_response(p, result, ident);
-			if ((c = get_header(&req, "I")))
-				strncpy(p->cxident, c, sizeof(p->cxident) - 1);
+			if ((c = get_header(&req, "I"))) {
+				if (strlen(c)) {
+					strncpy(p->cxident, c, sizeof(p->cxident) - 1);
+					if (p->tmpdest.sin_addr.s_addr) {
+						transmit_modify_with_sdp(p, NULL);
+					}
+				}
+			}
 			if (req.lines)
 				process_sdp(p, &req);
 		}
@@ -1483,6 +1507,31 @@ struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v)
 	return gw;
 }
 
+static struct ast_rtp *mgcp_get_rtp_peer(struct ast_channel *chan)
+{
+	struct mgcp_endpoint *p;
+	p = chan->pvt->pvt;
+	if (p && p->rtp)
+		return p->rtp;
+	return NULL;
+}
+
+static int mgcp_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp)
+{
+	struct mgcp_endpoint *p;
+	p = chan->pvt->pvt;
+	if (p) {
+		transmit_modify_with_sdp(p, rtp);
+		return 0;
+	}
+	return -1;
+}
+
+static struct ast_rtp_protocol mgcp_rtp = {
+	get_rtp_info: mgcp_get_rtp_peer,
+	set_rtp_peer: mgcp_set_rtp_peer,
+};
+
 int load_module()
 {
 	struct ast_config *cfg;
@@ -1590,6 +1639,8 @@ int load_module()
 		ast_destroy(cfg);
 		return -1;
 	}
+	mgcp_rtp.type = type;
+	ast_rtp_proto_register(&mgcp_rtp);
 	ast_cli_register(&cli_show_endpoints);
 	/* And start the monitor for the first time */
 	restart_monitor();
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index b9f235c3cc..a057eb89ef 100755
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -32,6 +32,7 @@
 #include <asterisk/cli.h>
 #include <asterisk/md5.h>
 #include <asterisk/app.h>
+#include <asterisk/musiconhold.h>
 #include <sys/socket.h>
 #include <sys/ioctl.h>
 #include <net/if.h>
@@ -47,8 +48,12 @@
 /* #define VOCAL_DATA_HACK */
 
 #define SIPDUMPER
-#define DEFAULT_EXPIREY 120
-#define MAX_EXPIREY     3600
+#define DEFAULT_DEFAULT_EXPIREY 120
+#define DEFAULT_MAX_EXPIREY     3600
+
+static int max_expirey = DEFAULT_MAX_EXPIREY;
+static int default_expirey = DEFAULT_DEFAULT_EXPIREY;
+
 #define DEFAULT_MAXMS		2000		/* Must be faster than 2 seconds by default */
 
 #define DEFAULT_MAXMS		2000		/* Must be faster than 2 seconds by default */
@@ -142,6 +147,7 @@ static struct sip_pvt {
 	char refer_to[AST_MAX_EXTENSION];	/* Place to store REFER-TO extension */
 	char referred_by[AST_MAX_EXTENSION];/* Place to store REFERRED-BY extension */
 	char refer_contact[AST_MAX_EXTENSION];/* Place to store Contact info from a REFER extension */
+	struct sip_pvt *refer_call;			/* Call we are referring */
 	char record_route[256];
 	char record_route_info[256];
 	char remote_party_id[256];
@@ -728,6 +734,7 @@ static int sip_indicate(struct ast_channel *ast, int condition)
 }
 
 
+#if 0
 static int sip_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc)
 {
 	struct sip_pvt *p0, *p1;
@@ -742,6 +749,7 @@ static int sip_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags,
 	ast_pthread_mutex_lock(&c1->lock);
 	p0 = c0->pvt->pvt;
 	p1 = c1->pvt->pvt;
+	ast_log(LOG_DEBUG, "Reinvite? %s: %s, %s: %s\n", c0->name, p0->canreinvite ? "yes" : "no", c1->name, p1->canreinvite ? "yes" : "no");
 	if (!p0->canreinvite || !p1->canreinvite) {
 		/* Not gonna support reinvite */
 		ast_pthread_mutex_unlock(&c0->lock);
@@ -796,6 +804,7 @@ static int sip_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags,
 	}
 	return -1;
 }
+#endif
 
 static struct ast_channel *sip_new(struct sip_pvt *i, int state, char *title)
 {
@@ -808,7 +817,7 @@ static struct ast_channel *sip_new(struct sip_pvt *i, int state, char *title)
 			tmp->nativeformats = capability;
 		fmt = ast_best_codec(tmp->nativeformats);
 		if (title)
-			snprintf(tmp->name, sizeof(tmp->name), "SIP/%s", title);
+			snprintf(tmp->name, sizeof(tmp->name), "SIP/%s-%04x", title, rand() & 0xffff);
 		else
 			snprintf(tmp->name, sizeof(tmp->name), "SIP/%s:%d", inet_ntoa(i->sa.sin_addr), ntohs(i->sa.sin_port));
 		tmp->type = type;
@@ -830,7 +839,7 @@ static struct ast_channel *sip_new(struct sip_pvt *i, int state, char *title)
 		tmp->pvt->indicate = sip_indicate;
 		tmp->pvt->fixup = sip_fixup;
 		tmp->pvt->send_digit = sip_senddigit;
-		tmp->pvt->bridge = sip_bridge;
+		tmp->pvt->bridge = ast_rtp_bridge;
 		if (strlen(i->language))
 			strncpy(tmp->language, i->language, sizeof(tmp->language)-1);
 		i->owner = tmp;
@@ -1087,7 +1096,7 @@ static int sip_register(char *value, int lineno)
 		if (secret)
 			strncpy(reg->secret, secret, sizeof(reg->secret)-1);
 		reg->expire = -1;
-		reg->refresh = DEFAULT_EXPIREY;
+		reg->refresh = default_expirey;
 		reg->addr.sin_family = AF_INET;
 		memcpy(&reg->addr.sin_addr, hp->h_addr, sizeof(&reg->addr.sin_addr));
 		reg->addr.sin_port = porta ? htons(atoi(porta)) : htons(DEFAULT_SIP_PORT);
@@ -1237,11 +1246,21 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req)
 		ast_log(LOG_WARNING, "No compatible codecs!\n");
 		return -1;
 	}
-	if (p->owner && !(p->owner->nativeformats & p->capability)) {
-		ast_log(LOG_DEBUG, "Oooh, we need to change our formats since our peer supports only %d and not %d\n", p->capability, p->owner->nativeformats);
-		p->owner->nativeformats = p->capability;
-		ast_set_read_format(p->owner, p->owner->readformat);
-		ast_set_write_format(p->owner, p->owner->writeformat);
+	if (p->owner) {
+		if (p->owner->nativeformats & p->capability) {
+			ast_log(LOG_DEBUG, "Oooh, we need to change our formats since our peer supports only %d and not %d\n", p->capability, p->owner->nativeformats);
+			p->owner->nativeformats = p->capability;
+			ast_set_read_format(p->owner, p->owner->readformat);
+			ast_set_write_format(p->owner, p->owner->writeformat);
+		}
+		if (p->owner->bridge) {
+			/* Turn on/off music on hold if we are holding/unholding */
+			if (sin.sin_addr.s_addr) {
+				ast_moh_stop(p->owner->bridge);
+			} else {
+				ast_moh_start(p->owner->bridge, NULL);
+			}
+		}
 	}
 	return 0;
 	
@@ -1830,7 +1849,7 @@ static int transmit_register(struct sip_registry *r, char *cmd, char *auth)
 	if (auth) 
 		add_header(&req, "Authorization", auth);
 
-	snprintf(tmp, sizeof(tmp), "%d", DEFAULT_EXPIREY);
+	snprintf(tmp, sizeof(tmp), "%d", default_expirey);
 	add_header(&req, "Expires", tmp);
 	add_header(&req, "Event", "registration");
 	copy_request(&p->initreq, &req);
@@ -1933,8 +1952,8 @@ static int parse_contact(struct sip_pvt *pvt, struct sip_peer *p, struct sip_req
 		strcpy(p->username, "");
 	if (p->expire > -1)
 		ast_sched_del(sched, p->expire);
-	if ((expirey < 1) || (expirey > MAX_EXPIREY))
-		expirey = DEFAULT_EXPIREY;
+	if ((expirey < 1) || (expirey > max_expirey))
+		expirey = max_expirey;
 	p->expire = ast_sched_add(sched, expirey * 1000, expire_register, p);
 	pvt->expirey = expirey;
 	if (memcmp(&p->addr, &oldsin, sizeof(oldsin))) {
@@ -2130,7 +2149,9 @@ static int get_refer_info(struct sip_pvt *p, struct sip_request *oreq)
 	char tmp2[256] = "", *c2, *a2;
 	char tmp3[256];
 	char tmp4[256];
+	char tmp5[256] = "";		/* CallID to replace */
 	struct sip_request *req;
+	struct sip_pvt *p2;
 	
 	req = oreq;
 	if (!req)
@@ -2151,32 +2172,81 @@ static int get_refer_info(struct sip_pvt *p, struct sip_request *oreq)
 	}
 	c += 4;
 	c2 += 4;
-	if ((a = strchr(c, '@')) || (a = strchr(c, ';'))) {
+	if ((a = strchr(c, '?'))) {
+		/* Search for arguemnts */
 		*a = '\0';
+		a++;
+		if (!strncasecmp(a, "REPLACES=", strlen("REPLACES="))) {
+			strncpy(tmp5, a + strlen("REPLACES="), sizeof(tmp5) - 1);
+			if ((a = strchr(tmp5, '%'))) {
+				/* Yuck!  Pingtel converts the '@' to a %40, icky icky!  Convert
+				   back to an '@' */
+				if ((a[1] == '4') && (a[2] == '0')) {
+					*a = '@';
+					memmove(a + 1, a+3, strlen(a + 3));
+				}
+			}
+			if ((a = strchr(tmp5, '%'))) 
+				*a = '\0';
+		}
 	}
-	if ((a2 = strchr(c2, '@')) || (a2 = strchr(c2, ';'))) {	
+	
+	if ((a = strchr(c, '@')))
+		*a = '\0';
+	if ((a = strchr(c, ';'))) 
+		*a = '\0';
+	
+
+	if ((a2 = strchr(c2, '@')))
 		*a2 = '\0';
-	}
+
+	if ((a2 = strchr(c2, ';'))) 
+		*a2 = '\0';
+	
 	
 	if (sipdebug)
 		ast_verbose("Looking for %s in %s\n", c, p->context);
 		ast_verbose("Looking for %s in %s\n", c2, p->context);
-	
-	if (ast_exists_extension(NULL, p->context, c, 1, NULL) && ast_exists_extension(NULL, p->context, c2, 1, NULL)) {
-		if (!oreq)
-			ast_log(LOG_DEBUG,"Something is wrong with this line.\n");	//This line is ignored for some reason....
-			ast_log(LOG_DEBUG,"Assigning Extension %s to REFER-TO\n", c);
-			ast_log(LOG_DEBUG,"Assigning Extension %s to REFERRED-BY\n", c2);
-			ast_log(LOG_DEBUG,"Assigning Contact Info %s to REFER_CONTACT\n", tmp3);
-			ast_log(LOG_DEBUG,"Assigning Remote-Party-ID Info %s to REMOTE_PARTY_ID\n",tmp4);
-			strncpy(p->refer_to, c, sizeof(p->refer_to) - 1);
-			strncpy(p->referred_by, c2, sizeof(p->referred_by) - 1);
-			strncpy(p->refer_contact, tmp3, sizeof(p->refer_contact) - 1);
-			strncpy(p->remote_party_id, tmp4, sizeof(p->remote_party_id) - 1);
+		
+	if (strlen(tmp5)) {	
+		/* This is a supervised transfer */
+		ast_log(LOG_DEBUG,"Assigning Replace-Call-ID Info %s to REPLACE_CALL_ID\n",tmp5);
+		
+		strncpy(p->refer_to, "", sizeof(p->refer_to) - 1);
+		strncpy(p->referred_by, "", sizeof(p->referred_by) - 1);
+		strncpy(p->refer_contact, "", sizeof(p->refer_contact) - 1);
+		strncpy(p->remote_party_id, "", sizeof(p->remote_party_id) - 1);
+		p->refer_call = NULL;
+		ast_pthread_mutex_lock(&iflock);
+		/* Search interfaces and find the match */
+		p2 = iflist;
+		while(p2) {
+			if (!strcmp(p2->callid, tmp5)) {
+				/* Go ahead and lock it before returning */
+				ast_pthread_mutex_lock(&p2->lock);
+				p->refer_call = p2;
+				break;
+			}
+			p2 = p2->next;
+		}
+		ast_pthread_mutex_unlock(&iflock);
+		if (p->refer_call)
 			return 0;
-	}
-
-	if (ast_canmatch_extension(NULL, p->context, c, 1, NULL)) {
+		else
+			ast_log(LOG_NOTICE, "Supervised transfer requested, but unable to find callid '%s'\n", tmp5);
+	} else if (ast_exists_extension(NULL, p->context, c, 1, NULL) && ast_exists_extension(NULL, p->context, c2, 1, NULL)) {
+		/* This is an unsupervised transfer */
+		ast_log(LOG_DEBUG,"Assigning Extension %s to REFER-TO\n", c);
+		ast_log(LOG_DEBUG,"Assigning Extension %s to REFERRED-BY\n", c2);
+		ast_log(LOG_DEBUG,"Assigning Contact Info %s to REFER_CONTACT\n", tmp3);
+		ast_log(LOG_DEBUG,"Assigning Remote-Party-ID Info %s to REMOTE_PARTY_ID\n",tmp4);
+		strncpy(p->refer_to, c, sizeof(p->refer_to) - 1);
+		strncpy(p->referred_by, c2, sizeof(p->referred_by) - 1);
+		strncpy(p->refer_contact, tmp3, sizeof(p->refer_contact) - 1);
+		strncpy(p->remote_party_id, tmp4, sizeof(p->remote_party_id) - 1);
+		p->refer_call = NULL;
+		return 0;
+	} else if (ast_canmatch_extension(NULL, p->context, c, 1, NULL)) {
 		return 1;
 	}
 
@@ -2735,7 +2805,7 @@ retrylock:
 				if (r->expire != -1)
 					ast_sched_del(sched, r->expire);
 				expires=atoi(get_header(req, "expires"));
-				if (!expires) expires=DEFAULT_EXPIREY;
+				if (!expires) expires=default_expirey;
 					r->expire=ast_sched_add(sched, (expires-2)*1000, sip_reregister, r); 
 
 			}
@@ -2879,6 +2949,37 @@ static int determine_firstline_parts( struct sip_request *req ) {
   return 1;
 }
 
+static int attempt_transfer(struct sip_pvt *p1, struct sip_pvt *p2)
+{
+	if (!p1->owner || !p2->owner) {
+		ast_log(LOG_WARNING, "Transfer attempted without dual ownership?\n");
+		return -1;
+	}
+	if (p1->owner->bridge) {
+		if (p2->owner->bridge)
+			ast_moh_stop(p2->owner->bridge);
+		ast_moh_stop(p1->owner->bridge);
+		ast_moh_stop(p1->owner);
+		ast_moh_stop(p2->owner);
+		if (ast_channel_masquerade(p2->owner, p1->owner->bridge)) {
+			ast_log(LOG_WARNING, "Failed to masquerade %s into %s\n", p2->owner->name, p1->owner->bridge->name);
+			return -1;
+		}
+	} else if (p2->owner->bridge) {
+		ast_moh_stop(p2->owner->bridge);
+		ast_moh_stop(p2->owner);
+		ast_moh_stop(p1->owner);
+		if (ast_channel_masquerade(p1->owner, p2->owner->bridge)) {
+			ast_log(LOG_WARNING, "Failed to masquerade %s into %s\n", p1->owner->name, p2->owner->bridge->name);
+			return -1;
+		}
+	} else {
+		ast_log(LOG_NOTICE, "Transfer attempted with no bridged calls to transfer\n");
+		return -1;
+	}
+	return 0;
+}
+
 static int handle_request(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin)
 {
 	struct sip_request resp;
@@ -3048,16 +3149,23 @@ static int handle_request(struct sip_pvt *p, struct sip_request *req, struct soc
 			transmit_response_with_allow(p, "404 Not Found", req);
 		else if (res > 0)
 			transmit_response_with_allow(p, "484 Address Incomplete", req);
-		else
+		else {
 			transmit_response(p, "202 Accepted", req);
-		ast_log(LOG_DEBUG,"202 Accepted\n");
-		c = p->owner;
-		if (c) {
-			transfer_to = c->bridge;
-			if (transfer_to)
-				ast_async_goto(transfer_to,"", p->refer_to,1, 1);
+			if (p->refer_call) {
+				ast_log(LOG_DEBUG,"202 Accepted (supervised)\n");
+				attempt_transfer(p, p->refer_call);
+				ast_pthread_mutex_unlock(&p->refer_call->lock);
+				p->refer_call = NULL;
+			} else {
+				ast_log(LOG_DEBUG,"202 Accepted (blind)\n");
+				c = p->owner;
+				if (c) {
+					transfer_to = c->bridge;
+					if (transfer_to)
+						ast_async_goto(transfer_to,"", p->refer_to,1, 1);
+				}
+			}
 		}
-			
 	} else if (!strcasecmp(cmd, "CANCEL") || !strcasecmp(cmd, "BYE")) {
 		copy_request(&p->initreq, req);
 		p->alreadygone = 1;
@@ -3140,7 +3248,7 @@ static int sipsock_read(int *id, int fd, short events, void *ignore)
 		/* Must have at least two headers */
 		return 1;
 	}
-	/* Process request, with iflock held */
+	/* Process request, with netlock held */
 	ast_pthread_mutex_lock(&netlock);
 	p = find_call(&req, &sin);
 	if (p) {
@@ -3495,6 +3603,8 @@ static struct sip_peer *build_peer(char *name, struct ast_variable *v)
 			peer->expirey = expirey;
 		}
 		peer->capability = capability;
+		/* Assume can reinvite */
+		peer->canreinvite = 1;
 		while(v) {
 			if (!strcasecmp(v->name, "secret")) 
 				strncpy(peer->secret, v->value, sizeof(peer->secret)-1);
@@ -3619,6 +3729,14 @@ static int reload_config()
 			strncpy(context, v->value, sizeof(context)-1);
 		} else if (!strcasecmp(v->name, "language")) {
 			strncpy(language, v->value, sizeof(language)-1);
+		} else if (!strcasecmp(v->name, "maxexpirey")) {
+			max_expirey = atoi(v->value);
+			if (max_expirey < 1)
+				max_expirey = DEFAULT_MAX_EXPIREY;
+		} else if (!strcasecmp(v->name, "defaultexpirey")) {
+			default_expirey = atoi(v->value);
+			if (default_expirey < 1)
+				default_expirey = DEFAULT_DEFAULT_EXPIREY;
 		} else if (!strcasecmp(v->name, "bindaddr")) {
 			if (!(hp = gethostbyname(v->value))) {
 				ast_log(LOG_WARNING, "Invalid address: %s\n", v->value);
@@ -3743,6 +3861,31 @@ static int reload_config()
 	return 0;
 }
 
+static struct ast_rtp *sip_get_rtp_peer(struct ast_channel *chan)
+{
+	struct sip_pvt *p;
+	p = chan->pvt->pvt;
+	if (p && p->rtp && p->canreinvite)
+		return p->rtp;
+	return NULL;
+}
+
+static int sip_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp)
+{
+	struct sip_pvt *p;
+	p = chan->pvt->pvt;
+	if (p) {
+		transmit_reinvite_with_sdp(p, rtp);
+		return 0;
+	}
+	return -1;
+}
+
+static struct ast_rtp_protocol sip_rtp = {
+	get_rtp_info: sip_get_rtp_peer,
+	set_rtp_peer: sip_set_rtp_peer,
+};
+
 int load_module()
 {
 	int res;
@@ -3761,6 +3904,8 @@ int load_module()
 		ast_cli_register(&cli_show_registry);
 		ast_cli_register(&cli_debug);
 		ast_cli_register(&cli_no_debug);
+		sip_rtp.type = type;
+		ast_rtp_proto_register(&sip_rtp);
 		sched = sched_context_create();
 		if (!sched) {
 			ast_log(LOG_WARNING, "Unable to create schedule context\n");
diff --git a/configs/sip.conf.sample b/configs/sip.conf.sample
index ed0ed6f931..dad7c15010 100755
--- a/configs/sip.conf.sample
+++ b/configs/sip.conf.sample
@@ -7,6 +7,8 @@ bindaddr = 0.0.0.0		; Address to bind to
 context = default		; Default for incoming calls
 ;tos=lowdelay
 ;tos=184
+;maxexpirey=3600		; Max length of incoming registration we allow
+;defaultexpirey=120		; Default length of incoming/outoing registration
 
 ;[snomsip]
 ;type=friend
diff --git a/frame.c b/frame.c
index e37e816b3b..fb255d83b6 100755
--- a/frame.c
+++ b/frame.c
@@ -35,10 +35,12 @@ struct ast_smoother {
 	int size;
 	int format;
 	int readdata;
+	int optimizablestream;
 	float samplesperbyte;
 	struct ast_frame f;
 	char data[SMOOTHER_SIZE];
 	char framedata[SMOOTHER_SIZE + AST_FRIENDLY_OFFSET];
+	struct ast_frame *opt;
 	int len;
 };
 
@@ -76,6 +78,28 @@ int ast_smoother_feed(struct ast_smoother *s, struct ast_frame *f)
 		ast_log(LOG_WARNING, "Out of smoother space\n");
 		return -1;
 	}
+	if ((f->datalen == s->size) && !s->opt) {
+		if (!s->len) {
+			/* Optimize by sending the frame we just got
+			   on the next read, thus eliminating the douple
+			   copy */
+			s->opt = f;
+			return 0;
+		} else {
+			s->optimizablestream++;
+			if (s->optimizablestream > 10) {
+				/* For the past 10 rounds, we have input and output
+				   frames of the correct size for this smoother, yet
+				   we were unable to optimize because there was still
+				   some cruft left over.  Lets just drop the cruft so
+				   we can move to a fully optimized path */
+				s->len = 0;
+				s->opt = f;
+				return 0;
+			}
+		}
+	} else 
+		s->optimizablestream = 0;
 	memcpy(s->data + s->len, f->data, f->datalen);
 	s->len += f->datalen;
 	return 0;
@@ -83,6 +107,15 @@ int ast_smoother_feed(struct ast_smoother *s, struct ast_frame *f)
 
 struct ast_frame *ast_smoother_read(struct ast_smoother *s)
 {
+	struct ast_frame *opt;
+
+	/* IF we have an optimization frame, send it */
+	if (s->opt) {
+		opt = s->opt;
+		s->opt = NULL;
+		return opt;
+	}
+
 	/* Make sure we have enough data */
 	if (s->len < s->size) {
 		return NULL;
diff --git a/include/asterisk/rtp.h b/include/asterisk/rtp.h
index 8137eb38d1..30639a5cd3 100755
--- a/include/asterisk/rtp.h
+++ b/include/asterisk/rtp.h
@@ -17,6 +17,7 @@
 #include <asterisk/frame.h>
 #include <asterisk/io.h>
 #include <asterisk/sched.h>
+#include <asterisk/channel.h>
 
 #include <netinet/in.h>
 
@@ -24,6 +25,14 @@
 extern "C" {
 #endif
 
+struct ast_rtp_protocol {
+	struct ast_rtp *(*get_rtp_info)(struct ast_channel *chan);				/* Get RTP struct, or NULL if unwilling to transfer */
+	int (*set_rtp_peer)(struct ast_channel *chan, struct ast_rtp *peer);	/* Set RTP peer */
+	int (*get_rtp_willing)(struct ast_channel *chan);		/* Willing to native bridge */
+	char *type;
+	struct ast_rtp_protocol *next;
+};
+
 struct ast_rtp;
 
 typedef int (*ast_rtp_callback)(struct ast_rtp *rtp, struct ast_frame *f, void *data);
@@ -58,6 +67,12 @@ int rtp2ast(int id);
 
 char *ast2rtpn(int id);
 
+int ast_rtp_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc);
+
+int ast_rtp_proto_register(struct ast_rtp_protocol *proto);
+
+void ast_rtp_proto_unregister(struct ast_rtp_protocol *proto);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
diff --git a/rtp.c b/rtp.c
index 21bee69801..ca5d7a1f2e 100755
--- a/rtp.c
+++ b/rtp.c
@@ -30,6 +30,7 @@
 #include <asterisk/logger.h>
 #include <asterisk/options.h>
 #include <asterisk/channel.h>
+#include <asterisk/channel_pvt.h>
 
 #define TYPE_SILENCE	 0x2
 #define TYPE_HIGH	 0x0
@@ -47,6 +48,7 @@ struct ast_rtp {
 	unsigned int lastts;
 	unsigned int lastrxts;
 	int lasttxformat;
+	int lastrxformat;
 	int dtmfcount;
 	struct sockaddr_in us;
 	struct sockaddr_in them;
@@ -61,6 +63,8 @@ struct ast_rtp {
 	ast_rtp_callback callback;
 };
 
+static struct ast_rtp_protocol *protos = NULL;
+
 int ast_rtp_fd(struct ast_rtp *rtp)
 {
 	return rtp->s;
@@ -151,6 +155,49 @@ static struct ast_frame *process_rfc2833(struct ast_rtp *rtp, unsigned char *dat
 	return f;
 }
 
+static struct ast_frame *process_rfc3389(struct ast_rtp *rtp, unsigned char *data, int len)
+{
+	struct ast_frame *f = NULL;
+	/* Convert comfort noise into audio with various codecs.  Unfortunately this doesn't
+	   totally help us out becuase we don't have an engine to keep it going and we are not
+	   guaranteed to have it every 20ms or anything */
+#if 0
+	printf("RFC3389: %d bytes, format is %d\n", len, rtp->lastrxformat);
+#endif	
+	ast_log(LOG_NOTICE, "RFC3389 support incomplete.  Turn off on client if possible\n");
+	if (!rtp->lastrxformat)
+		return 	NULL;
+	switch(rtp->lastrxformat) {
+	case AST_FORMAT_ULAW:
+		rtp->f.frametype = AST_FRAME_VOICE;
+		rtp->f.subclass = AST_FORMAT_ULAW;
+		rtp->f.datalen = 160;
+		rtp->f.samples = 160;
+		memset(rtp->f.data, 0x7f, rtp->f.datalen);
+		f = &rtp->f;
+		break;
+	case AST_FORMAT_ALAW:
+		rtp->f.frametype = AST_FRAME_VOICE;
+		rtp->f.subclass = AST_FORMAT_ALAW;
+		rtp->f.datalen = 160;
+		rtp->f.samples = 160;
+		memset(rtp->f.data, 0x7e, rtp->f.datalen); /* XXX Is this right? XXX */
+		f = &rtp->f;
+		break;
+	case AST_FORMAT_SLINEAR:
+		rtp->f.frametype = AST_FRAME_VOICE;
+		rtp->f.subclass = AST_FORMAT_SLINEAR;
+		rtp->f.datalen = 320;
+		rtp->f.samples = 160;
+		memset(rtp->f.data, 0x00, rtp->f.datalen);
+		f = &rtp->f;
+		break;
+	default:
+		ast_log(LOG_NOTICE, "Don't know how to handle RFC3389 for receive codec %d\n", rtp->lastrxformat);
+	}
+	return f;
+}
+
 static struct ast_frame *process_type121(struct ast_rtp *rtp, unsigned char *data, int len)
 {
 	char resp = 0;
@@ -247,6 +294,8 @@ struct ast_frame *ast_rtp_read(struct ast_rtp *rtp)
 		} else if (payloadtype == 100) {
 			/* CISCO's notso proprietary DTMF bridge */
 			f = process_rfc2833(rtp, rtp->rawdata + AST_FRIENDLY_OFFSET + hdrlen, res - hdrlen);
+		} else if (payloadtype == 13) {
+			f = process_rfc3389(rtp, rtp->rawdata + AST_FRIENDLY_OFFSET + hdrlen, res - hdrlen);
 		} else {
 			ast_log(LOG_NOTICE, "Unknown RTP codec %d received\n", payloadtype);
 		}
@@ -254,7 +303,8 @@ struct ast_frame *ast_rtp_read(struct ast_rtp *rtp)
 			return f;
 		else
 			return &null_frame;
-	}
+	} else
+		rtp->lastrxformat = rtp->f.subclass;
 
 	if (!rtp->lastrxts)
 		rtp->lastrxts = timestamp;
@@ -651,3 +701,155 @@ int ast_rtp_write(struct ast_rtp *rtp, struct ast_frame *_f)
 		
 	return 0;
 }
+
+void ast_rtp_proto_unregister(struct ast_rtp_protocol *proto)
+{
+	struct ast_rtp_protocol *cur, *prev;
+	cur = protos;
+	prev = NULL;
+	while(cur) {
+		if (cur == proto) {
+			if (prev)
+				prev->next = proto->next;
+			else
+				protos = proto->next;
+			return;
+		}
+		prev = cur;
+		cur = cur->next;
+	}
+}
+
+int ast_rtp_proto_register(struct ast_rtp_protocol *proto)
+{
+	struct ast_rtp_protocol *cur;
+	cur = protos;
+	while(cur) {
+		if (cur->type == proto->type) {
+			ast_log(LOG_WARNING, "Tried to register same protocol '%s' twice\n", cur->type);
+			return -1;
+		}
+		cur = cur->next;
+	}
+	proto->next = protos;
+	protos = proto;
+	return 0;
+}
+
+static struct ast_rtp_protocol *get_proto(struct ast_channel *chan)
+{
+	struct ast_rtp_protocol *cur;
+	cur = protos;
+	while(cur) {
+		if (cur->type == chan->type) {
+			return cur;
+		}
+		cur = cur->next;
+	}
+	return NULL;
+}
+
+int ast_rtp_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc)
+{
+	struct ast_frame *f;
+	struct ast_channel *who, *cs[3];
+	struct ast_rtp *p0, *p1;
+	struct ast_rtp_protocol *pr0, *pr1;
+	void *pvt0, *pvt1;
+	int to;
+
+	/* XXX Wait a half a second for things to settle up 
+			this really should be fixed XXX */
+	ast_autoservice_start(c0);
+	ast_autoservice_start(c1);
+	usleep(500000);
+	ast_autoservice_stop(c0);
+	ast_autoservice_stop(c1);
+
+	/* if need DTMF, cant native bridge */
+	if (flags & (AST_BRIDGE_DTMF_CHANNEL_0 | AST_BRIDGE_DTMF_CHANNEL_1))
+		return -2;
+	ast_pthread_mutex_lock(&c0->lock);
+	ast_pthread_mutex_lock(&c1->lock);
+	pr0 = get_proto(c0);
+	pr1 = get_proto(c1);
+	if (!pr0) {
+		ast_log(LOG_WARNING, "Can't find native functions for channel '%s'\n", c0->name);
+		ast_pthread_mutex_unlock(&c0->lock);
+		ast_pthread_mutex_unlock(&c1->lock);
+		return -1;
+	}
+	if (!pr1) {
+		ast_log(LOG_WARNING, "Can't find native functions for channel '%s'\n", c1->name);
+		ast_pthread_mutex_unlock(&c0->lock);
+		ast_pthread_mutex_unlock(&c1->lock);
+		return -1;
+	}
+	pvt0 = c0->pvt->pvt;
+	pvt1 = c1->pvt->pvt;
+	p0 = pr0->get_rtp_info(c0);
+	p1 = pr1->get_rtp_info(c1);
+	if (!p0 || !p1) {
+		/* Somebody doesn't want to play... */
+		ast_pthread_mutex_unlock(&c0->lock);
+		ast_pthread_mutex_unlock(&c1->lock);
+		return -2;
+	}
+	if (pr0->set_rtp_peer(c0, p1)) 
+		ast_log(LOG_WARNING, "Channel '%s' failed to talk to '%s'\n", c0->name, c1->name);
+	if (pr1->set_rtp_peer(c1, p0)) 
+		ast_log(LOG_WARNING, "Channel '%s' failed to talk back to '%s'\n", c1->name, c0->name);
+	ast_pthread_mutex_unlock(&c0->lock);
+	ast_pthread_mutex_unlock(&c1->lock);
+	cs[0] = c0;
+	cs[1] = c1;
+	cs[2] = NULL;
+	for (;;) {
+		if ((c0->pvt->pvt != pvt0)  ||
+			(c1->pvt->pvt != pvt1) ||
+			(c0->masq || c0->masqr || c1->masq || c1->masqr)) {
+				ast_log(LOG_DEBUG, "Oooh, something is weird, backing out\n");
+				if (c0->pvt->pvt == pvt0) {
+					if (pr0->set_rtp_peer(c0, NULL)) 
+						ast_log(LOG_WARNING, "Channel '%s' failed to revert\n", c0->name);
+				}
+				if (c1->pvt->pvt == pvt1) {
+					if (pr1->set_rtp_peer(c1, NULL)) 
+						ast_log(LOG_WARNING, "Channel '%s' failed to revert back\n", c1->name);
+				}
+				/* Tell it to try again later */
+				return -3;
+		}
+		to = -1;
+		who = ast_waitfor_n(cs, 2, &to);
+		if (!who) {
+			ast_log(LOG_DEBUG, "Ooh, empty read...\n");
+			continue;
+		}
+		f = ast_read(who);
+		if (!f || ((f->frametype == AST_FRAME_DTMF) &&
+				   (((who == c0) && (flags & AST_BRIDGE_DTMF_CHANNEL_0)) || 
+			       ((who == c1) && (flags & AST_BRIDGE_DTMF_CHANNEL_1))))) {
+			*fo = f;
+			*rc = who;
+			ast_log(LOG_DEBUG, "Oooh, got a %s\n", f ? "digit" : "hangup");
+			if ((c0->pvt->pvt == pvt0) && (!c0->_softhangup)) {
+				if (pr0->set_rtp_peer(c0, NULL)) 
+					ast_log(LOG_WARNING, "Channel '%s' failed to revert\n", c0->name);
+			}
+			if ((c1->pvt->pvt == pvt1) && (!c1->_softhangup)) {
+				if (pr1->set_rtp_peer(c1, NULL)) 
+					ast_log(LOG_WARNING, "Channel '%s' failed to revert back\n", c1->name);
+			}
+			/* That's all we needed */
+			return 0;
+		} else 
+			ast_frfree(f);
+		/* Swap priority not that it's a big deal at this point */
+		cs[2] = cs[0];
+		cs[0] = cs[1];
+		cs[1] = cs[2];
+		
+	}
+	return -1;
+}
diff --git a/sounds.txt b/sounds.txt
index bb08559bf8..33f57be4ae 100755
--- a/sounds.txt
+++ b/sounds.txt
@@ -14,6 +14,8 @@
 
 %agent-user.gsm%Agent login.  Please enter your agent number followed by the pound key.
 
+%auth-incorrect.gsm%Login incorrect.  Please enter your password followed by the pound key.
+
 %beep.gsm%(this is a simple beep tone)
 
 %conf-getconfno.gsm%Please enter your conference number followed by the pound key.
diff --git a/sounds/auth-incorrect.gsm b/sounds/auth-incorrect.gsm
new file mode 100755
index 0000000000000000000000000000000000000000..d8ef6e1562a2af9dc2085cdaf82fe63c18cd8985
GIT binary patch
literal 7524
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
DXEID>

literal 0
HcmV?d00001

-- 
GitLab