Coverage Report

Created: 2026-08-14 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 */