/src/pupnp/upnp/src/gena/gena_device.c
Line | Count | Source |
1 | | /******************************************************************************* |
2 | | * |
3 | | * Copyright (c) 2000-2003 Intel Corporation |
4 | | * All rights reserved. |
5 | | * Copyright (c) 2012 France Telecom All rights reserved. |
6 | | * |
7 | | * Redistribution and use in source and binary forms, with or without |
8 | | * modification, are permitted provided that the following conditions are met: |
9 | | * |
10 | | * - Redistributions of source code must retain the above copyright notice, |
11 | | * this list of conditions and the following disclaimer. |
12 | | * - Redistributions in binary form must reproduce the above copyright notice, |
13 | | * this list of conditions and the following disclaimer in the documentation |
14 | | * and/or other materials provided with the distribution. |
15 | | * - Neither name of Intel Corporation nor the names of its contributors |
16 | | * may be used to endorse or promote products derived from this software |
17 | | * without specific prior written permission. |
18 | | * |
19 | | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
20 | | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
21 | | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22 | | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR |
23 | | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
24 | | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
25 | | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
26 | | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
27 | | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
28 | | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
29 | | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
30 | | * |
31 | | ******************************************************************************/ |
32 | | |
33 | | /*! |
34 | | * \file |
35 | | */ |
36 | | |
37 | | #include "config.h" // IWYU pragma: keep - must be first; defines UPNP_USE_RWLOCK before ithread.h |
38 | | |
39 | | #include "Callback.h" |
40 | | #include "LinkedList.h" |
41 | | #include "ThreadPool.h" |
42 | | #include "UpnpGlobal.h" |
43 | | #include "UpnpInet.h" |
44 | | #include "UpnpSubscriptionRequest.h" |
45 | | #include "httpparser.h" |
46 | | #include "ithread.h" |
47 | | #include "ixml.h" |
48 | | #include "membuffer.h" |
49 | | #include "service_table.h" |
50 | | #include "sock.h" |
51 | | #include "upnp.h" |
52 | | #include "upnpdebug.h" |
53 | | #include "upnpdebug_internal.h" |
54 | | #include "uri.h" |
55 | | #include <stddef.h> |
56 | | #include <stdint.h> |
57 | | #include <stdio.h> |
58 | | #include <stdlib.h> |
59 | | #include <string.h> |
60 | | #include <sys/types.h> |
61 | | #include <time.h> |
62 | | |
63 | | #include "gena_device.h" |
64 | | |
65 | | #if EXCLUDE_GENA == 0 |
66 | | #ifdef INCLUDE_DEVICE_APIS |
67 | | |
68 | | #include <assert.h> |
69 | | |
70 | | #include "gena.h" |
71 | | #include "httpreadwrite.h" |
72 | | #include "posix_overwrites.h" // IWYU pragma: keep |
73 | | #include "ssdplib.h" |
74 | | #include "statcodes.h" |
75 | | #include "upnpapi.h" |
76 | | #include "uuid.h" |
77 | | |
78 | 0 | #define STALE_JOBID (INVALID_JOB_ID - 1) |
79 | | |
80 | | /*! |
81 | | * \brief Unregisters a device. |
82 | | * |
83 | | * \return UPNP_E_SUCCESS on success, GENA_E_BAD_HANDLE on failure. |
84 | | */ |
85 | | int genaUnregisterDevice( |
86 | | /*! [in] Device handle. */ |
87 | | UpnpDevice_Handle device_handle) |
88 | 0 | { |
89 | 0 | int ret = 0; |
90 | 0 | struct Handle_Info *handle_info; |
91 | |
|
92 | 0 | HandleLock(__FILE__, __LINE__); |
93 | 0 | if (GetHandleInfo(device_handle, &handle_info) != HND_DEVICE) { |
94 | 0 | UpnpPrintf(UPNP_CRITICAL, |
95 | 0 | GENA, |
96 | 0 | __FILE__, |
97 | 0 | __LINE__, |
98 | 0 | "genaUnregisterDevice: BAD Handle: %d\n", |
99 | 0 | device_handle); |
100 | 0 | ret = GENA_E_BAD_HANDLE; |
101 | 0 | } else { |
102 | 0 | freeServiceTable(&handle_info->ServiceTable); |
103 | 0 | ret = UPNP_E_SUCCESS; |
104 | 0 | } |
105 | 0 | HandleUnlock(__FILE__, __LINE__); |
106 | |
|
107 | 0 | return ret; |
108 | 0 | } |
109 | | |
110 | | /*! |
111 | | * \brief Generates XML property set for notifications. |
112 | | * |
113 | | * \return UPNP_E_SUCCESS if successful else returns GENA_E_BAD_HANDLE. |
114 | | * |
115 | | * \note The XML_VERSION comment is NOT sent due to interoperability issues |
116 | | * with other UPnP vendors. |
117 | | */ |
118 | | static int GeneratePropertySet( |
119 | | /*! [in] Array of variable names (go in the event notify). */ |
120 | | char **names, |
121 | | /*! [in] Array of variable values (go in the event notify). */ |
122 | | char **values, |
123 | | /*! [in] number of variables. */ |
124 | | int count, |
125 | | /*! [out] PropertySet node in the string format. */ |
126 | | DOMString *out) |
127 | 0 | { |
128 | 0 | char *buffer; |
129 | 0 | int counter = 0; |
130 | 0 | size_t size = 0; |
131 | | |
132 | | /*size += strlen(XML_VERSION);*/ |
133 | 0 | size += strlen(XML_PROPERTYSET_HEADER); |
134 | 0 | size += strlen("</e:propertyset>\n\n"); |
135 | 0 | for (counter = 0; counter < count; counter++) { |
136 | 0 | size += strlen("<e:property>\n</e:property>\n"); |
137 | 0 | size += 2 * strlen(names[counter]) + strlen(values[counter]) + |
138 | 0 | strlen("<></>\n"); |
139 | 0 | } |
140 | |
|
141 | 0 | buffer = (char *)malloc(size + 1); |
142 | 0 | if (buffer == NULL) |
143 | 0 | return UPNP_E_OUTOF_MEMORY; |
144 | 0 | memset(buffer, 0, size + 1); |
145 | | /* |
146 | | strcpy(buffer,XML_VERSION); |
147 | | strcat(buffer, XML_PROPERTYSET_HEADER); |
148 | | */ |
149 | 0 | strcpy(buffer, XML_PROPERTYSET_HEADER); |
150 | 0 | for (counter = 0; counter < count; counter++) { |
151 | 0 | strcat(buffer, "<e:property>\n"); |
152 | 0 | sprintf(&buffer[strlen(buffer)], |
153 | 0 | "<%s>%s</%s>\n</e:property>\n", |
154 | 0 | names[counter], |
155 | 0 | values[counter], |
156 | 0 | names[counter]); |
157 | 0 | } |
158 | 0 | strcat(buffer, "</e:propertyset>\n\n"); |
159 | 0 | *out = ixmlCloneDOMString(buffer); |
160 | 0 | free(buffer); |
161 | |
|
162 | 0 | return XML_SUCCESS; |
163 | 0 | } |
164 | | |
165 | | /*! |
166 | | * \brief Frees memory used in notify_threads if the reference count is 0, |
167 | | * otherwise decrements the refrence count. |
168 | | */ |
169 | | static void free_notify_struct( |
170 | | /*! [in] Notify structure. */ |
171 | | void *input) |
172 | 0 | { |
173 | 0 | notify_thread_struct *p = input; |
174 | |
|
175 | 0 | (*p->reference_count)--; |
176 | 0 | if (*p->reference_count == 0) { |
177 | 0 | free(p->headers); |
178 | 0 | ixmlFreeDOMString(p->propertySet); |
179 | 0 | free(p->servId); |
180 | 0 | free(p->UDN); |
181 | 0 | free(p->reference_count); |
182 | 0 | } |
183 | 0 | free(p); |
184 | 0 | } |
185 | | |
186 | | /*! |
187 | | * \brief Sends the notify message and returns a reply. |
188 | | * |
189 | | * \return on success returns UPNP_E_SUCCESS, otherwise returns a UPNP error. |
190 | | * |
191 | | * \note called by genaNotify |
192 | | */ |
193 | | static UPNP_INLINE int notify_send_and_recv( |
194 | | /*! [in] subscription callback URL (URL of the control point). */ |
195 | | uri_type *destination_url, |
196 | | /*! [in] Common HTTP headers. */ |
197 | | membuffer *mid_msg, |
198 | | /*! [in] The evented XML. */ |
199 | | char *propertySet, |
200 | | /*! [out] The response from the control point. */ |
201 | | http_parser_t *response) |
202 | 0 | { |
203 | 0 | uri_type url; |
204 | 0 | SOCKET conn_fd; |
205 | 0 | membuffer start_msg; |
206 | 0 | int ret_code; |
207 | 0 | int err_code; |
208 | 0 | int timeout; |
209 | 0 | SOCKINFO info; |
210 | 0 | const char *CRLF = "\r\n"; |
211 | | |
212 | | /* connect */ |
213 | 0 | UpnpPrintf(UPNP_ALL, |
214 | 0 | GENA, |
215 | 0 | __FILE__, |
216 | 0 | __LINE__, |
217 | 0 | "gena notify to: %.*s\n", |
218 | 0 | (int)destination_url->hostport.text.size, |
219 | 0 | destination_url->hostport.text.buff); |
220 | |
|
221 | 0 | conn_fd = http_Connect(destination_url, &url); |
222 | 0 | if (conn_fd < 0) |
223 | | /* return UPNP error */ |
224 | 0 | return UPNP_E_SOCKET_CONNECT; |
225 | 0 | ret_code = sock_init(&info, conn_fd); |
226 | 0 | if (ret_code) { |
227 | 0 | sock_destroy(&info, SD_BOTH); |
228 | 0 | return ret_code; |
229 | 0 | } |
230 | | /* make start line and HOST header */ |
231 | 0 | membuffer_init(&start_msg); |
232 | 0 | if (http_MakeMessage(&start_msg, |
233 | 0 | 1, |
234 | 0 | 1, |
235 | 0 | "q" |
236 | 0 | "s", |
237 | 0 | HTTPMETHOD_NOTIFY, |
238 | 0 | &url, |
239 | 0 | mid_msg->buf) != 0) { |
240 | 0 | membuffer_destroy(&start_msg); |
241 | 0 | sock_destroy(&info, SD_BOTH); |
242 | 0 | return UPNP_E_OUTOF_MEMORY; |
243 | 0 | } |
244 | 0 | timeout = GENA_NOTIFICATION_SENDING_TIMEOUT; |
245 | | /* send msg (note: end of notification will contain "\r\n" twice) */ |
246 | 0 | ret_code = http_SendMessage(&info, |
247 | 0 | &timeout, |
248 | 0 | "bbb", |
249 | 0 | start_msg.buf, |
250 | 0 | start_msg.length, |
251 | 0 | propertySet, |
252 | 0 | strlen(propertySet), |
253 | 0 | CRLF, |
254 | 0 | strlen(CRLF)); |
255 | 0 | if (ret_code) { |
256 | 0 | membuffer_destroy(&start_msg); |
257 | 0 | sock_destroy(&info, SD_BOTH); |
258 | 0 | return ret_code; |
259 | 0 | } |
260 | 0 | timeout = GENA_NOTIFICATION_ANSWERING_TIMEOUT; |
261 | 0 | ret_code = http_RecvMessage( |
262 | 0 | &info, response, HTTPMETHOD_NOTIFY, &timeout, &err_code); |
263 | 0 | if (ret_code) { |
264 | 0 | membuffer_destroy(&start_msg); |
265 | 0 | sock_destroy(&info, SD_BOTH); |
266 | 0 | httpmsg_destroy(&response->msg); |
267 | 0 | return ret_code; |
268 | 0 | } |
269 | | /* should shutdown completely when closing socket */ |
270 | 0 | sock_destroy(&info, SD_BOTH); |
271 | 0 | membuffer_destroy(&start_msg); |
272 | |
|
273 | 0 | return UPNP_E_SUCCESS; |
274 | 0 | } |
275 | | |
276 | | /*! |
277 | | * \brief Function to Notify a particular subscription of a particular event. |
278 | | * |
279 | | * In general the service should NOT be blocked around this call (this may |
280 | | * cause deadlock with a client). |
281 | | * |
282 | | * NOTIFY http request is sent and the reply is processed. |
283 | | * |
284 | | * \return GENA_SUCCESS if the event was delivered, otherwise returns the |
285 | | * appropriate error code. |
286 | | */ |
287 | | static int genaNotify( |
288 | | /*! [in] Null terminated, includes all headers (including \\r\\n) except |
289 | | SID and SEQ. */ |
290 | | char *headers, |
291 | | /*! [in] The evented XML. */ |
292 | | char *propertySet, |
293 | | /*! [in] subscription to be Notified, assumes this is valid for life of |
294 | | function. */ |
295 | | subscription *sub) |
296 | 0 | { |
297 | 0 | size_t i; |
298 | 0 | membuffer mid_msg; |
299 | 0 | uri_type *url; |
300 | 0 | http_parser_t response; |
301 | 0 | int return_code = -1; |
302 | |
|
303 | 0 | membuffer_init(&mid_msg); |
304 | 0 | if (http_MakeMessage(&mid_msg, |
305 | 0 | 1, |
306 | 0 | 1, |
307 | 0 | "s" |
308 | 0 | "ssc" |
309 | 0 | "sdcc", |
310 | 0 | headers, |
311 | 0 | "SID: ", |
312 | 0 | sub->sid, |
313 | 0 | "SEQ: ", |
314 | 0 | sub->ToSendEventKey) != 0) { |
315 | 0 | membuffer_destroy(&mid_msg); |
316 | 0 | return UPNP_E_OUTOF_MEMORY; |
317 | 0 | } |
318 | | /* send a notify to each url until one goes thru */ |
319 | 0 | for (i = 0; i < sub->DeliveryURLs.size; i++) { |
320 | 0 | url = &sub->DeliveryURLs.parsedURLs[i]; |
321 | 0 | return_code = notify_send_and_recv( |
322 | 0 | url, &mid_msg, propertySet, &response); |
323 | 0 | if (return_code == UPNP_E_SUCCESS) |
324 | 0 | break; |
325 | 0 | } |
326 | 0 | membuffer_destroy(&mid_msg); |
327 | 0 | if (return_code == UPNP_E_SUCCESS) { |
328 | 0 | if (response.msg.status_code == HTTP_OK) |
329 | 0 | return_code = GENA_SUCCESS; |
330 | 0 | else { |
331 | 0 | if (response.msg.status_code == |
332 | 0 | HTTP_PRECONDITION_FAILED) |
333 | | /*Invalid SID gets removed */ |
334 | 0 | return_code = |
335 | 0 | GENA_E_NOTIFY_UNACCEPTED_REMOVE_SUB; |
336 | 0 | else |
337 | 0 | return_code = GENA_E_NOTIFY_UNACCEPTED; |
338 | 0 | } |
339 | 0 | httpmsg_destroy(&response.msg); |
340 | 0 | } |
341 | |
|
342 | 0 | return return_code; |
343 | 0 | } |
344 | | |
345 | | /*! |
346 | | * \brief Thread job to Notify a control point. |
347 | | * |
348 | | * It validates the subscription and copies the subscription. Also make sure |
349 | | * that events are sent in order. |
350 | | * |
351 | | * \note calls the genaNotify to do the actual work. |
352 | | */ |
353 | | static void genaNotifyThread( |
354 | | /*! [in] notify thread structure containing all the headers and property |
355 | | set info. */ |
356 | | void *input) |
357 | 0 | { |
358 | 0 | subscription *sub; |
359 | 0 | service_info *service; |
360 | 0 | subscription sub_copy; |
361 | 0 | notify_thread_struct *in = (notify_thread_struct *)input; |
362 | 0 | int return_code; |
363 | 0 | struct Handle_Info *handle_info; |
364 | | |
365 | | /* This should be a HandleLock and not a HandleReadLock otherwise if |
366 | | * there is a lot of notifications, then multiple threads will acquire a |
367 | | * read lock and the thread which sends the notification will be blocked |
368 | | * forever on the HandleLock at the end of this function. */ |
369 | | /*HandleReadLock(__FILE__, __LINE__); */ |
370 | 0 | HandleLock(__FILE__, __LINE__); |
371 | | /* validate context */ |
372 | |
|
373 | 0 | if (GetHandleInfo(in->device_handle, &handle_info) != HND_DEVICE) { |
374 | 0 | free_notify_struct(in); |
375 | 0 | HandleUnlock(__FILE__, __LINE__); |
376 | 0 | return; |
377 | 0 | } |
378 | | |
379 | 0 | if (!(service = FindServiceId( |
380 | 0 | &handle_info->ServiceTable, in->servId, in->UDN)) || |
381 | 0 | !service->active || |
382 | 0 | !(sub = GetSubscriptionSID(in->sid, service)) || |
383 | 0 | copy_subscription(sub, &sub_copy) != HTTP_SUCCESS) { |
384 | 0 | free_notify_struct(in); |
385 | 0 | HandleUnlock(__FILE__, __LINE__); |
386 | 0 | return; |
387 | 0 | } |
388 | | |
389 | 0 | HandleUnlock(__FILE__, __LINE__); |
390 | | |
391 | | /* send the notify */ |
392 | 0 | return_code = genaNotify(in->headers, in->propertySet, &sub_copy); |
393 | 0 | freeSubscription(&sub_copy); |
394 | 0 | HandleLock(__FILE__, __LINE__); |
395 | 0 | if (GetHandleInfo(in->device_handle, &handle_info) != HND_DEVICE) { |
396 | 0 | free_notify_struct(in); |
397 | 0 | HandleUnlock(__FILE__, __LINE__); |
398 | 0 | return; |
399 | 0 | } |
400 | | /* validate context */ |
401 | 0 | if (!(service = FindServiceId( |
402 | 0 | &handle_info->ServiceTable, in->servId, in->UDN)) || |
403 | 0 | !service->active || |
404 | 0 | !(sub = GetSubscriptionSID(in->sid, service))) { |
405 | 0 | free_notify_struct(in); |
406 | 0 | HandleUnlock(__FILE__, __LINE__); |
407 | 0 | return; |
408 | 0 | } |
409 | 0 | sub->ToSendEventKey++; |
410 | 0 | if (sub->ToSendEventKey < 0) |
411 | | /* wrap to 1 for overflow */ |
412 | 0 | sub->ToSendEventKey = 1; |
413 | | |
414 | | /* Remove head of event queue. Possibly activate next */ |
415 | 0 | { |
416 | 0 | ListNode *node = ListHead(&sub->outgoing); |
417 | 0 | if (node) |
418 | 0 | ListDelNode(&sub->outgoing, node, 1); |
419 | 0 | if (ListSize(&sub->outgoing) > 0) { |
420 | 0 | ThreadPoolJob *job; |
421 | 0 | ListNode *node = ListHead(&sub->outgoing); |
422 | 0 | job = (ThreadPoolJob *)node->item; |
423 | | /* The new head of queue should not have already been |
424 | | added to the pool, else something is very wrong */ |
425 | 0 | assert(job->jobId != STALE_JOBID); |
426 | |
|
427 | 0 | ThreadPoolAdd(&gSendThreadPool, job, NULL); |
428 | 0 | job->jobId = STALE_JOBID; |
429 | 0 | } |
430 | 0 | } |
431 | |
|
432 | 0 | if (return_code == GENA_E_NOTIFY_UNACCEPTED_REMOVE_SUB) |
433 | 0 | RemoveSubscriptionSID(in->sid, service); |
434 | 0 | free_notify_struct(in); |
435 | |
|
436 | 0 | HandleUnlock(__FILE__, __LINE__); |
437 | 0 | } |
438 | | |
439 | | /*! |
440 | | * \brief Allocates the GENA header. |
441 | | * |
442 | | * \note The header must be destroyed after with a call to free(), otherwise |
443 | | * there will be a memory leak. |
444 | | * |
445 | | * \return The constructed header. |
446 | | */ |
447 | | static char *AllocGenaHeaders( |
448 | | /*! [in] The property set string. */ |
449 | | const DOMString propertySet) |
450 | 0 | { |
451 | 0 | static const char *HEADER_LINE_1 = |
452 | 0 | "Content-Type: text/xml; charset=\"utf-8\"\r\n"; |
453 | 0 | static const char *HEADER_LINE_2A = "Content-Length: "; |
454 | 0 | static const char *HEADER_LINE_2B = "\r\n"; |
455 | 0 | static const char *HEADER_LINE_3 = "NT: upnp:event\r\n"; |
456 | 0 | static const char *HEADER_LINE_4 = "NTS: upnp:propchange\r\n"; |
457 | 0 | char *headers = NULL; |
458 | 0 | size_t headers_size = 0; |
459 | 0 | int line = 0; |
460 | 0 | int rc = 0; |
461 | |
|
462 | 0 | headers_size = strlen(HEADER_LINE_1) + strlen(HEADER_LINE_2A) + |
463 | 0 | MAX_CONTENT_LENGTH + strlen(HEADER_LINE_2B) + |
464 | 0 | strlen(HEADER_LINE_3) + strlen(HEADER_LINE_4) + 1; |
465 | 0 | headers = (char *)malloc(headers_size); |
466 | 0 | if (headers == NULL) { |
467 | 0 | line = __LINE__; |
468 | 0 | goto ExitFunction; |
469 | 0 | } |
470 | 0 | rc = snprintf(headers, |
471 | 0 | headers_size, |
472 | 0 | "%s%s%" PRIzu "%s%s%s", |
473 | 0 | HEADER_LINE_1, |
474 | 0 | HEADER_LINE_2A, |
475 | 0 | strlen(propertySet) + 2, |
476 | 0 | HEADER_LINE_2B, |
477 | 0 | HEADER_LINE_3, |
478 | 0 | HEADER_LINE_4); |
479 | |
|
480 | 0 | ExitFunction: |
481 | 0 | if (headers == NULL || rc < 0 || (unsigned int)rc >= headers_size) { |
482 | 0 | UpnpPrintf(UPNP_ALL, |
483 | 0 | GENA, |
484 | 0 | __FILE__, |
485 | 0 | line, |
486 | 0 | "AllocGenaHeaders(): Error UPNP_E_OUTOF_MEMORY\n"); |
487 | 0 | } |
488 | 0 | return headers; |
489 | 0 | } |
490 | | |
491 | | void freeSubscriptionQueuedEvents(subscription *sub) |
492 | 0 | { |
493 | 0 | if (ListSize(&sub->outgoing) > 0) { |
494 | | /* The first event is discarded without dealing |
495 | | notify_thread_struct: there is a mirror ThreadPool entry for |
496 | | this one, and it will take care of the refcount etc. Other |
497 | | entries must be fully cleaned-up here */ |
498 | 0 | int first = 1; |
499 | 0 | ListNode *node = ListHead(&sub->outgoing); |
500 | 0 | while (node) { |
501 | 0 | ThreadPoolJob *job = (ThreadPoolJob *)node->item; |
502 | 0 | if (first) { |
503 | 0 | first = 0; |
504 | 0 | } else { |
505 | 0 | free_notify_struct( |
506 | 0 | (notify_thread_struct *)job->arg); |
507 | 0 | } |
508 | 0 | free(node->item); |
509 | 0 | ListDelNode(&sub->outgoing, node, 0); |
510 | 0 | node = ListHead(&sub->outgoing); |
511 | 0 | } |
512 | 0 | } |
513 | 0 | } |
514 | | |
515 | | /* We take ownership of propertySet and will free it */ |
516 | | static int genaInitNotifyCommon(UpnpDevice_Handle device_handle, |
517 | | char *UDN, |
518 | | char *servId, |
519 | | DOMString propertySet, |
520 | | const Upnp_SID sid) |
521 | 0 | { |
522 | 0 | int ret = GENA_SUCCESS; |
523 | 0 | int line = 0; |
524 | |
|
525 | 0 | int *reference_count = NULL; |
526 | 0 | char *UDN_copy = NULL; |
527 | 0 | char *servId_copy = NULL; |
528 | 0 | char *headers = NULL; |
529 | 0 | notify_thread_struct *thread_struct = NULL; |
530 | |
|
531 | 0 | subscription *sub = NULL; |
532 | 0 | service_info *service = NULL; |
533 | 0 | struct Handle_Info *handle_info; |
534 | 0 | ThreadPoolJob *job = NULL; |
535 | |
|
536 | 0 | UpnpPrintf(UPNP_INFO, |
537 | 0 | GENA, |
538 | 0 | __FILE__, |
539 | 0 | __LINE__, |
540 | 0 | "GENA BEGIN INITIAL NOTIFY COMMON\n"); |
541 | |
|
542 | 0 | job = (ThreadPoolJob *)malloc(sizeof(ThreadPoolJob)); |
543 | 0 | if (job == NULL) { |
544 | 0 | line = __LINE__; |
545 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
546 | 0 | goto ExitFunction; |
547 | 0 | } |
548 | 0 | memset(job, 0, sizeof(ThreadPoolJob)); |
549 | |
|
550 | 0 | reference_count = (int *)malloc(sizeof(int)); |
551 | 0 | if (reference_count == NULL) { |
552 | 0 | line = __LINE__; |
553 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
554 | 0 | goto ExitFunction; |
555 | 0 | } |
556 | 0 | *reference_count = 0; |
557 | |
|
558 | 0 | UDN_copy = strdup(UDN); |
559 | 0 | if (UDN_copy == NULL) { |
560 | 0 | line = __LINE__; |
561 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
562 | 0 | goto ExitFunction; |
563 | 0 | } |
564 | | |
565 | 0 | servId_copy = strdup(servId); |
566 | 0 | if (servId_copy == NULL) { |
567 | 0 | line = __LINE__; |
568 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
569 | 0 | goto ExitFunction; |
570 | 0 | } |
571 | | |
572 | 0 | HandleLock(__FILE__, __LINE__); |
573 | |
|
574 | 0 | if (GetHandleInfo(device_handle, &handle_info) != HND_DEVICE) { |
575 | 0 | line = __LINE__; |
576 | 0 | ret = GENA_E_BAD_HANDLE; |
577 | 0 | goto ExitFunction; |
578 | 0 | } |
579 | | |
580 | 0 | service = FindServiceId(&handle_info->ServiceTable, servId, UDN); |
581 | 0 | if (service == NULL) { |
582 | 0 | line = __LINE__; |
583 | 0 | ret = GENA_E_BAD_SERVICE; |
584 | 0 | goto ExitFunction; |
585 | 0 | } |
586 | 0 | UpnpPrintf(UPNP_INFO, |
587 | 0 | GENA, |
588 | 0 | __FILE__, |
589 | 0 | __LINE__, |
590 | 0 | "FOUND SERVICE IN INIT NOTFY: UDN %s, ServID: %s\n", |
591 | 0 | UDN, |
592 | 0 | servId); |
593 | |
|
594 | 0 | sub = GetSubscriptionSID(sid, service); |
595 | 0 | if (sub == NULL || sub->active) { |
596 | 0 | line = __LINE__; |
597 | 0 | ret = GENA_E_BAD_SID; |
598 | 0 | goto ExitFunction; |
599 | 0 | } |
600 | 0 | UpnpPrintf(UPNP_INFO, |
601 | 0 | GENA, |
602 | 0 | __FILE__, |
603 | 0 | __LINE__, |
604 | 0 | "FOUND SUBSCRIPTION IN INIT NOTIFY: SID %s\n", |
605 | 0 | sid); |
606 | 0 | sub->active = 1; |
607 | |
|
608 | 0 | headers = AllocGenaHeaders(propertySet); |
609 | 0 | if (headers == NULL) { |
610 | 0 | line = __LINE__; |
611 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
612 | 0 | goto ExitFunction; |
613 | 0 | } |
614 | | |
615 | | /* schedule thread for initial notification */ |
616 | | |
617 | 0 | thread_struct = |
618 | 0 | (notify_thread_struct *)malloc(sizeof(notify_thread_struct)); |
619 | 0 | if (thread_struct == NULL) { |
620 | 0 | line = __LINE__; |
621 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
622 | 0 | } else { |
623 | 0 | *reference_count = 1; |
624 | 0 | thread_struct->servId = servId_copy; |
625 | 0 | thread_struct->UDN = UDN_copy; |
626 | 0 | thread_struct->headers = headers; |
627 | 0 | thread_struct->propertySet = propertySet; |
628 | 0 | memset(thread_struct->sid, 0, sizeof(thread_struct->sid)); |
629 | 0 | strncpy(thread_struct->sid, |
630 | 0 | sid, |
631 | 0 | sizeof(thread_struct->sid) - 1); |
632 | 0 | thread_struct->ctime = time(0); |
633 | 0 | thread_struct->reference_count = reference_count; |
634 | 0 | thread_struct->device_handle = device_handle; |
635 | |
|
636 | 0 | TPJobInit(job, (start_routine)genaNotifyThread, thread_struct); |
637 | 0 | TPJobSetFreeFunction(job, (free_routine)free_notify_struct); |
638 | 0 | TPJobSetPriority(job, MED_PRIORITY); |
639 | |
|
640 | 0 | ret = ThreadPoolAdd(&gSendThreadPool, job, NULL); |
641 | 0 | if (ret != 0) { |
642 | 0 | if (ret == EOUTOFMEM) { |
643 | 0 | line = __LINE__; |
644 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
645 | 0 | } |
646 | 0 | } else { |
647 | 0 | ListNode *node = ListAddTail(&sub->outgoing, job); |
648 | 0 | if (node != NULL) { |
649 | 0 | ((ThreadPoolJob *)node->item)->jobId = |
650 | 0 | STALE_JOBID; |
651 | 0 | line = __LINE__; |
652 | 0 | ret = GENA_SUCCESS; |
653 | 0 | } else { |
654 | 0 | line = __LINE__; |
655 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
656 | 0 | } |
657 | 0 | } |
658 | 0 | } |
659 | |
|
660 | 0 | ExitFunction: |
661 | 0 | if (ret != GENA_SUCCESS) { |
662 | 0 | free(job); |
663 | 0 | free(thread_struct); |
664 | 0 | free(headers); |
665 | 0 | ixmlFreeDOMString(propertySet); |
666 | 0 | free(servId_copy); |
667 | 0 | free(UDN_copy); |
668 | 0 | free(reference_count); |
669 | 0 | } |
670 | |
|
671 | 0 | HandleUnlock(__FILE__, __LINE__); |
672 | |
|
673 | 0 | UpnpPrintf(UPNP_INFO, |
674 | 0 | GENA, |
675 | 0 | __FILE__, |
676 | 0 | line, |
677 | 0 | "GENA END INITIAL NOTIFY COMMON, ret = %d\n", |
678 | 0 | ret); |
679 | |
|
680 | 0 | return ret; |
681 | 0 | } |
682 | | |
683 | | int genaInitNotify(UpnpDevice_Handle device_handle, |
684 | | char *UDN, |
685 | | char *servId, |
686 | | char **VarNames, |
687 | | char **VarValues, |
688 | | int var_count, |
689 | | const Upnp_SID sid) |
690 | 0 | { |
691 | 0 | int ret = GENA_SUCCESS; |
692 | 0 | int line = 0; |
693 | 0 | DOMString propertySet = NULL; |
694 | |
|
695 | 0 | UpnpPrintf(UPNP_INFO, |
696 | 0 | GENA, |
697 | 0 | __FILE__, |
698 | 0 | __LINE__, |
699 | 0 | "GENA BEGIN INITIAL NOTIFY\n"); |
700 | |
|
701 | 0 | if (var_count <= 0) { |
702 | 0 | line = __LINE__; |
703 | 0 | ret = GENA_SUCCESS; |
704 | 0 | goto ExitFunction; |
705 | 0 | } |
706 | | |
707 | 0 | ret = GeneratePropertySet(VarNames, VarValues, var_count, &propertySet); |
708 | 0 | if (ret != XML_SUCCESS) { |
709 | 0 | line = __LINE__; |
710 | 0 | goto ExitFunction; |
711 | 0 | } |
712 | 0 | UpnpPrintf(UPNP_INFO, |
713 | 0 | GENA, |
714 | 0 | __FILE__, |
715 | 0 | __LINE__, |
716 | 0 | "GENERATED PROPERTY SET IN INIT NOTIFY: %s\n", |
717 | 0 | propertySet); |
718 | |
|
719 | 0 | ret = genaInitNotifyCommon( |
720 | 0 | device_handle, UDN, servId, propertySet, sid); |
721 | |
|
722 | 0 | ExitFunction: |
723 | |
|
724 | 0 | UpnpPrintf(UPNP_INFO, |
725 | 0 | GENA, |
726 | 0 | __FILE__, |
727 | 0 | line, |
728 | 0 | "GENA END INITIAL NOTIFY, ret = %d\n", |
729 | 0 | ret); |
730 | |
|
731 | 0 | return ret; |
732 | 0 | } |
733 | | |
734 | | int genaInitNotifyExt(UpnpDevice_Handle device_handle, |
735 | | char *UDN, |
736 | | char *servId, |
737 | | IXML_Document *PropSet, |
738 | | const Upnp_SID sid) |
739 | 0 | { |
740 | 0 | int ret = GENA_SUCCESS; |
741 | 0 | int line = 0; |
742 | |
|
743 | 0 | DOMString propertySet = NULL; |
744 | |
|
745 | 0 | UpnpPrintf(UPNP_INFO, |
746 | 0 | GENA, |
747 | 0 | __FILE__, |
748 | 0 | __LINE__, |
749 | 0 | "GENA BEGIN INITIAL NOTIFY EXT\n"); |
750 | |
|
751 | 0 | if (PropSet == 0) { |
752 | 0 | line = __LINE__; |
753 | 0 | ret = GENA_SUCCESS; |
754 | 0 | goto ExitFunction; |
755 | 0 | } |
756 | | |
757 | 0 | propertySet = ixmlPrintNode((IXML_Node *)PropSet); |
758 | 0 | if (propertySet == NULL) { |
759 | 0 | line = __LINE__; |
760 | 0 | ret = UPNP_E_INVALID_PARAM; |
761 | 0 | goto ExitFunction; |
762 | 0 | } |
763 | 0 | UpnpPrintf(UPNP_INFO, |
764 | 0 | GENA, |
765 | 0 | __FILE__, |
766 | 0 | __LINE__, |
767 | 0 | "GENERATED PROPERTY SET IN INIT EXT NOTIFY: %s\n", |
768 | 0 | propertySet); |
769 | |
|
770 | 0 | ret = genaInitNotifyCommon( |
771 | 0 | device_handle, UDN, servId, propertySet, sid); |
772 | |
|
773 | 0 | ExitFunction: |
774 | |
|
775 | 0 | UpnpPrintf(UPNP_INFO, |
776 | 0 | GENA, |
777 | 0 | __FILE__, |
778 | 0 | line, |
779 | 0 | "GENA END INITIAL NOTIFY EXT, ret = %d\n", |
780 | 0 | ret); |
781 | |
|
782 | 0 | return ret; |
783 | 0 | } |
784 | | |
785 | | /* |
786 | | * This gets called before queuing a new event. |
787 | | * - The list size can never go over MAX_SUBSCRIPTION_QUEUED_EVENTS so we |
788 | | * discard the oldest non-active event if it is already at the max |
789 | | * - We also discard any non-active event older than MAX_SUBSCRIPTION_EVENT_AGE. |
790 | | * non-active: any but the head of queue, which is already copied to |
791 | | * the thread pool |
792 | | */ |
793 | | static void maybeDiscardEvents(LinkedList *listp) |
794 | 0 | { |
795 | 0 | time_t now = time(0L); |
796 | 0 | notify_thread_struct *ntsp; |
797 | 0 | ThreadPoolJob *p; |
798 | |
|
799 | 0 | while (ListSize(listp) > 1) { |
800 | 0 | ListNode *node = ListHead(listp); |
801 | | /* The first candidate is the second event: first non-active */ |
802 | 0 | if (node == 0 || (node = node->next) == 0) { |
803 | | /* Major inconsistency, really, should abort here. */ |
804 | 0 | fprintf(stderr, |
805 | 0 | "gena_device: maybeDiscardEvents: " |
806 | 0 | "list is inconsistent\n"); |
807 | 0 | break; |
808 | 0 | } |
809 | | |
810 | 0 | p = (ThreadPoolJob *)node->item; |
811 | 0 | ntsp = (notify_thread_struct *)(p->arg); |
812 | 0 | if (ListSize(listp) > g_UpnpSdkEQMaxLen || |
813 | 0 | now - ntsp->ctime > g_UpnpSdkEQMaxAge) { |
814 | 0 | free_notify_struct(ntsp); |
815 | 0 | free(node->item); |
816 | 0 | ListDelNode(listp, node, 0); |
817 | 0 | } else { |
818 | | /* If the list is smaller than the max and the oldest |
819 | | * task is young enough, stop pruning */ |
820 | 0 | break; |
821 | 0 | } |
822 | 0 | } |
823 | 0 | } |
824 | | |
825 | | /* We take ownership of propertySet and will free it */ |
826 | | static int genaNotifyAllCommon(UpnpDevice_Handle device_handle, |
827 | | char *UDN, |
828 | | char *servId, |
829 | | DOMString propertySet) |
830 | 0 | { |
831 | 0 | int ret = GENA_SUCCESS; |
832 | 0 | int line = 0; |
833 | |
|
834 | 0 | int *reference_count = NULL; |
835 | 0 | char *UDN_copy = NULL; |
836 | 0 | char *servId_copy = NULL; |
837 | 0 | char *headers = NULL; |
838 | 0 | notify_thread_struct *thread_s = NULL; |
839 | |
|
840 | 0 | subscription *finger = NULL; |
841 | 0 | service_info *service = NULL; |
842 | 0 | struct Handle_Info *handle_info; |
843 | |
|
844 | 0 | UpnpPrintf(UPNP_INFO, |
845 | 0 | GENA, |
846 | 0 | __FILE__, |
847 | 0 | __LINE__, |
848 | 0 | "GENA BEGIN NOTIFY ALL COMMON\n"); |
849 | | |
850 | | /* Keep this allocation first */ |
851 | 0 | reference_count = (int *)malloc(sizeof(int)); |
852 | 0 | if (reference_count == NULL) { |
853 | 0 | line = __LINE__; |
854 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
855 | 0 | goto ExitFunction; |
856 | 0 | } |
857 | 0 | *reference_count = 0; |
858 | |
|
859 | 0 | UDN_copy = strdup(UDN); |
860 | 0 | if (UDN_copy == NULL) { |
861 | 0 | line = __LINE__; |
862 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
863 | 0 | goto ExitFunction; |
864 | 0 | } |
865 | | |
866 | 0 | servId_copy = strdup(servId); |
867 | 0 | if (servId_copy == NULL) { |
868 | 0 | line = __LINE__; |
869 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
870 | 0 | goto ExitFunction; |
871 | 0 | } |
872 | | |
873 | 0 | headers = AllocGenaHeaders(propertySet); |
874 | 0 | if (headers == NULL) { |
875 | 0 | line = __LINE__; |
876 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
877 | 0 | goto ExitFunction; |
878 | 0 | } |
879 | | |
880 | 0 | HandleLock(__FILE__, __LINE__); |
881 | |
|
882 | 0 | if (GetHandleInfo(device_handle, &handle_info) != HND_DEVICE) { |
883 | 0 | line = __LINE__; |
884 | 0 | ret = GENA_E_BAD_HANDLE; |
885 | 0 | } else { |
886 | 0 | service = |
887 | 0 | FindServiceId(&handle_info->ServiceTable, servId, UDN); |
888 | 0 | if (service != NULL) { |
889 | 0 | finger = GetFirstSubscription(service); |
890 | 0 | while (finger) { |
891 | 0 | ThreadPoolJob *job = NULL; |
892 | 0 | ListNode *node; |
893 | |
|
894 | 0 | thread_s = (notify_thread_struct *)malloc( |
895 | 0 | sizeof(notify_thread_struct)); |
896 | 0 | if (thread_s == NULL) { |
897 | 0 | line = __LINE__; |
898 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
899 | 0 | break; |
900 | 0 | } |
901 | | |
902 | 0 | (*reference_count)++; |
903 | 0 | thread_s->reference_count = reference_count; |
904 | 0 | thread_s->UDN = UDN_copy; |
905 | 0 | thread_s->servId = servId_copy; |
906 | 0 | thread_s->headers = headers; |
907 | 0 | thread_s->propertySet = propertySet; |
908 | 0 | strncpy(thread_s->sid, |
909 | 0 | finger->sid, |
910 | 0 | sizeof thread_s->sid); |
911 | 0 | thread_s->sid[sizeof thread_s->sid - 1] = 0; |
912 | 0 | thread_s->ctime = time(0); |
913 | 0 | thread_s->device_handle = device_handle; |
914 | |
|
915 | 0 | maybeDiscardEvents(&finger->outgoing); |
916 | 0 | job = (ThreadPoolJob *)malloc( |
917 | 0 | sizeof(ThreadPoolJob)); |
918 | 0 | if (!job) { |
919 | 0 | free(thread_s); |
920 | 0 | line = __LINE__; |
921 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
922 | 0 | break; |
923 | 0 | } |
924 | 0 | memset(job, 0, sizeof(ThreadPoolJob)); |
925 | 0 | TPJobInit(job, |
926 | 0 | (start_routine)genaNotifyThread, |
927 | 0 | thread_s); |
928 | 0 | TPJobSetFreeFunction( |
929 | 0 | job, (free_routine)free_notify_struct); |
930 | 0 | TPJobSetPriority(job, MED_PRIORITY); |
931 | 0 | node = ListAddTail(&finger->outgoing, job); |
932 | | |
933 | | /* If there is only one element on the list |
934 | | (which we just |
935 | | added), need to kickstart the threadpool */ |
936 | 0 | if (ListSize(&finger->outgoing) == 1) { |
937 | 0 | ret = ThreadPoolAdd( |
938 | 0 | &gSendThreadPool, job, NULL); |
939 | 0 | if (ret != 0) { |
940 | 0 | line = __LINE__; |
941 | 0 | if (ret == EOUTOFMEM) { |
942 | 0 | line = __LINE__; |
943 | 0 | ret = UPNP_E_OUTOF_MEMORY; |
944 | 0 | } |
945 | 0 | break; |
946 | 0 | } |
947 | 0 | if (node) { |
948 | 0 | ((ThreadPoolJob *)(node->item)) |
949 | 0 | ->jobId = STALE_JOBID; |
950 | 0 | } |
951 | 0 | } |
952 | 0 | finger = GetNextSubscription(service, finger); |
953 | 0 | } |
954 | 0 | } else { |
955 | 0 | line = __LINE__; |
956 | 0 | ret = GENA_E_BAD_SERVICE; |
957 | 0 | } |
958 | 0 | } |
959 | |
|
960 | 0 | ExitFunction: |
961 | | /* The only case where we want to free memory here is if the |
962 | | struct was never queued. Else, let the normal cleanup take place. |
963 | | reference_count is allocated first so it's ok to do nothing if it's 0 |
964 | | */ |
965 | 0 | if (reference_count && *reference_count == 0) { |
966 | 0 | free(headers); |
967 | 0 | ixmlFreeDOMString(propertySet); |
968 | 0 | free(servId_copy); |
969 | 0 | free(UDN_copy); |
970 | 0 | free(reference_count); |
971 | 0 | } |
972 | |
|
973 | 0 | HandleUnlock(__FILE__, __LINE__); |
974 | |
|
975 | 0 | UpnpPrintf(UPNP_INFO, |
976 | 0 | GENA, |
977 | 0 | __FILE__, |
978 | 0 | line, |
979 | 0 | "GENA END NOTIFY ALL COMMON, ret = %d\n", |
980 | 0 | ret); |
981 | |
|
982 | 0 | return ret; |
983 | 0 | } |
984 | | |
985 | | int genaNotifyAllExt(UpnpDevice_Handle device_handle, |
986 | | char *UDN, |
987 | | char *servId, |
988 | | IXML_Document *PropSet) |
989 | 0 | { |
990 | 0 | int ret = GENA_SUCCESS; |
991 | 0 | int line = 0; |
992 | |
|
993 | 0 | DOMString propertySet = NULL; |
994 | |
|
995 | 0 | UpnpPrintf(UPNP_INFO, |
996 | 0 | GENA, |
997 | 0 | __FILE__, |
998 | 0 | __LINE__, |
999 | 0 | "GENA BEGIN NOTIFY ALL EXT\n"); |
1000 | |
|
1001 | 0 | propertySet = ixmlPrintNode((IXML_Node *)PropSet); |
1002 | 0 | if (propertySet == NULL) { |
1003 | 0 | line = __LINE__; |
1004 | 0 | ret = UPNP_E_INVALID_PARAM; |
1005 | 0 | goto ExitFunction; |
1006 | 0 | } |
1007 | 0 | UpnpPrintf(UPNP_INFO, |
1008 | 0 | GENA, |
1009 | 0 | __FILE__, |
1010 | 0 | __LINE__, |
1011 | 0 | "GENERATED PROPERTY SET IN EXT NOTIFY: %s\n", |
1012 | 0 | propertySet); |
1013 | |
|
1014 | 0 | ret = genaNotifyAllCommon(device_handle, UDN, servId, propertySet); |
1015 | |
|
1016 | 0 | ExitFunction: |
1017 | |
|
1018 | 0 | UpnpPrintf(UPNP_INFO, |
1019 | 0 | GENA, |
1020 | 0 | __FILE__, |
1021 | 0 | line, |
1022 | 0 | "GENA END NOTIFY ALL EXT, ret = %d\n", |
1023 | 0 | ret); |
1024 | |
|
1025 | 0 | return ret; |
1026 | 0 | } |
1027 | | |
1028 | | int genaNotifyAll(UpnpDevice_Handle device_handle, |
1029 | | char *UDN, |
1030 | | char *servId, |
1031 | | char **VarNames, |
1032 | | char **VarValues, |
1033 | | int var_count) |
1034 | 0 | { |
1035 | 0 | int ret = GENA_SUCCESS; |
1036 | 0 | int line = 0; |
1037 | |
|
1038 | 0 | DOMString propertySet = NULL; |
1039 | |
|
1040 | 0 | UpnpPrintf( |
1041 | 0 | UPNP_INFO, GENA, __FILE__, __LINE__, "GENA BEGIN NOTIFY ALL\n"); |
1042 | |
|
1043 | 0 | ret = GeneratePropertySet(VarNames, VarValues, var_count, &propertySet); |
1044 | 0 | if (ret != XML_SUCCESS) { |
1045 | 0 | line = __LINE__; |
1046 | 0 | goto ExitFunction; |
1047 | 0 | } |
1048 | 0 | UpnpPrintf(UPNP_INFO, |
1049 | 0 | GENA, |
1050 | 0 | __FILE__, |
1051 | 0 | __LINE__, |
1052 | 0 | "GENERATED PROPERTY SET IN EXT NOTIFY: %s\n", |
1053 | 0 | propertySet); |
1054 | |
|
1055 | 0 | ret = genaNotifyAllCommon(device_handle, UDN, servId, propertySet); |
1056 | |
|
1057 | 0 | ExitFunction: |
1058 | |
|
1059 | 0 | UpnpPrintf(UPNP_INFO, |
1060 | 0 | GENA, |
1061 | 0 | __FILE__, |
1062 | 0 | line, |
1063 | 0 | "GENA END NOTIFY ALL, ret = %d\n", |
1064 | 0 | ret); |
1065 | |
|
1066 | 0 | return ret; |
1067 | 0 | } |
1068 | | |
1069 | | /*! |
1070 | | * \brief Returns OK message in the case of a subscription request. |
1071 | | * |
1072 | | * \return UPNP_E_SUCCESS if successful, otherwise the appropriate error code. |
1073 | | */ |
1074 | | static int respond_ok( |
1075 | | /*! [in] Socket connection of request. */ |
1076 | | SOCKINFO *info, |
1077 | | /*! [in] Accepted duration. */ |
1078 | | int time_out, |
1079 | | /*! [in] Accepted subscription. */ |
1080 | | subscription *sub, |
1081 | | /*! [in] Http request. */ |
1082 | | http_message_t *request) |
1083 | 0 | { |
1084 | 0 | int major; |
1085 | 0 | int minor; |
1086 | 0 | membuffer response; |
1087 | 0 | int return_code; |
1088 | 0 | char timeout_str[100]; |
1089 | 0 | int upnp_timeout = UPNP_TIMEOUT; |
1090 | 0 | int rc = 0; |
1091 | |
|
1092 | 0 | http_CalcResponseVersion( |
1093 | 0 | request->major_version, request->minor_version, &major, &minor); |
1094 | |
|
1095 | 0 | if (time_out >= 0) { |
1096 | 0 | rc = snprintf(timeout_str, |
1097 | 0 | sizeof(timeout_str), |
1098 | 0 | "TIMEOUT: Second-%d", |
1099 | 0 | time_out); |
1100 | 0 | } else { |
1101 | 0 | memset(timeout_str, 0, sizeof(timeout_str)); |
1102 | 0 | strncpy(timeout_str, |
1103 | 0 | "TIMEOUT: Second-infinite", |
1104 | 0 | sizeof(timeout_str) - 1); |
1105 | 0 | } |
1106 | 0 | if (rc < 0 || (unsigned int)rc >= sizeof(timeout_str)) { |
1107 | 0 | error_respond(info, HTTP_INTERNAL_SERVER_ERROR, request); |
1108 | 0 | return UPNP_E_OUTOF_MEMORY; |
1109 | 0 | } |
1110 | | |
1111 | 0 | membuffer_init(&response); |
1112 | 0 | response.size_inc = 30; |
1113 | 0 | if (http_MakeMessage(&response, |
1114 | 0 | major, |
1115 | 0 | minor, |
1116 | 0 | "R" |
1117 | 0 | "D" |
1118 | 0 | "S" |
1119 | 0 | "N" |
1120 | 0 | "Xc" |
1121 | 0 | "ssc" |
1122 | 0 | "scc", |
1123 | 0 | HTTP_OK, |
1124 | 0 | (off_t)0, |
1125 | 0 | X_USER_AGENT, |
1126 | 0 | "SID: ", |
1127 | 0 | sub->sid, |
1128 | 0 | timeout_str) != 0) { |
1129 | 0 | membuffer_destroy(&response); |
1130 | 0 | error_respond(info, HTTP_INTERNAL_SERVER_ERROR, request); |
1131 | 0 | return UPNP_E_OUTOF_MEMORY; |
1132 | 0 | } |
1133 | | |
1134 | 0 | return_code = http_SendMessage( |
1135 | 0 | info, &upnp_timeout, "b", response.buf, response.length); |
1136 | |
|
1137 | 0 | membuffer_destroy(&response); |
1138 | |
|
1139 | 0 | return return_code; |
1140 | 0 | } |
1141 | | |
1142 | | /*! |
1143 | | * \brief Function to parse the Callback header value in subscription requests. |
1144 | | * |
1145 | | * Takes in a buffer containing URLS delimited by '<' and '>'. The entire buffer |
1146 | | * is copied into dynamic memory and stored in the URL_list. Pointers to the |
1147 | | * individual urls within this buffer are allocated and stored in the URL_list. |
1148 | | * Only URLs with network addresses are considered (i.e. host:port or domain |
1149 | | * name). |
1150 | | * |
1151 | | * \return The number of URLs parsed if successful, otherwise |
1152 | | * UPNP_E_OUTOF_MEMORY. |
1153 | | */ |
1154 | | static int create_url_list( |
1155 | | /*! [in] . */ |
1156 | | memptr *url_list, |
1157 | | /*! [out] . */ |
1158 | | URL_list *out) |
1159 | 0 | { |
1160 | 0 | size_t URLcount = 0, URLcount2 = 0; |
1161 | 0 | size_t i; |
1162 | 0 | int return_code = 0; |
1163 | 0 | uri_type temp; |
1164 | 0 | token urls; |
1165 | 0 | token *URLS; |
1166 | |
|
1167 | 0 | urls.buff = url_list->buf; |
1168 | 0 | urls.size = url_list->length; |
1169 | 0 | URLS = &urls; |
1170 | |
|
1171 | 0 | out->size = 0; |
1172 | 0 | out->URLs = NULL; |
1173 | 0 | out->parsedURLs = NULL; |
1174 | |
|
1175 | 0 | for (i = 0; i < URLS->size; i++) { |
1176 | 0 | if ((URLS->buff[i] == '<') && (i + 1 < URLS->size)) { |
1177 | 0 | if (((return_code = parse_uri(&URLS->buff[i + 1], |
1178 | 0 | URLS->size - i + 1, |
1179 | 0 | &temp)) == HTTP_SUCCESS) && |
1180 | 0 | (temp.hostport.text.size != 0) && |
1181 | 0 | (temp.hostport.IPaddress.ss_family != |
1182 | 0 | (sa_family_t)AF_UNSPEC)) { |
1183 | 0 | URLcount++; |
1184 | 0 | } else { |
1185 | 0 | if (return_code == UPNP_E_OUTOF_MEMORY) { |
1186 | 0 | return return_code; |
1187 | 0 | } |
1188 | 0 | } |
1189 | 0 | } |
1190 | 0 | } |
1191 | | |
1192 | 0 | if (URLcount > 0) { |
1193 | 0 | out->URLs = malloc(URLS->size + 1); |
1194 | 0 | out->parsedURLs = malloc(sizeof(uri_type) * URLcount); |
1195 | 0 | if (!out->URLs || !out->parsedURLs) { |
1196 | 0 | free(out->URLs); |
1197 | 0 | free(out->parsedURLs); |
1198 | 0 | out->URLs = NULL; |
1199 | 0 | out->parsedURLs = NULL; |
1200 | 0 | return UPNP_E_OUTOF_MEMORY; |
1201 | 0 | } |
1202 | 0 | memcpy(out->URLs, URLS->buff, URLS->size); |
1203 | 0 | out->URLs[URLS->size] = 0; |
1204 | 0 | for (i = 0; i < URLS->size; i++) { |
1205 | 0 | if ((URLS->buff[i] == '<') && (i + 1 < URLS->size)) { |
1206 | 0 | if (((return_code = parse_uri(&out->URLs[i + 1], |
1207 | 0 | URLS->size - i + 1, |
1208 | 0 | &out->parsedURLs[URLcount2])) == |
1209 | 0 | HTTP_SUCCESS) && |
1210 | 0 | (out->parsedURLs[URLcount2] |
1211 | 0 | .hostport.text.size != |
1212 | 0 | 0) && |
1213 | 0 | (out->parsedURLs[URLcount2] |
1214 | 0 | .hostport.IPaddress |
1215 | 0 | .ss_family != |
1216 | 0 | (sa_family_t)AF_UNSPEC)) { |
1217 | 0 | URLcount2++; |
1218 | 0 | if (URLcount2 >= URLcount) |
1219 | | /* |
1220 | | * break early here in case |
1221 | | * there is a bogus URL that was |
1222 | | * skipped above. This prevents |
1223 | | * to access |
1224 | | * out->parsedURLs[URLcount] |
1225 | | * which is beyond the |
1226 | | * allocation. |
1227 | | */ |
1228 | 0 | break; |
1229 | 0 | } else { |
1230 | 0 | if (return_code == |
1231 | 0 | UPNP_E_OUTOF_MEMORY) { |
1232 | 0 | free(out->URLs); |
1233 | 0 | free(out->parsedURLs); |
1234 | 0 | out->URLs = NULL; |
1235 | 0 | out->parsedURLs = NULL; |
1236 | 0 | return return_code; |
1237 | 0 | } |
1238 | 0 | } |
1239 | 0 | } |
1240 | 0 | } |
1241 | 0 | } |
1242 | 0 | out->size = URLcount; |
1243 | |
|
1244 | 0 | return (int)URLcount; |
1245 | 0 | } |
1246 | | |
1247 | | /*! |
1248 | | * \brief Return 1 if addr is an RFC 1918 private address, 0 otherwise. |
1249 | | */ |
1250 | | static int is_rfc1918_addr(struct in_addr addr) |
1251 | 0 | { |
1252 | 0 | uint32_t a = ntohl(addr.s_addr); |
1253 | | /* 10.0.0.0/8 */ |
1254 | 0 | if ((a & 0xFF000000u) == 0x0A000000u) |
1255 | 0 | return 1; |
1256 | | /* 172.16.0.0/12 */ |
1257 | 0 | if ((a & 0xFFF00000u) == 0xAC100000u) |
1258 | 0 | return 1; |
1259 | | /* 192.168.0.0/16 */ |
1260 | 0 | if ((a & 0xFFFF0000u) == 0xC0A80000u) |
1261 | 0 | return 1; |
1262 | 0 | return 0; |
1263 | 0 | } |
1264 | | |
1265 | | /*! |
1266 | | * \brief Validate that the URLs passed by the user are on the same network |
1267 | | * segment than the device. |
1268 | | * |
1269 | | * Note: This is a fix for CallStanger a.k.a. CVE-2020-12695 |
1270 | | * |
1271 | | * For private networks (RFC 1918) the UPnP spec requires that the delivery |
1272 | | * URL is within any RFC 1918 range, not necessarily the same subnet. |
1273 | | * Strict same-subnet checking is kept for non-private device addresses. |
1274 | | * |
1275 | | * \return 0 if all URLs are on the same segment or -1 otherwise. |
1276 | | */ |
1277 | | int gena_validate_delivery_urls( |
1278 | | /*! [in] . */ |
1279 | | SOCKINFO *info, |
1280 | | /*! [in] . */ |
1281 | | URL_list *url_list) |
1282 | 0 | { |
1283 | 0 | size_t i = 0; |
1284 | 0 | struct in_addr genaAddr4; |
1285 | 0 | struct in_addr genaNetmask; |
1286 | 0 | struct sockaddr_in *deliveryAddr4 = NULL; |
1287 | 0 | struct in6_addr genaAddr6Lla; |
1288 | 0 | struct in6_addr genaAddr6UlaGua; |
1289 | 0 | struct in6_addr *genaAddr6 = NULL; |
1290 | 0 | unsigned int if_prefix; |
1291 | 0 | struct sockaddr_in6 *deliveryAddr6 = NULL; |
1292 | 0 | char deliveryAddrString[INET6_ADDRSTRLEN]; |
1293 | |
|
1294 | 0 | if (info == NULL || url_list == NULL) { |
1295 | 0 | return 0; |
1296 | 0 | } |
1297 | | |
1298 | 0 | switch (info->foreign_sockaddr.ss_family) { |
1299 | 0 | case AF_INET: |
1300 | 0 | if (!inet_pton(AF_INET, gIF_IPV4, &genaAddr4)) { |
1301 | 0 | return -1; |
1302 | 0 | } |
1303 | | |
1304 | 0 | if (!inet_pton(AF_INET, gIF_IPV4_NETMASK, &genaNetmask)) { |
1305 | 0 | return -1; |
1306 | 0 | } |
1307 | | |
1308 | 0 | for (i = 0; i < url_list->size; i++) { |
1309 | 0 | deliveryAddr4 = |
1310 | 0 | (struct sockaddr_in *)&url_list->parsedURLs[i] |
1311 | 0 | .hostport.IPaddress; |
1312 | | /* For RFC 1918 private networks the UPnP spec requires |
1313 | | * the delivery URL to be in any private range, not |
1314 | | * necessarily the same subnet (issue #379). */ |
1315 | 0 | if (is_rfc1918_addr(genaAddr4) && |
1316 | 0 | is_rfc1918_addr(deliveryAddr4->sin_addr)) { |
1317 | 0 | continue; |
1318 | 0 | } |
1319 | 0 | if ((deliveryAddr4->sin_addr.s_addr & |
1320 | 0 | genaNetmask.s_addr) != |
1321 | 0 | (genaAddr4.s_addr & genaNetmask.s_addr)) { |
1322 | 0 | inet_ntop(AF_INET, |
1323 | 0 | &deliveryAddr4->sin_addr, |
1324 | 0 | deliveryAddrString, |
1325 | 0 | sizeof(deliveryAddrString)); |
1326 | 0 | UpnpPrintf(UPNP_CRITICAL, |
1327 | 0 | GENA, |
1328 | 0 | __FILE__, |
1329 | 0 | __LINE__, |
1330 | 0 | "DeliveryURL %s is invalid.\n" |
1331 | 0 | "It is not in the expected network " |
1332 | 0 | "segment (IPv4: %s, netmask: %s)\n", |
1333 | 0 | deliveryAddrString, |
1334 | 0 | gIF_IPV4, |
1335 | 0 | gIF_IPV4_NETMASK); |
1336 | 0 | return -1; |
1337 | 0 | } |
1338 | 0 | } |
1339 | 0 | break; |
1340 | 0 | case AF_INET6: |
1341 | 0 | if (!inet_pton(AF_INET6, gIF_IPV6, &genaAddr6Lla)) { |
1342 | 0 | return -1; |
1343 | 0 | } |
1344 | | |
1345 | 0 | if (!inet_pton(AF_INET6, gIF_IPV6_ULA_GUA, &genaAddr6UlaGua)) { |
1346 | 0 | return -1; |
1347 | 0 | } |
1348 | | |
1349 | 0 | for (i = 0; i < url_list->size; i++) { |
1350 | 0 | deliveryAddr6 = |
1351 | 0 | (struct sockaddr_in6 *)&url_list->parsedURLs[i] |
1352 | 0 | .hostport.IPaddress; |
1353 | 0 | if (IN6_IS_ADDR_LINKLOCAL(&deliveryAddr6->sin6_addr)) { |
1354 | 0 | genaAddr6 = &genaAddr6Lla; |
1355 | 0 | if_prefix = gIF_IPV6_PREFIX_LENGTH; |
1356 | 0 | } else { |
1357 | 0 | genaAddr6 = &genaAddr6UlaGua; |
1358 | 0 | if_prefix = gIF_IPV6_ULA_GUA_PREFIX_LENGTH; |
1359 | 0 | } |
1360 | | /* We assume that IPv6 prefix is a multiple of 8 */ |
1361 | 0 | if (memcmp(deliveryAddr6->sin6_addr.s6_addr, |
1362 | 0 | genaAddr6->s6_addr, |
1363 | 0 | if_prefix / 8)) { |
1364 | 0 | inet_ntop(AF_INET6, |
1365 | 0 | &deliveryAddr6->sin6_addr, |
1366 | 0 | deliveryAddrString, |
1367 | 0 | sizeof(deliveryAddrString)); |
1368 | 0 | UpnpPrintf(UPNP_CRITICAL, |
1369 | 0 | GENA, |
1370 | 0 | __FILE__, |
1371 | 0 | __LINE__, |
1372 | 0 | "DeliveryURL %s is invalid.\n" |
1373 | 0 | "It is not in the expected network " |
1374 | 0 | "segment (IPv6: %s, prefix: %d)\n", |
1375 | 0 | deliveryAddrString, |
1376 | 0 | IN6_IS_ADDR_LINKLOCAL( |
1377 | 0 | &deliveryAddr6->sin6_addr) |
1378 | 0 | ? gIF_IPV6 |
1379 | 0 | : gIF_IPV6_ULA_GUA, |
1380 | 0 | if_prefix); |
1381 | 0 | return -1; |
1382 | 0 | } |
1383 | 0 | } |
1384 | 0 | break; |
1385 | 0 | } |
1386 | 0 | return 0; |
1387 | 0 | } |
1388 | | |
1389 | | void gena_process_subscription_request(SOCKINFO *info, http_message_t *request) |
1390 | 0 | { |
1391 | 0 | UpnpSubscriptionRequest *request_struct = UpnpSubscriptionRequest_new(); |
1392 | 0 | Upnp_SID temp_sid; |
1393 | 0 | int return_code = 1; |
1394 | 0 | int time_out = 1801; |
1395 | 0 | service_info *service; |
1396 | 0 | subscription *sub; |
1397 | 0 | uuid_upnp uid; |
1398 | 0 | struct Handle_Info *handle_info; |
1399 | 0 | void *cookie; |
1400 | 0 | Upnp_FunPtr callback_fun; |
1401 | 0 | UpnpDevice_Handle device_handle; |
1402 | 0 | memptr nt_hdr; |
1403 | 0 | char *event_url_path = NULL; |
1404 | 0 | memptr callback_hdr; |
1405 | 0 | memptr timeout_hdr; |
1406 | 0 | int rc = 0; |
1407 | |
|
1408 | 0 | UpnpPrintf(UPNP_INFO, |
1409 | 0 | GENA, |
1410 | 0 | __FILE__, |
1411 | 0 | __LINE__, |
1412 | 0 | "Subscription Request Received:\n"); |
1413 | |
|
1414 | 0 | if (httpmsg_find_hdr(request, HDR_NT, &nt_hdr) == NULL) { |
1415 | 0 | error_respond(info, HTTP_BAD_REQUEST, request); |
1416 | 0 | goto exit_function; |
1417 | 0 | } |
1418 | | |
1419 | | /* check NT header */ |
1420 | | /* Windows Millenium Interoperability: */ |
1421 | | /* we accept either upnp:event, or upnp:propchange for the NT header */ |
1422 | 0 | if (memptr_cmp_nocase(&nt_hdr, "upnp:event") != 0) { |
1423 | 0 | error_respond(info, HTTP_PRECONDITION_FAILED, request); |
1424 | 0 | goto exit_function; |
1425 | 0 | } |
1426 | | |
1427 | | /* if a SID is present then the we have a bad request "incompatible |
1428 | | * headers" */ |
1429 | 0 | if (httpmsg_find_hdr(request, HDR_SID, NULL) != NULL) { |
1430 | 0 | error_respond(info, HTTP_BAD_REQUEST, request); |
1431 | 0 | goto exit_function; |
1432 | 0 | } |
1433 | | /* look up service by eventURL */ |
1434 | 0 | event_url_path = str_alloc( |
1435 | 0 | request->uri.pathquery.buff, request->uri.pathquery.size); |
1436 | 0 | if (event_url_path == NULL) { |
1437 | 0 | error_respond(info, HTTP_INTERNAL_SERVER_ERROR, request); |
1438 | 0 | goto exit_function; |
1439 | 0 | } |
1440 | | |
1441 | 0 | UpnpPrintf(UPNP_INFO, |
1442 | 0 | GENA, |
1443 | 0 | __FILE__, |
1444 | 0 | __LINE__, |
1445 | 0 | "SubscriptionRequest for event URL path: %s\n", |
1446 | 0 | event_url_path); |
1447 | |
|
1448 | 0 | HandleLock(__FILE__, __LINE__); |
1449 | |
|
1450 | 0 | if (GetDeviceHandleInfoForPath(event_url_path, |
1451 | 0 | info->foreign_sockaddr.ss_family, |
1452 | 0 | &device_handle, |
1453 | 0 | &handle_info, |
1454 | 0 | &service) != HND_DEVICE) { |
1455 | 0 | free(event_url_path); |
1456 | 0 | error_respond(info, HTTP_INTERNAL_SERVER_ERROR, request); |
1457 | 0 | HandleUnlock(__FILE__, __LINE__); |
1458 | 0 | goto exit_function; |
1459 | 0 | } |
1460 | 0 | free(event_url_path); |
1461 | |
|
1462 | 0 | if (service == NULL || !service->active) { |
1463 | 0 | error_respond(info, HTTP_NOT_FOUND, request); |
1464 | 0 | HandleUnlock(__FILE__, __LINE__); |
1465 | 0 | goto exit_function; |
1466 | 0 | } |
1467 | | |
1468 | 0 | UpnpPrintf(UPNP_INFO, |
1469 | 0 | GENA, |
1470 | 0 | __FILE__, |
1471 | 0 | __LINE__, |
1472 | 0 | "Subscription Request: Number of Subscriptions already %d\n " |
1473 | 0 | "Max Subscriptions allowed: %d\n", |
1474 | 0 | service->TotalSubscriptions, |
1475 | 0 | handle_info->MaxSubscriptions); |
1476 | | |
1477 | | /* too many subscriptions */ |
1478 | 0 | if (handle_info->MaxSubscriptions != -1 && |
1479 | 0 | service->TotalSubscriptions >= handle_info->MaxSubscriptions) { |
1480 | 0 | error_respond(info, HTTP_INTERNAL_SERVER_ERROR, request); |
1481 | 0 | HandleUnlock(__FILE__, __LINE__); |
1482 | 0 | goto exit_function; |
1483 | 0 | } |
1484 | | /* generate new subscription */ |
1485 | 0 | sub = (subscription *)malloc(sizeof(subscription)); |
1486 | 0 | if (sub == NULL) { |
1487 | 0 | error_respond(info, HTTP_INTERNAL_SERVER_ERROR, request); |
1488 | 0 | HandleUnlock(__FILE__, __LINE__); |
1489 | 0 | goto exit_function; |
1490 | 0 | } |
1491 | 0 | sub->ToSendEventKey = 0; |
1492 | 0 | sub->active = 0; |
1493 | 0 | sub->next = NULL; |
1494 | 0 | sub->DeliveryURLs.size = 0; |
1495 | 0 | sub->DeliveryURLs.URLs = NULL; |
1496 | 0 | sub->DeliveryURLs.parsedURLs = NULL; |
1497 | 0 | if (ListInit(&sub->outgoing, 0, free) != 0) { |
1498 | 0 | error_respond(info, HTTP_INTERNAL_SERVER_ERROR, request); |
1499 | 0 | HandleUnlock(__FILE__, __LINE__); |
1500 | 0 | goto exit_function; |
1501 | 0 | } |
1502 | | |
1503 | | /* check for valid callbacks */ |
1504 | 0 | if (httpmsg_find_hdr(request, HDR_CALLBACK, &callback_hdr) == NULL) { |
1505 | 0 | error_respond(info, HTTP_PRECONDITION_FAILED, request); |
1506 | 0 | freeSubscriptionList(sub); |
1507 | 0 | HandleUnlock(__FILE__, __LINE__); |
1508 | 0 | goto exit_function; |
1509 | 0 | } |
1510 | | /* Reject oversized Callback headers before allocating storage. |
1511 | | * Avoids memory exhaustion when an attacker sends many SUBSCRIBE |
1512 | | * requests with large random callback URLs (issue #435). */ |
1513 | 0 | if (callback_hdr.length > MAX_SUBSCRIPTION_CALLBACK_HEADER_SIZE) { |
1514 | 0 | error_respond(info, HTTP_PRECONDITION_FAILED, request); |
1515 | 0 | freeSubscriptionList(sub); |
1516 | 0 | HandleUnlock(__FILE__, __LINE__); |
1517 | 0 | goto exit_function; |
1518 | 0 | } |
1519 | 0 | return_code = create_url_list(&callback_hdr, &sub->DeliveryURLs); |
1520 | 0 | if (return_code == 0) { |
1521 | 0 | error_respond(info, HTTP_PRECONDITION_FAILED, request); |
1522 | 0 | freeSubscriptionList(sub); |
1523 | 0 | HandleUnlock(__FILE__, __LINE__); |
1524 | 0 | goto exit_function; |
1525 | 0 | } |
1526 | 0 | if (return_code == UPNP_E_OUTOF_MEMORY) { |
1527 | 0 | error_respond(info, HTTP_INTERNAL_SERVER_ERROR, request); |
1528 | 0 | freeSubscriptionList(sub); |
1529 | 0 | HandleUnlock(__FILE__, __LINE__); |
1530 | 0 | goto exit_function; |
1531 | 0 | } |
1532 | 0 | return_code = gena_validate_delivery_urls(info, &sub->DeliveryURLs); |
1533 | 0 | if (return_code != 0) { |
1534 | 0 | error_respond(info, HTTP_PRECONDITION_FAILED, request); |
1535 | 0 | freeSubscriptionList(sub); |
1536 | 0 | HandleUnlock(__FILE__, __LINE__); |
1537 | 0 | goto exit_function; |
1538 | 0 | } |
1539 | | /* set the timeout */ |
1540 | 0 | if (httpmsg_find_hdr(request, HDR_TIMEOUT, &timeout_hdr) != NULL) { |
1541 | 0 | if (matchstr(timeout_hdr.buf, |
1542 | 0 | timeout_hdr.length, |
1543 | 0 | "%iSecond-%d%0", |
1544 | 0 | &time_out) == PARSE_OK) { |
1545 | | /* nothing */ |
1546 | 0 | } else if (memptr_cmp_nocase(&timeout_hdr, "Second-infinite") == |
1547 | 0 | 0) { |
1548 | | /* infinite timeout */ |
1549 | 0 | time_out = -1; |
1550 | 0 | } else { |
1551 | | /* default is > 1800 seconds */ |
1552 | 0 | time_out = DEFAULT_TIMEOUT; |
1553 | 0 | } |
1554 | 0 | } |
1555 | | /* replace infinite timeout with max timeout, if possible */ |
1556 | 0 | if (handle_info->MaxSubscriptionTimeOut != -1) { |
1557 | 0 | if (time_out == -1 || |
1558 | 0 | time_out > handle_info->MaxSubscriptionTimeOut) { |
1559 | 0 | time_out = handle_info->MaxSubscriptionTimeOut; |
1560 | 0 | } |
1561 | 0 | } |
1562 | 0 | if (time_out >= 0) { |
1563 | 0 | sub->expireTime = time(NULL) + time_out; |
1564 | 0 | } else { |
1565 | | /* infinite time */ |
1566 | 0 | sub->expireTime = 0; |
1567 | 0 | } |
1568 | | |
1569 | | /* generate SID */ |
1570 | 0 | uuid_create(&uid); |
1571 | 0 | upnp_uuid_unpack(&uid, temp_sid); |
1572 | 0 | rc = snprintf(sub->sid, sizeof(sub->sid), "uuid:%s", temp_sid); |
1573 | | |
1574 | | /* respond OK */ |
1575 | 0 | if (rc < 0 || (unsigned int)rc >= sizeof(sub->sid) || |
1576 | 0 | (respond_ok(info, time_out, sub, request) != UPNP_E_SUCCESS)) { |
1577 | 0 | freeSubscriptionList(sub); |
1578 | 0 | HandleUnlock(__FILE__, __LINE__); |
1579 | 0 | goto exit_function; |
1580 | 0 | } |
1581 | | /* add to subscription list */ |
1582 | 0 | sub->next = service->subscriptionList; |
1583 | 0 | service->subscriptionList = sub; |
1584 | 0 | service->TotalSubscriptions++; |
1585 | | |
1586 | | /* finally generate callback for init table dump */ |
1587 | 0 | UpnpSubscriptionRequest_strcpy_ServiceId( |
1588 | 0 | request_struct, service->serviceId); |
1589 | 0 | UpnpSubscriptionRequest_strcpy_UDN(request_struct, service->UDN); |
1590 | 0 | UpnpSubscriptionRequest_strcpy_SID(request_struct, sub->sid); |
1591 | | |
1592 | | /* copy callback */ |
1593 | 0 | callback_fun = handle_info->Callback; |
1594 | 0 | cookie = handle_info->Cookie; |
1595 | |
|
1596 | 0 | HandleUnlock(__FILE__, __LINE__); |
1597 | | |
1598 | | /* make call back with request struct */ |
1599 | | /* in the future should find a way of mainting that the handle */ |
1600 | | /* is not unregistered in the middle of a callback */ |
1601 | 0 | callback_fun(UPNP_EVENT_SUBSCRIPTION_REQUEST, request_struct, cookie); |
1602 | |
|
1603 | 0 | exit_function: |
1604 | 0 | UpnpSubscriptionRequest_delete(request_struct); |
1605 | 0 | } |
1606 | | |
1607 | | void gena_process_subscription_renewal_request( |
1608 | | SOCKINFO *info, http_message_t *request) |
1609 | 0 | { |
1610 | 0 | Upnp_SID sid; |
1611 | 0 | subscription *sub; |
1612 | 0 | int time_out = 1801; |
1613 | 0 | service_info *service; |
1614 | 0 | struct Handle_Info *handle_info; |
1615 | 0 | UpnpDevice_Handle device_handle; |
1616 | 0 | memptr temp_hdr; |
1617 | 0 | membuffer event_url_path; |
1618 | 0 | memptr timeout_hdr; |
1619 | | |
1620 | | /* if a CALLBACK or NT header is present, then it is an error */ |
1621 | 0 | if (httpmsg_find_hdr(request, HDR_CALLBACK, NULL) != NULL || |
1622 | 0 | httpmsg_find_hdr(request, HDR_NT, NULL) != NULL) { |
1623 | 0 | error_respond(info, HTTP_BAD_REQUEST, request); |
1624 | 0 | return; |
1625 | 0 | } |
1626 | | /* get SID */ |
1627 | 0 | if (httpmsg_find_hdr(request, HDR_SID, &temp_hdr) == NULL || |
1628 | 0 | temp_hdr.length > SID_SIZE) { |
1629 | 0 | error_respond(info, HTTP_PRECONDITION_FAILED, request); |
1630 | 0 | return; |
1631 | 0 | } |
1632 | 0 | memcpy(sid, temp_hdr.buf, temp_hdr.length); |
1633 | 0 | sid[temp_hdr.length] = '\0'; |
1634 | | |
1635 | | /* lookup service by eventURL */ |
1636 | 0 | membuffer_init(&event_url_path); |
1637 | 0 | if (membuffer_append(&event_url_path, |
1638 | 0 | request->uri.pathquery.buff, |
1639 | 0 | request->uri.pathquery.size) != 0) { |
1640 | 0 | error_respond(info, HTTP_INTERNAL_SERVER_ERROR, request); |
1641 | 0 | return; |
1642 | 0 | } |
1643 | | |
1644 | 0 | HandleLock(__FILE__, __LINE__); |
1645 | |
|
1646 | 0 | if (GetDeviceHandleInfoForPath(event_url_path.buf, |
1647 | 0 | info->foreign_sockaddr.ss_family, |
1648 | 0 | &device_handle, |
1649 | 0 | &handle_info, |
1650 | 0 | &service) != HND_DEVICE) { |
1651 | 0 | error_respond(info, HTTP_PRECONDITION_FAILED, request); |
1652 | 0 | membuffer_destroy(&event_url_path); |
1653 | 0 | HandleUnlock(__FILE__, __LINE__); |
1654 | 0 | return; |
1655 | 0 | } |
1656 | 0 | membuffer_destroy(&event_url_path); |
1657 | | |
1658 | | /* get subscription */ |
1659 | 0 | if (service == NULL || !service->active || |
1660 | 0 | ((sub = GetSubscriptionSID(sid, service)) == NULL)) { |
1661 | 0 | error_respond(info, HTTP_PRECONDITION_FAILED, request); |
1662 | 0 | HandleUnlock(__FILE__, __LINE__); |
1663 | 0 | return; |
1664 | 0 | } |
1665 | | |
1666 | 0 | UpnpPrintf(UPNP_INFO, |
1667 | 0 | GENA, |
1668 | 0 | __FILE__, |
1669 | 0 | __LINE__, |
1670 | 0 | "Renew request: Number of subscriptions already: %d\n " |
1671 | 0 | "Max Subscriptions allowed:%d\n", |
1672 | 0 | service->TotalSubscriptions, |
1673 | 0 | handle_info->MaxSubscriptions); |
1674 | | /* too many subscriptions */ |
1675 | 0 | if (handle_info->MaxSubscriptions != -1 && |
1676 | 0 | service->TotalSubscriptions > handle_info->MaxSubscriptions) { |
1677 | 0 | error_respond(info, HTTP_INTERNAL_SERVER_ERROR, request); |
1678 | 0 | RemoveSubscriptionSID(sub->sid, service); |
1679 | 0 | HandleUnlock(__FILE__, __LINE__); |
1680 | 0 | return; |
1681 | 0 | } |
1682 | | /* set the timeout */ |
1683 | 0 | if (httpmsg_find_hdr(request, HDR_TIMEOUT, &timeout_hdr) != NULL) { |
1684 | 0 | if (matchstr(timeout_hdr.buf, |
1685 | 0 | timeout_hdr.length, |
1686 | 0 | "%iSecond-%d%0", |
1687 | 0 | &time_out) == PARSE_OK) { |
1688 | | |
1689 | | /*nothing */ |
1690 | |
|
1691 | 0 | } else if (memptr_cmp_nocase(&timeout_hdr, "Second-infinite") == |
1692 | 0 | 0) { |
1693 | |
|
1694 | 0 | time_out = -1; /* inifinite timeout */ |
1695 | |
|
1696 | 0 | } else { |
1697 | 0 | time_out = |
1698 | 0 | DEFAULT_TIMEOUT; /* default is > 1800 seconds */ |
1699 | 0 | } |
1700 | 0 | } |
1701 | | |
1702 | | /* replace infinite timeout with max timeout, if possible */ |
1703 | 0 | if (handle_info->MaxSubscriptionTimeOut != -1) { |
1704 | 0 | if (time_out == -1 || |
1705 | 0 | time_out > handle_info->MaxSubscriptionTimeOut) { |
1706 | 0 | time_out = handle_info->MaxSubscriptionTimeOut; |
1707 | 0 | } |
1708 | 0 | } |
1709 | |
|
1710 | 0 | if (time_out == -1) { |
1711 | 0 | sub->expireTime = 0; |
1712 | 0 | } else { |
1713 | 0 | sub->expireTime = time(NULL) + time_out; |
1714 | 0 | } |
1715 | |
|
1716 | 0 | if (respond_ok(info, time_out, sub, request) != UPNP_E_SUCCESS) { |
1717 | 0 | RemoveSubscriptionSID(sub->sid, service); |
1718 | 0 | } |
1719 | |
|
1720 | 0 | HandleUnlock(__FILE__, __LINE__); |
1721 | 0 | } |
1722 | | |
1723 | | void gena_process_unsubscribe_request(SOCKINFO *info, http_message_t *request) |
1724 | 0 | { |
1725 | 0 | Upnp_SID sid; |
1726 | 0 | service_info *service; |
1727 | 0 | struct Handle_Info *handle_info; |
1728 | 0 | UpnpDevice_Handle device_handle; |
1729 | |
|
1730 | 0 | memptr temp_hdr; |
1731 | 0 | membuffer event_url_path; |
1732 | | |
1733 | | /* if a CALLBACK or NT header is present, then it is an error */ |
1734 | 0 | if (httpmsg_find_hdr(request, HDR_CALLBACK, NULL) != NULL || |
1735 | 0 | httpmsg_find_hdr(request, HDR_NT, NULL) != NULL) { |
1736 | 0 | error_respond(info, HTTP_BAD_REQUEST, request); |
1737 | 0 | return; |
1738 | 0 | } |
1739 | | /* get SID */ |
1740 | 0 | if (httpmsg_find_hdr(request, HDR_SID, &temp_hdr) == NULL || |
1741 | 0 | temp_hdr.length > SID_SIZE) { |
1742 | 0 | error_respond(info, HTTP_PRECONDITION_FAILED, request); |
1743 | 0 | return; |
1744 | 0 | } |
1745 | 0 | memcpy(sid, temp_hdr.buf, temp_hdr.length); |
1746 | 0 | sid[temp_hdr.length] = '\0'; |
1747 | | |
1748 | | /* lookup service by eventURL */ |
1749 | 0 | membuffer_init(&event_url_path); |
1750 | 0 | if (membuffer_append(&event_url_path, |
1751 | 0 | request->uri.pathquery.buff, |
1752 | 0 | request->uri.pathquery.size) != 0) { |
1753 | 0 | error_respond(info, HTTP_INTERNAL_SERVER_ERROR, request); |
1754 | 0 | return; |
1755 | 0 | } |
1756 | | |
1757 | 0 | HandleLock(__FILE__, __LINE__); |
1758 | |
|
1759 | 0 | if (GetDeviceHandleInfoForPath(event_url_path.buf, |
1760 | 0 | info->foreign_sockaddr.ss_family, |
1761 | 0 | &device_handle, |
1762 | 0 | &handle_info, |
1763 | 0 | &service) != HND_DEVICE) { |
1764 | 0 | error_respond(info, HTTP_PRECONDITION_FAILED, request); |
1765 | 0 | membuffer_destroy(&event_url_path); |
1766 | 0 | HandleUnlock(__FILE__, __LINE__); |
1767 | 0 | return; |
1768 | 0 | } |
1769 | 0 | membuffer_destroy(&event_url_path); |
1770 | | |
1771 | | /* validate service */ |
1772 | 0 | if (service == NULL || !service->active || |
1773 | 0 | GetSubscriptionSID(sid, service) == NULL) { |
1774 | 0 | error_respond(info, HTTP_PRECONDITION_FAILED, request); |
1775 | 0 | HandleUnlock(__FILE__, __LINE__); |
1776 | 0 | return; |
1777 | 0 | } |
1778 | | |
1779 | 0 | RemoveSubscriptionSID(sid, service); |
1780 | 0 | error_respond(info, HTTP_OK, request); /* success */ |
1781 | |
|
1782 | 0 | HandleUnlock(__FILE__, __LINE__); |
1783 | 0 | } |
1784 | | |
1785 | | #endif /* INCLUDE_DEVICE_APIS */ |
1786 | | #endif /* EXCLUDE_GENA */ |