Squashed 'third_party/rawrtc/re/' content from commit f3163ce8b

Change-Id: I6a235e6ac0f03269d951026f9d195da05c40fdab
git-subtree-dir: third_party/rawrtc/re
git-subtree-split: f3163ce8b526a13b35ef71ce4dd6f43585064d8a
diff --git a/src/sipevent/listen.c b/src/sipevent/listen.c
new file mode 100644
index 0000000..ab35d95
--- /dev/null
+++ b/src/sipevent/listen.c
@@ -0,0 +1,357 @@
+/**
+ * @file sipevent/listen.c  SIP Event Listen
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipevent.h>
+#include "sipevent.h"
+
+
+struct subcmp {
+	const struct sipevent_event *evt;
+	const struct sip_msg *msg;
+};
+
+
+static void destructor(void *arg)
+{
+	struct sipevent_sock *sock = arg;
+
+	mem_deref(sock->lsnr);
+	hash_flush(sock->ht_not);
+	hash_flush(sock->ht_sub);
+	mem_deref(sock->ht_not);
+	mem_deref(sock->ht_sub);
+}
+
+
+static bool event_cmp(const struct sipevent_event *evt,
+		      const char *event, const char *id,
+		      int32_t refer_cseq)
+{
+	if (pl_strcmp(&evt->event, event))
+		return false;
+
+	if (!pl_isset(&evt->id) && !id)
+		return true;
+
+	if (!pl_isset(&evt->id))
+		return false;
+
+	if (!id) {
+		if (refer_cseq >= 0 && (int32_t)pl_u32(&evt->id) == refer_cseq)
+			return true;
+
+		return false;
+	}
+
+	if (pl_strcmp(&evt->id, id))
+		return false;
+
+	return true;
+}
+
+
+static bool not_cmp_handler(struct le *le, void *arg)
+{
+	const struct subcmp *cmp = arg;
+	struct sipnot *not = le->data;
+
+	return sip_dialog_cmp(not->dlg, cmp->msg) &&
+		event_cmp(cmp->evt, not->event, not->id, -1);
+}
+
+
+static bool sub_cmp_handler(struct le *le, void *arg)
+{
+	const struct subcmp *cmp = arg;
+	struct sipsub *sub = le->data;
+
+	return sip_dialog_cmp(sub->dlg, cmp->msg) &&
+		(!cmp->evt || event_cmp(cmp->evt, sub->event, sub->id,
+					sub->refer_cseq));
+}
+
+
+static bool sub_cmp_half_handler(struct le *le, void *arg)
+{
+	const struct subcmp *cmp = arg;
+	struct sipsub *sub = le->data;
+
+	return sip_dialog_cmp_half(sub->dlg, cmp->msg) &&
+		!sip_dialog_established(sub->dlg) &&
+		(!cmp->evt || event_cmp(cmp->evt, sub->event, sub->id,
+					sub->refer_cseq));
+}
+
+
+static struct sipnot *sipnot_find(struct sipevent_sock *sock,
+				  const struct sip_msg *msg,
+				  const struct sipevent_event *evt)
+{
+	struct subcmp cmp;
+
+	cmp.msg = msg;
+	cmp.evt = evt;
+
+	return list_ledata(hash_lookup(sock->ht_not,
+				       hash_joaat_pl(&msg->callid),
+				       not_cmp_handler, &cmp));
+}
+
+
+struct sipsub *sipsub_find(struct sipevent_sock *sock,
+			   const struct sip_msg *msg,
+			   const struct sipevent_event *evt, bool full)
+{
+	struct subcmp cmp;
+
+	cmp.msg = msg;
+	cmp.evt = evt;
+
+	return list_ledata(hash_lookup(sock->ht_sub,
+				       hash_joaat_pl(&msg->callid), full ?
+				       sub_cmp_handler : sub_cmp_half_handler,
+				       &cmp));
+}
+
+
+static void notify_handler(struct sipevent_sock *sock,
+			   const struct sip_msg *msg)
+{
+	struct sipevent_substate state;
+	struct sipevent_event event;
+	struct sip *sip = sock->sip;
+	const struct sip_hdr *hdr;
+	struct sipsub *sub;
+	uint32_t nrefs;
+	char m[256];
+	int err;
+
+	hdr = sip_msg_hdr(msg, SIP_HDR_EVENT);
+	if (!hdr || sipevent_event_decode(&event, &hdr->val)) {
+		(void)sip_reply(sip, msg, 489, "Bad Event");
+		return;
+	}
+
+	hdr = sip_msg_hdr(msg, SIP_HDR_SUBSCRIPTION_STATE);
+	if (!hdr || sipevent_substate_decode(&state, &hdr->val)) {
+		(void)sip_reply(sip, msg, 400,"Bad Subscription-State Header");
+		return;
+	}
+
+	sub = sipsub_find(sock, msg, &event, true);
+	if (!sub) {
+		sub = sipsub_find(sock, msg, &event, false);
+		if (!sub) {
+			(void)sip_reply(sip, msg,
+					481, "Subscription Does Not Exist");
+			return;
+		}
+
+		if (sub->forkh) {
+
+			struct sipsub *fsub;
+
+			err = sub->forkh(&fsub, sub, msg, sub->arg);
+			if (err) {
+				(void)sip_reply(sip, msg, 500,
+						str_error(err, m, sizeof(m)));
+				return;
+			}
+
+			sub = fsub;
+		}
+		else {
+			err = sip_dialog_create(sub->dlg, msg);
+			if (err) {
+				(void)sip_reply(sip, msg, 500,
+						str_error(err, m, sizeof(m)));
+				return;
+			}
+		}
+	}
+	else {
+		if (!sip_dialog_rseq_valid(sub->dlg, msg)) {
+			(void)sip_reply(sip, msg, 500, "Bad Sequence");
+			return;
+		}
+
+		(void)sip_dialog_update(sub->dlg, msg);
+	}
+
+	if (sub->refer_cseq >= 0 && !sub->id && pl_isset(&event.id)) {
+
+		err = pl_strdup(&sub->id, &event.id);
+		if (err) {
+			(void)sip_treply(NULL, sip, msg, 500,
+					 str_error(err, m, sizeof(m)));
+			return;
+		}
+	}
+
+	switch (state.state) {
+
+	case SIPEVENT_ACTIVE:
+	case SIPEVENT_PENDING:
+		if (!sub->termconf)
+			sub->subscribed = true;
+
+		if (!sub->terminated && !sub->termwait &&
+		    pl_isset(&state.expires))
+			sipsub_reschedule(sub, pl_u32(&state.expires) * 900);
+		break;
+
+	case SIPEVENT_TERMINATED:
+		sub->subscribed = false;
+		sub->termconf = true;
+		break;
+	}
+
+	mem_ref(sub);
+	sub->notifyh(sip, msg, sub->arg);
+	nrefs = mem_nrefs(sub);
+	mem_deref(sub);
+
+	/* check if subscription was deref'd from notify handler */
+	if (nrefs == 1)
+		return;
+
+	if (state.state == SIPEVENT_TERMINATED) {
+
+		if (!sub->terminated) {
+			sub->termwait = false;
+			sipsub_terminate(sub, 0, msg, &state);
+		}
+		else if (sub->termwait) {
+			sub->termwait = false;
+			tmr_cancel(&sub->tmr);
+			mem_deref(sub);
+		}
+	}
+}
+
+
+static void subscribe_handler(struct sipevent_sock *sock,
+			      const struct sip_msg *msg)
+{
+	struct sipevent_event event;
+	struct sip *sip = sock->sip;
+	const struct sip_hdr *hdr;
+	struct sipnot *not;
+	uint32_t expires;
+
+	hdr = sip_msg_hdr(msg, SIP_HDR_EVENT);
+	if (!hdr || sipevent_event_decode(&event, &hdr->val)) {
+		(void)sip_reply(sip, msg, 400, "Bad Event Header");
+		return;
+	}
+
+	not = sipnot_find(sock, msg, &event);
+	if (!not || not->terminated) {
+		(void)sip_reply(sip, msg, 481, "Subscription Does Not Exist");
+		return;
+	}
+
+	if (pl_isset(&msg->expires))
+		expires = pl_u32(&msg->expires);
+	else
+		expires = not->expires_dfl;
+
+	if (expires > 0 && expires < not->expires_min) {
+		(void)sip_replyf(sip, msg, 423, "Interval Too Brief",
+				 "Min-Expires: %u\r\n"
+				 "Content-Length: 0\r\n"
+				 "\r\n",
+				 not->expires_min);
+		return;
+	}
+
+	if (!sip_dialog_rseq_valid(not->dlg, msg)) {
+		(void)sip_reply(sip, msg, 500, "Bad Sequence");
+		return;
+	}
+
+	(void)sip_dialog_update(not->dlg, msg);
+
+	sipnot_refresh(not, expires);
+
+	(void)sipnot_reply(not, msg, 200, "OK");
+
+	(void)sipnot_notify(not);
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+	struct sipevent_sock *sock = arg;
+
+	if (!pl_strcmp(&msg->met, "SUBSCRIBE")) {
+
+		if (pl_isset(&msg->to.tag)) {
+			subscribe_handler(sock, msg);
+			return true;
+		}
+
+		return sock->subh ? sock->subh(msg, sock->arg) : false;
+	}
+	else if (!pl_strcmp(&msg->met, "NOTIFY")) {
+
+		notify_handler(sock, msg);
+		return true;
+	}
+	else {
+		return false;
+	}
+}
+
+
+int sipevent_listen(struct sipevent_sock **sockp, struct sip *sip,
+		    uint32_t htsize_not, uint32_t htsize_sub,
+		    sip_msg_h *subh, void *arg)
+{
+	struct sipevent_sock *sock;
+	int err;
+
+	if (!sockp || !sip || !htsize_not || !htsize_sub)
+		return EINVAL;
+
+	sock = mem_zalloc(sizeof(*sock), destructor);
+	if (!sock)
+		return ENOMEM;
+
+	err = sip_listen(&sock->lsnr, sip, true, request_handler, sock);
+	if (err)
+		goto out;
+
+	err = hash_alloc(&sock->ht_not, htsize_not);
+	if (err)
+		goto out;
+
+	err = hash_alloc(&sock->ht_sub, htsize_sub);
+	if (err)
+		goto out;
+
+	sock->sip  = sip;
+	sock->subh = subh;
+	sock->arg  = arg;
+
+ out:
+	if (err)
+		mem_deref(sock);
+	else
+		*sockp = sock;
+
+	return err;
+}
diff --git a/src/sipevent/mod.mk b/src/sipevent/mod.mk
new file mode 100644
index 0000000..3426b8c
--- /dev/null
+++ b/src/sipevent/mod.mk
@@ -0,0 +1,10 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS	+= sipevent/listen.c
+SRCS	+= sipevent/msg.c
+SRCS	+= sipevent/notify.c
+SRCS	+= sipevent/subscribe.c
diff --git a/src/sipevent/msg.c b/src/sipevent/msg.c
new file mode 100644
index 0000000..472c828
--- /dev/null
+++ b/src/sipevent/msg.c
@@ -0,0 +1,120 @@
+/**
+ * @file sipevent/msg.c  SIP event messages
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipevent.h>
+
+
+int sipevent_event_decode(struct sipevent_event *se, const struct pl *pl)
+{
+	struct pl param;
+	int err;
+
+	if (!se || !pl)
+		return EINVAL;
+
+	err = re_regex(pl->p, pl->l, "[^; \t\r\n]+[ \t\r\n]*[^]*",
+		       &se->event, NULL, &se->params);
+	if (err)
+		return EBADMSG;
+
+	if (!msg_param_decode(&se->params, "id", &param))
+		se->id = param;
+	else
+		se->id = pl_null;
+
+	return 0;
+}
+
+
+int sipevent_substate_decode(struct sipevent_substate *ss, const struct pl *pl)
+{
+	struct pl state, param;
+	int err;
+
+	if (!ss || !pl)
+		return EINVAL;
+
+	err = re_regex(pl->p, pl->l, "[a-z]+[ \t\r\n]*[^]*",
+		       &state, NULL, &ss->params);
+	if (err)
+		return EBADMSG;
+
+	if (!pl_strcasecmp(&state, "active"))
+		ss->state = SIPEVENT_ACTIVE;
+	else if (!pl_strcasecmp(&state, "pending"))
+		ss->state = SIPEVENT_PENDING;
+	else if (!pl_strcasecmp(&state, "terminated"))
+		ss->state = SIPEVENT_TERMINATED;
+	else
+		ss->state = -1;
+
+	if (!msg_param_decode(&ss->params, "reason", &param)) {
+
+		if (!pl_strcasecmp(&param, "deactivated"))
+			ss->reason = SIPEVENT_DEACTIVATED;
+		else if (!pl_strcasecmp(&param, "probation"))
+			ss->reason = SIPEVENT_PROBATION;
+		else if (!pl_strcasecmp(&param, "rejected"))
+			ss->reason = SIPEVENT_REJECTED;
+		else if (!pl_strcasecmp(&param, "timeout"))
+			ss->reason = SIPEVENT_TIMEOUT;
+		else if (!pl_strcasecmp(&param, "giveup"))
+			ss->reason = SIPEVENT_GIVEUP;
+		else if (!pl_strcasecmp(&param, "noresource"))
+			ss->reason = SIPEVENT_NORESOURCE;
+		else
+			ss->reason = -1;
+	}
+	else {
+		ss->reason = -1;
+	}
+
+	if (!msg_param_decode(&ss->params, "expires", &param))
+		ss->expires = param;
+	else
+		ss->expires = pl_null;
+
+	if (!msg_param_decode(&ss->params, "retry-after", &param))
+		ss->retry_after = param;
+	else
+		ss->retry_after = pl_null;
+
+	return 0;
+}
+
+
+const char *sipevent_substate_name(enum sipevent_subst state)
+{
+	switch (state) {
+
+	case SIPEVENT_ACTIVE:     return "active";
+	case SIPEVENT_PENDING:    return "pending";
+	case SIPEVENT_TERMINATED: return "terminated";
+	default:                  return "unknown";
+	}
+}
+
+
+const char *sipevent_reason_name(enum sipevent_reason reason)
+{
+	switch (reason) {
+
+	case SIPEVENT_DEACTIVATED: return "deactivated";
+	case SIPEVENT_PROBATION:   return "probation";
+	case SIPEVENT_REJECTED:    return "rejected";
+	case SIPEVENT_TIMEOUT:     return "timeout";
+	case SIPEVENT_GIVEUP:      return "giveup";
+	case SIPEVENT_NORESOURCE:  return "noresource";
+	default:                   return "unknown";
+	}
+}
diff --git a/src/sipevent/notify.c b/src/sipevent/notify.c
new file mode 100644
index 0000000..7a7a87d
--- /dev/null
+++ b/src/sipevent/notify.c
@@ -0,0 +1,488 @@
+/**
+ * @file notify.c  SIP Event Notify
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipevent.h>
+#include "sipevent.h"
+
+
+static int notify_request(struct sipnot *not, bool reset_ls);
+
+
+static void internal_close_handler(int err, const struct sip_msg *msg,
+				   void *arg)
+{
+	(void)err;
+	(void)msg;
+	(void)arg;
+}
+
+
+static bool terminate(struct sipnot *not, enum sipevent_reason reason)
+{
+	not->terminated = true;
+	not->reason     = reason;
+	not->closeh     = internal_close_handler;
+
+	if (not->req) {
+		mem_ref(not);
+		return true;
+	}
+
+	if (not->subscribed && !notify_request(not, true)) {
+		mem_ref(not);
+		return true;
+	}
+
+	return false;
+}
+
+
+static void destructor(void *arg)
+{
+	struct sipnot *not = arg;
+
+	tmr_cancel(&not->tmr);
+
+	if (!not->terminated) {
+
+		if (terminate(not, SIPEVENT_DEACTIVATED))
+			return;
+	}
+
+	hash_unlink(&not->he);
+	mem_deref(not->req);
+	mem_deref(not->dlg);
+	mem_deref(not->auth);
+	mem_deref(not->mb);
+	mem_deref(not->event);
+	mem_deref(not->id);
+	mem_deref(not->cuser);
+	mem_deref(not->hdrs);
+	mem_deref(not->ctype);
+	mem_deref(not->sock);
+	mem_deref(not->sip);
+}
+
+
+static void sipnot_terminate(struct sipnot *not, int err,
+			     const struct sip_msg *msg,
+			     enum sipevent_reason reason)
+{
+	sipnot_close_h *closeh;
+	void *arg;
+
+	closeh = not->closeh;
+	arg    = not->arg;
+
+	tmr_cancel(&not->tmr);
+	(void)terminate(not, reason);
+
+	closeh(err, msg, arg);
+}
+
+
+static void tmr_handler(void *arg)
+{
+	struct sipnot *not = arg;
+
+	if (not->terminated)
+		return;
+
+	sipnot_terminate(not, ETIMEDOUT, NULL, SIPEVENT_TIMEOUT);
+}
+
+
+void sipnot_refresh(struct sipnot *not, uint32_t expires)
+{
+	not->expires = min(expires, not->expires_max);
+
+	tmr_start(&not->tmr, not->expires * 1000, tmr_handler, not);
+}
+
+
+static void response_handler(int err, const struct sip_msg *msg, void *arg)
+{
+	struct sipnot *not = arg;
+
+	if (err) {
+		if (err == ETIMEDOUT)
+			not->subscribed = false;
+		goto out;
+	}
+
+	if (sip_request_loops(&not->ls, msg->scode)) {
+		not->subscribed = false;
+		goto out;
+	}
+
+	if (msg->scode < 200) {
+		return;
+	}
+	else if (msg->scode < 300) {
+
+		(void)sip_dialog_update(not->dlg, msg);
+	}
+	else {
+		switch (msg->scode) {
+
+		case 401:
+		case 407:
+			err = sip_auth_authenticate(not->auth, msg);
+			if (err) {
+				err = (err == EAUTH) ? 0 : err;
+				break;
+			}
+
+			err = notify_request(not, false);
+			if (err)
+				break;
+
+			return;
+		}
+
+		not->subscribed = false;
+	}
+
+ out:
+	if (not->termsent) {
+		mem_deref(not);
+	}
+	else if (not->terminated) {
+		if (!not->subscribed || notify_request(not, true))
+			mem_deref(not);
+	}
+	else if (!not->subscribed) {
+		sipnot_terminate(not, err, msg, -1);
+	}
+	else if (not->notify_pending) {
+		(void)notify_request(not, true);
+	}
+}
+
+
+static int send_handler(enum sip_transp tp, const struct sa *src,
+			const struct sa *dst, struct mbuf *mb, void *arg)
+{
+	struct sip_contact contact;
+	struct sipnot *not = arg;
+	(void)dst;
+
+	sip_contact_set(&contact, not->cuser, src, tp);
+
+	return mbuf_printf(mb, "%H", sip_contact_print, &contact);
+}
+
+
+static int print_event(struct re_printf *pf, const struct sipnot *not)
+{
+	if (not->id)
+		return re_hprintf(pf, "%s;id=%s", not->event, not->id);
+	else
+		return re_hprintf(pf, "%s", not->event);
+}
+
+
+static int print_substate(struct re_printf *pf, const struct sipnot *not)
+{
+	int err;
+
+	if (not->terminated) {
+
+		err = re_hprintf(pf, "terminated;reason=%s",
+				 sipevent_reason_name(not->reason));
+
+		if (not->retry_after)
+			err |= re_hprintf(pf, ";retry-after=%u",
+					  not->retry_after);
+	}
+	else {
+		uint32_t expires;
+
+		expires = (uint32_t)(tmr_get_expire(&not->tmr) / 1000);
+
+		err = re_hprintf(pf, "%s;expires=%u",
+				 sipevent_substate_name(not->substate),
+				 expires);
+	}
+
+	return err;
+}
+
+
+static int print_content(struct re_printf *pf, const struct sipnot *not)
+{
+	if (!not->mb)
+		return re_hprintf(pf,
+				  "Content-Length: 0\r\n"
+				  "\r\n");
+	else
+		return re_hprintf(pf,
+				  "Content-Type: %s\r\n"
+				  "Content-Length: %zu\r\n"
+				  "\r\n"
+				  "%b",
+				  not->ctype,
+				  mbuf_get_left(not->mb),
+				  mbuf_buf(not->mb),
+				  mbuf_get_left(not->mb));
+}
+
+
+static int notify_request(struct sipnot *not, bool reset_ls)
+{
+	if (reset_ls)
+		sip_loopstate_reset(&not->ls);
+
+	if (not->terminated)
+		not->termsent = true;
+
+	not->notify_pending = false;
+
+	return sip_drequestf(&not->req, not->sip, true, "NOTIFY",
+			     not->dlg, 0, not->auth,
+			     send_handler, response_handler, not,
+			     "Event: %H\r\n"
+			     "Subscription-State: %H\r\n"
+			     "%s"
+			     "%H",
+			     print_event, not,
+			     print_substate, not,
+			     not->hdrs,
+			     print_content, not);
+}
+
+
+int sipnot_notify(struct sipnot *not)
+{
+	if (not->expires == 0) {
+		return 0;
+	}
+
+	if (not->req) {
+		not->notify_pending = true;
+		return 0;
+	}
+
+	return notify_request(not, true);
+}
+
+
+int sipnot_reply(struct sipnot *not, const struct sip_msg *msg,
+		 uint16_t scode, const char *reason)
+{
+	struct sip_contact contact;
+	uint32_t expires;
+
+	expires = (uint32_t)(tmr_get_expire(&not->tmr) / 1000);
+
+	sip_contact_set(&contact, not->cuser, &msg->dst, msg->tp);
+
+	return sip_treplyf(NULL, NULL, not->sip, msg, true, scode, reason,
+			   "%H"
+			   "Expires: %u\r\n"
+			   "Content-Length: 0\r\n"
+			   "\r\n",
+			   sip_contact_print, &contact,
+			   expires);
+}
+
+
+int sipevent_accept(struct sipnot **notp, struct sipevent_sock *sock,
+		    const struct sip_msg *msg, struct sip_dialog *dlg,
+		    const struct sipevent_event *event,
+		    uint16_t scode, const char *reason, uint32_t expires_min,
+		    uint32_t expires_dfl, uint32_t expires_max,
+		    const char *cuser, const char *ctype,
+		    sip_auth_h *authh, void *aarg, bool aref,
+		    sipnot_close_h *closeh, void *arg, const char *fmt, ...)
+{
+	struct sipnot *not;
+	uint32_t expires;
+	int err;
+
+	if (!notp || !sock || !msg || !scode || !reason || !expires_dfl ||
+	    !expires_max || !cuser || !ctype || expires_dfl < expires_min)
+		return EINVAL;
+
+	not = mem_zalloc(sizeof(*not), destructor);
+	if (!not)
+		return ENOMEM;
+
+	if (!pl_strcmp(&msg->met, "REFER")) {
+
+		err = str_dup(&not->event, "refer");
+		if (err)
+			goto out;
+
+		err = re_sdprintf(&not->id, "%u", msg->cseq.num);
+		if (err)
+			goto out;
+	}
+	else {
+		if (!event) {
+			err = EINVAL;
+			goto out;
+		}
+
+		err = pl_strdup(&not->event, &event->event);
+		if (err)
+			goto out;
+
+		if (pl_isset(&event->id)) {
+
+			err = pl_strdup(&not->id, &event->id);
+			if (err)
+				goto out;
+		}
+	}
+
+	if (dlg) {
+		not->dlg = mem_ref(dlg);
+	}
+	else {
+		err = sip_dialog_accept(&not->dlg, msg);
+		if (err)
+			goto out;
+	}
+
+	hash_append(sock->ht_not,
+		    hash_joaat_str(sip_dialog_callid(not->dlg)),
+		    &not->he, not);
+
+	err = sip_auth_alloc(&not->auth, authh, aarg, aref);
+	if (err)
+		goto out;
+
+	err = str_dup(&not->cuser, cuser);
+	if (err)
+		goto out;
+
+	err = str_dup(&not->ctype, ctype);
+	if (err)
+		goto out;
+
+	if (fmt) {
+		va_list ap;
+
+		va_start(ap, fmt);
+		err = re_vsdprintf(&not->hdrs, fmt, ap);
+		va_end(ap);
+		if (err)
+			goto out;
+	}
+
+	not->expires_min = expires_min;
+	not->expires_dfl = expires_dfl;
+	not->expires_max = expires_max;
+	not->substate = SIPEVENT_PENDING;
+	not->sock   = mem_ref(sock);
+	not->sip    = mem_ref(sock->sip);
+	not->closeh = closeh ? closeh : internal_close_handler;
+	not->arg    = arg;
+
+	if (pl_isset(&msg->expires))
+		expires = pl_u32(&msg->expires);
+	else
+		expires = not->expires_dfl;
+
+	sipnot_refresh(not, expires);
+
+	err = sipnot_reply(not, msg, scode, reason);
+	if (err)
+		goto out;
+
+	not->subscribed = true;
+
+ out:
+	if (err)
+		mem_deref(not);
+	else
+		*notp = not;
+
+	return err;
+}
+
+
+int sipevent_notify(struct sipnot *not, struct mbuf *mb,
+		    enum sipevent_subst state, enum sipevent_reason reason,
+		    uint32_t retry_after)
+{
+	if (!not || not->terminated)
+		return EINVAL;
+
+	if (mb || state != SIPEVENT_TERMINATED) {
+		mem_deref(not->mb);
+		not->mb = mem_ref(mb);
+	}
+
+	switch (state) {
+
+	case SIPEVENT_ACTIVE:
+	case SIPEVENT_PENDING:
+		not->substate = state;
+		return sipnot_notify(not);
+
+	case SIPEVENT_TERMINATED:
+		tmr_cancel(&not->tmr);
+		not->retry_after = retry_after;
+		(void)terminate(not, reason);
+		return 0;
+
+	default:
+		return EINVAL;
+	}
+}
+
+
+int sipevent_notifyf(struct sipnot *not, struct mbuf **mbp,
+		     enum sipevent_subst state, enum sipevent_reason reason,
+		     uint32_t retry_after, const char *fmt, ...)
+{
+	struct mbuf *mb;
+	va_list ap;
+	int err;
+
+	if (!not || not->terminated || !fmt)
+		return EINVAL;
+
+	if (mbp && *mbp)
+		return sipevent_notify(not, *mbp, state, reason, retry_after);
+
+	mb = mbuf_alloc(1024);
+	if (!mb)
+		return ENOMEM;
+
+	va_start(ap, fmt);
+	err = mbuf_vprintf(mb, fmt, ap);
+	va_end(ap);
+	if (err)
+		goto out;
+
+	mb->pos = 0;
+
+	err = sipevent_notify(not, mb, state, reason, retry_after);
+	if (err)
+		goto out;
+
+ out:
+	if (err || !mbp)
+		mem_deref(mb);
+	else
+		*mbp = mb;
+
+	return err;
+}
diff --git a/src/sipevent/sipevent.h b/src/sipevent/sipevent.h
new file mode 100644
index 0000000..e186efb
--- /dev/null
+++ b/src/sipevent/sipevent.h
@@ -0,0 +1,92 @@
+/**
+ * @file sipevent.h  SIP Event Private Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+/* Listener Socket */
+
+struct sipevent_sock {
+	struct sip_lsnr *lsnr;
+	struct hash *ht_not;
+	struct hash *ht_sub;
+	struct sip *sip;
+	sip_msg_h *subh;
+	void *arg;
+};
+
+
+/* Notifier */
+
+struct sipnot {
+	struct le he;
+	struct sip_loopstate ls;
+	struct tmr tmr;
+	struct sipevent_sock *sock;
+	struct sip_request *req;
+	struct sip_dialog *dlg;
+	struct sip_auth *auth;
+	struct sip *sip;
+	struct mbuf *mb;
+	char *event;
+	char *id;
+	char *cuser;
+	char *hdrs;
+	char *ctype;
+	sipnot_close_h *closeh;
+	void *arg;
+	uint32_t expires;
+	uint32_t expires_min;
+	uint32_t expires_dfl;
+	uint32_t expires_max;
+	uint32_t retry_after;
+	enum sipevent_subst substate;
+	enum sipevent_reason reason;
+	bool notify_pending;
+	bool subscribed;
+	bool terminated;
+	bool termsent;
+};
+
+void sipnot_refresh(struct sipnot *not, uint32_t expires);
+int  sipnot_notify(struct sipnot *not);
+int  sipnot_reply(struct sipnot *not, const struct sip_msg *msg,
+		  uint16_t scode, const char *reason);
+
+
+/* Subscriber */
+
+struct sipsub {
+	struct le he;
+	struct sip_loopstate ls;
+	struct tmr tmr;
+	struct sipevent_sock *sock;
+	struct sip_request *req;
+	struct sip_dialog *dlg;
+	struct sip_auth *auth;
+	struct sip *sip;
+	char *event;
+	char *id;
+	char *cuser;
+	char *hdrs;
+	char *refer_hdrs;
+	sipsub_fork_h *forkh;
+	sipsub_notify_h *notifyh;
+	sipsub_close_h *closeh;
+	void *arg;
+	int32_t refer_cseq;
+	uint32_t expires;
+	uint32_t failc;
+	bool subscribed;
+	bool terminated;
+	bool termconf;
+	bool termwait;
+	bool refer;
+};
+
+struct sipsub *sipsub_find(struct sipevent_sock *sock,
+			   const struct sip_msg *msg,
+			   const struct sipevent_event *evt, bool full);
+void sipsub_reschedule(struct sipsub *sub, uint64_t wait);
+void sipsub_terminate(struct sipsub *sub, int err, const struct sip_msg *msg,
+		      const struct sipevent_substate *substate);
diff --git a/src/sipevent/subscribe.c b/src/sipevent/subscribe.c
new file mode 100644
index 0000000..fe6ba56
--- /dev/null
+++ b/src/sipevent/subscribe.c
@@ -0,0 +1,669 @@
+/**
+ * @file subscribe.c  SIP Event Subscribe
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipevent.h>
+#include "sipevent.h"
+
+
+enum {
+	DEFAULT_EXPIRES = 3600,
+	RESUB_FAIL_WAIT = 60000,
+	RESUB_FAILC_MAX = 7,
+	NOTIFY_TIMEOUT  = 10000,
+};
+
+
+static int request(struct sipsub *sub, bool reset_ls);
+
+
+static void internal_notify_handler(struct sip *sip, const struct sip_msg *msg,
+				    void *arg)
+{
+	(void)arg;
+
+	(void)sip_treply(NULL, sip, msg, 200, "OK");
+}
+
+
+static void internal_close_handler(int err, const struct sip_msg *msg,
+				   const struct sipevent_substate *substate,
+				   void *arg)
+{
+	(void)err;
+	(void)msg;
+	(void)substate;
+	(void)arg;
+}
+
+
+static bool terminate(struct sipsub *sub)
+{
+	sub->terminated = true;
+	sub->forkh      = NULL;
+	sub->notifyh    = internal_notify_handler;
+	sub->closeh     = internal_close_handler;
+
+	if (sub->termwait) {
+		mem_ref(sub);
+		return true;
+	}
+
+	tmr_cancel(&sub->tmr);
+
+	if (sub->req) {
+		mem_ref(sub);
+		return true;
+	}
+
+	if (sub->expires && sub->subscribed && !request(sub, true)) {
+		mem_ref(sub);
+		return true;
+	}
+
+	return false;
+}
+
+
+static void destructor(void *arg)
+{
+	struct sipsub *sub = arg;
+
+	if (!sub->terminated) {
+
+		if (terminate(sub))
+			return;
+	}
+
+	tmr_cancel(&sub->tmr);
+	hash_unlink(&sub->he);
+	mem_deref(sub->req);
+	mem_deref(sub->dlg);
+	mem_deref(sub->auth);
+	mem_deref(sub->event);
+	mem_deref(sub->id);
+	mem_deref(sub->cuser);
+	mem_deref(sub->hdrs);
+	mem_deref(sub->refer_hdrs);
+	mem_deref(sub->sock);
+	mem_deref(sub->sip);
+}
+
+
+static void notify_timeout_handler(void *arg)
+{
+	struct sipsub *sub = arg;
+
+	sub->termwait = false;
+
+	if (sub->terminated)
+		mem_deref(sub);
+	else
+		sipsub_terminate(sub, ETIMEDOUT, NULL, NULL);
+}
+
+
+static void tmr_handler(void *arg)
+{
+	struct sipsub *sub = arg;
+	int err;
+
+	if (sub->req || sub->terminated)
+		return;
+
+	err = request(sub, true);
+	if (err) {
+		if (++sub->failc < RESUB_FAILC_MAX) {
+			sipsub_reschedule(sub, RESUB_FAIL_WAIT);
+		}
+		else {
+			sipsub_terminate(sub, err, NULL, NULL);
+		}
+	}
+}
+
+
+void sipsub_reschedule(struct sipsub *sub, uint64_t wait)
+{
+	tmr_start(&sub->tmr, wait, tmr_handler, sub);
+}
+
+
+void sipsub_terminate(struct sipsub *sub, int err, const struct sip_msg *msg,
+		      const struct sipevent_substate *substate)
+{
+	sipsub_close_h *closeh;
+	void *arg;
+
+	closeh = sub->closeh;
+	arg    = sub->arg;
+
+	(void)terminate(sub);
+
+	closeh(err, msg, substate, arg);
+}
+
+
+static void response_handler(int err, const struct sip_msg *msg, void *arg)
+{
+	const struct sip_hdr *minexp;
+	struct sipsub *sub = arg;
+
+	if (err || sip_request_loops(&sub->ls, msg->scode))
+		goto out;
+
+	if (msg->scode < 200) {
+		return;
+	}
+	else if (msg->scode < 300) {
+
+		uint32_t wait;
+
+		if (sub->forkh) {
+
+			struct sipsub *fsub;
+
+			fsub = sipsub_find(sub->sock, msg, NULL, true);
+			if (!fsub) {
+
+				err = sub->forkh(&fsub, sub, msg, sub->arg);
+				if (err)
+					return;
+			}
+			else {
+				(void)sip_dialog_update(fsub->dlg, msg);
+			}
+
+			sub = fsub;
+		}
+		else if (!sip_dialog_established(sub->dlg)) {
+
+			err = sip_dialog_create(sub->dlg, msg);
+			if (err) {
+				sub->subscribed = false;
+				goto out;
+			}
+		}
+		else {
+			/* Ignore 2xx responses for other dialogs
+			 * if forking is disabled */
+			if (!sip_dialog_cmp(sub->dlg, msg))
+				return;
+
+			(void)sip_dialog_update(sub->dlg, msg);
+		}
+
+		if (!sub->termconf)
+			sub->subscribed = true;
+
+		sub->failc = 0;
+
+		if (!sub->expires && !sub->termconf) {
+
+			tmr_start(&sub->tmr, NOTIFY_TIMEOUT,
+				  notify_timeout_handler, sub);
+			sub->termwait = true;
+			return;
+		}
+
+		if (sub->terminated)
+			goto out;
+
+		if (sub->refer) {
+			sub->refer = false;
+			return;
+		}
+
+		if (pl_isset(&msg->expires))
+			wait = pl_u32(&msg->expires);
+		else
+			wait = sub->expires;
+
+		sipsub_reschedule(sub, wait * 900);
+		return;
+	}
+	else {
+		if (sub->terminated && !sub->subscribed)
+			goto out;
+
+		switch (msg->scode) {
+
+		case 401:
+		case 407:
+			err = sip_auth_authenticate(sub->auth, msg);
+			if (err) {
+				err = (err == EAUTH) ? 0 : err;
+				break;
+			}
+
+			err = request(sub, false);
+			if (err)
+				break;
+
+			return;
+
+		case 403:
+			sip_auth_reset(sub->auth);
+			break;
+
+		case 423:
+			minexp = sip_msg_hdr(msg, SIP_HDR_MIN_EXPIRES);
+			if (!minexp || !pl_u32(&minexp->val) || !sub->expires)
+				break;
+
+			sub->expires = pl_u32(&minexp->val);
+
+			err = request(sub, false);
+			if (err)
+				break;
+
+			return;
+
+		case 481:
+			sub->subscribed = false;
+			break;
+		}
+	}
+
+ out:
+	sub->refer = false;
+
+	if (sub->terminated) {
+
+		if (!sub->expires || !sub->subscribed || request(sub, true))
+			mem_deref(sub);
+	}
+	else {
+		if (sub->subscribed && ++sub->failc < RESUB_FAILC_MAX)
+			sipsub_reschedule(sub, RESUB_FAIL_WAIT);
+		else
+			sipsub_terminate(sub, err, msg, NULL);
+	}
+}
+
+
+static int send_handler(enum sip_transp tp, const struct sa *src,
+			const struct sa *dst, struct mbuf *mb, void *arg)
+{
+	struct sip_contact contact;
+	struct sipsub *sub = arg;
+	(void)dst;
+
+	sip_contact_set(&contact, sub->cuser, src, tp);
+
+	return mbuf_printf(mb, "%H", sip_contact_print, &contact);
+}
+
+
+static int print_event(struct re_printf *pf, const struct sipsub *sub)
+{
+	if (sub->id)
+		return re_hprintf(pf, "%s;id=%s", sub->event, sub->id);
+	else
+		return re_hprintf(pf, "%s", sub->event);
+}
+
+
+static int request(struct sipsub *sub, bool reset_ls)
+{
+	if (reset_ls)
+		sip_loopstate_reset(&sub->ls);
+
+	if (sub->refer) {
+
+		sub->refer_cseq = sip_dialog_lseq(sub->dlg);
+
+		return sip_drequestf(&sub->req, sub->sip, true, "REFER",
+				     sub->dlg, 0, sub->auth,
+				     send_handler, response_handler, sub,
+				     "%s"
+				     "Content-Length: 0\r\n"
+				     "\r\n",
+				     sub->refer_hdrs);
+	}
+	else {
+		if (sub->terminated)
+			sub->expires = 0;
+
+		return sip_drequestf(&sub->req, sub->sip, true, "SUBSCRIBE",
+				     sub->dlg, 0, sub->auth,
+				     send_handler, response_handler, sub,
+				     "Event: %H\r\n"
+				     "Expires: %u\r\n"
+				     "%s"
+				     "Content-Length: 0\r\n"
+				     "\r\n",
+				     print_event, sub,
+				     sub->expires,
+				     sub->hdrs);
+	}
+}
+
+
+static int sipsub_alloc(struct sipsub **subp, struct sipevent_sock *sock,
+			bool refer, struct sip_dialog *dlg, const char *uri,
+			const char *from_name, const char *from_uri,
+			const char *event, const char *id, uint32_t expires,
+			const char *cuser,
+			const char *routev[], uint32_t routec,
+			sip_auth_h *authh, void *aarg, bool aref,
+			sipsub_fork_h *forkh, sipsub_notify_h *notifyh,
+			sipsub_close_h *closeh, void *arg,
+			const char *fmt, va_list ap)
+{
+	struct sipsub *sub;
+	int err;
+
+	if (!subp || !sock || !event || !cuser)
+		return EINVAL;
+
+	if (!dlg && (!uri || !from_uri))
+		return EINVAL;
+
+	sub = mem_zalloc(sizeof(*sub), destructor);
+	if (!sub)
+		return ENOMEM;
+
+	if (dlg) {
+		sub->dlg = mem_ref(dlg);
+	}
+	else {
+		err = sip_dialog_alloc(&sub->dlg, uri, uri, from_name,
+				       from_uri, routev, routec);
+		if (err)
+			goto out;
+	}
+
+	hash_append(sock->ht_sub,
+		    hash_joaat_str(sip_dialog_callid(sub->dlg)),
+		    &sub->he, sub);
+
+	err = sip_auth_alloc(&sub->auth, authh, aarg, aref);
+	if (err)
+		goto out;
+
+	err = str_dup(&sub->event, event);
+	if (err)
+		goto out;
+
+	if (id) {
+		err = str_dup(&sub->id, id);
+		if (err)
+			goto out;
+	}
+
+	err = str_dup(&sub->cuser, cuser);
+	if (err)
+		goto out;
+
+	if (fmt) {
+		err = re_vsdprintf(refer ? &sub->refer_hdrs : &sub->hdrs,
+				   fmt, ap);
+		if (err)
+			goto out;
+	}
+
+	sub->refer_cseq = -1;
+	sub->refer   = refer;
+	sub->sock    = mem_ref(sock);
+	sub->sip     = mem_ref(sock->sip);
+	sub->expires = expires;
+	sub->forkh   = forkh;
+	sub->notifyh = notifyh ? notifyh : internal_notify_handler;
+	sub->closeh  = closeh  ? closeh  : internal_close_handler;
+	sub->arg     = arg;
+
+	err = request(sub, true);
+	if (err)
+		goto out;
+
+ out:
+	if (err)
+		mem_deref(sub);
+	else
+		*subp = sub;
+
+	return err;
+}
+
+
+/**
+ * Allocate a SIP subscriber client
+ *
+ * @param subp      Pointer to allocated SIP subscriber client
+ * @param sock      SIP Event socket
+ * @param uri       SIP Request URI
+ * @param from_name SIP From-header Name (optional)
+ * @param from_uri  SIP From-header URI
+ * @param event     SIP Event to subscribe to
+ * @param id        SIP Event ID (optional)
+ * @param expires   Subscription expires value
+ * @param cuser     Contact username or URI
+ * @param routev    Optional route vector
+ * @param routec    Number of routes
+ * @param authh     Authentication handler
+ * @param aarg      Authentication handler argument
+ * @param aref      True to ref argument
+ * @param forkh     Fork handler
+ * @param notifyh   Notify handler
+ * @param closeh    Close handler
+ * @param arg       Response handler argument
+ * @param fmt       Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipevent_subscribe(struct sipsub **subp, struct sipevent_sock *sock,
+		       const char *uri, const char *from_name,
+		       const char *from_uri, const char *event, const char *id,
+		       uint32_t expires, const char *cuser,
+		       const char *routev[], uint32_t routec,
+		       sip_auth_h *authh, void *aarg, bool aref,
+		       sipsub_fork_h *forkh, sipsub_notify_h *notifyh,
+		       sipsub_close_h *closeh, void *arg,
+		       const char *fmt, ...)
+{
+	va_list ap;
+	int err;
+
+	va_start(ap, fmt);
+	err = sipsub_alloc(subp, sock, false, NULL, uri, from_name, from_uri,
+			   event, id, expires, cuser,
+			   routev, routec, authh, aarg, aref, forkh, notifyh,
+			   closeh, arg, fmt, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+/**
+ * Allocate a SIP subscriber client using an existing dialog
+ *
+ * @param subp      Pointer to allocated SIP subscriber client
+ * @param sock      SIP Event socket
+ * @param dlg       Established SIP Dialog
+ * @param event     SIP Event to subscribe to
+ * @param id        SIP Event ID (optional)
+ * @param expires   Subscription expires value
+ * @param cuser     Contact username or URI
+ * @param authh     Authentication handler
+ * @param aarg      Authentication handler argument
+ * @param aref      True to ref argument
+ * @param notifyh   Notify handler
+ * @param closeh    Close handler
+ * @param arg       Response handler argument
+ * @param fmt       Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipevent_dsubscribe(struct sipsub **subp, struct sipevent_sock *sock,
+			struct sip_dialog *dlg, const char *event,
+			const char *id, uint32_t expires, const char *cuser,
+			sip_auth_h *authh, void *aarg, bool aref,
+			sipsub_notify_h *notifyh, sipsub_close_h *closeh,
+			void *arg, const char *fmt, ...)
+{
+	va_list ap;
+	int err;
+
+	va_start(ap, fmt);
+	err = sipsub_alloc(subp, sock, false, dlg, NULL, NULL, NULL,
+			   event, id, expires, cuser,
+			   NULL, 0, authh, aarg, aref, NULL, notifyh,
+			   closeh, arg, fmt, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+/**
+ * Allocate a SIP refer client
+ *
+ * @param subp      Pointer to allocated SIP subscriber client
+ * @param sock      SIP Event socket
+ * @param uri       SIP Request URI
+ * @param from_name SIP From-header Name (optional)
+ * @param from_uri  SIP From-header URI
+ * @param cuser     Contact username or URI
+ * @param routev    Optional route vector
+ * @param routec    Number of routes
+ * @param authh     Authentication handler
+ * @param aarg      Authentication handler argument
+ * @param aref      True to ref argument
+ * @param forkh     Fork handler
+ * @param notifyh   Notify handler
+ * @param closeh    Close handler
+ * @param arg       Response handler argument
+ * @param fmt       Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipevent_refer(struct sipsub **subp, struct sipevent_sock *sock,
+		   const char *uri, const char *from_name,
+		   const char *from_uri, const char *cuser,
+		   const char *routev[], uint32_t routec,
+		   sip_auth_h *authh, void *aarg, bool aref,
+		   sipsub_fork_h *forkh, sipsub_notify_h *notifyh,
+		   sipsub_close_h *closeh, void *arg,
+		   const char *fmt, ...)
+{
+	va_list ap;
+	int err;
+
+	va_start(ap, fmt);
+	err = sipsub_alloc(subp, sock, true, NULL, uri, from_name, from_uri,
+			   "refer", NULL, DEFAULT_EXPIRES, cuser,
+			   routev, routec, authh, aarg, aref, forkh, notifyh,
+			   closeh, arg, fmt, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+/**
+ * Allocate a SIP refer client using an existing dialog
+ *
+ * @param subp      Pointer to allocated SIP subscriber client
+ * @param sock      SIP Event socket
+ * @param dlg       Established SIP Dialog
+ * @param cuser     Contact username or URI
+ * @param authh     Authentication handler
+ * @param aarg      Authentication handler argument
+ * @param aref      True to ref argument
+ * @param notifyh   Notify handler
+ * @param closeh    Close handler
+ * @param arg       Response handler argument
+ * @param fmt       Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipevent_drefer(struct sipsub **subp, struct sipevent_sock *sock,
+		    struct sip_dialog *dlg, const char *cuser,
+		    sip_auth_h *authh, void *aarg, bool aref,
+		    sipsub_notify_h *notifyh, sipsub_close_h *closeh,
+		    void *arg, const char *fmt, ...)
+{
+	va_list ap;
+	int err;
+
+	va_start(ap, fmt);
+	err = sipsub_alloc(subp, sock, true, dlg, NULL, NULL, NULL,
+			   "refer", NULL, DEFAULT_EXPIRES, cuser,
+			   NULL, 0, authh, aarg, aref, NULL, notifyh,
+			   closeh, arg, fmt, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+int sipevent_fork(struct sipsub **subp, struct sipsub *osub,
+		  const struct sip_msg *msg,
+		  sip_auth_h *authh, void *aarg, bool aref,
+		  sipsub_notify_h *notifyh, sipsub_close_h *closeh,
+		  void *arg)
+{
+	struct sipsub *sub;
+	int err;
+
+	if (!subp || !osub || !msg)
+		return EINVAL;
+
+	sub = mem_zalloc(sizeof(*sub), destructor);
+	if (!sub)
+		return ENOMEM;
+
+	err = sip_dialog_fork(&sub->dlg, osub->dlg, msg);
+	if (err)
+		goto out;
+
+	hash_append(osub->sock->ht_sub,
+		    hash_joaat_str(sip_dialog_callid(sub->dlg)),
+		    &sub->he, sub);
+
+	err = sip_auth_alloc(&sub->auth, authh, aarg, aref);
+	if (err)
+		goto out;
+
+	sub->event   = mem_ref(osub->event);
+	sub->id      = mem_ref(osub->id);
+	sub->cuser   = mem_ref(osub->cuser);
+	sub->hdrs    = mem_ref(osub->hdrs);
+	sub->refer   = osub->refer;
+	sub->sock    = mem_ref(osub->sock);
+	sub->sip     = mem_ref(osub->sip);
+	sub->expires = osub->expires;
+	sub->forkh   = NULL;
+	sub->notifyh = notifyh ? notifyh : internal_notify_handler;
+	sub->closeh  = closeh  ? closeh  : internal_close_handler;
+	sub->arg     = arg;
+
+	if (!sub->expires) {
+		tmr_start(&sub->tmr, NOTIFY_TIMEOUT,
+			  notify_timeout_handler, sub);
+		sub->termwait = true;
+	}
+
+ out:
+	if (err)
+		mem_deref(sub);
+	else
+		*subp = sub;
+
+	return err;
+}