diff --git a/apps/Makefile b/apps/Makefile
index 1d3ad29ad4f97c8a6672117e2fa29ac8aa51ad14..3ea4494a330df84293f0e7ce136a614345bc2d46 100755
--- a/apps/Makefile
+++ b/apps/Makefile
@@ -18,7 +18,7 @@ APPS=app_dial.so app_playback.so app_voicemail.so app_directory.so app_intercom.
      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_authenticate.so
+     app_authenticate.so app_softhangup.so app_lookupblacklist.so
 
 #APPS+=app_sql_postgres.so
 #APPS+=app_sql_odbc.so
diff --git a/apps/app_lookupblacklist.c b/apps/app_lookupblacklist.c
new file mode 100755
index 0000000000000000000000000000000000000000..d82a950f2d02d13291a5d1cff45ca120b4087603
--- /dev/null
+++ b/apps/app_lookupblacklist.c
@@ -0,0 +1,112 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * App to lookup the callerid number, and see if it is blacklisted
+ * 
+ * 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/options.h>
+#include <asterisk/channel.h>
+#include <asterisk/pbx.h>
+#include <asterisk/module.h>
+#include <asterisk/translate.h>
+#include <asterisk/image.h>
+#include <asterisk/callerid.h>
+#include <string.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+static char *tdesc = "Look up number from local blacklist database";
+
+static char *app = "LookupBlacklist";
+
+static char *synopsis = "Look up number from local blacklist database";
+
+static char *descrip =
+  "  LookupBlacklist: Looks up the Caller*ID number on the active\n"
+  "channel in the Asterisk database (family 'blacklist').  If the\n"
+  "number is found, and if there exists a priority n + 101,\n"
+  "where 'n' is the priority of the current instance, then  the\n"
+  "channel  will  be  setup  to continue at that priority level.\n"
+  "Otherwise, it returns 0.  Does nothing if no Caller*ID was received on the\n"
+  "channel.\n";
+
+STANDARD_LOCAL_USER;
+
+LOCAL_USER_DECL;
+
+static int
+lookupblacklist_exec (struct ast_channel *chan, void *data)
+{
+  char old_cid[144] = "", *num, *name;
+  char blacklist[1];
+  char shrunknum[64] = "";
+  struct localuser *u;
+  int bl = 0;
+
+  LOCAL_USER_ADD (u);
+  if (chan->callerid)
+    {
+      strncpy (old_cid, chan->callerid, sizeof (old_cid) - 1);
+      ast_callerid_parse (old_cid, &name, &num);        /* this destroys the original string */
+      if (num)			/* It's possible to get an empty number */
+	strncpy (shrunknum, num, sizeof (shrunknum) - 1);
+      else
+	num = shrunknum;
+      ast_shrink_phone_number (shrunknum);
+      if (!ast_db_get ("blacklist", shrunknum, blacklist, sizeof (blacklist)))
+	{
+	  if (option_verbose > 2)
+	    ast_verbose (VERBOSE_PREFIX_3 "Blacklisted number %s found\n",shrunknum);
+          bl = 1;
+	}
+
+    }
+  if (bl && ast_exists_extension(chan, chan->context, chan->exten, chan->priority + 101, chan->callerid))
+       chan->priority+=100;
+  LOCAL_USER_REMOVE (u);
+  return 0;
+}
+
+int
+unload_module (void)
+{
+  STANDARD_HANGUP_LOCALUSERS;
+  return ast_unregister_application (app);
+}
+
+int
+load_module (void)
+{
+  return ast_register_application (app, lookupblacklist_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/apps/app_softhangup.c b/apps/app_softhangup.c
new file mode 100755
index 0000000000000000000000000000000000000000..b245bb9269623421fc8d0c9a7b3152726933bd5e
--- /dev/null
+++ b/apps/app_softhangup.c
@@ -0,0 +1,88 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * SoftHangup application
+ * 
+ * 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/file.h>
+#include <asterisk/logger.h>
+#include <asterisk/channel.h>
+#include <asterisk/pbx.h>
+#include <asterisk/module.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <pthread.h>
+
+
+static char *synopsis = "Soft Hangup Application";
+
+static char *tdesc = "Hangs up the requested channel";
+
+static char *desc = "  SoftHangup(Technology/resource)\n"
+"Hangs up the requested channel.  Always returns 0\n";
+
+static char *app = "SoftHangup";
+
+STANDARD_LOCAL_USER;
+
+LOCAL_USER_DECL;
+
+static int softhangup_exec(struct ast_channel *chan, void *data)
+{
+	struct localuser *u;
+	struct ast_channel *c=NULL;
+	if (!data) {
+                ast_log(LOG_WARNING, "SoftHangup requires an argument (Technology/resource)\n");
+		return 0;
+	}
+	LOCAL_USER_ADD(u);
+	c = ast_channel_walk(NULL);
+	while (c) {
+		if (!strcasecmp(c->name, data)) {
+			ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
+			break;
+		}
+		c = ast_channel_walk(c);
+	}
+	LOCAL_USER_REMOVE(u);
+
+	return 0;
+}
+
+int unload_module(void)
+{
+	STANDARD_HANGUP_LOCALUSERS;
+	return ast_unregister_application(app);
+}
+
+int load_module(void)
+{
+	return ast_register_application(app, softhangup_exec, synopsis, desc);
+}
+
+char *description(void)
+{
+	return tdesc;
+}
+
+int usecount(void)
+{
+	int res;
+	STANDARD_USECOUNT(res);
+	return res;
+}
+
+char *key()
+{
+	return ASTERISK_GPL_KEY;
+}
diff --git a/asterisk.c b/asterisk.c
index 5aedc3495d415e32909372ec6578533f4c6303ad..ddfa0582a1f4877a9c2ac573773776a9a7d23c54 100755
--- a/asterisk.c
+++ b/asterisk.c
@@ -787,7 +787,7 @@ static char *cli_complete(EditLine *el, int ch)
 	LineInfo *lf = (LineInfo *)el_line(el);
 
 	*lf->cursor = '\0';
-	ptr = (char *)lf->cursor-1;
+	ptr = (char *)lf->cursor;
 	if (ptr) {
 		while (ptr > lf->buffer) {
 			if (isspace(*ptr)) {
diff --git a/manager.c b/manager.c
index c5f57384f070f7811f0a24320c7752e11cf60f29..f7a3b51262dd7f3827e0156ba21f64c67d82724b 100755
--- a/manager.c
+++ b/manager.c
@@ -69,14 +69,39 @@ static int handle_showmancmds(int fd, int argc, char *argv[])
 	return RESULT_SUCCESS;
 }
 
+static int handle_showmanconn(int fd, int argc, char *argv[])
+{
+	struct mansession *s;
+
+	ast_pthread_mutex_lock(&sessionlock);
+	s = sessions;
+	ast_cli(fd, "  Username\tIP Address\n");
+	while(s) {
+		ast_cli(fd, "  %s\t\t%s\r\n",s->username, inet_ntoa(s->sin.sin_addr));
+		s = s->next;
+	}
+
+	ast_pthread_mutex_unlock(&sessionlock);
+	return RESULT_SUCCESS;
+}
+
 static char showmancmds_help[] = 
 "Usage: show manager commands\n"
 "	Prints a listing of all the available manager commands.\n";
 
+static char showmanconn_help[] = 
+"Usage: show manager connected\n"
+"	Prints a listing of the users that are connected to the\n"
+"manager interface.\n";
+
 static struct ast_cli_entry show_mancmds_cli =
 	{ { "show", "manager", "commands", NULL },
 	handle_showmancmds, "Show manager commands", showmancmds_help };
 
+static struct ast_cli_entry show_manconn_cli =
+	{ { "show", "manager", "connected", NULL },
+	handle_showmanconn, "Show connected manager users", showmanconn_help };
+
 static void destroy_session(struct mansession *s)
 {
 	struct mansession *cur, *prev = NULL;
@@ -616,6 +641,7 @@ int init_manager(void)
 		ast_manager_register( "Command", EVENT_FLAG_COMMAND, action_command, "Execute Command" );
 
 		ast_cli_register(&show_mancmds_cli);
+		ast_cli_register(&show_manconn_cli);
 		registered = 1;
 	}
 	portno = DEFAULT_MANAGER_PORT;
diff --git a/pbx.c b/pbx.c
index 6e0f073b23c1c014c351d4df7764686f4fe6f028..bd09b720dff97914d5c8f3d3fd91f18d270e5ce0 100755
--- a/pbx.c
+++ b/pbx.c
@@ -674,82 +674,15 @@ static struct ast_exten *pbx_find_extension(struct ast_channel *chan, char *cont
 	return NULL;
 }
 
-static void *pbx_substitute_variables(struct ast_channel *c, struct ast_exten *e) {
-	char *cp1,*cp3,*cp4,*cp5;
-	void *cp2;
-	char c1,c2;
-	int m,mve,origlen,quoted,dolsign,docopy,offset;
+static void pbx_substitute_variables_temp(struct ast_channel *c,char *cp3,char **cp4)
+{
+	int offset;
 	struct ast_var_t *variables;
+	char *name, *num; /* for callerid name + num variables */
 	struct varshead *headp;
 	char pri[80];
-	char *name, *num; /* for callerid name + num variables */
-        
         headp=&c->varshead;
-        origlen=strlen(e->data)+1;
-	cp2=malloc(origlen);
-        memset(cp2,0,origlen);
-        
-	if ((strchr(e->data,'$')==NULL) && (strchr(e->data,'[')==NULL)) {
-		strncpy(cp2,e->data,strlen(e->data));
-		return(cp2);
-		/* No variables or expressions in e->data, so why scan it? */
-	}
-	
-	cp4=NULL;
-	cp1=e->data;
-	quoted=0;
-	dolsign=0;
-	docopy=1;
-	
-	/* First stage, variable substitution */
-	
-	do {
-		c1=*cp1;
-		mve=0;
-	        switch (c1) {
-	        	case '\\' :
-	        		dolsign=0;
-	        		if (quoted==1) {
-	        			quoted=0;
-	        			docopy=1;
-	        		} else {
-	        			quoted=1;
-	        			docopy=0;
-	        		}
-	        		break;
-	        	case '$' :
-	        		if (quoted==1) {
-	        			quoted=0;
-	        			docopy=1;
-	        			dolsign=0;
-	        		} else {
-	        			docopy=0;
-	        			dolsign=1;
-	        		}
-	        		break;
-	        	case '{' : 
-	        		if (quoted==1) {
-	        			quoted=0;
-	        			dolsign=0;
-	        			docopy=1;
-	        			break;
-	        		}
-	        		if (dolsign==0) {
-	        			docopy=1;
-	        			break;
-	        		}
-	        		docopy=0;
-	        		dolsign=0;
-	        		m=0;
-	        		cp1++;
-	        		while (((c2=*(cp1+m))!='}') && (c2!='\0')) {
-        				m++;
-        			}
-	        		mve=1;
-	        		cp3=malloc(m+2);
-	        		strncpy(cp3,cp1,m);
-	        		cp3[m]='\0';
-	        		cp1+=m;
+        *cp4=NULL;
 	        		/* Now we have the variable name on cp3 */
 				if (!strcmp(cp3, "CALLERIDNUM")) {
 						char cid[256] = "";
@@ -758,100 +691,145 @@ static void *pbx_substitute_variables(struct ast_channel *c, struct ast_exten *e
 						ast_callerid_parse(cid, &name, &num);
 						if (num) {
 							ast_shrink_phone_number(num);
-							cp4 = num;
+							*cp4 = num;
 						} else
-							cp4 = "";
-						break;
+							*cp4 = "";
 					} else if (!strcmp(cp3, "CALLERIDNAME")) {
 						char cid[256] = "";
 						if (c->callerid)
 							strncpy(cid, c->callerid, sizeof(cid) - 1);
 						ast_callerid_parse(cid, &name, &num);
 						if (name)
-							cp4 = name;
+							*cp4 = name;
 						else
-							cp4 = "";
-						break;
+							*cp4 = "";
 	        			} else if (!strcmp(cp3, "CALLERID")) {
-						cp4 = c->callerid;
-						if (!cp4)
-							cp4 = "";
-						break;
+						*cp4 = c->callerid;
+						if (!(*cp4))
+							*cp4 = "";
 					} else if (!strcmp(cp3, "EXTEN")) {
-						cp4 = c->exten;
-						break;
+						*cp4 = c->exten;
 					} else if (!strncmp(cp3, "EXTEN-", strlen("EXTEN-")) && 
 						(sscanf(cp3 + strlen("EXTEN-"), "%d", &offset) == 1)) {
 						if (offset < 0)
 							offset=0;
 						if (offset > strlen(c->exten))
 							offset = strlen(c->exten);
-						cp4 = c->exten + offset;
+						*cp4 = c->exten + offset;
 					} else if (!strcmp(cp3, "RDNIS")) {
-						cp4 = c->rdnis;
-						if (!cp4)
-							cp4 = "";
-						break;
+						*cp4 = c->rdnis;
+						if (!(*cp4))
+							*cp4 = "";
 					} else if (!strcmp(cp3, "CONTEXT")) {
-						cp4 = c->context;
-						break;
+						*cp4 = c->context;
 					} else if (!strcmp(cp3, "PRIORITY")) {
 						snprintf(pri, sizeof(pri), "%d", c->priority);
-						cp4 = pri;
-						break;
+						*cp4 = pri;
 					} else {
 		        		AST_LIST_TRAVERSE(headp,variables,entries) {
 //		        			ast_log(LOG_WARNING,"Comparing variable '%s' with '%s'\n",cp3,ast_var_name(variables));
-							if (strncasecmp(ast_var_name(variables),cp3,m)==0) {
-								cp4=ast_var_value(variables);
-								break;
-							}
+							if (strncasecmp(ast_var_name(variables),cp3,strlen(cp3))==0)
+								*cp4=ast_var_value(variables);
 						}
-						if (!cp4) {
+						if (!(*cp4)) {
 							/* Try globals */
 			        		AST_LIST_TRAVERSE(&globals,variables,entries) {
-	//		        			ast_log(LOG_WARNING,"Comparing variable '%s' with '%s'\n",cp3,ast_var_name(variables));
-								if (strncasecmp(ast_var_name(variables),cp3,m)==0) {
-									cp4=ast_var_value(variables);
-									break;
-								}
+//			        			ast_log(LOG_WARNING,"Comparing variable '%s' with '%s'\n",cp3,ast_var_name(variables));
+								if (strncasecmp(ast_var_name(variables),cp3,strlen(cp3))==0)
+									*cp4=ast_var_value(variables);
 							}
 						}
 					}
-	        		free(cp3);
-	        		break;
-	        	default :
-	        		if (dolsign==1) {
-	        			strncat((char *)cp2,"$",1);
-	        		} 
-	        		if (quoted==1) {
-	        			quoted=0;
-	        		}
-	        		mve=0;
-		        	dolsign=0;
-		        	docopy=1;
-	        		break;
-	        }
-	        if (cp1!='\0') {
-	        	if (mve==0) {
-	        		if (docopy==1) {
-		        		strncat((char *)cp2,&c1,1);
-		        	}
-	        	} else {
-	        		if (cp4!=NULL) {
-		        		cp2=realloc(cp2,origlen+strlen(cp4)+1);
-		        		strncat((char *)cp2,cp4,strlen(cp4));
-					origlen += strlen(cp4);
-		        	} else {
-		        		ast_log(LOG_WARNING,"mve!=0 and cp4=NULL, something gone astray\n");
-		        	}
-	        	}
-	        }
-	              cp4 = NULL;
-	} while (*cp1++!='\0');
+}
+
+static void pbx_substitute_variables_helper(struct ast_channel *c,char *cp1,char **ecp2)
+{
+	char *cp4,*cp2;
+	char *tmp,*wherearewe,*finish;
+	int length;
+
+	wherearewe=tmp=cp1;
+	cp2=*ecp2;
+	*cp2='\0';
+	do {
+		if (!(*wherearewe)) break;
+		if ((tmp=strstr(wherearewe,"${"))) {
+			length=(int)(tmp-wherearewe);
+			strncat(cp2,wherearewe,length);
+			wherearewe=tmp;
+			if (!strncmp(tmp+2,"${",2)) {
+				char *ltmp,*lval;
+				ltmp=malloc(sizeof(char)*256);
+				finish=strchr(tmp+2,'}');
+				/* get the one before the last closing bracket */
+				do {
+					if (strlen(finish)<2)
+						break;
+					if (finish[1]=='}' && finish[2]=='}')
+						finish++;
+					else break;
+				} while (1);
+				
+				if (!finish) {
+					ast_log(LOG_WARNING, "Something went wrong with ${VARIABLE}\n");
+					*ecp2="";
+					break;
+				}
+				length=(int)(finish-tmp-1);
+				wherearewe+=length+3;
+				lval=strndup(tmp+2,length);
+				pbx_substitute_variables_helper(c,lval,&ltmp);
+				free(lval);
+				pbx_substitute_variables_temp(c,ltmp,&cp4);
+				if (cp4) {
+					length=strlen(cp4);
+					strncat(cp2,cp4,length);
+				}
+			} else {
+				char value[256];
+				finish=strchr(tmp+2,'}');
+				if (!finish) {
+					ast_log(LOG_WARNING, "Something went wrong with ${VARIABLE}\n");
+					*ecp2="";
+					break;					
+				}
+				length=(int)(finish-tmp)-2;
+				wherearewe+=length+3;
+				strncpy(value,tmp+2,length);
+				value[length]='\0';
+				pbx_substitute_variables_temp(c,value,&cp4);
+				if (cp4) {
+					length=strlen(cp4);
+					strncat(cp2,cp4,length);
+				}
+			}
+		} else {
+			if (*wherearewe) {
+				length=strlen(wherearewe);
+				strncat(cp2,wherearewe,length);
+			}
+			strcat(cp2,"\0");
+			break;
+		}
+	} while(1);
+}
+
+static void *pbx_substitute_variables(struct ast_channel *c, struct ast_exten *e) {
+	char *cp1,*cp3,*cp4,*cp5;
+	char *cp2;
+	char c1,c2;
+	int m,mve,origlen,quoted,dolsign,docopy;
+        
+	/* No variables or expressions in e->data, so why scan it? */
+	if (!strstr(e->data,"${") && !strstr(e->data,"$[")) {
+		return strndup(e->data,strlen(e->data)+1);
+	}
 	
+	cp1=e->data;
+	cp2=malloc(sizeof(char)*256);
+	pbx_substitute_variables_helper(c,cp1,(char **)&cp2);
+
 	/* Second stage, expression evaluation */
-		
 	if ((strstr(cp2,"$[")==NULL)) {
 		/* No expressions in cp2, return it */
 		return(cp2);