/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program 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. * * This program 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define REFRESH_TIMER 1 #define DELAY_BEFORE_REFRESH PJSIP_PUBLISHC_DELAY_BEFORE_REFRESH #define THIS_FILE "publishc.c" /* Let's define this enum, so that it'll trigger compilation error * when somebody define the same enum in sip_msg.h */ enum { PJSIP_PUBLISH_METHOD = PJSIP_OTHER_METHOD, }; const pjsip_method pjsip_publish_method = { (pjsip_method_e)PJSIP_PUBLISH_METHOD, { "PUBLISH", 7 } }; /** * Pending request list. */ typedef struct pending_publish { PJ_DECL_LIST_MEMBER(struct pending_publish); pjsip_tx_data *tdata; } pending_publish; /** * SIP client publication structure. */ struct pjsip_publishc { pj_pool_t *pool; pjsip_endpoint *endpt; pj_bool_t _delete_flag; int pending_tsx; pj_bool_t in_callback; pj_mutex_t *mutex; pjsip_publishc_opt opt; void *token; pjsip_publishc_cb *cb; pj_str_t event; pj_str_t str_target_uri; pjsip_uri *target_uri; pjsip_cid_hdr *cid_hdr; pjsip_cseq_hdr *cseq_hdr; pj_str_t from_uri; pjsip_from_hdr *from_hdr; pjsip_to_hdr *to_hdr; pj_str_t etag; pjsip_expires_hdr *expires_hdr; pj_uint32_t expires; pjsip_route_hdr route_set; pjsip_hdr usr_hdr; pjsip_host_port via_addr; const void *via_tp; /* Authorization sessions. */ pjsip_auth_clt_sess auth_sess; /* Auto refresh publication. */ pj_bool_t auto_refresh; pj_time_val last_refresh; pj_time_val next_refresh; pj_timer_entry timer; /* Pending PUBLISH request */ pending_publish pending_reqs; pending_publish pending_reqs_empty; }; PJ_DEF(void) pjsip_publishc_opt_default(pjsip_publishc_opt *opt) { pj_bzero(opt, sizeof(*opt)); opt->queue_request = PJSIP_PUBLISHC_QUEUE_REQUEST; } /* * Initialize client publication module. */ PJ_DEF(pj_status_t) pjsip_publishc_init_module(pjsip_endpoint *endpt) { /* Note: Commented out the capability registration below, since it's wrong to include PUBLISH in Allow header of INVITE requests/ responses. 13.2.1 Creating the Initial INVITE An Allow header field (Section 20.5) SHOULD be present in the INVITE. It indicates what methods can be invoked within a dialog 20.5 Allow The Allow header field lists the set of methods supported by the UA generating the message. While the semantic of Allow header in non-dialog requests is unclear, it's probably best not to include PUBLISH in Allow header for now until we can find out how to customize the inclusion of methods in Allow header for in-dialog vs out-dialog requests. return pjsip_endpt_add_capability( endpt, NULL, PJSIP_H_ALLOW, NULL, 1, &pjsip_publish_method.name); */ PJ_UNUSED_ARG(endpt); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjsip_publishc_create( pjsip_endpoint *endpt, const pjsip_publishc_opt *opt, void *token, pjsip_publishc_cb *cb, pjsip_publishc **p_pubc) { pj_pool_t *pool; pjsip_publishc *pubc; pjsip_publishc_opt default_opt; pj_status_t status; /* Verify arguments. */ PJ_ASSERT_RETURN(endpt && cb && p_pubc, PJ_EINVAL); pool = pjsip_endpt_create_pool(endpt, "pubc%p", 1024, 1024); PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); pubc = PJ_POOL_ZALLOC_T(pool, pjsip_publishc); pubc->pool = pool; pubc->endpt = endpt; pubc->token = token; pubc->cb = cb; pubc->expires = PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED; if (!opt) { pjsip_publishc_opt_default(&default_opt); opt = &default_opt; } pj_memcpy(&pubc->opt, opt, sizeof(*opt)); pj_list_init(&pubc->pending_reqs); pj_list_init(&pubc->pending_reqs_empty); status = pj_mutex_create_recursive(pubc->pool, "pubc%p", &pubc->mutex); if (status != PJ_SUCCESS) { pj_pool_release(pool); return status; } status = pjsip_auth_clt_init(&pubc->auth_sess, endpt, pubc->pool, 0); if (status != PJ_SUCCESS) { pj_mutex_destroy(pubc->mutex); pj_pool_release(pool); return status; } pj_list_init(&pubc->route_set); pj_list_init(&pubc->usr_hdr); /* Done */ *p_pubc = pubc; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjsip_publishc_destroy(pjsip_publishc *pubc) { PJ_ASSERT_RETURN(pubc, PJ_EINVAL); if (pubc->pending_tsx || pubc->in_callback) { pubc->_delete_flag = 1; pubc->cb = NULL; } else { /* Cancel existing timer, if any */ if (pubc->timer.id != 0) { pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer); pubc->timer.id = 0; } if (pubc->mutex) pj_mutex_destroy(pubc->mutex); pjsip_auth_clt_deinit(&pubc->auth_sess); pjsip_endpt_release_pool(pubc->endpt, pubc->pool); } return PJ_SUCCESS; } PJ_DEF(pj_pool_t*) pjsip_publishc_get_pool(pjsip_publishc *pubc) { return pubc->pool; } static void set_expires( pjsip_publishc *pubc, pj_uint32_t expires) { if (expires != pubc->expires && expires != PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED) { pubc->expires_hdr = pjsip_expires_hdr_create(pubc->pool, expires); } else { pubc->expires_hdr = NULL; } } PJ_DEF(pj_status_t) pjsip_publishc_init(pjsip_publishc *pubc, const pj_str_t *event, const pj_str_t *target_uri, const pj_str_t *from_uri, const pj_str_t *to_uri, pj_uint32_t expires) { pj_str_t tmp; PJ_ASSERT_RETURN(pubc && event && target_uri && from_uri && to_uri && expires, PJ_EINVAL); /* Copy event type */ pj_strdup_with_null(pubc->pool, &pubc->event, event); /* Copy server URL. */ pj_strdup_with_null(pubc->pool, &pubc->str_target_uri, target_uri); /* Set server URL. */ tmp = pubc->str_target_uri; pubc->target_uri = pjsip_parse_uri( pubc->pool, tmp.ptr, tmp.slen, 0); if (pubc->target_uri == NULL) { return PJSIP_EINVALIDURI; } /* Set "From" header. */ pj_strdup_with_null(pubc->pool, &pubc->from_uri, from_uri); tmp = pubc->from_uri; pubc->from_hdr = pjsip_from_hdr_create(pubc->pool); pubc->from_hdr->uri = pjsip_parse_uri(pubc->pool, tmp.ptr, tmp.slen, PJSIP_PARSE_URI_AS_NAMEADDR); if (!pubc->from_hdr->uri) { return PJSIP_EINVALIDURI; } /* Set "To" header. */ pj_strdup_with_null(pubc->pool, &tmp, to_uri); pubc->to_hdr = pjsip_to_hdr_create(pubc->pool); pubc->to_hdr->uri = pjsip_parse_uri(pubc->pool, tmp.ptr, tmp.slen, PJSIP_PARSE_URI_AS_NAMEADDR); if (!pubc->to_hdr->uri) { return PJSIP_EINVALIDURI; } /* Set "Expires" header, if required. */ set_expires( pubc, expires); /* Set "Call-ID" header. */ pubc->cid_hdr = pjsip_cid_hdr_create(pubc->pool); pj_create_unique_string(pubc->pool, &pubc->cid_hdr->id); /* Set "CSeq" header. */ pubc->cseq_hdr = pjsip_cseq_hdr_create(pubc->pool); pubc->cseq_hdr->cseq = pj_rand() % 0xFFFF; pjsip_method_set( &pubc->cseq_hdr->method, PJSIP_REGISTER_METHOD); /* Done. */ return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjsip_publishc_set_credentials( pjsip_publishc *pubc, int count, const pjsip_cred_info cred[] ) { PJ_ASSERT_RETURN(pubc && count && cred, PJ_EINVAL); return pjsip_auth_clt_set_credentials(&pubc->auth_sess, count, cred); } PJ_DEF(pj_status_t) pjsip_publishc_set_route_set( pjsip_publishc *pubc, const pjsip_route_hdr *route_set) { const pjsip_route_hdr *chdr; PJ_ASSERT_RETURN(pubc && route_set, PJ_EINVAL); pj_list_init(&pubc->route_set); chdr = route_set->next; while (chdr != route_set) { pj_list_push_back(&pubc->route_set, pjsip_hdr_clone(pubc->pool, chdr)); chdr = chdr->next; } return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjsip_publishc_set_headers( pjsip_publishc *pubc, const pjsip_hdr *hdr_list) { const pjsip_hdr *h; PJ_ASSERT_RETURN(pubc && hdr_list, PJ_EINVAL); pj_list_init(&pubc->usr_hdr); h = hdr_list->next; while (h != hdr_list) { pj_list_push_back(&pubc->usr_hdr, pjsip_hdr_clone(pubc->pool, h)); h = h->next; } return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjsip_publishc_set_via_sent_by(pjsip_publishc *pubc, pjsip_host_port *via_addr, pjsip_transport *via_tp) { PJ_ASSERT_RETURN(pubc, PJ_EINVAL); if (!via_addr) pj_bzero(&pubc->via_addr, sizeof(pubc->via_addr)); else { if (pj_strcmp(&pubc->via_addr.host, &via_addr->host)) pj_strdup(pubc->pool, &pubc->via_addr.host, &via_addr->host); pubc->via_addr.port = via_addr->port; } pubc->via_tp = via_tp; return PJ_SUCCESS; } static pj_status_t create_request(pjsip_publishc *pubc, pjsip_tx_data **p_tdata) { const pj_str_t STR_EVENT = { "Event", 5 }; pj_status_t status; pjsip_generic_string_hdr *hdr; pjsip_tx_data *tdata; PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL); /* Create the request. */ status = pjsip_endpt_create_request_from_hdr( pubc->endpt, &pjsip_publish_method, pubc->target_uri, pubc->from_hdr, pubc->to_hdr, NULL, pubc->cid_hdr, pubc->cseq_hdr->cseq, NULL, &tdata); if (status != PJ_SUCCESS) return status; /* Add cached authorization headers. */ pjsip_auth_clt_init_req( &pubc->auth_sess, tdata ); /* Add Route headers from route set, ideally after Via header */ if (!pj_list_empty(&pubc->route_set)) { pjsip_hdr *route_pos; const pjsip_route_hdr *route; route_pos = (pjsip_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); if (!route_pos) route_pos = &tdata->msg->hdr; route = pubc->route_set.next; while (route != &pubc->route_set) { pjsip_hdr *new_hdr = (pjsip_hdr*) pjsip_hdr_shallow_clone(tdata->pool, route); pj_list_insert_after(route_pos, new_hdr); route_pos = new_hdr; route = route->next; } } /* Add Event header */ hdr = pjsip_generic_string_hdr_create(tdata->pool, &STR_EVENT, &pubc->event); if (hdr) pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr); /* Add SIP-If-Match if we have etag */ if (pubc->etag.slen) { const pj_str_t STR_HNAME = { "SIP-If-Match", 12 }; hdr = pjsip_generic_string_hdr_create(tdata->pool, &STR_HNAME, &pubc->etag); if (hdr) pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr); } /* Add user headers */ if (!pj_list_empty(&pubc->usr_hdr)) { const pjsip_hdr *uhdr; uhdr = pubc->usr_hdr.next; while (uhdr != &pubc->usr_hdr) { pjsip_hdr *new_hdr = (pjsip_hdr*) pjsip_hdr_shallow_clone(tdata->pool, uhdr); pjsip_msg_add_hdr(tdata->msg, new_hdr); uhdr = uhdr->next; } } /* Done. */ *p_tdata = tdata; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjsip_publishc_publish(pjsip_publishc *pubc, pj_bool_t auto_refresh, pjsip_tx_data **p_tdata) { pj_status_t status; pjsip_tx_data *tdata; PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL); status = create_request(pubc, &tdata); if (status != PJ_SUCCESS) return status; /* Add Expires header */ if (pubc->expires_hdr) { pjsip_hdr *dup; dup = (pjsip_hdr*) pjsip_hdr_shallow_clone(tdata->pool, pubc->expires_hdr); if (dup) pjsip_msg_add_hdr(tdata->msg, dup); } /* Cancel existing timer */ if (pubc->timer.id != 0) { pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer); pubc->timer.id = 0; } pubc->auto_refresh = auto_refresh; /* Done */ *p_tdata = tdata; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjsip_publishc_unpublish(pjsip_publishc *pubc, pjsip_tx_data **p_tdata) { pjsip_tx_data *tdata; pjsip_msg *msg; pjsip_expires_hdr *expires; pj_status_t status; PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL); if (pubc->timer.id != 0) { pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer); pubc->timer.id = 0; } status = create_request(pubc, &tdata); if (status != PJ_SUCCESS) return status; msg = tdata->msg; /* Add Expires:0 header */ expires = pjsip_expires_hdr_create(tdata->pool, 0); pjsip_msg_add_hdr( msg, (pjsip_hdr*)expires); *p_tdata = tdata; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjsip_publishc_update_expires( pjsip_publishc *pubc, pj_uint32_t expires ) { PJ_ASSERT_RETURN(pubc, PJ_EINVAL); set_expires( pubc, expires ); return PJ_SUCCESS; } static void call_callback(pjsip_publishc *pubc, pj_status_t status, int st_code, const pj_str_t *reason, pjsip_rx_data *rdata, pj_uint32_t expiration) { struct pjsip_publishc_cbparam cbparam; cbparam.pubc = pubc; cbparam.token = pubc->token; cbparam.status = status; cbparam.code = st_code; cbparam.reason = *reason; cbparam.rdata = rdata; cbparam.expiration = expiration; (*pubc->cb)(&cbparam); } static void pubc_refresh_timer_cb( pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) { pjsip_publishc *pubc = (pjsip_publishc*) entry->user_data; pjsip_tx_data *tdata; pj_status_t status; PJ_UNUSED_ARG(timer_heap); entry->id = 0; status = pjsip_publishc_publish(pubc, 1, &tdata); if (status != PJ_SUCCESS) { char errmsg[PJ_ERR_MSG_SIZE]; pj_str_t reason = pj_strerror(status, errmsg, sizeof(errmsg)); call_callback(pubc, status, 400, &reason, NULL, PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED); return; } status = pjsip_publishc_send(pubc, tdata); /* No need to call callback as it should have been called */ } static void tsx_callback(void *token, pjsip_event *event) { pj_status_t status; pjsip_publishc *pubc = (pjsip_publishc*) token; pjsip_transaction *tsx = event->body.tsx_state.tsx; /* Decrement pending transaction counter. */ pj_assert(pubc->pending_tsx > 0); --pubc->pending_tsx; /* Mark that we're in callback to prevent deletion (#1164) */ ++pubc->in_callback; /* If publication data has been deleted by user then remove publication * data from transaction's callback, and don't call callback. */ if (pubc->_delete_flag) { /* Nothing to do */ ; } else if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED || tsx->status_code == PJSIP_SC_UNAUTHORIZED) { pjsip_rx_data *rdata = event->body.tsx_state.src.rdata; pjsip_tx_data *tdata; status = pjsip_auth_clt_reinit_req( &pubc->auth_sess, rdata, tsx->last_tx, &tdata); if (status != PJ_SUCCESS) { call_callback(pubc, status, tsx->status_code, &rdata->msg_info.msg->line.status.reason, rdata, PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED); } else { status = pjsip_publishc_send(pubc, tdata); } } else { pjsip_rx_data *rdata; pj_uint32_t expiration = PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED; if (tsx->status_code/100 == 2) { pjsip_msg *msg; pjsip_expires_hdr *expires; pjsip_generic_string_hdr *etag_hdr; const pj_str_t STR_ETAG = { "SIP-ETag", 8 }; rdata = event->body.tsx_state.src.rdata; msg = rdata->msg_info.msg; /* Save ETag value */ etag_hdr = (pjsip_generic_string_hdr*) pjsip_msg_find_hdr_by_name(msg, &STR_ETAG, NULL); if (etag_hdr) { pj_strdup(pubc->pool, &pubc->etag, &etag_hdr->hvalue); } else { pubc->etag.slen = 0; } /* Update expires value */ expires = (pjsip_expires_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); if (pubc->auto_refresh && expires) expiration = expires->ivalue; if (pubc->auto_refresh && expiration!=0 && expiration!=PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED) { pj_time_val delay = { 0, 0}; /* Cancel existing timer, if any */ if (pubc->timer.id != 0) { pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer); pubc->timer.id = 0; } delay.sec = expiration - DELAY_BEFORE_REFRESH; if (pubc->expires != PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED && delay.sec > (pj_int32_t)pubc->expires) { delay.sec = pubc->expires; } if (delay.sec < DELAY_BEFORE_REFRESH) delay.sec = DELAY_BEFORE_REFRESH; pubc->timer.cb = &pubc_refresh_timer_cb; pubc->timer.id = REFRESH_TIMER; pubc->timer.user_data = pubc; pjsip_endpt_schedule_timer( pubc->endpt, &pubc->timer, &delay); pj_gettimeofday(&pubc->last_refresh); pubc->next_refresh = pubc->last_refresh; pubc->next_refresh.sec += delay.sec; } } else { rdata = (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ? event->body.tsx_state.src.rdata : NULL; } /* Call callback. */ // if (expiration == 0xFFFF) expiration = -1; /* Temporarily increment pending_tsx to prevent callback from * destroying pubc. */ ++pubc->pending_tsx; call_callback(pubc, PJ_SUCCESS, tsx->status_code, (rdata ? &rdata->msg_info.msg->line.status.reason : pjsip_get_status_text(tsx->status_code)), rdata, expiration); --pubc->pending_tsx; /* If we have pending request(s), send them now */ pj_mutex_lock(pubc->mutex); while (!pj_list_empty(&pubc->pending_reqs)) { pending_publish *pp = pubc->pending_reqs.next; pjsip_tx_data *tdata = pp->tdata; /* Remove the request from pending request list, * and keep the unused entry into pending_reqs_empty pool. */ pj_list_erase(pp); pj_list_push_back(&pubc->pending_reqs_empty, pp); /* Add SIP-If-Match if we have etag and the request doesn't have * one (http://trac.pjsip.org/repos/ticket/996) */ if (pubc->etag.slen) { const pj_str_t STR_HNAME = { "SIP-If-Match", 12 }; pjsip_generic_string_hdr *sim_hdr; sim_hdr = (pjsip_generic_string_hdr*) pjsip_msg_find_hdr_by_name(tdata->msg, &STR_HNAME, NULL); if (!sim_hdr) { /* Create the header */ sim_hdr = pjsip_generic_string_hdr_create(tdata->pool, &STR_HNAME, &pubc->etag); pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)sim_hdr); } else { /* Update */ if (pj_strcmp(&pubc->etag, &sim_hdr->hvalue)) pj_strdup(tdata->pool, &sim_hdr->hvalue, &pubc->etag); } } status = pjsip_publishc_send(pubc, tdata); if (status == PJ_EPENDING) { pj_assert(!"Not expected"); pjsip_tx_data_dec_ref(tdata); } else if (status == PJ_SUCCESS) { break; } } pj_mutex_unlock(pubc->mutex); } /* No longer in callback. */ --pubc->in_callback; /* Delete the record if user destroy pubc during the callback. */ if (pubc->_delete_flag && pubc->pending_tsx==0) { pjsip_publishc_destroy(pubc); } } PJ_DEF(pj_status_t) pjsip_publishc_send(pjsip_publishc *pubc, pjsip_tx_data *tdata) { pj_status_t status; pjsip_cseq_hdr *cseq_hdr; pj_uint32_t cseq; PJ_ASSERT_RETURN(pubc && tdata, PJ_EINVAL); /* Make sure we don't have pending transaction. */ pj_mutex_lock(pubc->mutex); if (pubc->pending_tsx) { if (pubc->opt.queue_request) { pending_publish *pp = NULL; if (pj_list_empty(&pubc->pending_reqs_empty)) { pp = PJ_POOL_ZALLOC_T(pubc->pool, pending_publish); } else { pp = pubc->pending_reqs_empty.next; pj_list_erase(pp); } pp->tdata = tdata; pj_list_push_back(&pubc->pending_reqs, pp); pj_mutex_unlock(pubc->mutex); PJ_LOG(4,(THIS_FILE, "Request is queued, pubc has another " "transaction pending")); return PJ_EPENDING; } else { pjsip_tx_data_dec_ref(tdata); pj_mutex_unlock(pubc->mutex); PJ_LOG(4,(THIS_FILE, "Unable to send request, pubc has another " "transaction pending")); return PJ_EBUSY; } } pj_mutex_unlock(pubc->mutex); /* If via_addr is set, use this address for the Via header. */ if (pubc->via_addr.host.slen > 0) { tdata->via_addr = pubc->via_addr; tdata->via_tp = pubc->via_tp; } /* Invalidate message buffer. */ pjsip_tx_data_invalidate_msg(tdata); /* Increment CSeq */ cseq = ++pubc->cseq_hdr->cseq; cseq_hdr = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); cseq_hdr->cseq = cseq; /* Increment pending transaction first, since transaction callback * may be called even before send_request() returns! */ ++pubc->pending_tsx; status = pjsip_endpt_send_request(pubc->endpt, tdata, -1, pubc, &tsx_callback); if (status!=PJ_SUCCESS) { // no need to decrement, callback has been called and it should // already decremented pending_tsx. Decrementing this here may // cause accessing freed memory location. //--pubc->pending_tsx; PJ_PERROR(4,(THIS_FILE, status, "Error sending request")); } return status; }