/* * Presence Agent, notifications * * $Id: notify.c,v 1.21 2004/08/24 22:11:43 jamey Exp $ * * Copyright (C) 2001-2003 FhG Fokus * * This file is part of ser, a free SIP server. * * ser is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version * * For a license to use the ser software under conditions * other than those described here, or to purchase support for this * software, please contact iptel.org by e-mail at the following addresses: * info@iptel.org * * ser is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * History: * -------- * 2003-02-28 protocolization of t_uac_dlg completed (jiri) */ #include "../../str.h" #include "../../dprint.h" #include "../../trim.h" #include "../../parser/parse_event.h" #include "pa_mod.h" #include "lpidf.h" #include "xpidf.h" #include "presentity.h" #include "pidf.h" #include "common.h" #include "paerrno.h" #include "notify.h" #include "watcher.h" #include "location.h" #define CONTENT_TYPE "Content-Type: " #define CONTENT_TYPE_L (sizeof(CONTENT_TYPE) - 1) #define EVENT "Event: " #define EVENT_L (sizeof(EVENT) - 1) #define PRESENCE_TEXT "presence" #define PRESENCE_TEXT_L (sizeof(PRESENCE_TEXT) - 1) #define WINFO_TEXT "presence.winfo" #define WINFO_TEXT_L (sizeof(WINFO_TEXT) - 1) #define XCAP_CHANGE_TEXT "xcap-change" #define XCAP_CHANGE_TEXT_L (sizeof(XCAP_CHANGE_TEXT) - 1) #define CONT_TYPE_XPIDF "application/xpidf+xml" #define CONT_TYPE_XPIDF_L (sizeof(CONT_TYPE_XPIDF) - 1) #define CONT_TYPE_LPIDF "text/lpidf" #define CONT_TYPE_LPIDF_L (sizeof(CONT_TYPE_LPIDF) - 1) #define CONT_TYPE_PIDF "application/pidf+xml" #define CONT_TYPE_PIDF_L (sizeof(CONT_TYPE_PIDF) - 1) #define CONT_TYPE_WINFO "application/watcherinfo+xml" #define CONT_TYPE_WINFO_L (sizeof(CONT_TYPE_WINFO) - 1) #define CONT_TYPE_XCAP_CHANGE "application/xcap-change+xml" #define CONT_TYPE_XCAP_CHANGE_L (sizeof(CONT_TYPE_XCAP_CHANGE) - 1) #define SUBSCRIPTION_STATE "Subscription-State: " #define SUBSCRIPTION_STATE_L (sizeof(SUBSCRIPTION_STATE) - 1) #define SS_EXPIRES ";expires=" #define SS_EXPIRES_L (sizeof(SS_EXPIRES) - 1) #define SS_REASON ";reason=" #define SS_REASON_L (sizeof(SS_REASON) - 1) #define CRLF "\r\n" #define CRLF_L (sizeof(CRLF) - 1) #define ST_ACTIVE "active" #define ST_ACTIVE_L (sizeof(ST_ACTIVE) - 1) #define ST_TERMINATED "terminated" #define ST_TERMINATED_L (sizeof(ST_TERMINATED) - 1) #define ST_PENDING "pending" #define ST_PENDING_L (sizeof(ST_PENDING) - 1) #define REASON_DEACTIVATED "deactivated" #define REASON_DEACTIVATED_L (sizeof(REASON_DEACTIVATED) - 1) #define REASON_NORESOURCE "noresource" #define REASON_NORESOURCE_L (sizeof(REASON_NORESOURCE) - 1) #define REASON_PROBATION "probation" #define REASON_PROBATION_L (sizeof(REASON_PROBATION) - 1) #define REASON_REJECTED "rejected" #define REASON_REJECTED_L (sizeof(REASON_REJECTED) - 1) #define REASON_TIMEOUT "timeout" #define REASON_TIMEOUT_L (sizeof(REASON_TIMEOUT) - 1) #define REASON_GIVEUP "giveup" #define REASON_GIVEUP_L (sizeof(REASON_GIVEUP) - 1) #define METHOD_NOTIFY "NOTIFY" #define METHOD_NOTIFY_L (sizeof(METHOD_NOTIFY) - 1) /* * Subscription-State values */ static str subs_states[] = { {ST_ACTIVE, ST_ACTIVE_L }, {ST_TERMINATED, ST_TERMINATED_L}, {ST_PENDING, ST_PENDING_L } }; /* * Subscription-State reason parameter values */ static str reason[] = { {REASON_DEACTIVATED, REASON_DEACTIVATED_L}, {REASON_NORESOURCE, REASON_NORESOURCE_L }, {REASON_PROBATION, REASON_PROBATION_L }, {REASON_REJECTED, REASON_REJECTED_L }, {REASON_TIMEOUT, REASON_TIMEOUT_L }, {REASON_GIVEUP, REASON_GIVEUP_L } }; static str method = {METHOD_NOTIFY, METHOD_NOTIFY_L}; #define BUF_LEN 4096 static char headers_buf[BUF_LEN]; static char buffer[BUF_LEN]; static str headers = {headers_buf, 0}; static str body = {buffer, 0}; static inline int add_event_hf(str* _h, int _l, int accept) { int event_l; char *event; if (accept == DOC_WINFO) { event = WINFO_TEXT; event_l = WINFO_TEXT_L; } else if (accept == DOC_XCAP_CHANGE) { event = XCAP_CHANGE_TEXT; event_l = XCAP_CHANGE_TEXT_L; } else { event = PRESENCE_TEXT; event_l = PRESENCE_TEXT_L; } if (_l < EVENT_L + event_l + CRLF_L) { paerrno = PA_SMALL_BUFFER; LOG(L_ERR, "add_event_hf(): Buffer too small\n"); return -1; } str_append(_h, EVENT, EVENT_L); str_append(_h, event, event_l); str_append(_h, CRLF, CRLF_L); return 0; } static inline int add_cont_type_hf(str* _h, int _l, doctype_t _d) { switch(_d) { case DOC_XPIDF: if (_l < CONTENT_TYPE_L + CONT_TYPE_XPIDF_L + CRLF_L) { paerrno = PA_SMALL_BUFFER; LOG(L_ERR, "add_cont_type_hf(): Buffer too small\n"); return -1; } str_append(_h, CONTENT_TYPE CONT_TYPE_XPIDF CRLF, CONTENT_TYPE_L + CONT_TYPE_XPIDF_L + CRLF_L); return 0; case DOC_LPIDF: if (_l < CONTENT_TYPE_L + CONT_TYPE_LPIDF_L + CRLF_L) { paerrno = PA_SMALL_BUFFER; LOG(L_ERR, "add_cont_type_hf(): Buffer too small\n"); return -2; } str_append(_h, CONTENT_TYPE CONT_TYPE_LPIDF CRLF, CONTENT_TYPE_L + CONT_TYPE_LPIDF_L + CRLF_L); return 0; case DOC_PIDF: if (_l < CONTENT_TYPE_L + CONT_TYPE_PIDF_L + CRLF_L) { paerrno = PA_SMALL_BUFFER; LOG(L_ERR, "add_cont_type_hf(): Buffer too small\n"); return -2; } str_append(_h, CONTENT_TYPE CONT_TYPE_PIDF CRLF, CONTENT_TYPE_L + CONT_TYPE_PIDF_L + CRLF_L); return 0; case DOC_WINFO: if (_l < CONTENT_TYPE_L + CONT_TYPE_WINFO_L + CRLF_L) { paerrno = PA_SMALL_BUFFER; LOG(L_ERR, "add_cont_type_hf(): Buffer too small\n"); return -2; } str_append(_h, CONTENT_TYPE CONT_TYPE_WINFO CRLF, CONTENT_TYPE_L + CONT_TYPE_WINFO_L + CRLF_L); return 0; case DOC_XCAP_CHANGE: if (_l < CONTENT_TYPE_L + CONT_TYPE_XCAP_CHANGE_L + CRLF_L) { paerrno = PA_SMALL_BUFFER; LOG(L_ERR, "add_cont_type_hf(): Buffer too small\n"); return -2; } str_append(_h, CONTENT_TYPE CONT_TYPE_XCAP_CHANGE CRLF, CONTENT_TYPE_L + CONT_TYPE_XCAP_CHANGE_L + CRLF_L); return 0; default: paerrno = PA_UNSUPP_DOC; LOG(L_ERR, "add_cont_type_hf(): Unsupported document type\n"); return -3; } } static inline int add_subs_state_hf(str* _h, int _l, subs_state_t _s, ss_reason_t _r, time_t _e) { char* num; int len; if (_l < SUBSCRIPTION_STATE_L + subs_states[_s].len + SS_EXPIRES_L + SS_REASON_L + reason[_r].len + CRLF_L) { paerrno = PA_SMALL_BUFFER; LOG(L_ERR, "add_subs_state_hf(): Buffer too small\n"); return -1; } str_append(_h, SUBSCRIPTION_STATE, SUBSCRIPTION_STATE_L); str_append(_h, subs_states[_s].s, subs_states[_s].len); switch(_s) { case SS_ACTIVE: str_append(_h, SS_EXPIRES, SS_EXPIRES_L); num = int2str((unsigned int)_e, &len); str_append(_h, num, len); break; case SS_TERMINATED: str_append(_h, SS_REASON, SS_REASON_L); str_append(_h, reason[_r].s, reason[_r].len); break; case SS_PENDING: break; } str_append(_h, CRLF, CRLF_L); return 0; } static inline int create_headers(struct watcher* _w) { time_t t; subs_state_t s; headers.len = 0; if (add_event_hf(&headers, BUF_LEN, _w->accept) < 0) { LOG(L_ERR, "create_headers(): Error while adding Event header field\n"); return -1; } if (add_cont_type_hf(&headers, BUF_LEN - headers.len, _w->accept) < 0) { LOG(L_ERR, "create_headers(): Error while adding Content-Type header field\n"); return -2; } if (_w && _w->expires) t = _w->expires - time(0); else t = 0; if (t == 0) { s = SS_TERMINATED; } else { s = SS_ACTIVE; } if (add_subs_state_hf(&headers, BUF_LEN - headers.len, s, SR_TIMEOUT, t) < 0) { LOG(L_ERR, "create_headers(): Error while adding Subscription-State\n"); return -3; } return 0; } static int send_xpidf_notify(struct presentity* _p, struct watcher* _w) { xpidf_status_t st; presence_tuple_t *tuple = _p->tuples; /* Send a notify, saved Contact will be put in * Request-URI, To will be put in from and new tag * will be generated, callid will be callid, * from will be put in to including tag */ if (start_xpidf_doc(&body, BUF_LEN) < 0) { LOG(L_ERR, "send_xpidf_notify(): start_xpidf_doc failed\n"); return -1; } if (xpidf_add_presentity(&body, BUF_LEN - body.len, &_p->uri) < 0) { LOG(L_ERR, "send_xpidf_notify(): xpidf_add_presentity failed\n"); return -3; } while (tuple) { switch(tuple->state) { case PS_ONLINE: st = XPIDF_ST_OPEN; break; default: st = XPIDF_ST_CLOSED; break; } if (xpidf_add_address(&body, BUF_LEN - body.len, &_p->uri, st) < 0) { LOG(L_ERR, "send_xpidf_notify(): xpidf_add_address failed\n"); return -3; } tuple = tuple->next; } if (end_xpidf_doc(&body, BUF_LEN - body.len) < 0) { LOG(L_ERR, "send_xpidf_notify(): end_xpidf_doc failed\n"); return -5; } if (create_headers(_w) < 0) { LOG(L_ERR, "send_xpidf_notify(): Error while adding headers\n"); return -6; } tmb.t_request_within(&method, &headers, &body, _w->dialog, 0, 0); return 0; } static int send_lpidf_notify(struct presentity* _p, struct watcher* _w) { lpidf_status_t st; presence_tuple_t *tuple = _p->tuples; if (lpidf_add_presentity(&body, BUF_LEN - body.len, &_p->uri) < 0) { LOG(L_ERR, "send_lpidf_notify(): Error in lpidf_add_presentity\n"); return -2; } while (tuple) { switch(tuple->state) { case PS_OFFLINE: st = LPIDF_ST_CLOSED; break; default: st = LPIDF_ST_OPEN; break; } if (lpidf_add_address(&body, BUF_LEN - body.len, &_p->uri, st) < 0) { LOG(L_ERR, "send_lpidf_notify(): lpidf_add_address failed\n"); return -3; } tuple = tuple->next; } if (create_headers(_w) < 0) { LOG(L_ERR, "send_lpidf_notify(): Error while adding headers\n"); return -4; } tmb.t_request_within(&method, &headers, &body, _w->dialog, 0, 0); return 0; } static int send_pidf_notify(struct presentity* _p, struct watcher* _w) { xpidf_status_t st; presence_tuple_t *tuple = _p->tuples; /* Send a notify, saved Contact will be put in * Request-URI, To will be put in from and new tag * will be generated, callid will be callid, * from will be put in to including tag */ LOG(L_ERR, " send_pidf_notify\n"); if (start_pidf_doc(&body, BUF_LEN) < 0) { LOG(L_ERR, "send_pidf_notify(): start_pidf_doc failed\n"); return -1; } if (pidf_add_presentity(&body, BUF_LEN - body.len, &_p->uri) < 0) { LOG(L_ERR, "send_pidf_notify(): pidf_add_presentity failed\n"); return -3; } if (tuple) { while (tuple) { if (pidf_start_tuple(&body, &tuple->id, BUF_LEN - body.len) < 0) { LOG(L_ERR, "send_pidf_notify(): start_pidf_tuple failed\n"); return -4; } switch(tuple->state) { case PS_ONLINE: st = XPIDF_ST_OPEN; break; default: st = XPIDF_ST_CLOSED; break; } if (pidf_add_contact(&body, BUF_LEN - body.len, &tuple->contact, tuple->priority) < 0) { LOG(L_ERR, "send_pidf_notify(): pidf_add_contact failed\n"); return -3; } if (pidf_start_status(&body, BUF_LEN - body.len, st) < 0) { LOG(L_ERR, "send_pidf_notify(): pidf_start_status failed\n"); return -3; } if (pidf_add_location(&body, BUF_LEN - body.len, &tuple->location.loc, &tuple->location.site, &tuple->location.floor, &tuple->location.room, tuple->location.x, tuple->location.y, tuple->location.radius, tuple->prescaps) < 0) { LOG(L_ERR, "send_pidf_notify(): pidf_add_location failed\n"); return -4; } if (pidf_end_status(&body, BUF_LEN - body.len) < 0) { LOG(L_ERR, "send_pidf_notify(): pidf_end_status failed\n"); return -5; } if (pidf_end_tuple(&body, BUF_LEN - body.len) < 0) { LOG(L_ERR, "send_pidf_notify(): end_pidf_tuple failed\n"); return -5; } tuple = tuple->next; } } else { str id = { "ser", 3 }; str contact = { NULL, 0 }; float priority = 0.8; st = XPIDF_ST_CLOSED; if (pidf_start_tuple(&body, &id, BUF_LEN - body.len) < 0) { LOG(L_ERR, "send_pidf_notify(): start_pidf_tuple failed\n"); return -4; } if (pidf_add_contact(&body, BUF_LEN - body.len, &contact, priority) < 0) { LOG(L_ERR, "send_pidf_notify(): pidf_add_contact failed\n"); return -3; } if (pidf_start_status(&body, BUF_LEN - body.len, st) < 0) { LOG(L_ERR, "send_pidf_notify(): pidf_start_status failed\n"); return -3; } if (pidf_end_status(&body, BUF_LEN - body.len) < 0) { LOG(L_ERR, "send_pidf_notify(): pidf_end_status failed\n"); return -5; } if (pidf_end_tuple(&body, BUF_LEN - body.len) < 0) { LOG(L_ERR, "send_pidf_notify(): end_pidf_tuple failed\n"); return -5; } } if (end_pidf_doc(&body, BUF_LEN - body.len) < 0) { LOG(L_ERR, "send_pidf_notify(): end_xpidf_doc failed\n"); return -6; } if (create_headers(_w) < 0) { LOG(L_ERR, "send_pidf_notify(): Error while adding headers\n"); return -7; } tmb.t_request_within(&method, &headers, &body, _w->dialog, 0, 0); return 0; } static int send_winfo_notify(struct presentity* _p, struct watcher* _w) { watcher_t *watcher = _p->watchers; LOG(L_INFO, "send_winfo_notify: watcher=%p winfo_watcher=%p\n", watcher, _w); if (start_winfo_doc(&body, BUF_LEN) < 0) { LOG(L_ERR, "send_winfo_notify(): start_winfo_doc failed\n"); return -1; } if (winfo_start_resource(&body, BUF_LEN - body.len, &_p->uri, _w) < 0) { LOG(L_ERR, "send_winfo_notify(): winfo_add_resource failed\n"); return -3; } while (watcher) { if (winfo_add_watcher(&body, BUF_LEN - body.len, watcher) < 0) { LOG(L_ERR, "send_winfo_notify(): winfo_add_watcher failed\n"); return -3; } watcher = watcher->next; } if (winfo_end_resource(&body, BUF_LEN - body.len) < 0) { LOG(L_ERR, "send_winfo_notify(): winfo_add_resource failed\n"); return -5; } if (end_winfo_doc(&body, BUF_LEN - body.len) < 0) { LOG(L_ERR, "send_winfo_notify(): end_xwinfo_doc failed\n"); return -6; } if (create_headers(_w) < 0) { LOG(L_ERR, "send_winfo_notify(): Error while adding headers\n"); return -7; } tmb.t_request_within(&method, &headers, &body, _w->dialog, 0, 0); return 0; } static int send_xcap_change_notify(struct presentity* _p, struct watcher* _w) { int len = 0; int presence_list_changed = _p->flags & PFLAG_PRESENCE_LISTS_CHANGED; int watcherinfo_changed = _p->flags & PFLAG_WATCHERINFO_CHANGED; LOG(L_ERR, " send_xcap_change flags=%x\n", _p->flags); len += sprintf(body.s + len, "\r\n"); len += sprintf(body.s + len, "\r\n"); if (presence_list_changed) { len += sprintf(body.s + len, " \r\n", pa_domain.len, pa_domain.s, _p->uri.len, _p->uri.s); len += sprintf(body.s + len, " someone@example.com\r\n"); len += sprintf(body.s + len, " \r\n"); } if (watcherinfo_changed) { len += sprintf(body.s + len, " \r\n", pa_domain.len, pa_domain.s, _p->uri.len, _p->uri.s); len += sprintf(body.s + len, " someone@example.com\r\n"); len += sprintf(body.s + len, " \r\n"); } len += sprintf(body.s + len, "\r\n"); body.len = len; if (create_headers(_w) < 0) { LOG(L_ERR, "send_location_notify(): Error while adding headers\n"); return -7; } tmb.t_request_within(&method, &headers, &body, _w->dialog, 0, 0); return 0; } int send_location_notify(struct presentity* _p, struct watcher* _w) { resource_list_t *user = _p->location_package.users; LOG(L_ERR, "send_location_notify to watcher %.*s\n", _w->uri.len, _w->uri.s); if (location_doc_start(&body, BUF_LEN) < 0) { LOG(L_ERR, "send_location_notify(): start_location_doc failed\n"); return -1; } if (location_doc_start_userlist(&body, BUF_LEN - body.len, &_p->uri) < 0) { LOG(L_ERR, "send_location_notify(): location_add_uri failed\n"); return -3; } while (user) { if (location_doc_add_user(&body, BUF_LEN - body.len, &user->uri) < 0) { LOG(L_ERR, "send_location_notify(): location_add_watcher failed\n"); return -3; } user = user->next; } if (location_doc_end_resource(&body, BUF_LEN - body.len) < 0) { LOG(L_ERR, "send_location_notify(): location_add_resource failed\n"); return -5; } if (location_doc_end(&body, BUF_LEN - body.len) < 0) { LOG(L_ERR, "send_location_notify(): end_xlocation_doc failed\n"); return -6; } if (create_headers(_w) < 0) { LOG(L_ERR, "send_location_notify(): Error while adding headers\n"); return -7; } tmb.t_request_within(&method, &headers, &body, _w->dialog, 0, 0); return 0; } int send_notify(struct presentity* _p, struct watcher* _w) { int rc = 0; body.len = 0; if (_w->uri.s == NULL) { LOG(L_ERR, "watcher uri.s is NULL\n"); return -1; } if (strlen(_w->uri.s) == 0) { LOG(L_ERR, "watcher uri.s is zero length\n"); return -2; } LOG(L_ERR, "notifying %.*s _p->flags=%x _w->event_package=%d _w->accept=%d _w->status=%d\n", _w->uri.len, _w->uri.s, _p->flags, _w->event_package, _w->accept, _w->status); if ((_p->flags & (PFLAG_PRESENCE_CHANGED|PFLAG_WATCHERINFO_CHANGED)) && (_w->event_package == EVENT_PRESENCE) && (_w->status = WS_ACTIVE)) { switch(_w->accept) { case DOC_XPIDF: rc = send_xpidf_notify(_p, _w); if (rc) LOG(L_ERR, "send_xpidf_notify returned %d\n", rc); break; case DOC_LPIDF: rc = send_lpidf_notify(_p, _w); if (rc) LOG(L_ERR, "send_lpidf_notify returned %d\n", rc); break; case DOC_PIDF: default: rc = send_pidf_notify(_p, _w); if (rc) LOG(L_ERR, "send_pidf_notify returned %d\n", rc); } } if ((_p->flags & PFLAG_WATCHERINFO_CHANGED) && (_w->event_package == EVENT_PRESENCE_WINFO)) { switch(_w->accept) { case DOC_WINFO: rc = send_winfo_notify(_p, _w); if (rc) LOG(L_ERR, "send_winfo_notify returned %d\n", rc); return rc; default: /* inapplicable */ ; } } if ((_p->flags & PFLAG_XCAP_CHANGED) && (_w->event_package == EVENT_XCAP_CHANGE)) { switch(_w->accept) { case DOC_XCAP_CHANGE: default: rc = send_xcap_change_notify(_p, _w); if (rc) LOG(L_ERR, "send_xcap_change_notify returned %d\n", rc); } } if ((_p->flags & PFLAG_LOCATION_CHANGED) && (_w->event_package == EVENT_LOCATION)) { switch(_w->accept) { case DOC_LOCATION: rc = send_location_notify(_p, _w); if (rc) LOG(L_ERR, "send_location_notify returned %d\n", rc); return rc; default: rc = -1; ; } } return rc; }