1 /*
2 * This file is part of the Sofia-SIP package
3 *
4 * Copyright (C) 2006 Nokia Corporation.
5 *
6 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation; either version 2.1 of
11 * the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 * 02110-1301 USA
22 *
23 */
24
25 /**@CFILE nua_publish.c
26 * @brief PUBLISH and publications
27 *
28 * @sa @RFC3903
29 *
30 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
31 *
32 * @date Created: Wed Mar 8 17:01:32 EET 2006 ppessi
33 */
34
35 #include "config.h"
36
37 #include <stddef.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <limits.h>
41
42 #include <assert.h>
43
44 #include <sofia-sip/su_string.h>
45 #include <sofia-sip/sip_protos.h>
46 #include <sofia-sip/sip_status.h>
47
48 #include "nua_stack.h"
49
50 /* ====================================================================== */
51 /* Publish usage */
52
53 struct publish_usage {
54 sip_etag_t *pu_etag;
55 int pu_published;
56 };
57
58 static char const *nua_publish_usage_name(nua_dialog_usage_t const *du);
59 static int nua_publish_usage_add(nua_handle_t *nh,
60 nua_dialog_state_t *ds,
61 nua_dialog_usage_t *du);
62 static void nua_publish_usage_remove(nua_handle_t *nh,
63 nua_dialog_state_t *ds,
64 nua_dialog_usage_t *du,
65 nua_client_request_t *cr,
66 nua_server_request_t *sr);
67 static void nua_publish_usage_refresh(nua_handle_t *nh,
68 nua_dialog_state_t *ds,
69 nua_dialog_usage_t *du,
70 sip_time_t now);
71 static int nua_publish_usage_shutdown(nua_handle_t *nh,
72 nua_dialog_state_t *ds,
73 nua_dialog_usage_t *du);
74
75 static nua_usage_class const nua_publish_usage[1] = {
76 {
77 sizeof (struct publish_usage),
78 sizeof nua_publish_usage,
79 nua_publish_usage_add,
80 nua_publish_usage_remove,
81 nua_publish_usage_name,
82 nua_base_usage_update_params,
83 NULL,
84 nua_publish_usage_refresh,
85 nua_publish_usage_shutdown,
86 }};
87
88 static
nua_publish_usage_name(nua_dialog_usage_t const * du)89 char const *nua_publish_usage_name(nua_dialog_usage_t const *du)
90 {
91 return "publish";
92 }
93
94 static
nua_publish_usage_add(nua_handle_t * nh,nua_dialog_state_t * ds,nua_dialog_usage_t * du)95 int nua_publish_usage_add(nua_handle_t *nh,
96 nua_dialog_state_t *ds,
97 nua_dialog_usage_t *du)
98 {
99 if (ds->ds_has_publish)
100 return -1; /* There can be only one */
101 ds->ds_has_publish = 1;
102 return 0;
103 }
104
105 static
nua_publish_usage_remove(nua_handle_t * nh,nua_dialog_state_t * ds,nua_dialog_usage_t * du,nua_client_request_t * cr,nua_server_request_t * sr)106 void nua_publish_usage_remove(nua_handle_t *nh,
107 nua_dialog_state_t *ds,
108 nua_dialog_usage_t *du,
109 nua_client_request_t *cr,
110 nua_server_request_t *sr
111 )
112 {
113 struct publish_usage *pu = NUA_DIALOG_USAGE_PRIVATE(du);
114
115 su_free(nh->nh_home, pu->pu_etag);
116
117 ds->ds_has_publish = 0; /* There can be only one */
118 }
119
120 /* ======================================================================== */
121 /* PUBLISH */
122
123 /**@fn \
124 * void nua_publish(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...);
125 *
126 * Send PUBLISH request to publication server.
127 *
128 * Request status will be delivered to the application using #nua_r_publish
129 * event. When successful the publication will be updated periodically until
130 * nua_unpublish() is called or handle is destroyed. Note that the periodic
131 * updates and unpublish do not include the original message body nor the @b
132 * Content-Type header. Instead, the periodic update will include the
133 * @SIPIfMatch header, which was generated from the latest @SIPETag
134 * header received in response to @b PUBLISH request.
135 *
136 * The handle used for publication cannot be used for any other purposes.
137 *
138 * @param nh Pointer to operation handle
139 * @param tag, value, ... List of tagged parameters
140 *
141 * @return
142 * nothing
143 *
144 * @par Related Tags:
145 * NUTAG_URL() \n
146 * Tags of nua_set_hparams() \n
147 * Header tags defined in <sofia-sip/sip_tag.h>
148 *
149 * @par Events:
150 * #nua_r_publish
151 *
152 * @sa #nua_r_publish, @RFC3903, @SIPIfMatch,
153 * nua_unpublish(), #nua_r_unpublish, #nua_i_publish
154 */
155
156 /** @NUA_EVENT nua_r_publish
157 *
158 * Response to an outgoing PUBLISH.
159 *
160 * The PUBLISH request may be sent explicitly by nua_publish() or implicitly
161 * by NUA state machine.
162 *
163 * @param status status code of PUBLISH request
164 * (if the request is retried, @a status is 100, the @a
165 * sip->sip_status->st_status contain the real status code
166 * from the response message, e.g., 302, 401, or 407)
167 * @param phrase a short textual description of @a status code
168 * @param nh operation handle associated with the publication
169 * @param hmagic application context associated with the handle
170 * @param sip response to PUBLISH request or NULL upon an error
171 * (status code is in @a status and
172 * descriptive message in @a phrase parameters)
173 * @param tags empty
174 *
175 * @sa nua_publish(), @RFC3903, @SIPETag, @Expires,
176 * nua_unpublish(), #nua_r_unpublish, #nua_i_publish
177 *
178 * @END_NUA_EVENT
179 */
180
181 /**@fn \
182 void nua_unpublish(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...);
183 *
184 * Send un-PUBLISH request to publication server. Un-PUBLISH request is just
185 * a PUBLISH request with @Expires set to 0. It is possible to un-publish a
186 * publication not associated with the handle by providing correct ETag in
187 * SIPTAG_IF_MATCH() or SIPTAG_IF_MATCH_STR() tags.
188 *
189 * Response to the un-PUBLISH request will be delivered to the application
190 * using #nua_r_unpublish event.
191 *
192 * The handle used for publication cannot be used for any other purposes.
193 *
194 * @param nh Pointer to operation handle
195 * @param tag, value, ... List of tagged parameters
196 *
197 * @return
198 * nothing
199 *
200 * @par Related Tags:
201 * NUTAG_URL() \n
202 * SIPTAG_IF_MATCH(), SIPTAG_IF_MATCH_STR() \n
203 * SIPTAG_EVENT(), SIPTAG_EVENT_STR() \n
204 * Tags of nua_set_hparams() \n
205 * Other header tags defined in <sofia-sip/sip_tag.h> except SIPTAG_EXPIRES() or SIPTAG_EXPIRES_STR()
206 *
207 * @par Events:
208 * #nua_r_unpublish
209 *
210 * @sa #nua_r_unpublish, @RFC3903, @SIPIfMatch,
211 * #nua_i_publish, nua_publish(), #nua_r_publish
212 */
213
214 /** @NUA_EVENT nua_r_unpublish
215 *
216 * Response to an outgoing un-PUBLISH.
217 *
218 * @param status response status code
219 * (if the request is retried, @a status is 100, the @a
220 * sip->sip_status->st_status contain the real status code
221 * from the response message, e.g., 302, 401, or 407)
222 * @param phrase a short textual description of @a status code
223 * @param nh operation handle associated with the publication
224 * @param hmagic application context associated with the handle
225 * @param sip response to PUBLISH request or NULL upon an error
226 * (status code is in @a status and
227 * descriptive message in @a phrase parameters)
228 * @param tags empty
229 *
230 * @sa nua_unpublish(), @RFC3903, @SIPETag, @Expires,
231 * nua_publish(), #nua_r_publish, #nua_i_publish
232 *
233 * @END_NUA_EVENT
234 */
235
236 static int nua_publish_client_template(nua_client_request_t *cr,
237 msg_t **return_msg,
238 tagi_t const *tags);
239 static int nua_publish_client_init(nua_client_request_t *cr,
240 msg_t *, sip_t *,
241 tagi_t const *tags);
242 static int nua_publish_client_request(nua_client_request_t *cr,
243 msg_t *, sip_t *,
244 tagi_t const *tags);
245 static int nua_publish_client_check_restart(nua_client_request_t *cr,
246 int status, char const *phrase,
247 sip_t const *sip);
248 static int nua_publish_client_response(nua_client_request_t *cr,
249 int status, char const *phrase,
250 sip_t const *sip);
251
252 static nua_client_methods_t const nua_publish_client_methods = {
253 SIP_METHOD_PUBLISH, /* crm_method, crm_method_name */
254 0, /* crm_extra */
255 { /* crm_flags */
256 /* create_dialog */ 0,
257 /* in_dialog */ 0,
258 /* target refresh */ 0
259 },
260 nua_publish_client_template, /* crm_template */
261 nua_publish_client_init, /* crm_init */
262 nua_publish_client_request, /* crm_send */
263 nua_publish_client_check_restart, /* crm_check_restart */
264 nua_publish_client_response, /* crm_recv */
265 NULL, /* crm_preliminary */
266 NULL, /* crm_report */
267 NULL, /* crm_complete */
268 };
269
270 /**@internal Send PUBLISH. */
nua_stack_publish(nua_t * nua,nua_handle_t * nh,nua_event_t e,tagi_t const * tags)271 int nua_stack_publish(nua_t *nua,
272 nua_handle_t *nh,
273 nua_event_t e,
274 tagi_t const *tags)
275 {
276 return nua_client_create(nh, e, &nua_publish_client_methods, tags);
277 }
278
nua_publish_client_template(nua_client_request_t * cr,msg_t ** return_msg,tagi_t const * tags)279 static int nua_publish_client_template(nua_client_request_t *cr,
280 msg_t **return_msg,
281 tagi_t const *tags)
282 {
283 nua_dialog_usage_t *du;
284
285 if (cr->cr_event == nua_r_publish)
286 return 0;
287
288 du = nua_dialog_usage_get(cr->cr_owner->nh_ds, nua_publish_usage, NULL);
289 if (du && du->du_cr) {
290 if (nua_client_set_target(cr, du->du_cr->cr_target) < 0)
291 return -1;
292 *return_msg = msg_copy(du->du_cr->cr_msg);
293 return 1;
294 }
295
296 return 0;
297 }
298
nua_publish_client_init(nua_client_request_t * cr,msg_t * msg,sip_t * sip,tagi_t const * tags)299 static int nua_publish_client_init(nua_client_request_t *cr,
300 msg_t *msg, sip_t *sip,
301 tagi_t const *tags)
302 {
303 nua_handle_t *nh = cr->cr_owner;
304 nua_dialog_usage_t *du;
305 struct publish_usage *pu;
306
307 if (cr->cr_event == nua_r_publish) {
308 du = nua_dialog_usage_add(nh, nh->nh_ds, nua_publish_usage, NULL);
309 if (!du)
310 return -1;
311 pu = nua_dialog_usage_private(du);
312 pu->pu_published = 0;
313 if (sip->sip_if_match) {
314 pu->pu_etag = sip_etag_dup(nh->nh_home, sip->sip_if_match);
315 if (!pu->pu_etag)
316 return -1;
317 sip_header_remove(msg, sip, (sip_header_t *)sip->sip_if_match);
318 }
319 }
320 else
321 du = nua_dialog_usage_get(nh->nh_ds, nua_publish_usage, NULL);
322
323 cr->cr_usage = du;
324
325 return 0;
326 }
327
328 static
nua_publish_client_request(nua_client_request_t * cr,msg_t * msg,sip_t * sip,tagi_t const * tags)329 int nua_publish_client_request(nua_client_request_t *cr,
330 msg_t *msg, sip_t *sip,
331 tagi_t const *tags)
332 {
333 nua_dialog_usage_t *du = cr->cr_usage;
334 int un, done;
335 sip_etag_t const *etag = NULL;
336
337 un = cr->cr_terminating ||
338 cr->cr_event != nua_r_publish ||
339 (du && du->du_shutdown) ||
340 (sip->sip_expires && sip->sip_expires->ex_delta == 0);
341 nua_client_set_terminating(cr, un);
342 done = un;
343
344 if (du) {
345 struct publish_usage *pu = nua_dialog_usage_private(du);
346
347 if (nua_client_bind(cr, du) < 0)
348 return -1;
349 if (pu->pu_published)
350 done = 1;
351 etag = pu->pu_etag;
352 }
353
354 return nua_base_client_trequest(cr, msg, sip,
355 SIPTAG_IF_MATCH(etag),
356 TAG_IF(done, SIPTAG_PAYLOAD(NONE)),
357 TAG_IF(done, SIPTAG_CONTENT_TYPE(NONE)),
358 TAG_IF(un, SIPTAG_EXPIRES_STR("0")),
359 TAG_NEXT(tags));
360 }
361
nua_publish_client_check_restart(nua_client_request_t * cr,int status,char const * phrase,sip_t const * sip)362 static int nua_publish_client_check_restart(nua_client_request_t *cr,
363 int status, char const *phrase,
364 sip_t const *sip)
365 {
366 char const *restarting = NULL;
367
368 if (cr->cr_terminating || !cr->cr_usage)
369 ;
370 else if (status == 412)
371 restarting = phrase;
372 else if (200 <= status && status < 300 &&
373 sip->sip_expires && sip->sip_expires->ex_delta == 0)
374 restarting = "Immediate re-PUBLISH";
375
376 if (restarting) {
377 struct publish_usage *pu = nua_dialog_usage_private(cr->cr_usage);
378
379 if (pu) {
380 pu->pu_published = 0;
381 su_free(cr->cr_owner->nh_home, pu->pu_etag), pu->pu_etag = NULL;
382 if (nua_client_restart(cr, 100, restarting))
383 return 0;
384 }
385 }
386
387 return nua_base_client_check_restart(cr, status, phrase, sip);
388 }
389
nua_publish_client_response(nua_client_request_t * cr,int status,char const * phrase,sip_t const * sip)390 static int nua_publish_client_response(nua_client_request_t *cr,
391 int status, char const *phrase,
392 sip_t const *sip)
393 {
394 nua_handle_t *nh = cr->cr_owner;
395 nua_dialog_usage_t *du = cr->cr_usage;
396
397 if (!cr->cr_terminated && du && sip) {
398 struct publish_usage *pu = nua_dialog_usage_private(du);
399 sip_expires_t const *ex = sip->sip_expires;
400
401 /* Reset state */
402 pu->pu_published = 0;
403 if (pu->pu_etag)
404 su_free(nh->nh_home, pu->pu_etag), pu->pu_etag = NULL;
405
406 if (status < 300) {
407 pu->pu_published = 1;
408 pu->pu_etag = sip_etag_dup(nh->nh_home, sip->sip_etag);
409
410 if (!ex || ex->ex_delta == 0 || !pu->pu_etag) {
411 cr->cr_terminated = 1;
412
413 if (!ex || ex->ex_delta == 0)
414 SET_STATUS(900, "Received Invalid Expiration Time");
415 else
416 SET_STATUS(900, _NUA_INTERNAL_ERROR_AT(__FILE__, __LINE__));
417 }
418 else
419 nua_dialog_usage_set_refresh(du, ex->ex_delta);
420 }
421 }
422
423 return nua_base_client_response(cr, status, phrase, sip, NULL);
424 }
425
nua_publish_usage_refresh(nua_handle_t * nh,nua_dialog_state_t * ds,nua_dialog_usage_t * du,sip_time_t now)426 static void nua_publish_usage_refresh(nua_handle_t *nh,
427 nua_dialog_state_t *ds,
428 nua_dialog_usage_t *du,
429 sip_time_t now)
430 {
431 nua_client_request_t *cr = du->du_cr;
432
433 if (cr) {
434 if (nua_client_resend_request(cr, 0) >= 0)
435 return;
436 }
437
438 nua_stack_event(nh->nh_nua, nh, NULL,
439 nua_r_publish, NUA_ERROR_AT(__FILE__, __LINE__),
440 NULL);
441
442 nua_dialog_usage_remove(nh, ds, du, NULL, NULL);
443 }
444
445 /** @interal Shut down PUBLISH usage.
446 *
447 * @retval >0 shutdown done
448 * @retval 0 shutdown in progress
449 * @retval <0 try again later
450 */
nua_publish_usage_shutdown(nua_handle_t * nh,nua_dialog_state_t * ds,nua_dialog_usage_t * du)451 static int nua_publish_usage_shutdown(nua_handle_t *nh,
452 nua_dialog_state_t *ds,
453 nua_dialog_usage_t *du)
454 {
455 nua_client_request_t *cr = du->du_cr;
456
457 if (cr) {
458 if (nua_client_resend_request(cr, 1) >= 0)
459 return 0;
460 }
461
462 /* XXX - report to user */
463 nua_dialog_usage_remove(nh, ds, du, NULL, NULL);
464 return 200;
465 }
466
467 /* ---------------------------------------------------------------------- */
468 /* Server side */
469
470 /** @NUA_EVENT nua_i_publish
471 *
472 * Incoming PUBLISH request.
473 *
474 * In order to receive #nua_i_publish events, the application must enable
475 * both the PUBLISH method with NUTAG_ALLOW() tag and the acceptable SIP
476 * events with nua_set_params() tag NUTAG_ALLOW_EVENTS().
477 *
478 * The nua_response() call responding to a PUBLISH request must have
479 * NUTAG_WITH() (or NUTAG_WITH_THIS()/NUTAG_WITH_SAVED()) tag. Note that
480 * a successful response to PUBLISH @b MUST include @Expires and @SIPETag
481 * headers.
482 *
483 * The PUBLISH request does not create a dialog. Currently the processing
484 * of incoming PUBLISH creates a new handle for each incoming request which
485 * is not assiciated with an existing dialog. If the handle @a nh is not
486 * bound, you should probably destroy it after responding to the PUBLISH
487 * request.
488 *
489 * @param status status code of response sent automatically by stack
490 * @param phrase a short textual description of @a status code
491 * @param nh operation handle associated with the incoming request
492 * @param hmagic application context associated with the call
493 * (usually NULL)
494 * @param sip incoming PUBLISH request
495 * @param tags empty
496 *
497 * @sa @RFC3903, nua_respond(),
498 * @Expires, @SIPETag, @SIPIfMatch, @Event,
499 * nua_subscribe(), #nua_i_subscribe,
500 * nua_notifier(), #nua_i_subscription,
501 *
502 * @since First used in @VERSION_1_12_4
503 *
504 * @END_NUA_EVENT
505 */
506
507 int nua_publish_server_init(nua_server_request_t *sr);
508
509 nua_server_methods_t const nua_publish_server_methods =
510 {
511 SIP_METHOD_PUBLISH,
512 nua_i_publish, /* Event */
513 {
514 0, /* Do not create dialog */
515 0, /* Initial request */
516 0, /* Not a target refresh request */
517 1, /* Add Contact */
518 },
519 nua_publish_server_init,
520 nua_base_server_preprocess,
521 nua_base_server_params,
522 nua_base_server_respond,
523 nua_base_server_report,
524 };
525
nua_publish_server_init(nua_server_request_t * sr)526 int nua_publish_server_init(nua_server_request_t *sr)
527 {
528 sip_allow_events_t *allow_events = NH_PGET(sr->sr_owner, allow_events);
529 sip_event_t *o = sr->sr_request.sip->sip_event;
530 char const *event = o ? o->o_type : NULL;
531
532 if (!allow_events)
533 return SR_STATUS1(sr, SIP_501_NOT_IMPLEMENTED);
534 else if (!event || !msg_header_find_param(allow_events->k_common, event))
535 return SR_STATUS1(sr, SIP_489_BAD_EVENT);
536
537 return 0;
538 }
539