diff --git a/Makefile b/Makefile index c606cb0..dc1893d 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ MK := mk/re.mk include $(MK) # List of modules -MODULES += sip sipreg sipsess +MODULES += sip sipevent sipreg sipsess MODULES += uri httpauth MODULES += stun turn ice MODULES += natbd diff --git a/docs/README b/docs/README index a5cdd93..0688847 100644 --- a/docs/README +++ b/docs/README @@ -46,6 +46,7 @@ Modules: * sdp unstable Session Description Protocol * sha testing Secure Hash Standard, NIST, FIPS PUB 180-1 * sip unstable Core SIP library +* sipevent unstable SIP Event framework * sipreg testing SIP register client * sipsess unstable SIP Sessions * stun unstable Session Traversal Utilities for NAT (STUN) @@ -76,9 +77,11 @@ Features: * RFC 3261 - SIP: Session Initiation Protocol * RFC 3263 - Locating SIP Servers * RFC 3264 - An Offer/Answer Model with SDP +* RFC 3265 - SIP-Specific Event Notification * RFC 3327 - SIP Extension Header Field for Registering Non-Adjacent Contacts * RFC 3428 - SIP Extension for Instant Messaging * RFC 3489 - STUN - Simple Traversal of UDP Through NATs +* RFC 3515 - The SIP Refer Method * RFC 3550 - RTP: A Transport Protocol for Real-Time Applications * RFC 3551 - RTP Profile for Audio and Video Conferences with Minimal Control * RFC 3555 - MIME Type Registration of RTP Payload Formats diff --git a/include/re.h b/include/re.h index ffca789..4aa80ef 100644 --- a/include/re.h +++ b/include/re.h @@ -36,6 +36,7 @@ extern "C" { #include "re_sdp.h" #include "re_uri.h" #include "re_sip.h" +#include "re_sipevent.h" #include "re_sipreg.h" #include "re_sipsess.h" #include "re_stun.h" diff --git a/include/re_sip.h b/include/re_sip.h index 8d946a3..c7db95e 100644 --- a/include/re_sip.h +++ b/include/re_sip.h @@ -301,10 +301,16 @@ int sip_dialog_alloc(struct sip_dialog **dlgp, const char *routev[], uint32_t routec); int sip_dialog_accept(struct sip_dialog **dlgp, const struct sip_msg *msg); int sip_dialog_create(struct sip_dialog *dlg, const struct sip_msg *msg); +int sip_dialog_fork(struct sip_dialog **dlgp, struct sip_dialog *odlg, + const struct sip_msg *msg); int sip_dialog_update(struct sip_dialog *dlg, const struct sip_msg *msg); bool sip_dialog_rseq_valid(struct sip_dialog *dlg, const struct sip_msg *msg); const char *sip_dialog_callid(const struct sip_dialog *dlg); +uint32_t sip_dialog_lseq(const struct sip_dialog *dlg); +bool sip_dialog_established(const struct sip_dialog *dlg); bool sip_dialog_cmp(const struct sip_dialog *dlg, const struct sip_msg *msg); +bool sip_dialog_cmp_half(const struct sip_dialog *dlg, + const struct sip_msg *msg); /* msg */ diff --git a/include/re_sipevent.h b/include/re_sipevent.h new file mode 100644 index 0000000..6e680ce --- /dev/null +++ b/include/re_sipevent.h @@ -0,0 +1,123 @@ +/** + * @file re_sipevent.h SIP Event Framework + * + * Copyright (C) 2010 Creytiv.com + */ + + +/* Message Components */ + +struct sipevent_event { + struct pl event; + struct pl params; + struct pl id; +}; + +enum sipevent_subst { + SIPEVENT_ACTIVE = 0, + SIPEVENT_PENDING, + SIPEVENT_TERMINATED, +}; + +enum sipevent_reason { + SIPEVENT_DEACTIVATED = 0, + SIPEVENT_PROBATION, + SIPEVENT_REJECTED, + SIPEVENT_TIMEOUT, + SIPEVENT_GIVEUP, + SIPEVENT_NORESOURCE, +}; + +struct sipevent_substate { + enum sipevent_subst state; + enum sipevent_reason reason; + struct pl expires; + struct pl retry_after; + struct pl params; +}; + +int sipevent_event_decode(struct sipevent_event *se, const struct pl *pl); +int sipevent_substate_decode(struct sipevent_substate *ss, + const struct pl *pl); +const char *sipevent_substate_name(enum sipevent_subst state); +const char *sipevent_reason_name(enum sipevent_reason reason); + + +/* Listener Socket */ + +struct sipevent_sock; + +int sipevent_listen(struct sipevent_sock **sockp, struct sip *sip, + uint32_t htsize_not, uint32_t htsize_sub, + sip_msg_h *subh, void *arg); + + +/* Notifier */ + +struct sipnot; + +typedef void (sipnot_close_h)(int err, const struct sip_msg *msg, void *arg); + +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, ...); +int sipevent_notify(struct sipnot *not, struct mbuf *mb, + enum sipevent_subst state, enum sipevent_reason reason, + uint32_t retry_after); +int sipevent_notifyf(struct sipnot *not, struct mbuf **mbp, + enum sipevent_subst state, enum sipevent_reason reason, + uint32_t retry_after, const char *fmt, ...); + + +/* Subscriber */ + +struct sipsub; + +typedef int (sipsub_fork_h)(struct sipsub **subp, struct sipsub *osub, + const struct sip_msg *msg, void *arg); +typedef void (sipsub_notify_h)(struct sip *sip, const struct sip_msg *msg, + void *arg); +typedef void (sipsub_close_h)(int err, const struct sip_msg *msg, + const struct sipevent_substate *substate, + void *arg); + +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, ...); +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, ...); + +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, ...); +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, ...); + +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); diff --git a/mk/symbian/bld.inf b/mk/symbian/bld.inf index fcfb8c3..9a428f9 100644 --- a/mk/symbian/bld.inf +++ b/mk/symbian/bld.inf @@ -35,6 +35,7 @@ PRJ_EXPORTS ..\..\include\re_sdp.h \epoc32\include\re\re_sdp.h ..\..\include\re_sha.h \epoc32\include\re\re_sha.h ..\..\include\re_sip.h \epoc32\include\re\re_sip.h +..\..\include\re_sipevent.h \epoc32\include\re\re_sipevent.h ..\..\include\re_sipreg.h \epoc32\include\re\re_sipreg.h ..\..\include\re_sipsess.h \epoc32\include\re\re_sipsess.h ..\..\include\re_stun.h \epoc32\include\re\re_stun.h @@ -56,5 +57,6 @@ rebfcp.mmp redns.mmp resdp.mmp resip.mmp +resipevent.mmp resipsess.mmp restun.mmp diff --git a/mk/symbian/resipevent.mmp b/mk/symbian/resipevent.mmp new file mode 100644 index 0000000..c0eb19b --- /dev/null +++ b/mk/symbian/resipevent.mmp @@ -0,0 +1,34 @@ +/** + * @file resipevent.mmp Symbian makefile for libre SIP Event + * + * Copyright (C) 2010 Creytiv.com + */ +TARGET resipevent.lib +TARGETTYPE lib +TARGETPATH system\libs +UID 0x10000fd3 0x20011307 + +#ifdef EKA2 +VENDORID 0 +CAPABILITY NetworkServices +#endif + +SOURCEPATH . +SOURCE dll.cpp + +SOURCEPATH ..\..\src\sipevent +SOURCE listen.c +SOURCE msg.c +SOURCE notify.c +SOURCE subscribe.c + +USERINCLUDE . ..\..\include +SYSTEMINCLUDE \epoc32\include +SYSTEMINCLUDE \epoc32\include\libc +SYSTEMINCLUDE ..\..\include +#ifndef EKA2 +LIBRARY estlib.lib euser.lib +LIBRARY esock.lib insock.lib +#endif + +EXPORTUNFROZEN diff --git a/src/sip/dialog.c b/src/sip/dialog.c index b3e706c..1516a8a 100644 --- a/src/sip/dialog.c +++ b/src/sip/dialog.c @@ -219,7 +219,6 @@ int sip_dialog_accept(struct sip_dialog **dlgp, const struct sip_msg *msg) err |= sip_msg_hdr_apply(msg, true, SIP_HDR_RECORD_ROUTE, record_route_handler, &renc) ? ENOMEM : 0; - dlg->cpos = dlg->mb->pos; err |= mbuf_printf(dlg->mb, "To: %r\r\n", &msg->from.val); err |= mbuf_printf(dlg->mb, "From: %r;tag=%016llx\r\n", &msg->to.val, msg->tag); @@ -251,13 +250,14 @@ int sip_dialog_accept(struct sip_dialog **dlgp, const struct sip_msg *msg) int sip_dialog_create(struct sip_dialog *dlg, const struct sip_msg *msg) { + char *uri = NULL, *rtag = NULL; const struct sip_hdr *contact; struct route_enc renc; struct sip_addr addr; struct pl pl; int err; - if (!dlg || dlg->rtag || !msg) + if (!dlg || dlg->rtag || !dlg->cpos || !msg) return EINVAL; contact = sip_msg_hdr(msg, SIP_HDR_CONTACT); @@ -268,20 +268,18 @@ int sip_dialog_create(struct sip_dialog *dlg, const struct sip_msg *msg) if (sip_addr_decode(&addr, &contact->val)) return EBADMSG; - dlg->uri = mem_deref(dlg->uri); - - err = pl_strdup(&dlg->uri, &addr.auri); - if (err) - return err; - - err = pl_strdup(&dlg->rtag, msg->req ? &msg->from.tag : &msg->to.tag); - if (err) - return err; - renc.mb = mbuf_alloc(512); if (!renc.mb) return ENOMEM; + err = pl_strdup(&uri, &addr.auri); + if (err) + goto out; + + err = pl_strdup(&rtag, msg->req ? &msg->from.tag : &msg->to.tag); + if (err) + goto out; + renc.end = 0; err |= sip_msg_hdr_apply(msg, msg->req, SIP_HDR_RECORD_ROUTE, @@ -303,6 +301,106 @@ int sip_dialog_create(struct sip_dialog *dlg, const struct sip_msg *msg) pl.p = (const char *)mbuf_buf(renc.mb) + ROUTE_OFFSET; pl.l = renc.end - ROUTE_OFFSET; err = sip_addr_decode(&addr, &pl); + if (err) + goto out; + + dlg->route = addr.uri; + } + else { + struct uri tmp; + + pl_set_str(&pl, uri); + err = uri_decode(&tmp, &pl); + if (err) + goto out; + + dlg->route = tmp; + } + + mem_deref(dlg->mb); + mem_deref(dlg->uri); + + dlg->mb = mem_ref(renc.mb); + dlg->rtag = mem_ref(rtag); + dlg->uri = mem_ref(uri); + dlg->rseq = msg->req ? msg->cseq.num : 0; + dlg->cpos = 0; + + out: + mem_deref(renc.mb); + mem_deref(rtag); + mem_deref(uri); + + return err; +} + + +int sip_dialog_fork(struct sip_dialog **dlgp, struct sip_dialog *odlg, + const struct sip_msg *msg) +{ + const struct sip_hdr *contact; + struct sip_dialog *dlg; + struct route_enc renc; + struct sip_addr addr; + struct pl pl; + int err; + + if (!dlgp || !odlg || !odlg->cpos || !msg) + return EINVAL; + + contact = sip_msg_hdr(msg, SIP_HDR_CONTACT); + + if (!contact || !msg->callid.p) + return EBADMSG; + + if (sip_addr_decode(&addr, &contact->val)) + return EBADMSG; + + dlg = mem_zalloc(sizeof(*dlg), destructor); + if (!dlg) + return ENOMEM; + + dlg->callid = mem_ref(odlg->callid); + dlg->ltag = mem_ref(odlg->ltag); + dlg->lseq = odlg->lseq; + dlg->rseq = msg->req ? msg->cseq.num : 0; + + err = pl_strdup(&dlg->uri, &addr.auri); + if (err) + goto out; + + err = pl_strdup(&dlg->rtag, msg->req ? &msg->from.tag : &msg->to.tag); + if (err) + goto out; + + dlg->mb = mbuf_alloc(512); + if (!dlg->mb) { + err = ENOMEM; + goto out; + } + + renc.mb = dlg->mb; + renc.end = 0; + + err |= sip_msg_hdr_apply(msg, msg->req, SIP_HDR_RECORD_ROUTE, + record_route_handler, &renc) ? ENOMEM : 0; + err |= mbuf_printf(dlg->mb, "To: %r\r\n", + msg->req ? &msg->from.val : &msg->to.val); + + odlg->mb->pos = odlg->cpos; + err |= mbuf_write_mem(dlg->mb, mbuf_buf(odlg->mb), + mbuf_get_left(odlg->mb)); + odlg->mb->pos = 0; + + if (err) + goto out; + + dlg->mb->pos = 0; + + if (renc.end) { + pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET; + pl.l = renc.end - ROUTE_OFFSET; + err = sip_addr_decode(&addr, &pl); dlg->route = addr.uri; } else { @@ -310,14 +408,11 @@ int sip_dialog_create(struct sip_dialog *dlg, const struct sip_msg *msg) err = uri_decode(&dlg->route, &pl); } - if (err) - goto out; - - mem_deref(dlg->mb); - dlg->mb = mem_ref(renc.mb); - out: - mem_deref(renc.mb); + if (err) + mem_deref(dlg); + else + *dlgp = dlg; return err; } @@ -327,7 +422,6 @@ int sip_dialog_update(struct sip_dialog *dlg, const struct sip_msg *msg) { const struct sip_hdr *contact; struct sip_addr addr; - struct pl pl; char *uri; int err; @@ -347,10 +441,15 @@ int sip_dialog_update(struct sip_dialog *dlg, const struct sip_msg *msg) if (dlg->route.scheme.p == dlg->uri) { + struct uri tmp; + struct pl pl; + pl_set_str(&pl, uri); - err = uri_decode(&dlg->route, &pl); + err = uri_decode(&tmp, &pl); if (err) goto out; + + dlg->route = tmp; } mem_deref(dlg->uri); @@ -412,6 +511,18 @@ const char *sip_dialog_callid(const struct sip_dialog *dlg) } +uint32_t sip_dialog_lseq(const struct sip_dialog *dlg) +{ + return dlg ? dlg->lseq : 0; +} + + +bool sip_dialog_established(const struct sip_dialog *dlg) +{ + return dlg && dlg->rtag; +} + + bool sip_dialog_cmp(const struct sip_dialog *dlg, const struct sip_msg *msg) { if (!dlg || !msg) @@ -428,3 +539,19 @@ bool sip_dialog_cmp(const struct sip_dialog *dlg, const struct sip_msg *msg) return true; } + + +bool sip_dialog_cmp_half(const struct sip_dialog *dlg, + const struct sip_msg *msg) +{ + if (!dlg || !msg) + return false; + + if (pl_strcmp(&msg->callid, dlg->callid)) + return false; + + if (pl_strcmp(msg->req ? &msg->to.tag : &msg->from.tag, dlg->ltag)) + return false; + + return true; +} diff --git a/src/sipevent/listen.c b/src/sipevent/listen.c new file mode 100644 index 0000000..1ef0d82 --- /dev/null +++ b/src/sipevent/listen.c @@ -0,0 +1,353 @@ +/** + * @file listen.c SIP Event Listen + * + * Copyright (C) 2010 Creytiv.com + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; + 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, strerror(err)); + return; + } + + sub = fsub; + } + else { + err = sip_dialog_create(sub->dlg, msg); + if (err) { + (void)sip_reply(sip, msg, 500, strerror(err)); + 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, strerror(err)); + 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, 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..4cfc753 --- /dev/null +++ b/src/sipevent/msg.c @@ -0,0 +1,119 @@ +/** + * @file msg.c SIP event messages + * + * Copyright (C) 2010 Creytiv.com + */ +#include +#include +#include +#include +#include +#include +#include +#include + + +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 (!sip_param_decode(&se->params, "id", ¶m)) + 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 (!sip_param_decode(&ss->params, "reason", ¶m)) { + + if (!pl_strcasecmp(¶m, "deactivated")) + ss->reason = SIPEVENT_DEACTIVATED; + else if (!pl_strcasecmp(¶m, "probation")) + ss->reason = SIPEVENT_PROBATION; + else if (!pl_strcasecmp(¶m, "rejected")) + ss->reason = SIPEVENT_REJECTED; + else if (!pl_strcasecmp(¶m, "timeout")) + ss->reason = SIPEVENT_TIMEOUT; + else if (!pl_strcasecmp(¶m, "giveup")) + ss->reason = SIPEVENT_GIVEUP; + else if (!pl_strcasecmp(¶m, "noresource")) + ss->reason = SIPEVENT_NORESOURCE; + else + ss->reason = -1; + } + else { + ss->reason = -1; + } + + if (!sip_param_decode(&ss->params, "expires", ¶m)) + ss->expires = param; + else + ss->expires = pl_null; + + if (!sip_param_decode(&ss->params, "retry-after", ¶m)) + 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..0e0059e --- /dev/null +++ b/src/sipevent/notify.c @@ -0,0 +1,482 @@ +/** + * @file notify.c SIP Event Notify + * + * Copyright (C) 2010 Creytiv.com + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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(¬->tmr); + + if (!not->terminated) { + + if (terminate(not, SIPEVENT_DEACTIVATED)) + return; + } + + hash_unlink(¬->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(¬->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(¬->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(¬->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 sipnot *not = arg; + (void)dst; + + return mbuf_printf(mb, "Contact: \r\n", + not->cuser, src, sip_transp_param(tp)); +} + + +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(¬->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(¬->ls); + + if (not->terminated) + not->termsent = true; + + not->notify_pending = false; + + return sip_drequestf(¬->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) +{ + uint32_t expires; + + expires = (uint32_t)(tmr_get_expire(¬->tmr) / 1000); + + return sip_treplyf(NULL, NULL, not->sip, msg, true, scode, reason, + "Contact: \r\n" + "Expires: %u\r\n" + "Content-Length: 0\r\n" + "\r\n", + not->cuser, &msg->dst, sip_transp_param(msg->tp), + 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(¬->event, "refer"); + if (err) + goto out; + + err = re_sdprintf(¬->id, "%u", msg->cseq.num); + if (err) + goto out; + } + else { + if (!event) { + err = EINVAL; + goto out; + } + + err = pl_strdup(¬->event, &event->event); + if (err) + goto out; + + if (pl_isset(&event->id)) { + + err = pl_strdup(¬->id, &event->id); + if (err) + goto out; + } + } + + if (dlg) { + not->dlg = mem_ref(dlg); + } + else { + err = sip_dialog_accept(¬->dlg, msg); + if (err) + goto out; + } + + hash_append(sock->ht_not, + hash_joaat_str(sip_dialog_callid(not->dlg)), + ¬->he, not); + + err = sip_auth_alloc(¬->auth, authh, aarg, aref); + if (err) + goto out; + + err = str_dup(¬->cuser, cuser); + if (err) + goto out; + + err = str_dup(¬->ctype, ctype); + if (err) + goto out; + + if (fmt) { + va_list ap; + + va_start(ap, fmt); + err = re_vsdprintf(¬->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(¬->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..3f17a42 --- /dev/null +++ b/src/sipevent/subscribe.c @@ -0,0 +1,666 @@ +/** + * @file subscribe.c SIP Event Subscribe + * + * Copyright (C) 2010 Creytiv.com + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 sipsub *sub = arg; + (void)dst; + + return mbuf_printf(mb, "Contact: \r\n", + sub->cuser, src, sip_transp_param(tp)); +} + + +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 + * @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 + * @param authh Authentication handler + * @param aarg Authentication handler argument + * @param aref True to ref argument + * @param noth 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 + * @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 + * @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; +} diff --git a/src/sipsess/listen.c b/src/sipsess/listen.c index 139653f..81820ee 100644 --- a/src/sipsess/listen.c +++ b/src/sipsess/listen.c @@ -266,6 +266,10 @@ static bool request_handler(const struct sip_msg *msg, void *arg) return true; } else if (!pl_strcmp(&msg->met, "REFER")) { + + if (!pl_isset(&msg->to.tag)) + return false; + refer_handler(sock, msg); return true; }