/* * Presence Agent, publish handling * * $Id$ * * Copyright (C) 2001-2003 FhG Fokus * Copyright (C) 2003-2004 Hewlett-Packard Company * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include "../../str.h" #include "../../dprint.h" #include "../../mem/mem.h" #include "../../parser/parse_uri.h" #include "../../parser/parse_from.h" #include "../../parser/contact/parse_contact.h" #include "../../parser/parse_expires.h" #include "../../parser/parse_event.h" #include "dlist.h" #include "presentity.h" #include "watcher.h" #include "notify.h" #include "paerrno.h" #include "pdomain.h" #include "pa_mod.h" #include "ptime.h" #include "reply.h" #include "subscribe.h" #include "publish.h" #include "tuple.h" #include "pres_notes.h" #include "extension_elements.h" #include "../../data_lump_rpl.h" #include "../../parser/parse_sipifmatch.h" #include #include #include #include #include /* ------------ Helper functions ------------ */ /* * Parse all header fields that will be needed * to handle a PUBLISH request */ static int parse_publish_hfs(struct sip_msg* _m) { int rc = 0; if ((rc = parse_headers(_m, HDR_FROM_F | HDR_EVENT_F | HDR_EXPIRES_F | HDR_SIPIFMATCH_F | HDR_CONTENTTYPE_F | HDR_CONTENTLENGTH_F, 0)) == -1) { paerrno = PA_PARSE_ERR; LOG(L_ERR, "parse_publish_hfs(): Error while parsing headers\n"); return -1; } if (parse_from_header(_m) < 0) { paerrno = PA_FROM_ERR; LOG(L_ERR, "parse_publish_hfs(): From malformed or missing\n"); return -6; } if (_m->event) { if (parse_event(_m->event) < 0) { paerrno = PA_EVENT_PARSE; LOG(L_ERR, "parse_publish_hfs(): Error while parsing Event header field\n"); return -8; } } else { paerrno = PA_EVENT_PARSE; LOG(L_ERR, "parse_publish_hfs(): Missing Event header field\n"); return -7; } if (_m->expires) { if (parse_expires(_m->expires) < 0) { paerrno = PA_EXPIRES_PARSE; LOG(L_ERR, "parse_publish_hfs(): Error while parsing Expires header field\n"); return -9; } } /* patch from PIC-SER */ if (_m->sipifmatch) { if (parse_sipifmatch(_m->sipifmatch) < 0) { paerrno = PA_PARSE_ERR; LOG(L_ERR, "parse_hfs(): Error while parsing SIP-If-Match header field\n"); return -10; } } if (_m->content_type) { if (parse_content_type_hdr(_m) < 0) { LOG(L_ERR, "parse_hfs(): Can't parse Content-Type\n"); return -12; } } return 0; } static inline void generate_etag(dbid_t dst, presentity_t *p) { generate_dbid_ptr(dst, p); } static void add_expires_to_rpl(struct sip_msg *_m, int expires) { char tmp[64]; if (expires < 0) expires = 0; sprintf(tmp, "Expires: %d\r\n", expires); if (!add_lump_rpl(_m, tmp, strlen(tmp), LUMP_RPL_HDR)) { LOG(L_ERR, "Can't add expires header to the response\n"); } } static void add_etag_to_rpl(struct sip_msg *_m, str *etag) { char *tmp; tmp = (char*)pkg_malloc(32 + etag->len); if (!tmp) { LOG(L_ERR, "Can't allocate package memory for SIP-ETag header to the response\n"); return; } sprintf(tmp, "SIP-ETag: %.*s\r\n", etag->len, etag->s); if (!add_lump_rpl(_m, tmp, strlen(tmp), LUMP_RPL_HDR)) { LOG(L_ERR, "Can't add SIP-ETag header to the response\n"); /* return -1; */ } pkg_free(tmp); } /* ------------ publishing functions ------------ */ static void add_presentity_notes(presentity_t *presentity, presentity_info_t *p, str *etag, time_t expires) { presence_note_t *n; pa_presence_note_t *pan; if (!p) return; n = p->first_note; while (n) { pan = presence_note2pa(n, etag, expires); if (pan) add_pres_note(presentity, pan); n = n->next; } } static void add_extension_elements(presentity_t *presentity, presentity_info_t *p, str *etag, time_t expires) { extension_element_t *n; pa_extension_element_t *pan; if (!p) return; n = p->first_unknown_element; while (n) { pan = extension_element2pa(n, etag, expires); if (pan) add_extension_element(presentity, pan); n = n->next; } } static void add_published_tuples(presentity_t *presentity, presentity_info_t *p, str *etag, time_t expires) { presence_tuple_info_t *i; presence_tuple_t *t; if (!p) return; i = p->first_tuple; while (i) { t = presence_tuple_info2pa(i, etag, expires); if (t) add_presence_tuple(presentity, t); i = i->next; } } static int update_published_tuples(presentity_t *presentity, presentity_info_t *p, str *etag, time_t expires) { presence_tuple_info_t *i; presence_tuple_t *t, *tt; int found = 0; double mark = -149.386; if (!p) return 0; /* mark tuples as unprocessed */ t = get_first_tuple(presentity); while (t) { if (str_case_equals(&t->etag, etag) == 0) { t->data.priority = mark; found++; } t = get_next_tuple(t); } /* add previously not published tuples and update previously published */ i = p->first_tuple; while (i) { t = find_published_tuple(presentity, etag, &i->id); if (t) { /* the tuple was published this way */ found++; update_tuple(presentity, t, i, expires); } else { /* this tuple was not published => add it */ t = presence_tuple_info2pa(i, etag, expires); if (t) add_presence_tuple(presentity, t); } i = i->next; } /* remove previously published tuples which were not processed (not present now) */ t = get_first_tuple(presentity); while (t) { tt = get_next_tuple(t); if (t->data.priority == mark) { remove_presence_tuple(presentity, t); free_presence_tuple(t); } t = tt; } return found; } static int update_all_published_tuples(presentity_t *p, str *etag, time_t expires) { int found = 0; presence_tuple_t *tuple = get_first_tuple(p); while (tuple) { if (str_case_equals(&tuple->etag, etag) == 0) { tuple->expires = expires; found++; db_update_presence_tuple(p, tuple, 0); } tuple = get_next_tuple(tuple); } return found; } static int update_pres_notes(presentity_t *p, str *etag, time_t expires) { int found = 0; pa_presence_note_t *note = get_first_note(p); while (note) { if (str_case_equals(¬e->etag, etag) == 0) { note->expires = expires; found++; db_update_pres_note(p, note); } note = get_next_note(note); } return found; } static int update_extension_elements(presentity_t *p, str *etag, time_t expires) { int found = 0; pa_extension_element_t *e = (pa_extension_element_t *)p->data.first_unknown_element; while (e) { if (str_case_equals(&e->etag, etag) == 0) { e->expires = expires; db_update_extension_element(p, e); found++; } e = (pa_extension_element_t *)e->data.next; } return found; } int process_published_presentity_info(presentity_t *presentity, presentity_info_t *p, str *etag, time_t expires, int has_etag) { if (!has_etag) { if (!p) return -1; /* must be published something */ /* add all notes for presentity */ add_presentity_notes(presentity, p, etag, expires); /* add all tuples */ add_published_tuples(presentity, p, etag, expires); /* add all extension elements (RPID) */ add_extension_elements(presentity, p, etag, expires); } else { if (p) { /* remove all notes for this etag */ remove_pres_notes(presentity, etag); /* remove all extension elements (RPID) */ remove_extension_elements(presentity, etag); /* add all notes for presentity */ add_presentity_notes(presentity, p, etag, expires); update_published_tuples(presentity, p, etag, expires); add_extension_elements(presentity, p, etag, expires); } else { /* all expirations must be refreshed, nothing cleared */ update_all_published_tuples(presentity, etag, expires); update_extension_elements(presentity, etag, expires); update_pres_notes(presentity, etag, expires); } } presentity->flags |= PFLAG_PRESENCE_CHANGED; return 0; } /* ------------ PUBLISH handling functions ------------ */ static int publish_presence(struct sip_msg* _m, struct presentity* presentity) { char *body = get_body(_m); int body_len = 0; int msg_expires = default_expires; time_t expires = 0; str etag; dbid_t generated_etag; int has_etag; presentity_info_t *p = NULL; int content_type = -1; if (_m->content_type) content_type = get_content_type(_m); if (_m->content_length) body_len = get_content_length(_m); if (_m->expires) { if (_m->expires->parsed) { msg_expires = ((exp_body_t*)_m->expires->parsed)->val; } } if (msg_expires > max_publish_expiration) msg_expires = max_publish_expiration; if (msg_expires != 0) expires = msg_expires + act_time; if (_m->sipifmatch) { if (_m->sipifmatch->parsed) etag = *(str*)_m->sipifmatch->parsed; else str_clear(&etag); has_etag = 1; } else { /* ETag was not set, generate a new one */ generate_dbid(generated_etag); etag.len = dbid_strlen(generated_etag); etag.s = dbid_strptr(generated_etag); has_etag = 0; } if (body_len > 0) { switch (content_type) { case MIMETYPE(APPLICATION,PIDFXML): if (parse_pidf_document(&p, body, body_len) != 0) { LOG(L_ERR, "can't parse PIDF document\n"); paerrno = PA_UNSUPP_DOC; /* ? PA_PARSE_ERR */ } break; case MIMETYPE(APPLICATION,CPIM_PIDFXML): if (parse_cpim_pidf_document(&p, body, body_len) != 0) { LOG(L_ERR, "can't parse CPIM-PIDF document\n"); paerrno = PA_UNSUPP_DOC; } break; default: LOG(L_ERR, "unsupported Content-Type 0x%x for PUBLISH handling\n", content_type); paerrno = PA_UNSUPP_DOC; } if (paerrno != PA_OK) return -1; } if (process_published_presentity_info(presentity, p, &etag, expires, has_etag) == 0) { /* add header fields into response */ add_expires_to_rpl(_m, msg_expires); add_etag_to_rpl(_m, &etag); } if (p) free_presentity_info(p); return 0; } static int publish_presentity(struct sip_msg* _m, struct pdomain* _d, struct presentity* presentity) { event_t *parsed_event = NULL; int event_package = EVENT_OTHER; str callid = STR_STATIC_INIT("???"); int res; if (_m->event) parsed_event = (event_t *)_m->event->parsed; if (parsed_event) event_package = parsed_event->parsed; LOG(L_DBG, "publish_presentity: event_package=%d -1-\n", event_package); switch (event_package) { case EVENT_PRESENCE: res = publish_presence(_m, presentity); break; default: if (_m->callid) callid = _m->callid->body; LOG(L_WARN, "publish_presentity: no handler for event_package=%d" " callid=%.*s\n", event_package, callid.len, ZSW(callid.s)); paerrno = PA_EVENT_UNSUPP; res = -1; } return res; } /* * Handle a publish Request */ int handle_publish(struct sip_msg* _m, char* _domain, char* _s2) { struct pdomain* d; struct presentity *p; str p_uri = STR_NULL; str uid = STR_NULL; xcap_query_params_t xcap_params; get_act_time(); paerrno = PA_OK; if (parse_publish_hfs(_m) < 0) { LOG(L_ERR, "handle_publish(): Error while parsing message header\n"); goto error; } d = (struct pdomain*)_domain; if (get_pres_uri(_m, &p_uri) < 0 || p_uri.s == NULL || p_uri.len == 0) { LOG(L_ERR, "handle_publish(): Error while extracting presentity URI\n"); goto error; } if (get_presentity_uid(&uid, _m) < 0) { ERR("Error while extracting presentity UID\n"); goto error; } lock_pdomain(d); if (find_presentity_uid(d, &uid, &p) > 0) { memset(&xcap_params, 0, sizeof(xcap_params)); if (fill_xcap_params) fill_xcap_params(_m, &xcap_params); if (new_presentity(d, &p_uri, &uid, &xcap_params, &p) < 0) { LOG(L_ERR, "handle_publish can't create presentity\n"); goto error2; } } /* update presentity event state */ if (p) publish_presentity(_m, d, p); unlock_pdomain(d); if (send_reply(_m) < 0) return -1; return 1; error2: unlock_pdomain(d); error: send_reply(_m); return 0; }