--- asterisk-1.2.1/Makefile	2005-12-05 01:47:51.000000000 -0500
+++ mwi/Makefile	2006-01-19 11:00:47.000000000 -0500
@@ -343,7 +343,7 @@
 	astmm.o enum.o srv.o dns.o aescrypt.o aestab.o aeskey.o \
 	utils.o plc.o jitterbuf.o dnsmgr.o devicestate.o \
 	netsock.o slinfactory.o ast_expr2.o ast_expr2f.o \
-	cryptostub.o
+	cryptostub.o subscribe.o
 
 ifeq ($(wildcard $(CROSS_COMPILE_TARGET)/usr/include/sys/poll.h),)
   OBJS+= poll.o
--- asterisk-1.2.1/apps/app_voicemail.c	2005-12-03 14:55:05.000000000 -0500
+++ mwi/apps/app_voicemail.c	2006-01-19 10:06:32.000000000 -0500
@@ -49,6 +49,7 @@
 #include <sys/mman.h>
 #include <time.h>
 #include <dirent.h>
+#include <signal.h>
 
 #include "asterisk.h"
 
@@ -70,6 +71,9 @@
 #include "asterisk/localtime.h"
 #include "asterisk/cli.h"
 #include "asterisk/utils.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/linkedlists.h"
+
 #ifdef USE_ODBC_STORAGE
 #include "asterisk/res_odbc.h"
 #endif
@@ -96,6 +100,8 @@
 #define MAX_DATETIME_FORMAT	512
 #define MAX_NUM_CID_CONTEXTS 10
 
+#define MIN_RESUBSCRIBE_MINS 15
+
 #define VM_REVIEW		(1 << 0)
 #define VM_OPERATOR		(1 << 1)
 #define VM_SAYCID		(1 << 2)
@@ -114,6 +120,14 @@
 
 #define ERROR_LOCK_PATH		-100
 
+enum vm_request_type {
+	VM_SUBSCRIBE,
+	VM_UNSUBSCRIBE
+};
+
+#define VM_SUB_NOINFORM		(1 << 0)
+	
+
 enum {
 	OPT_SILENT =           (1 << 0),
 	OPT_BUSY_GREETING =    (1 << 1),
@@ -208,6 +222,28 @@
 	unsigned char iobuf[BASEMAXINLINE];
 };
 
+/* used to store information about a peer we are subscribed to
+   or a peer that is subscribed to us */
+struct vm_remote_subpeer {
+	char tech[20];
+	char name[80];
+	ast_sub_listener_func listener_func;
+
+	int subscribed;
+	int ref;
+
+	AST_LIST_ENTRY(vm_remote_subpeer) list;
+};
+
+/* used to store information about a subscription to a specific mailbox */
+struct vm_subscriber {
+	struct vm_remote_subpeer *peer;
+	char context[AST_MAX_CONTEXT];
+	char mailbox[AST_MAX_EXTENSION];
+
+	AST_LIST_ENTRY(vm_subscriber) list;
+};
+
 /*! Structure for linked list of users */
 struct ast_vm_user {
 	char context[AST_MAX_CONTEXT];	/*!< Voicemail context */
@@ -228,6 +264,7 @@
 	int saydurationm;
 	int maxmsg;			/*!< Maximum number of msgs per folder for this mailbox */
 	struct ast_vm_user *next;
+	AST_LIST_HEAD_NOLOCK (subscriber_list, vm_subscriber) subscribers;
 };
 
 struct vm_zone {
@@ -253,6 +290,48 @@
 	int starting;
 	int repeats;
 };
+
+struct vm_remote_box {
+	char mailbox[AST_MAX_EXTENSION];
+	char context[AST_MAX_CONTEXT];
+	int newmsgs;
+	int oldmsgs;
+	int hasvoicemail;
+
+	AST_LIST_ENTRY(vm_remote_box) list;	
+};
+
+
+struct vm_subscription {
+	struct vm_remote_subpeer *peer;
+	char remote_mailbox[AST_MAX_EXTENSION];
+	char remote_context[AST_MAX_CONTEXT];
+	char local_mailbox[AST_MAX_EXTENSION];
+	char local_context[AST_MAX_CONTEXT];
+
+	int is_wildcard;
+	AST_LIST_HEAD_NOLOCK(box_list, vm_remote_box) boxes;
+
+	AST_LIST_ENTRY(vm_subscription) list;
+};
+
+struct vm_sub_request {
+        char mailbox[AST_MAX_EXTENSION];
+        char context[AST_MAX_CONTEXT];
+};
+
+struct vm_sub_info {
+	char mailbox[AST_MAX_EXTENSION];
+	char context[AST_MAX_CONTEXT];
+        int newmsgs;
+        int oldmsgs;
+        int hasvm;
+};
+
+int (*ast_request_messagecount)(const char *peername, const char *mailbox) = NULL;
+int (*ast_send_messagecount)(const char *peername, const char *mailbox, int newmsgs, int oldmsgs, int hasvoicemail) = NULL;
+
+
 static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg,
 			    int option, signed char record_gain);
 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
@@ -263,6 +342,7 @@
 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
 
 static void apply_options(struct ast_vm_user *vmu, const char *options);
+static void *resubscribe_thread(void *data);
 
 #ifdef USE_ODBC_STORAGE
 static char odbc_database[80];
@@ -371,9 +451,20 @@
 static char *app3 = "MailboxExists";
 static char *app4 = "VMAuthenticate";
 
+static pthread_t resub_thread = AST_PTHREADT_NULL;
 AST_MUTEX_DEFINE_STATIC(vmlock);
+AST_MUTEX_DEFINE_STATIC(subslock);
 struct ast_vm_user *users;
 struct ast_vm_user *usersl;
+
+/* List of subscriptions we have on remote servers */
+AST_LIST_HEAD_STATIC(subs_out, vm_subscription);
+/* List of subscriptions that other servers have with us */
+AST_LIST_HEAD_STATIC(subs_in, vm_subscription);
+AST_LIST_HEAD_NOLOCK(peerlist, vm_remote_subpeer);
+struct peerlist subservers;
+struct peerlist subclients;
+
 struct vm_zone *zones = NULL;
 struct vm_zone *zonesl = NULL;
 static int maxsilence;
@@ -389,6 +480,7 @@
 static int maxgreet;
 static int skipms;
 static int maxlogins;
+static int resubscribe_mins;
 
 static struct ast_flags globalflags = {0};
 
@@ -569,12 +661,12 @@
 	return retval;
 }
 
-static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
+/* returns a pointer to an existing mailbox matching the given mailbox/context
+   vmlock must be locked before this function is called */
+static struct ast_vm_user *find_user_ptr(const char *context, const char *mailbox)
 {
 	/* This function could be made to generate one from a database, too */
-	struct ast_vm_user *vmu=NULL, *cur;
-	ast_mutex_lock(&vmlock);
-	cur = users;
+	struct ast_vm_user *cur = users;
 
 	if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
 		context = "default";
@@ -586,6 +678,19 @@
 			break;
 		cur=cur->next;
 	}
+
+	return cur;	
+}
+
+static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
+{
+	/* This function could be made to generate one from a database, too */
+	struct ast_vm_user *vmu=NULL, *cur;
+	
+	ast_mutex_lock(&vmlock);
+	
+	cur = find_user_ptr(context, mailbox);
+
 	if (cur) {
 		if (ivm)
 			vmu = ivm;
@@ -1930,6 +2035,21 @@
 	return res;
 }
 
+static void free_subscriber(struct vm_subscriber *subr)
+{
+	free(subr);
+}
+
+static void free_user_subscribers(struct ast_vm_user *vmu)
+{
+	struct vm_subscriber *scur;
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&vmu->subscribers, scur, list) {
+		AST_LIST_REMOVE_CURRENT(&vmu->subscribers, list);
+		free_subscriber(scur);
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+}
+
 static void free_user(struct ast_vm_user *vmu)
 {
 	if (ast_test_flag(vmu, VM_ALLOCED))
@@ -1941,6 +2061,28 @@
 	free(z);
 }
 
+static void free_remote_box(struct vm_remote_box *box)
+{
+	free(box);
+}
+
+static void free_subscription(struct vm_subscription *sub)
+{
+	struct vm_remote_box *cbox;
+	
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&sub->boxes, cbox, list) {
+		AST_LIST_REMOVE_CURRENT(&sub->boxes, list);
+		free_remote_box(cbox);
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	free(sub);
+}
+
+static void free_subpeer(struct vm_remote_subpeer *subpeer)
+{
+	free(subpeer);
+}
+
 static char *mbox(int id)
 {
 	switch(id) {
@@ -1969,11 +2111,163 @@
 	}
 }
 
+/*
+ * Parses the remote mailbox specifier 'spec' and stores the components in 'sub'
+ * The specifier has the following format: tech/name/mailbox@context
+ * The only mandatory field is 'name'.
+ * If the specifier doesn't include a context, then it is set to the value of 'defmbox'
+ * If the specifier doesn't include a mailbox, then it is set to the value of 'defcontext'
+ *
+ * Returns 0 if the passed string is not a valid remote mailbox specifier.
+ */
+static int parse_remote_mailbox(const char *spec, struct vm_remote_subpeer *peer, struct vm_subscription *sub, char *defmbox, char *defcontext)
+{
+	char *str;
+	char *strptr;
+	char *p1, *p2;
+
+	str = ast_strdupa(spec);	
+	strptr = str;	
+
+	if(!(p1 = strsep(&strptr, "/"))) {
+		return 0;
+	}
+	
+	p2 = strsep(&strptr, "/");
+	if(strptr != NULL) { // has tech
+		ast_copy_string(peer->tech, p1, sizeof(peer->tech));
+		ast_copy_string(peer->name, p2, sizeof(peer->name));
+	} else { // doesn't have tech
+		ast_copy_string(peer->tech, "IAX2", sizeof(peer->tech));
+		ast_copy_string(peer->name, p1, sizeof(peer->name));
+		strptr = p2;
+	}
+
+	p2 = strsep(&strptr, "@");
+	ast_copy_string(sub->remote_mailbox, p2, sizeof(sub->remote_mailbox));
+	if(sub->remote_mailbox[0] == 0) {
+		ast_copy_string(sub->remote_mailbox, defmbox, sizeof(sub->remote_mailbox));
+	}
+	
+	if(strptr != NULL) { // has context
+		ast_copy_string(sub->remote_context, strptr, sizeof(sub->remote_context));
+	} else { // does't have context
+		ast_copy_string(sub->remote_context, defcontext, sizeof(sub->remote_context));
+	}
+
+	return 1;	
+
+}
+
+/*
+ * Searches the given subscription for a remote box matching the given mailbox/context name
+ */
+static struct vm_remote_box *find_remote_box(struct vm_subscription *sub, const char *mailbox, const char *context)
+{
+	struct vm_remote_box *box;
+	AST_LIST_TRAVERSE(&sub->boxes, box, list) {
+		if(!strcasecmp(box->mailbox, mailbox) && !strcasecmp(box->context, context)) {
+			return box;
+		}
+	}
+
+	return NULL;
+}
+
+/* 
+ * Finds a remote mailbox subscription matching the given mailbox/context
+ * The type parameter determines which side of the subscription is matched.
+ * Returns NULL if a matching mailbox cannot be found.
+ *
+ * The subslock mutex must be locked before calling this function 
+ */
+enum box_search_type {BOX_LOCAL, BOX_REMOTE};
+
+static struct vm_subscription *find_subscription(const char *mailbox, const char *context, enum box_search_type type)
+{
+	struct vm_subscription *sub;
+	char *curmailbox;
+	char *curcontext;
+
+	AST_LIST_TRAVERSE(&subs_out, sub, list) {
+		if(type == BOX_LOCAL) {
+			curmailbox = sub->local_mailbox;
+			curcontext = sub->local_context;
+		} else {
+			curmailbox = sub->remote_mailbox;
+			curcontext = sub->remote_context;
+		}
+
+		if ((!sub->is_wildcard && !strcasecmp(curmailbox, mailbox) && !strcasecmp(curcontext, context))
+		    || (sub->is_wildcard && ast_extension_match(curmailbox, mailbox))) {
+			return sub;
+		}
+	}
+
+	return NULL;
+}
+
+/* 
+ * Returns -1 if the mailbox described by the given specifier is not in our 
+ * subscription list 
+ */
+static int has_voicemail_remote(const char *local_mailbox, const char *local_context)
+{
+	struct vm_subscription *sub;
+	struct vm_remote_box *box;
+	int rv;
+
+	ast_mutex_lock(&subslock);
+
+	sub = find_subscription(local_mailbox, local_context, BOX_LOCAL);
+	if(sub)	{
+		box = find_remote_box(sub, local_mailbox, local_context);
+		/* if we have a subscription for this mailbox, but no information then pretend
+		   that there's no voicemail */ 
+		rv = box ? box->hasvoicemail : 0;
+	} else {
+		rv = -1;
+	}
+
+	ast_mutex_unlock(&subslock);
+
+	return rv;
+}
+
+/* 
+ * Returns -1 if the mailbox described by the given specifier is not in our 
+ ( subscription list
+ */ 
+static int messagecount_remote(const char *local_mailbox, const char *local_context, int *newmsgs, int *oldmsgs)
+{
+	struct vm_subscription *sub;
+	struct vm_remote_box *box;
+
+	ast_mutex_lock(&subslock);
+	sub = find_subscription(local_mailbox, local_context, BOX_LOCAL);
+	if(sub) {
+		box = find_remote_box(sub, local_mailbox, local_context);
+		if(box) {
+			*newmsgs = box->newmsgs;
+			*oldmsgs = box->oldmsgs;
+		} else {
+			*newmsgs = 0;
+			*newmsgs = 0;
+		}
+	} 
+	ast_mutex_unlock(&subslock);
+
+	/* if we have a subscription, return success even if we didn't find the box info */
+	return sub ? 1 : -1;
+}
+
+
 #ifdef USE_ODBC_STORAGE
 static int messagecount(const char *mailbox, int *newmsgs, int *oldmsgs)
 {
 	int x = 0;
 	int res;
+	int rv;
 	SQLHSTMT stmt;
 	char sql[256];
 	char rowdata[20];
@@ -1987,7 +2281,7 @@
         /* If no mailbox, return immediately */
         if (ast_strlen_zero(mailbox))
                 return 0;
-
+	
         ast_copy_string(tmp, mailbox, sizeof(tmp));
         
 	context = strchr(tmp, '@');
@@ -2077,6 +2371,7 @@
 {
 	int nummsgs = 0;
         int res;
+	int rv;
         SQLHSTMT stmt;
         char sql[256];
         char rowdata[20];
@@ -2084,6 +2379,7 @@
         char *context;
 	if (!folder)
                 folder = "INBOX";
+	
 	/* If no mailbox, return immediately */
         if (ast_strlen_zero(mailbox))
                 return 0;
@@ -2097,6 +2393,7 @@
         } else
                 context = "default";
 
+	
         odbc_obj *obj;
         obj = fetch_odbc_obj(odbc_database, 0);
         if (obj) {
@@ -2153,11 +2450,13 @@
 	char *mb, *cur;
 	char *context;
 	int ret;
+	int rv;
 	if (!folder)
 		folder = "INBOX";
 	/* If no mailbox, return immediately */
 	if (ast_strlen_zero(mailbox))
 		return 0;
+	
 	if (strchr(mailbox, ',')) {
 		ast_copy_string(tmp, mailbox, sizeof(tmp));
 		mb = tmp;
@@ -2177,6 +2476,11 @@
 		context++;
 	} else
 		context = "default";
+	
+	rv = has_voicemail_remote(tmp, context);
+	if (rv != -1)
+		return rv;
+
 	snprintf(fn, sizeof(fn), "%s/%s/%s/%s", VM_SPOOL_DIR, context, tmp, folder);
 	dir = opendir(fn);
 	if (!dir)
@@ -2201,13 +2505,16 @@
 	char *mb, *cur;
 	char *context;
 	int ret;
+	int rv;
 	if (newmsgs)
 		*newmsgs = 0;
 	if (oldmsgs)
 		*oldmsgs = 0;
+
 	/* If no mailbox, return immediately */
 	if (ast_strlen_zero(mailbox))
 		return 0;
+	
 	if (strchr(mailbox, ',')) {
 		int tmpnew, tmpold;
 		ast_copy_string(tmp, mailbox, sizeof(tmp));
@@ -2234,6 +2541,11 @@
 		context++;
 	} else
 		context = "default";
+	
+	rv = messagecount_remote(tmp, context, newmsgs, oldmsgs);
+	if (rv != -1)
+		return rv;
+
 	if (newmsgs) {
 		snprintf(fn, sizeof(fn), "%s/%s/%s/INBOX", VM_SPOOL_DIR, context, tmp);
 		dir = opendir(fn);
@@ -3282,6 +3594,487 @@
 	return cmd;
 }
 
+
+/* tell a subscriber about the state of a mailbox */
+static void inform_subscriber(struct ast_vm_user *user, struct vm_subscriber *sub, int newmsgs, int oldmsgs, int hasvoicemail)
+{
+	char buffer[1024];
+	struct ast_sub_pkt pkt;
+
+	ast_sub_pkt_init(&pkt, buffer, sizeof(buffer));
+	ast_sub_pkt_add_str(&pkt, sub->mailbox);
+	ast_sub_pkt_add_str(&pkt, sub->context);
+	ast_sub_pkt_add_int(&pkt, newmsgs);
+	ast_sub_pkt_add_int(&pkt, oldmsgs);
+	ast_sub_pkt_add_int(&pkt, hasvoicemail);
+
+	// this should never happen, but just in case...
+	if(!sub->peer->listener_func) {
+		ast_log(LOG_WARNING, "The peer for this subscription has a NULL listener function\n");
+		return;
+	}
+
+	sub->peer->listener_func(sub->peer->name, AST_SUB_VOICEMAIL, &pkt);
+}
+
+/* process a subscription for one specific mailbox
+ * assumes vmlock has been locked 
+ */
+static void remote_subunsub_single_request(struct ast_vm_user *user, struct vm_remote_subpeer *peer, const char *mbox, const char *context, enum vm_request_type type, int flags)
+{
+	struct vm_subscriber *sub;
+	int newmsgs = 0, oldmsgs = 0;
+	char ext_context[AST_MAX_CONTEXT];
+
+	ast_log(LOG_NOTICE, "Recieved a subscribe request for %s @ %s\n", mbox, context);
+	
+	if(type == VM_SUBSCRIBE) {
+		snprintf(ext_context, sizeof(ext_context), "%s@%s", user->mailbox, user->context);
+		ast_app_messagecount(ext_context, &newmsgs, &oldmsgs);
+
+		AST_LIST_TRAVERSE(&user->subscribers, sub, list) {
+			if(!strcmp(sub->peer->name, peer->name) && !strcmp(sub->peer->tech, peer->tech) &&
+			   !strcmp(sub->mailbox, mbox) && !strcmp(sub->context, context)) {
+				ast_log(LOG_WARNING, "Peer '%s' is already subscribed to mailbox %s@%s\n", peer->name, mbox, context);
+				if(!(flags & VM_SUB_NOINFORM)) {
+					inform_subscriber(user, sub, newmsgs, oldmsgs, ast_app_has_voicemail(ext_context, NULL));
+				}
+				return;
+			}
+		}
+
+		sub = malloc(sizeof(struct vm_subscriber));
+		memset(sub, 0, sizeof(struct vm_subscriber));
+
+		peer->ref++;
+		sub->peer = peer;
+		ast_copy_string(sub->mailbox, mbox, sizeof(sub->mailbox));
+		ast_copy_string(sub->context, context, sizeof(sub->context));
+
+		AST_LIST_INSERT_TAIL(&user->subscribers, sub, list);
+
+		if(!(flags & VM_SUB_NOINFORM)) {
+			inform_subscriber(user, sub, newmsgs, oldmsgs, ast_app_has_voicemail(ext_context, NULL));
+		}
+
+	} else if(type == VM_UNSUBSCRIBE) {
+		ast_log(LOG_NOTICE, "Unsubscribe...\n");
+		AST_LIST_TRAVERSE_SAFE_BEGIN(&user->subscribers, sub, list) {
+			if(!strcmp(sub->peer->name, peer->name) && !strcmp(sub->peer->tech, peer->tech) && 
+			   !strcmp(sub->mailbox, mbox) && !strcmp(sub->context, context)) {
+				AST_LIST_REMOVE_CURRENT(&user->subscribers, list);
+				sub->peer->ref--;
+				free(sub);
+				break;	
+			}
+		}
+		AST_LIST_TRAVERSE_SAFE_END;
+	}
+}
+
+/* vmlock must be locked before this function is called */
+static struct vm_remote_subpeer *find_remote_peer(struct peerlist *list, const char *tech, const char *peername)
+{
+	struct vm_remote_subpeer *peer;
+	AST_LIST_TRAVERSE(list, peer, list) {
+		if(!strcasecmp(peer->name, peername) && !strcasecmp(peer->tech, tech)) {
+			return peer;
+		}
+	}
+
+	return NULL;
+}
+
+/* adds a peer to the given peerlist. 
+   if it's already in the list, a reference to the existing peer is returned 
+   vmlock must be locked before this function is called 
+*/
+static struct vm_remote_subpeer *add_remote_peer(struct peerlist *list, const char *tech, const char *peername, ast_sub_listener_func func)
+{
+	struct vm_remote_subpeer *peer = find_remote_peer(list, tech, peername);
+	if(peer)
+		return peer;
+
+	peer = (struct vm_remote_subpeer *) malloc(sizeof(struct vm_remote_subpeer));
+	memset(peer, 0, sizeof(struct vm_remote_subpeer));
+	ast_copy_string(peer->name, peername, sizeof(peer->name));
+	ast_copy_string(peer->tech, tech, sizeof(peer->tech));
+	peer->listener_func = func;
+
+	AST_LIST_INSERT_TAIL(list, peer, list);
+	
+	return peer;
+}
+
+/* 
+ * Process a subscribe request without modifying our internal subscription lists.
+ * If the mailbox starts with '_' we assume it's a pattern and subscribe
+ * to any matching mailboxes. 
+ *
+ * expects vmlock to be locked
+ */
+static int remote_subunsub_request_direct(struct vm_remote_subpeer *peer, const char *mailbox, const char *context, enum vm_request_type type, int flags)
+{
+	struct ast_vm_user *user;
+	
+	ast_log(LOG_NOTICE, "Incoming subscribe %s/%s/%s@%s %d\n", peer->tech, peer->name, mailbox, context, type);
+
+	// wildcard subscription, subscribe any matching users
+	if(mailbox[0] == '_') {
+		for(user = users; user; user = user->next) {
+			if(ast_extension_match(mailbox, user->mailbox)) {
+				// what status to we return here?
+				remote_subunsub_single_request(user, peer, user->mailbox, context, type, flags);
+			}
+		}
+	} else {
+		user = find_user_ptr(context, mailbox);
+		if(!user) {
+			ast_mutex_unlock(&vmlock);
+			ast_log(LOG_WARNING, "Subscribe request for non-existing mailbox %s@%s\n", mailbox, context);
+			return -1;
+		}
+
+		remote_subunsub_single_request(user, peer, mailbox, context, type, flags);
+	}
+	
+	return 0;
+}
+
+
+/* A remote peer has requested subscribe/unsubscribe
+ * If the mailbox starts with '_' we assume it's a pattern and subscribe
+ * to any matching mailboxes. */
+static int remote_subunsub_request(const char *tech, const char *peername, struct ast_sub_pkt *pkt, ast_sub_listener_func func, enum vm_request_type type)
+{
+	struct vm_subscription *sub_in;
+	struct vm_remote_subpeer *peer;
+	char mailbox[AST_MAX_EXTENSION];
+	char context[AST_MAX_CONTEXT];
+
+	if(ast_sub_pkt_get_str(pkt, mailbox, sizeof(mailbox)) == -1) {
+		ast_log(LOG_WARNING, "Unable to extract mailbox name from request\n");
+		return -1;
+	}
+
+	if(ast_sub_pkt_get_str(pkt, context, sizeof(context)) == -1) {
+		ast_log(LOG_WARNING, "Unable to extract context name from request\n");
+		return -1;
+	}
+
+	ast_mutex_lock(&vmlock);
+
+	peer = add_remote_peer(&subclients, tech, peername, func);
+
+	if(type == VM_SUBSCRIBE) {
+		int haveit = 0;
+		AST_LIST_TRAVERSE(&subs_in, sub_in, list) {
+			if(!strcmp(sub_in->local_mailbox, mailbox) && !strcmp(sub_in->local_context, context)) {
+				haveit = 1;
+				break;
+			}
+		}
+
+		if(!haveit) {
+			sub_in = malloc(sizeof(struct vm_subscription));
+			memset(sub_in, 0, sizeof(struct vm_subscription));
+			ast_copy_string(sub_in->local_mailbox, mailbox, sizeof(sub_in->local_mailbox));
+			ast_copy_string(sub_in->local_context, context, sizeof(sub_in->local_context));
+			peer->ref++;
+			sub_in->peer = peer;
+			AST_LIST_INSERT_TAIL(&subs_in, sub_in, list);
+		}
+	} else if(type == VM_UNSUBSCRIBE) {
+		AST_LIST_TRAVERSE_SAFE_BEGIN(&subs_in, sub_in, list) {
+			if(!strcmp(sub_in->local_mailbox, mailbox) && !strcmp(sub_in->local_context, context)) {
+				AST_LIST_REMOVE_CURRENT(&subs_in, list);
+				sub_in->peer->ref--;
+				break;
+			}
+		}
+		AST_LIST_TRAVERSE_SAFE_END;	
+
+	}
+
+	remote_subunsub_request_direct(peer, mailbox, context, type, 0);
+	
+	ast_mutex_unlock(&vmlock);
+
+	return 0;
+}
+
+
+/*a remote peer wishes to subscribe to one of our mailboxes */
+static int remote_subscribe_request(const char *tech, const char *peer, struct ast_sub_pkt *pkt, ast_sub_listener_func listener_func)
+{
+	return remote_subunsub_request(tech, peer, pkt, listener_func, VM_SUBSCRIBE);
+}
+
+/* a remote peer wishes to unsubscribe from one of our mailboxes */
+static int remote_unsubscribe_request(const char *tech, const char *peer, struct ast_sub_pkt *pkt, ast_sub_listener_func listener_func)
+{
+	ast_log(LOG_NOTICE, "Unsubscribe request came in...\n");
+	return remote_subunsub_request(tech, peer, pkt, listener_func, VM_UNSUBSCRIBE);
+}
+
+/* inform all subscribers of a given mailbox about its state */
+static void inform_subscribers(struct ast_vm_user *user, int newmsgs, int oldmsgs, char *ext_context)
+{
+	struct vm_subscriber *sub;
+	ast_log(LOG_NOTICE, "Informing subscribers of %s@%s (%p)\n", user->mailbox, user->context, user->subscribers.first);
+
+	if(newmsgs == -1 || oldmsgs == -1)
+		ast_app_messagecount(ext_context, &newmsgs, &oldmsgs);
+
+	ast_mutex_lock(&vmlock);
+
+	AST_LIST_TRAVERSE(&user->subscribers, sub, list) {
+		ast_log(LOG_NOTICE, "Informing %s@%s on %s\n", sub->mailbox, sub->context, sub->peer->name);
+		inform_subscriber(user, sub, newmsgs, oldmsgs, ast_app_has_voicemail(ext_context, NULL));
+	}
+
+	ast_mutex_unlock(&vmlock);
+}
+
+static struct vm_remote_box *new_remote_mailbox(const char *mailbox, const char *context, int newmsgs, int oldmsgs, int hasvm)
+{
+	struct vm_remote_box *box = (struct vm_remote_box *) malloc(sizeof(struct vm_remote_box));
+
+	memset(box, 0, sizeof(struct vm_remote_box));
+
+	box->hasvoicemail = hasvm;
+	box->newmsgs = newmsgs;
+	box->oldmsgs = oldmsgs;
+
+	ast_copy_string(box->mailbox, mailbox, sizeof(box->mailbox));
+	ast_copy_string(box->context, context, sizeof(box->context));
+
+	return box;
+}
+
+/* a remote peer tells us that a mailbox state has changed */
+static int vm_changed_event_parser(struct ast_sub_pkt *pkt, struct vm_sub_info *info)
+{
+	if(ast_sub_pkt_get_str(pkt, info->mailbox, sizeof(info->mailbox)) == -1) {
+		ast_log(LOG_WARNING, "Unable to extract mailbox name from request\n");
+		return -1;
+	}
+
+	if(ast_sub_pkt_get_str(pkt, info->context, sizeof(info->context)) == -1) {
+		ast_log(LOG_WARNING, "Unable to extract context name from request\n");
+		return -1;
+	}
+
+	if(ast_sub_pkt_get_int(pkt, &info->newmsgs) == -1) {
+		ast_log(LOG_WARNING, "Unable to extract new message count from request\n");
+		return -1;
+	}
+
+	if(ast_sub_pkt_get_int(pkt, &info->oldmsgs) == -1) {
+		ast_log(LOG_WARNING, "Unable to extract old message count from request\n");
+		return -1;
+	}
+
+	if(ast_sub_pkt_get_int(pkt, &info->hasvm) == -1) {
+		ast_log(LOG_WARNING, "Unable to extract voicemail flag from request\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+/* a remote peer tells us that a mailbox state has changed */
+static void remote_mailbox_callback(const char *tech, const char *peer, struct vm_sub_info *info)
+{
+	char *mailbox, *context;
+	struct vm_subscription *sub;
+	struct vm_remote_box *box;
+
+	ast_log(LOG_NOTICE, "A subscribed mailbox has changed (%s @ %s on %s) | %d %d %d\n", 
+		info->mailbox, info->context, peer, 
+		info->hasvm, info->newmsgs, info->oldmsgs);
+
+	ast_mutex_lock(&subslock);
+
+	sub = find_subscription(info->mailbox, info->context, BOX_REMOTE);
+	if(!sub) {
+		ast_log(LOG_WARNING, "This mailbox is not in our subscription list\n");
+		ast_mutex_unlock(&subslock);
+		return;
+	} 
+
+	if(sub->remote_mailbox[0] == '_') {
+		mailbox = info->mailbox;
+		context = info->context;
+	} else {
+		mailbox = sub->local_mailbox;
+		context = sub->local_context;
+	}
+
+	box = find_remote_box(sub, mailbox, context);
+	if(!box) {
+		box = new_remote_mailbox(mailbox, context, info->newmsgs, info->oldmsgs, info->hasvm);
+		AST_LIST_INSERT_TAIL(&sub->boxes, box, list);
+	}
+
+	box->hasvoicemail = info->hasvm;
+	box->newmsgs = info->newmsgs;
+	box->oldmsgs = info->oldmsgs;
+	
+	manager_event(EVENT_FLAG_CALL, "MessageWaiting", "Mailbox: %s@%s\r\nWaiting: %d\r\nNew: %d\r\nOld: %d\r\n", 
+		mailbox, context, info->hasvm, info->newmsgs, info->oldmsgs);
+	//inform_subscribers(vmu, newmsgs, oldmsgs, ext_context);
+	run_externnotify(context, mailbox);
+
+	ast_mutex_unlock(&subslock);
+}
+
+/* send a subscribe request to a remote peer */
+static int subscribe_remote_mailbox(const char *techname, const char *peer, const char *mbox, const char *context)
+{
+	const struct ast_channel_tech *tech = ast_get_channel_tech(techname);
+	struct ast_sub_pkt pkt;
+	char buffer[1024];
+
+	if(!tech) {
+		ast_log(LOG_WARNING, "'%s' technology specified in subscription, but this technology is not available\n", techname);
+		return 0;
+	}
+
+	if(!tech->subscribe) {
+		ast_log(LOG_WARNING, "Subscription requested over '%s' technology, but that technology does not support it\n", techname);
+		return 0;
+	}
+
+	ast_sub_pkt_init(&pkt, buffer, sizeof(buffer));
+	
+	if(ast_sub_pkt_add_str(&pkt, mbox) == -1) {
+		ast_log(LOG_WARNING, "Unable to store mailbox name in request\n");
+		return 0;
+	}
+
+	if(ast_sub_pkt_add_str(&pkt, context) == -1) {
+		ast_log(LOG_WARNING, "Unable to store context name in request\n");
+		return 0;
+	}
+
+	ast_log(LOG_NOTICE, "Packet length is %d\n", pkt.length);
+
+	ast_log(LOG_NOTICE, "Subscribe to %s @ %s on %s via %s\n", mbox, context, peer, techname);
+
+	tech->subscribe(peer, AST_SUB_VOICEMAIL, &pkt, (ast_sub_changed_func) remote_mailbox_callback);
+
+	ast_log(LOG_NOTICE, "Subscribe complete\n");
+
+	return 1;
+}
+
+/* send an unsubscribe request to a remote peer */
+static int unsubscribe_remote_mailbox(const char *techname, const char *peer, const char *mbox, const char *context)
+{	
+	const struct ast_channel_tech *tech = ast_get_channel_tech(techname);
+	struct ast_sub_pkt pkt;
+	char buffer[1024];
+
+	if(!tech) {
+		ast_log(LOG_WARNING, "'%s' technology specified in subscription, but this technology is not available\n", techname);
+		return 0;
+	}
+
+	if(!tech->unsubscribe) {
+		ast_log(LOG_WARNING, "Unsubscription requested over '%s' technology, but that technology does not support it\n", techname);
+		return 0;
+	}
+	
+
+	ast_sub_pkt_init(&pkt, buffer, sizeof(buffer));
+
+	if(ast_sub_pkt_add_str(&pkt, mbox) == -1) {
+		ast_log(LOG_WARNING, "Unable to store mailbox name in request\n");
+		return 0;
+	}
+
+	if(ast_sub_pkt_add_str(&pkt, context) == -1) {
+		ast_log(LOG_WARNING, "Unable to store context name in request\n");
+		return 0;
+	}
+	
+	ast_log(LOG_NOTICE, "Unsubscribe %s @ %s on %s\n", mbox, context, peer);
+	
+	tech->unsubscribe(peer, AST_SUB_VOICEMAIL, &pkt, (ast_sub_changed_func) remote_mailbox_callback);
+	
+	ast_log(LOG_NOTICE, "Unsubscribe complete\n");
+
+	return 1;
+}
+
+/* monitor the state of the peer and resubscribe as necessary */
+static int devstate_changed(const char *dev, int state, void *data)
+{
+	char *addr;
+	struct vm_subscription *sub;
+	struct vm_remote_subpeer *client;
+	struct vm_remote_subpeer *server;
+	char *tech;
+	char *tmp;
+	
+
+	addr = ast_strdupa(dev);
+	tmp = addr;
+	
+	if((tech = strsep(&tmp, "/")) == NULL) {
+		return 0;
+	}
+
+	ast_mutex_lock(&vmlock);
+	ast_mutex_lock(&subslock);
+
+	client = find_remote_peer(&subclients, tech, tmp);
+	server = find_remote_peer(&subservers, tech, tmp);
+
+	// this peer has nothing to do with us
+	if(!client && !server) {
+		ast_mutex_unlock(&vmlock);
+		ast_mutex_unlock(&subslock);
+		return 0;
+	}
+
+	if(state == AST_DEVICE_UNAVAILABLE) {
+		if(server) {
+			// this way we know that we need to resubscribe when the host comes around again
+			if(server->subscribed) {
+				server->subscribed = 0;
+			}
+		}
+
+		// we don't forget about a client if it goes away because we cannot assume it will
+		// resubscribe. we could forget about them if we could send them a 'WE LOST YOU' message
+		// so that the client can resubscribe if it needs to 
+	} else if(state != AST_DEVICE_UNAVAILABLE && state != AST_DEVICE_INVALID && state != AST_DEVICE_UNKNOWN) {
+		// our server has appeared, make sure to resubscribe if necessary
+		if(server) {
+			if(!server->subscribed) {
+				ast_log(LOG_NOTICE, "Since the device is now available... we will resubscribe\n");
+				AST_LIST_TRAVERSE(&subs_out, sub, list) {
+					if(!strcasecmp(tech, sub->peer->tech) && !strcasecmp(tmp, sub->peer->name)) {
+						subscribe_remote_mailbox(sub->peer->tech, sub->peer->name, sub->remote_mailbox, sub->remote_context);
+					}
+				}
+				server->subscribed = 1;
+			}
+		}
+	}
+
+	ast_mutex_unlock(&subslock);
+	ast_mutex_unlock(&vmlock);
+
+	return 0;	
+}
+
+
+
+
 static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, int msgnum, long duration, char *fmt, char *cidnum, char *cidname)
 {
 	char todir[256], fn[256], ext_context[256], *stringp;
@@ -3325,6 +4118,7 @@
 		ast_app_messagecount(ext_context, &newmsgs, &oldmsgs);
 	}
 	manager_event(EVENT_FLAG_CALL, "MessageWaiting", "Mailbox: %s@%s\r\nWaiting: %d\r\nNew: %d\r\nOld: %d\r\n", vmu->mailbox, vmu->context, ast_app_has_voicemail(ext_context, NULL), newmsgs, oldmsgs);
+	inform_subscribers(vmu, newmsgs, oldmsgs, ext_context);
 	run_externnotify(vmu->context, vmu->mailbox);
 	return 0;
 }
@@ -3542,6 +4336,7 @@
 				}
 				/* Leave voicemail for someone */
 				manager_event(EVENT_FLAG_CALL, "MessageWaiting", "Mailbox: %s\r\nWaiting: %d\r\n", ext_context, has_voicemail(ext_context, NULL));
+				inform_subscribers(vmtmp, -1, -1, ext_context);
 				run_externnotify(vmtmp->context, vmtmp->mailbox);
 	
 				saved_messages++;
@@ -5455,6 +6250,7 @@
 	if (valid) {
 		snprintf(ext_context, sizeof(ext_context), "%s@%s", vms.username, vmu->context);
 		manager_event(EVENT_FLAG_CALL, "MessageWaiting", "Mailbox: %s\r\nWaiting: %d\r\n", ext_context, has_voicemail(ext_context, NULL));
+		inform_subscribers(vmu, -1, -1, ext_context);
 		run_externnotify(vmu->context, vmu->mailbox);
 	}
 	if (vmu)
@@ -5555,6 +6351,65 @@
 	return res;
 }
 
+/* stores information about a remote mailbox subscription */
+static int append_remote_sub(char *spec, char *localbox, char *localcontext)
+{
+	int devstate;
+	char addr[256];
+	struct vm_remote_subpeer peer;
+	struct vm_subscription sub, *s;
+	struct vm_subscription *sub_alloc;
+
+	memset(&sub, 0, sizeof(sub));
+	if(!parse_remote_mailbox(spec, &peer, &sub, localbox, localcontext)) {
+		ast_log(LOG_WARNING, "Error parsing remote mailbox specifier '%s'\n", spec);
+		return -1;
+	}
+
+	if(localbox[0] == '_' && strcmp(localbox, sub.remote_mailbox)) {
+		ast_log(LOG_WARNING, "The local and remote mailbox in a subscription must be the same when using pattern matching\n");
+		return -1;
+	}
+
+	AST_LIST_TRAVERSE(&subs_out, s, list) {
+		if(!strcasecmp(s->local_mailbox, localbox) && !strcasecmp(s->local_context, localcontext) &&
+		   !strcasecmp(s->remote_mailbox, sub.remote_mailbox) && !strcasecmp(s->remote_context, sub.remote_context) &&
+		   !strcasecmp(s->peer->name, peer.name) && !strcasecmp(s->peer->tech, peer.tech)) {
+			ast_log(LOG_WARNING, "Already subscribed\n");
+			return 0;
+		}
+	}
+
+	if(localbox[0] == '_')
+		sub.is_wildcard = 1;
+
+	sub.peer = add_remote_peer(&subservers, peer.tech, peer.name, NULL);
+
+	ast_copy_string(sub.local_mailbox, localbox, sizeof(sub.local_mailbox));
+	ast_copy_string(sub.local_context, localcontext, sizeof(sub.local_context));
+
+	snprintf(addr, sizeof(addr), "%s/%s", sub.peer->tech, sub.peer->name); 
+	devstate = ast_device_state(addr);
+
+	sub_alloc = (struct vm_subscription *) malloc(sizeof(struct vm_subscription));
+	memcpy(sub_alloc, &sub, sizeof(struct vm_subscription));
+
+	ast_log(LOG_NOTICE, "Adding subscription %s %s %s %s\n", sub_alloc->peer->tech, sub_alloc->peer->name, sub_alloc->remote_mailbox, sub_alloc->remote_context);
+	
+	ast_mutex_lock(&subslock);
+
+	AST_LIST_INSERT_TAIL(&subs_out, sub_alloc, list);
+
+	ast_mutex_unlock(&subslock);
+
+	if(devstate != AST_DEVICE_UNAVAILABLE && devstate != AST_DEVICE_INVALID) {
+		sub.peer->subscribed = 1;
+		subscribe_remote_mailbox(sub.peer->tech, sub.peer->name, sub.remote_mailbox, sub.remote_context);
+	}
+	
+	return 0;
+}
+
 static int append_mailbox(char *context, char *mbox, char *data)
 {
 	/* Assumes lock is already held */
@@ -5564,6 +6419,10 @@
 	struct ast_vm_user *vmu;
 
 	ast_copy_string(tmp, data, sizeof(tmp));
+
+	if((s = strchr(tmp, '/')) && strchr(tmp, ',') == NULL)
+		return append_remote_sub(tmp, mbox, context);
+
 	vmu = malloc(sizeof(struct ast_vm_user));
 	if (vmu) {
 		memset(vmu, 0, sizeof(struct ast_vm_user));
@@ -5573,7 +6432,7 @@
 		populate_defaults(vmu);
 
 		stringp = tmp;
-		if ((s = strsep(&stringp, ","))) 
+		if ((s = strsep(&stringp, ",")))
 			ast_copy_string(vmu->password, s, sizeof(vmu->password));
 		if (stringp && (s = strsep(&stringp, ","))) 
 			ast_copy_string(vmu->fullname, s, sizeof(vmu->fullname));
@@ -5803,6 +6662,43 @@
 	handle_show_voicemail_zones, "List zone message formats",
 	show_voicemail_zones_help, NULL };
 
+
+void *resubscribe_thread(void *data)
+{
+	struct vm_subscription *sub;
+
+	for(;;)
+	{
+		unsigned long sleepamount = resubscribe_mins;
+		// just in case. we don't want it out of hand
+		if(sleepamount < MIN_RESUBSCRIBE_MINS)
+			sleepamount = MIN_RESUBSCRIBE_MINS;
+
+		ast_log(LOG_NOTICE, "Going to sleep\n");
+		usleep(sleepamount * 60 * 1000000);
+		ast_log(LOG_NOTICE, "Woke up\n");
+
+		ast_mutex_lock(&vmlock);	
+		AST_LIST_TRAVERSE(&subs_out, sub, list) {
+			subscribe_remote_mailbox(sub->peer->tech, sub->peer->name, sub->remote_mailbox, sub->remote_context);
+		}
+		ast_mutex_unlock(&vmlock);
+	}
+
+	return NULL;
+}
+
+static void kill_resubscribe_thread(void)
+{
+	if(resub_thread != AST_PTHREADT_NULL) {
+		pthread_cancel(resub_thread);
+		pthread_kill(resub_thread, SIGURG);
+		pthread_join(resub_thread, NULL);
+		resub_thread = AST_PTHREADT_NULL;
+	}
+}
+
+
 static int load_config(void)
 {
 	struct ast_vm_user *cur, *l;
@@ -5821,6 +6717,7 @@
 	char *asthearenv;
 	char *astsaydurationinfo;
 	char *astsaydurationminfo;
+	char *astresubscribe;
 	char *silencestr;
 	char *maxmsgstr;
 	char *astdirfwd;
@@ -5836,7 +6733,7 @@
 	char *emaildateformatstr;
 	int x;
 	int tmpadsi[4];
-
+	
 	cfg = ast_config_load(VOICEMAIL_CONFIG);
 	ast_mutex_lock(&vmlock);
 	cur = users;
@@ -5844,6 +6741,7 @@
 		l = cur;
 		cur = cur->next;
 		ast_set_flag(l, VM_ALLOCED);	
+		free_user_subscribers(l);
 		free_user(l);
 	}
 	zcur = zones;
@@ -5852,10 +6750,14 @@
 		zcur = zcur->next;
 		free_zone(zl);
 	}
+
 	zones = NULL;
 	zonesl = NULL;
 	users = NULL;
 	usersl = NULL;
+	resubscribe_mins = 0;
+	kill_resubscribe_thread();
+
 	memset(ext_pass_cmd, 0, sizeof(ext_pass_cmd));
 
 	if (cfg) {
@@ -5870,6 +6772,24 @@
 			astsearch = "no";
 		ast_set2_flag((&globalflags), ast_true(astsearch), VM_SEARCH);
 
+		if ((astresubscribe = ast_variable_retrieve(cfg, "general", "resubscribe_mins"))) {
+			int resubtime = atoi(astresubscribe);
+			if(resubtime < MIN_RESUBSCRIBE_MINS) {
+				ast_log(LOG_WARNING, "Resubscribe time should be at least %d minutes\n", MIN_RESUBSCRIBE_MINS);
+			} else {
+				if(resub_thread == AST_PTHREADT_NULL) {
+					pthread_attr_t attr;
+					pthread_attr_init(&attr);
+					pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+					ast_pthread_create(&resub_thread, &attr, &resubscribe_thread, NULL);
+				}
+				resubscribe_mins = resubtime;
+			}
+
+		}
+				
+
 #ifdef USE_ODBC_STORAGE
 		strcpy(odbc_database, "asterisk");
 		if ((thresholdstr = ast_variable_retrieve(cfg, "general", "odbcstorage"))) {
@@ -6234,6 +7154,9 @@
 		}
 		ast_mutex_unlock(&vmlock);
 		ast_config_destroy(cfg);
+
+		ast_log(LOG_NOTICE, "Config done\n");
+
 		return 0;
 	} else {
 		ast_mutex_unlock(&vmlock);
@@ -6244,12 +7167,61 @@
 
 int reload(void)
 {
-	return(load_config());
+	struct vm_subscription *sub;
+	int rv = load_config();
+
+	ast_mutex_lock(&vmlock);
+	/* after the config is loaded, all the subscriptions are cleared.
+	   we have to resubscribe all matching mailboxes */
+	AST_LIST_TRAVERSE(&subs_in, sub, list) {
+		remote_subunsub_request_direct(sub->peer, sub->local_mailbox, sub->local_context, VM_SUBSCRIBE, VM_SUB_NOINFORM);
+	}
+	ast_mutex_unlock(&vmlock);
+
+	return rv;
 }
 
 int unload_module(void)
 {
+	struct vm_subscription *mcur, *sub;
+	struct vm_remote_subpeer *pcur;
 	int res;
+
+	kill_resubscribe_thread();
+
+	ast_devstate_del(devstate_changed, NULL);
+		
+	AST_LIST_TRAVERSE(&subs_out, sub, list) {
+		unsubscribe_remote_mailbox(sub->peer->tech, sub->peer->name, sub->remote_mailbox, sub->remote_context);
+	}
+
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&subs_in, mcur, list) {
+		AST_LIST_REMOVE_CURRENT(&subs_in, list)
+		free_subscription(mcur);
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	AST_LIST_HEAD_INIT(&subs_in);
+	
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&subs_out, mcur, list) {
+		AST_LIST_REMOVE_CURRENT(&subs_out, list)
+		free_subscription(mcur);
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	AST_LIST_HEAD_INIT(&subs_out);
+	
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&subclients, pcur, list) {
+		AST_LIST_REMOVE_CURRENT(&subclients, list)
+		free_subpeer(pcur);
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	AST_LIST_HEAD_INIT_NOLOCK(&subclients);
+	
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&subservers, pcur, list) {
+		AST_LIST_REMOVE_CURRENT(&subservers, list)
+		free_subpeer(pcur);
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	AST_LIST_HEAD_INIT_NOLOCK(&subservers);
 	
 	res = ast_unregister_application(app);
 	res |= ast_unregister_application(app2);
@@ -6258,6 +7230,9 @@
 	res |= ast_cli_unregister(&show_voicemail_users_cli);
 	res |= ast_cli_unregister(&show_voicemail_zones_cli);
 	ast_uninstall_vm_functions();
+
+	ast_sub_event_parser_set(AST_SUB_VOICEMAIL, NULL, sizeof(struct vm_sub_info));
+	ast_sub_event_source_del(AST_SUB_VOICEMAIL, remote_subscribe_request, remote_unsubscribe_request);
 	
 	STANDARD_HANGUP_LOCALUSERS;
 
@@ -6273,6 +7248,15 @@
 	res |= ast_register_application(app4, vmauthenticate, synopsis_vmauthenticate, descrip_vmauthenticate);
 	if (res)
 		return(res);
+	
+	AST_LIST_HEAD_INIT_NOLOCK(&subclients);
+	AST_LIST_HEAD_INIT_NOLOCK(&subservers);
+	
+	ast_devstate_add(devstate_changed, NULL);
+	
+	ast_sub_event_source_add(AST_SUB_VOICEMAIL, remote_subscribe_request, remote_unsubscribe_request);
+	ast_sub_event_parser_set(AST_SUB_VOICEMAIL, vm_changed_event_parser, sizeof(struct vm_sub_info));
+
 
 	if ((res=load_config())) {
 		return(res);
--- asterisk-1.2.1/channels/iax2.h	2005-11-29 13:24:39.000000000 -0500
+++ mwi/channels/iax2.h	2006-01-19 11:00:48.000000000 -0500
@@ -67,6 +67,9 @@
 #define IAX_COMMAND_PROVISION	35	/* Provision device */
 #define IAX_COMMAND_FWDOWNL	36	/* Download firmware */
 #define IAX_COMMAND_FWDATA	37	/* Firmware Data */
+#define IAX_COMMAND_SUBSCRIBE	38	/* Get voicemail information */
+#define IAX_COMMAND_UNSUBSCRIBE	39	/* Set voicemail information */
+#define IAX_COMMAND_SUBINFO	40	/* Set voicemail information */
 
 #define IAX_DEFAULT_REG_EXPIRE  60	/* By default require re-registration once per minute */
 
@@ -128,6 +131,9 @@
 #define IAX_IE_RR_DROPPED			50		/* Dropped frames (presumably by jitterbuf) u32 */
 #define IAX_IE_RR_OOO				51		/* Frames received Out of Order u32 */
 
+#define IAX_IE_SUBID				60
+#define IAX_IE_SUBINFO				61
+
 
 #define IAX_AUTH_PLAINTEXT			(1 << 0)
 #define IAX_AUTH_MD5				(1 << 1)
--- asterisk-1.2.1/channels/chan_iax2.c	2005-11-29 13:24:39.000000000 -0500
+++ mwi/channels/chan_iax2.c	2006-01-19 09:52:20.000000000 -0500
@@ -1,3 +1,4 @@
+
 /*
  * Asterisk -- An open source telephony toolkit.
  *
@@ -406,6 +407,16 @@
 
 static struct iax2_registry *registrations;
 
+struct iax2_sub {
+	char peer[80];
+	int subid;
+	ast_sub_changed_func func;
+
+	AST_LIST_ENTRY(iax2_sub) list;
+};
+
+AST_LIST_HEAD_STATIC(subs, iax2_sub);
+
 /* Don't retry more frequently than every 10 ms, or less frequently than every 5 seconds */
 #define MIN_RETRY_TIME		100
 #define MAX_RETRY_TIME  	10000
@@ -741,6 +752,8 @@
 static enum ast_bridge_result iax2_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
 static int iax2_transfer(struct ast_channel *c, const char *dest);
 static int iax2_fixup(struct ast_channel *oldchannel, struct ast_channel *newchan);
+static int iax2_subscribe(const char *peer, int subid, struct ast_sub_pkt *pkt, ast_sub_changed_func func);
+static int iax2_unsubscribe(const char *peer, int subid, struct ast_sub_pkt *pkt, ast_sub_changed_func func);
 
 static const struct ast_channel_tech iax2_tech = {
 	.type = channeltype,
@@ -764,6 +777,8 @@
 	.bridge = iax2_bridge,
 	.transfer = iax2_transfer,
 	.fixup = iax2_fixup,
+	.subscribe = iax2_subscribe,
+	.unsubscribe = iax2_unsubscribe,
 };
 
 static int send_ping(void *data)
@@ -1043,6 +1058,9 @@
 	struct timeval now;
 	char iabuf[INET_ADDRSTRLEN];
 	char host[80];
+
+	//ast_log(LOG_NOTICE, "Finding call number\n");
+
 	if (new <= NEW_ALLOW) {
 		/* Look for an existing connection first */
 		for (x=1;(res < 1) && (x<maxnontrunkcall);x++) {
@@ -3042,6 +3060,110 @@
 	return 0;
 }
 
+static int iax2_new_callno(const char *peername)
+{
+	struct iax2_peer *peer = find_peer(peername, 1);
+
+	if (!peer) {
+		ast_log(LOG_WARNING, "Can't find peername '%s' ...\n", peername);
+		return -1;
+	}
+	return find_callno(0, 0, &peer->addr, NEW_FORCE, 1, defaultsockfd);
+}
+
+static int iax2_send_subunsub(const char *peer, int command, int subid, struct ast_sub_pkt *pkt)
+{
+	struct iax_ie_data ied;
+	int callno;
+
+	if(subid != AST_SUB_VOICEMAIL) {
+		ast_log(LOG_WARNING, "Attempt to send unknown subscription '%d'\n", subid);
+		return -1;
+	}	
+
+	callno = iax2_new_callno(peer);
+	if(callno < 0) {
+		ast_log(LOG_WARNING, "Invalid peer name '%s'\n", peer);
+		return -1;
+	}
+
+	memset(&ied, 0, sizeof(ied));
+	iax_ie_append_int(&ied, IAX_IE_SUBID, subid);
+	iax_ie_append_raw(&ied, IAX_IE_SUBINFO, pkt->data, pkt->pos);
+		
+	ast_mutex_lock(&iaxsl[callno]);
+	send_command_final(iaxs[callno], AST_FRAME_IAX, command, 0, ied.buf, ied.pos, -1);
+	ast_mutex_unlock(&iaxsl[callno]);
+
+	return 0;
+}
+
+int iax2_subscribe(const char *peer, int subid, struct ast_sub_pkt *pkt, ast_sub_changed_func func)
+{
+	struct iax2_sub *sub = NULL;
+
+	AST_LIST_LOCK(&subs);
+	AST_LIST_TRAVERSE(&subs, sub, list) {
+		if(sub->subid == subid && sub->func == func) {
+			break;
+		}
+	}
+	AST_LIST_UNLOCK(&subs);
+
+	if(!sub) {
+		sub = malloc(sizeof(struct iax2_sub));
+
+		memset(sub, 0, sizeof(struct iax2_sub));
+		ast_copy_string(sub->peer, peer, sizeof(sub->peer));
+		sub->subid = subid;
+		sub->func = func;
+
+		AST_LIST_LOCK(&subs);
+		AST_LIST_INSERT_TAIL(&subs, sub, list);
+		AST_LIST_UNLOCK(&subs);
+	}
+
+	return iax2_send_subunsub(peer, IAX_COMMAND_SUBSCRIBE, subid, pkt);
+}
+
+int iax2_unsubscribe(const char *peer, int subid, struct ast_sub_pkt *pkt, ast_sub_changed_func func)
+{
+	struct iax2_sub *sub;
+	
+	AST_LIST_LOCK(&subs);
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&subs, sub, list) {
+		if(sub->subid == subid && sub->func == func) {
+			AST_LIST_REMOVE_CURRENT(&subs, list);
+			free(sub);
+			break;
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	AST_LIST_UNLOCK(&subs);
+
+	return iax2_send_subunsub(peer, IAX_COMMAND_UNSUBSCRIBE, subid, pkt);
+}
+
+void iax2_sub_event_listener(const char *peer, int subid, struct ast_sub_pkt *pkt)
+{
+	struct iax_ie_data ied;
+	int callno;
+
+	callno = iax2_new_callno(peer);
+	if(callno < 0)
+		return;
+
+	memset(&ied, 0, sizeof(ied));
+	iax_ie_append_int(&ied, IAX_IE_SUBID, AST_SUB_VOICEMAIL);
+	iax_ie_append_raw(&ied, IAX_IE_SUBINFO, pkt->data, pkt->pos);
+	
+	ast_mutex_lock(&iaxsl[callno]);
+	send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_SUBINFO, 0, ied.buf, ied.pos, -1);
+	ast_mutex_unlock(&iaxsl[callno]);
+
+	return;
+}
+
 static int iax2_hangup(struct ast_channel *c) 
 {
 	unsigned short callno = PTR_TO_CALLNO(c->tech_pvt);
@@ -6430,8 +6552,9 @@
 		return 1;
 	}
 #ifdef DEBUG_SUPPORT
-	if (iaxdebug)
+	if (iaxdebug) {
 		iax_showframe(NULL, fh, 1, &sin, res - sizeof(struct ast_iax2_full_hdr));
+	}
 #endif
 	if (ntohs(mh->callno) & IAX_FLAG_FULL) {
 		/* Get the destination call number */
@@ -6445,7 +6568,8 @@
 		}
 		if ((f.frametype == AST_FRAME_IAX) && ((f.subclass == IAX_COMMAND_NEW) || (f.subclass == IAX_COMMAND_REGREQ) ||
 						       (f.subclass == IAX_COMMAND_POKE) || (f.subclass == IAX_COMMAND_FWDOWNL) ||
-						       (f.subclass == IAX_COMMAND_REGREL)))
+						       (f.subclass == IAX_COMMAND_REGREL) || (f.subclass == IAX_COMMAND_SUBSCRIBE) ||
+						       (f.subclass == IAX_COMMAND_UNSUBSCRIBE) || (f.subclass == IAX_COMMAND_SUBINFO)))
 			new = NEW_ALLOW;
 	} else {
 		/* Don't know anything about it yet */
@@ -6474,6 +6598,7 @@
 		}
 		if (fr.callno > 0) 
 			ast_mutex_unlock(&iaxsl[fr.callno]);
+
 		return 1;
 	}
 	if (ast_test_flag(iaxs[fr.callno], IAX_ENCRYPTED)) {
@@ -6555,6 +6680,7 @@
 					/* Send a VNAK requesting retransmission */
 					iax2_vnak(fr.callno);
 				}
+				
 				ast_mutex_unlock(&iaxsl[fr.callno]);
 				return 1;
 			}
@@ -6585,6 +6711,7 @@
 			/* XXX This code is not very efficient.  Surely there is a better way which still
 			       properly handles boundary conditions? XXX */
 			/* First we have to qualify that the ACKed value is within our window */
+
 			for (x=iaxs[fr.callno]->rseqno; x != iaxs[fr.callno]->oseqno; x++)
 				if (fr.iseqno == x)
 					break;
@@ -6647,6 +6774,7 @@
 				f.data = empty;
 			memset(&ies, 0, sizeof(ies));
 		}
+
 		if (f.frametype == AST_FRAME_VOICE) {
 			if (f.subclass != iaxs[fr.callno]->voiceformat) {
 					iaxs[fr.callno]->voiceformat = f.subclass;
@@ -6759,6 +6887,58 @@
 					iaxs[fr.callno]->transferring = TRANSFER_READY;
 				}
 				break;
+			case IAX_COMMAND_SUBSCRIBE:
+			case IAX_COMMAND_UNSUBSCRIBE:
+				{
+					int subid = ies.subid;
+					struct ast_sub_pkt *pkt = &ies.subinfo;
+					struct iax2_peer *peer = find_peer(iaxs[fr.callno]->host, 1);
+					
+					if(!peer) {
+						ast_log(LOG_WARNING, "Subscribe request from unknown host '%s'\n", iaxs[fr.callno]->host);
+						break;
+					}
+					
+					if(f.subclass == IAX_COMMAND_SUBSCRIBE) {
+						ast_sub_event_source_subscribe(subid, channeltype, iaxs[fr.callno]->host, pkt, iax2_sub_event_listener);
+					} else if(f.subclass == IAX_COMMAND_UNSUPPORT) {
+						ast_sub_event_source_unsubscribe(subid, channeltype, iaxs[fr.callno]->host, pkt, iax2_sub_event_listener);
+					}
+				}
+				break;
+			case IAX_COMMAND_SUBINFO:
+				{
+					int subid = ies.subid;
+					struct ast_sub_pkt *pkt = &ies.subinfo;
+					struct iax2_sub *sub;
+					int datasize = ast_sub_event_parser_get_datasize(subid);
+					void *params;
+
+					if(datasize == -1) {
+						ast_log(LOG_WARNING, "Unable to determine data size for incoming event\n");
+						break;
+					}
+
+					params = malloc(datasize);
+					if(ast_sub_event_parser_parse(subid, pkt, params) == -1) {
+						ast_log(LOG_WARNING, "Error parsing incoming update\n");
+						free(params);
+						break;
+					}
+
+					AST_LIST_LOCK(&subs);
+					AST_LIST_TRAVERSE(&subs, sub, list) {
+						if(sub->subid == subid && !strcasecmp(iaxs[fr.callno]->host, sub->peer)) {
+							sub->func(channeltype, sub->peer, params);
+						}
+					}
+					AST_LIST_UNLOCK(&subs);
+
+					free(params);
+					//vm_mailbox_changed(channeltype, iaxs[fr.callno]->host, mbox, context, newmsgs, oldmsgs, hasvm);
+				}
+				break;
+	
 			case IAX_COMMAND_NEW:
 				/* Ignore if it's already up */
 				if (ast_test_flag(&iaxs[fr.callno]->state, IAX_STATE_STARTED | IAX_STATE_TBD))
@@ -7555,6 +7735,7 @@
 		fr.ts = (iaxs[fr.callno]->last & 0xFFFF0000L) | ntohs(mh->ts);
 		/* FIXME? Surely right here would be the right place to undo timestamp wraparound? */
 	}
+
 	/* Don't pass any packets until we're started */
 	if (!ast_test_flag(&iaxs[fr.callno]->state, IAX_STATE_STARTED)) {
 		ast_mutex_unlock(&iaxsl[fr.callno]);
@@ -9483,6 +9664,7 @@
 
 static int __unload_module(void)
 {
+	struct iax2_sub *sub;
 	int x;
 	/* Cancel the network thread, close the net socket */
 	if (netthreadid != AST_PTHREADT_NULL) {
@@ -9501,6 +9683,15 @@
 	ast_channel_unregister(&iax2_tech);
 	delete_users();
 	iax_provision_unload();
+
+	AST_LIST_LOCK(&subs);
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&subs, sub, list) {
+		AST_LIST_REMOVE_CURRENT(&subs, list);
+		free(sub);
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	AST_LIST_UNLOCK(&subs);
+
 	return 0;
 }
 
@@ -9511,6 +9702,7 @@
 	ast_mutex_destroy(&peerl.lock);
 	ast_mutex_destroy(&waresl.lock);
 	ast_custom_function_unregister(&iaxpeer_function);
+
 	return __unload_module();
 }
 
@@ -9526,7 +9718,9 @@
 	
 	struct ast_netsock *ns;
 	struct sockaddr_in sin;
-	
+
+	AST_LIST_HEAD_INIT(&subs);
+
 	ast_custom_function_register(&iaxpeer_function);
 
 	iax_set_output(iax_debug_output);
@@ -9641,3 +9835,4 @@
 {
 	return ASTERISK_GPL_KEY;
 }
+
--- asterisk-1.2.1/channels/iax2-parser.c	2005-11-29 13:24:39.000000000 -0500
+++ mwi/channels/iax2-parser.c	2006-01-19 09:47:31.000000000 -0500
@@ -38,6 +38,8 @@
 #include "asterisk/frame.h"
 #include "asterisk/utils.h"
 #include "asterisk/unaligned.h"
+#include "asterisk/subscribe.h"
+
 #include "iax2.h"
 #include "iax2-parser.h"
 #include "iax2-provision.h"
@@ -224,6 +226,7 @@
 	{ IAX_IE_CAUSE, "CAUSE", dump_string },
 	{ IAX_IE_IAX_UNKNOWN, "UNKNOWN IAX CMD", dump_byte },
 	{ IAX_IE_MSGCOUNT, "MESSAGE COUNT", dump_short },
+	{ IAX_IE_SUBID, "SUBSCRIPTION ID", dump_int },
 	{ IAX_IE_AUTOANSWER, "AUTO ANSWER REQ" },
 	{ IAX_IE_TRANSFERID, "TRANSFER ID", dump_int },
 	{ IAX_IE_RDNIS, "REFERRING DNIS", dump_string },
@@ -433,7 +436,10 @@
 		"TRANSFR",
 		"PROVISN",
 		"FWDWNLD",
-		"FWDATA "
+		"FWDATA ",
+		"SUB    ",
+		"UNSUB  ",
+		"SUBINFO",
 	};
 	const char *cmds[] = {
 		"(0?)",
@@ -591,6 +597,9 @@
 	char tmp[256];
 	memset(ies, 0, (int)sizeof(struct iax_ies));
 	ies->msgcount = -1;
+	ies->subid = -1;
+	ies->subinfo.ptr = NULL;
+	ies->subinfo.length = 0;
 	ies->firmwarever = -1;
 	ies->calling_ton = -1;
 	ies->calling_tns = -1;
@@ -745,6 +754,17 @@
 			} else
 				ies->msgcount = ntohs(get_unaligned_uint16(data + 2));	
 			break;
+		case IAX_IE_SUBID:
+			if (len != (int)sizeof(int)) {
+				snprintf(tmp, (int)sizeof(tmp), "Expecting subid to be %d bytes long but was %d\n", (int)sizeof(int), len);
+				errorf(tmp);
+			} else
+				ies->subid = ntohl(get_unaligned_uint32(data + 2));	
+			break;
+		case IAX_IE_SUBINFO:
+			ies->subinfo.ptr = data + 2;
+			ies->subinfo.length = len;
+			break;
 		case IAX_IE_AUTOANSWER:
 			ies->autoanswer = 1;
 			break;
--- asterisk-1.2.1/channels/iax2-provision.c	2005-11-29 13:24:39.000000000 -0500
+++ mwi/channels/iax2-provision.c	2006-01-19 10:29:34.000000000 -0500
@@ -44,6 +44,8 @@
 #include "asterisk/md5.h"
 #include "asterisk/astdb.h"
 #include "asterisk/utils.h"
+#include "asterisk/subscribe.h"
+
 #include "iax2.h"
 #include "iax2-provision.h"
 #include "iax2-parser.h"
--- asterisk-1.2.1/channels/iax2-parser.h	2005-11-29 13:24:39.000000000 -0500
+++ mwi/channels/iax2-parser.h	2006-01-19 11:00:48.000000000 -0500
@@ -49,6 +49,10 @@
 	char *cause;
 	unsigned char causecode;
 	unsigned char iax_unknown;
+	char *mailbox;
+	char *context;
+	int subid;
+	struct ast_sub_pkt subinfo;
 	int msgcount;
 	int autoanswer;
 	int musiconhold;
--- asterisk-1.2.1/asterisk.c	2005-11-29 13:24:39.000000000 -0500
+++ mwi/asterisk.c	2006-01-19 10:30:16.000000000 -0500
@@ -2240,6 +2240,10 @@
 		printf(term_quit());
 		exit(1);
 	}
+	if (ast_sub_init()) {
+		printf(term_quit());
+		exit(1);
+	}
 	ast_rtp_init();
 	if (ast_image_init()) {
 		printf(term_quit());
--- asterisk-1.2.1/include/asterisk/channel.h	2005-11-29 13:24:39.000000000 -0500
+++ mwi/include/asterisk/channel.h	2006-01-19 11:00:48.000000000 -0500
@@ -114,6 +114,7 @@
 #include "asterisk/cdr.h"
 #include "asterisk/utils.h"
 #include "asterisk/linkedlists.h"
+#include "asterisk/subscribe.h"
 
 #define MAX_LANGUAGE		20
 
@@ -169,7 +170,6 @@
 	this driver supports and where different callbacks are 
 	implemented.
 */
-	
 
 struct ast_channel_tech {
 	const char * const type;
@@ -242,6 +242,13 @@
 
 	/*! Find bridged channel */
 	struct ast_channel *(* const bridged_channel)(struct ast_channel *chan, struct ast_channel *bridge);
+
+	/*! Subscribe to something on a remote peer */
+	int (* const subscribe)(const char *peer, int subid, struct ast_sub_pkt *pkt, ast_sub_changed_func func);
+
+	/*! Unsubscribe from something on a remote peer */
+	int (* const unsubscribe)(const char *peer, int subid, struct ast_sub_pkt *pkt, ast_sub_changed_func func);
+	
 };
 
 struct ast_channel_spy_list;
@@ -446,6 +453,7 @@
 #define AST_FEATURE_FLAG_CALLEE		(1 << 1)
 #define AST_FEATURE_FLAG_CALLER		(1 << 2)
 
+
 struct ast_bridge_config {
 	struct ast_flags features_caller;
 	struct ast_flags features_callee;
--- asterisk-1.2.1/include/asterisk/subscribe.h	2006-01-19 08:51:20.000000000 -0500
+++ mwi/include/asterisk/subscribe.h	2006-01-19 11:00:49.000000000 -0500
@@ -0,0 +1,45 @@
+struct ast_sub_pkt {
+	char *data;
+	char *ptr;
+	int length;
+	int pos;
+};
+
+enum
+{
+        AST_SUB_VOICEMAIL,
+        AST_SUB_ENUM_COUNT
+};
+
+#define AST_PKT_STR 0
+#define AST_PKT_INT 1
+
+int ast_sub_pkt_add_raw(struct ast_sub_pkt *pkt, char type, char *src, char len);
+
+typedef void (*ast_sub_listener_func)(const char *peer, int subid, struct ast_sub_pkt *pkt);
+typedef void (*ast_sub_changed_func)(const char *tech, const char *peer, struct ast_sub_pkt *pkt);
+typedef int  (*ast_sub_subscribe_func)(const char *tech, const char *peer, struct ast_sub_pkt *pkt, ast_sub_listener_func listener_func);
+typedef int  (*ast_sub_unsubscribe_func)(const char *tech, const char *peer, struct ast_sub_pkt *pkt, ast_sub_listener_func listener_func);
+typedef int  (*ast_sub_event_parser_func)(struct ast_sub_pkt *pkt, void *out);
+
+void ast_sub_event_source_add(int subid, ast_sub_subscribe_func subscribe_func, ast_sub_unsubscribe_func unsubscribe_func);
+void ast_sub_event_source_del(int subid, ast_sub_subscribe_func subscribe_func, ast_sub_unsubscribe_func unsubscribe_func);
+int ast_sub_event_source_subscribe(int subid, const char *tech, const char *peer, struct ast_sub_pkt *pkt, ast_sub_listener_func listener_func);
+int ast_sub_event_source_unsubscribe(int subid, const char *tech, const char *peer, struct ast_sub_pkt *pkt, ast_sub_listener_func listener_func);
+
+int ast_sub_event_parser_set(int subid, ast_sub_event_parser_func parser_func, int datasize);
+int ast_sub_event_parser_get_datasize(int subid);
+int ast_sub_event_parser_parse(int subid, struct ast_sub_pkt *pkt, void *out);
+
+int ast_sub_init(void);
+
+int ast_sub_pkt_init(struct ast_sub_pkt *pkt, char *buffer, int length);
+int ast_sub_pkt_add_str(struct ast_sub_pkt *pkt, const char *str);
+int ast_sub_pkt_add_int(struct ast_sub_pkt *pkt, int val);
+
+int ast_sub_pkt_get_raw(struct ast_sub_pkt *pkt, char type, char *data, int maxlen);
+int ast_sub_pkt_get_str(struct ast_sub_pkt *pkt, char *str, int maxlen);
+int ast_sub_pkt_get_int(struct ast_sub_pkt *pkt, int *val);
+
+
+
--- asterisk-1.2.1/subscribe.c	2006-01-19 08:06:48.000000000 -0500
+++ mwi/subscribe.c	2006-01-19 09:49:37.000000000 -0500
@@ -0,0 +1,263 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+
+#include "asterisk.h"
+
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+
+struct ast_sub_event_source {
+	int subid;
+	ast_sub_subscribe_func subscribe_func;
+	ast_sub_unsubscribe_func unsubscribe_func;
+
+	AST_LIST_ENTRY(ast_sub_event_source) list;
+};
+
+struct ast_sub_event_info {
+	ast_sub_event_parser_func parser_func;
+	int datasize;
+};
+
+AST_LIST_HEAD_STATIC(sources, ast_sub_event_source);
+
+static struct ast_sub_event_info event_infos[AST_SUB_ENUM_COUNT];
+
+int ast_sub_event_parser_set(int subid, ast_sub_event_parser_func parser_func, int datasize)
+{
+	if(subid < 0 || subid > AST_SUB_ENUM_COUNT) {
+		ast_log(LOG_WARNING, "The given subscription id '%d' is out of range\n", subid);
+		return -1;
+	}
+
+	if(parser_func && event_infos[subid].parser_func != NULL) {
+		ast_log(LOG_WARNING, "A parser for subscription type '%d' has alrady been set\n", subid);
+		return -1;
+	}
+
+	event_infos[subid].parser_func = parser_func;
+	event_infos[subid].datasize = datasize;
+
+	return 0;
+}
+
+int ast_sub_event_parser_get_datasize(int subid)
+{
+	if(subid < 0 || subid > AST_SUB_ENUM_COUNT) {
+		ast_log(LOG_WARNING, "The given subscript id '%d' is out of range\n", subid);
+		return -1;
+	}
+
+	if(event_infos[subid].parser_func == NULL) {
+		ast_log(LOG_WARNING, "Attempt to retrieve datasize for an unset event type '%d'\n", subid);
+		return -1;
+	}
+
+	return event_infos[subid].datasize;
+}
+
+int ast_sub_event_parser_parse(int subid, struct ast_sub_pkt *pkt, void *out)
+{
+	if(subid < 0 || subid > AST_SUB_ENUM_COUNT) {
+		ast_log(LOG_WARNING, "The given subscript id '%d' is out of range\n", subid);
+		return -1;
+	}
+
+	if(event_infos[subid].parser_func == NULL) {
+		ast_log(LOG_WARNING, "Attempt to retrieve datasize for an unset event type '%d'\n", subid);
+		return -1;
+	}
+
+	return event_infos[subid].parser_func(pkt, out);	
+}
+
+void ast_sub_event_source_add(int subid, ast_sub_subscribe_func subscribe_func, ast_sub_unsubscribe_func unsubscribe_func)
+{
+	struct ast_sub_event_source *source;
+
+	if(subscribe_func == NULL || unsubscribe_func == NULL) {
+		ast_log(LOG_WARNING, "An event source tried to register with a null subscribe/unsubscribe function\n");
+		return;
+	}
+
+	source = malloc(sizeof(struct ast_sub_event_source));
+	memset(source, 0, sizeof(struct ast_sub_event_source));
+
+	source->subid = subid;
+	source->subscribe_func = subscribe_func;
+	source->unsubscribe_func = unsubscribe_func;
+
+	AST_LIST_LOCK(&sources);
+	AST_LIST_INSERT_TAIL(&sources, source, list);
+	AST_LIST_UNLOCK(&sources);
+}
+
+void ast_sub_event_source_del(int subid, ast_sub_subscribe_func subscribe_func, ast_sub_unsubscribe_func unsubscribe_func)
+{
+	struct ast_sub_event_source *source;
+
+	AST_LIST_LOCK(&sources);
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&sources, source, list) {
+		if(source->subid == subid && source->subscribe_func == subscribe_func && source->unsubscribe_func == unsubscribe_func) {
+			AST_LIST_REMOVE_CURRENT(&sources, list);
+			free(source);
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	AST_LIST_UNLOCK(&sources);
+}
+
+int ast_sub_event_source_subscribe(int subid, const char *tech, const char *peer, struct ast_sub_pkt *pkt, ast_sub_listener_func listener_func)
+{
+	struct ast_sub_event_source *source;
+
+	AST_LIST_LOCK(&sources);
+	AST_LIST_TRAVERSE(&sources, source, list) {
+		if(source->subid == subid) {
+			source->subscribe_func(tech, peer, pkt, listener_func);
+		}
+	}
+	AST_LIST_UNLOCK(&sources);
+
+	return 0;
+}
+
+int ast_sub_event_source_unsubscribe(int subid, const char *tech, const char *peer, struct ast_sub_pkt *pkt, ast_sub_listener_func listener_func)
+{
+	struct ast_sub_event_source *source;
+
+	AST_LIST_LOCK(&sources);
+	AST_LIST_TRAVERSE(&sources, source, list) {
+		if(source->subid == subid) {
+			source->unsubscribe_func(tech, peer, pkt, listener_func);
+		}
+	}
+	AST_LIST_UNLOCK(&sources);
+
+	return 0;
+}
+
+int ast_sub_init()
+{
+	AST_LIST_HEAD_INIT(&sources);
+
+	memset(event_infos, 0, sizeof(event_infos));
+
+	return 0;
+}
+
+int ast_sub_pkt_init(struct ast_sub_pkt *pkt, char *buffer, int len)
+{
+	memset(buffer, 0, len);
+	pkt->data = buffer;
+	pkt->ptr = buffer;
+	pkt->length = len;
+	pkt->pos = 0;
+
+	return 0;
+}
+
+int ast_sub_pkt_add_raw(struct ast_sub_pkt *pkt, char type, char *data, char len)
+{
+	if(pkt->length - pkt->pos < len + 1) {
+		ast_log(LOG_WARNING, "Subscription packet is out of space\n");
+		return -1;
+	}
+	
+	pkt->ptr[0] = type;
+	pkt->ptr[1] = len;
+
+	memcpy(pkt->ptr + 2, data, len);
+	pkt->ptr += len + 2;
+	pkt->pos += len + 2;
+
+	return 0;
+}
+
+int ast_sub_pkt_add_str(struct ast_sub_pkt *pkt, const char *str)
+{
+	return ast_sub_pkt_add_raw(pkt, AST_PKT_STR, (char *) str, strlen(str));
+}
+
+int ast_sub_pkt_add_int(struct ast_sub_pkt *pkt, int val)
+{
+	int newval = htonl(val);
+	return ast_sub_pkt_add_raw(pkt, AST_PKT_INT, (char *) &newval, (int)sizeof(newval));
+}
+
+int ast_sub_pkt_get_raw(struct ast_sub_pkt *pkt, char type, char *dest, int maxlen)
+{
+	int len, pkttype;
+
+	if(pkt->length < 2) {
+		ast_log(LOG_WARNING, "Subscription packet doesn't have any more data\n");
+		return -1;
+	}
+
+	pkttype = pkt->ptr[0];
+	len = pkt->ptr[1];
+	
+	if(pkttype != type) {
+		ast_log(LOG_WARNING, "Data type mismatch in request (expecting %d got %d)\n", type, pkttype);
+		return -1;
+	}
+
+	if(len > pkt->length - 2) {
+		ast_log(LOG_WARNING, "Field has invalid length\n");
+		return -1;
+	}
+
+	if(len > maxlen) {
+		ast_log(LOG_WARNING, "Field is bigger than the allowed max\n");
+		return -1;
+	}
+	
+	memcpy(dest, pkt->ptr + 2, len);
+
+	pkt->ptr += (len + 2);
+	pkt->length -= (len + 2);
+
+	return len;
+}
+
+int ast_sub_pkt_get_str(struct ast_sub_pkt *pkt, char *str, int maxlen)
+{
+	int rv = ast_sub_pkt_get_raw(pkt, AST_PKT_STR, str, maxlen - 1);
+	if(rv == -1)
+		return -1;
+
+	str[rv] = 0;
+
+	return 0;	
+}
+
+int ast_sub_pkt_get_int(struct ast_sub_pkt *pkt, int *val)
+{
+	int newval;
+	int rv = ast_sub_pkt_get_raw(pkt, AST_PKT_INT, (char *) &newval, sizeof(int));
+	if(rv == -1)
+		return -1;
+
+	*val = ntohl(newval);
+
+	return 0;	
+}
