aboutsummaryrefslogtreecommitdiffstats
path: root/src/wps/wps_upnp.c
blob: 6d2de0e08459435183c0d90c9489a6b08a2fa524 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
/*
 * UPnP WPS Device
 * Copyright (c) 2000-2003 Intel Corporation
 * Copyright (c) 2006-2007 Sony Corporation
 * Copyright (c) 2008-2009 Atheros Communications
 * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
 *
 * See below for more details on licensing and code history.
 */

/*
 * This has been greatly stripped down from the original file
 * (upnp_wps_device.c) by Ted Merrill, Atheros Communications
 * in order to eliminate use of the bulky libupnp library etc.
 *
 * History:
 * upnp_wps_device.c is/was a shim layer between wps_opt_upnp.c and
 * the libupnp library.
 * The layering (by Sony) was well done; only a very minor modification
 * to API of upnp_wps_device.c was required.
 * libupnp was found to be undesirable because:
 * -- It consumed too much code and data space
 * -- It uses multiple threads, making debugging more difficult
 *      and possibly reducing reliability.
 * -- It uses static variables and only supports one instance.
 * The shim and libupnp are here replaced by special code written
 * specifically for the needs of hostapd.
 * Various shortcuts can and are taken to keep the code size small.
 * Generally, execution time is not as crucial.
 *
 * BUGS:
 * -- UPnP requires that we be able to resolve domain names.
 * While uncommon, if we have to do it then it will stall the entire
 * hostapd program, which is bad.
 * This is because we use the standard linux getaddrinfo() function
 * which is syncronous.
 * An asyncronous solution would be to use the free "ares" library.
 * -- Does not have a robust output buffering scheme.  Uses a single
 * fixed size output buffer per TCP/HTTP connection, with possible (although
 * unlikely) possibility of overflow and likely excessive use of RAM.
 * A better solution would be to write the HTTP output as a buffered stream,
 * using chunking: (handle header specially, then) generate data with
 * a printf-like function into a buffer, catching buffer full condition,
 * then send it out surrounded by http chunking.
 * -- There is some code that could be separated out into the common
 * library to be shared with wpa_supplicant.
 * -- Needs renaming with module prefix to avoid polluting the debugger
 * namespace and causing possible collisions with other static fncs
 * and structure declarations when using the debugger.
 * -- Just what should be in the first event message sent after subscription
 * for the WLANEvent field? If i pass it empty, Vista replies with OK
 * but apparently barfs on the message.
 * -- The http error code generation is pretty bogus, hopefully noone cares.
 *
 * Author: Ted Merrill, Atheros Communications, based upon earlier work
 * as explained above and below.
 *
 * Copyright:
 * Copyright 2008 Atheros Communications.
 *
 * The original header (of upnp_wps_device.c) reads:
 *
 *  Copyright (c) 2006-2007 Sony Corporation. All Rights Reserved.
 *
 *  File Name: upnp_wps_device.c
 *  Description: EAP-WPS UPnP device source
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *     * Neither the name of Sony Corporation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Portions from Intel libupnp files, e.g. genlib/net/http/httpreadwrite.c
 * typical header:
 *
 * Copyright (c) 2000-2003 Intel Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * * Neither name of Intel Corporation nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*
 * Overview of WPS over UPnP:
 *
 * UPnP is a protocol that allows devices to discover each other and control
 * each other. In UPnP terminology, a device is either a "device" (a server
 * that provides information about itself and allows itself to be controlled)
 * or a "control point" (a client that controls "devices") or possibly both.
 * This file implements a UPnP "device".
 *
 * For us, we use mostly basic UPnP discovery, but the control part of interest
 * is WPS carried via UPnP messages. There is quite a bit of basic UPnP
 * discovery to do before we can get to WPS, however.
 *
 * UPnP discovery begins with "devices" send out multicast UDP packets to a
 * certain fixed multicast IP address and port, and "control points" sending
 * out other such UDP packets.
 *
 * The packets sent by devices are NOTIFY packets (not to be confused with TCP
 * NOTIFY packets that are used later) and those sent by control points are
 * M-SEARCH packets. These packets contain a simple HTTP style header. The
 * packets are sent redundantly to get around packet loss. Devices respond to
 * M-SEARCH packets with HTTP-like UDP packets containing HTTP/1.1 200 OK
 * messages, which give similar information as the UDP NOTIFY packets.
 *
 * The above UDP packets advertise the (arbitrary) TCP ports that the
 * respective parties will listen to. The control point can then do a HTTP
 * SUBSCRIBE (something like an HTTP PUT) after which the device can do a
 * separate HTTP NOTIFY (also like an HTTP PUT) to do event messaging.
 *
 * The control point will also do HTTP GET of the "device file" listed in the
 * original UDP information from the device (see UPNP_WPS_DEVICE_XML_FILE
 * data), and based on this will do additional GETs... HTTP POSTs are done to
 * cause an action.
 *
 * Beyond some basic information in HTTP headers, additional information is in
 * the HTTP bodies, in a format set by the SOAP and XML standards, a markup
 * language related to HTML used for web pages. This language is intended to
 * provide the ultimate in self-documentation by providing a universal
 * namespace based on pseudo-URLs called URIs. Note that although a URI looks
 * like a URL (a web address), they are never accessed as such but are used
 * only as identifiers.
 *
 * The POST of a GetDeviceInfo gets information similar to what might be
 * obtained from a probe request or response on Wi-Fi. WPS messages M1-M8
 * are passed via a POST of a PutMessage; the M1-M8 WPS messages are converted
 * to a bin64 ascii representation for encapsulation. When proxying messages,
 * WLANEvent and PutWLANResponse are used.
 *
 * This of course glosses over a lot of details.
 */

#include "includes.h"

#include <assert.h>
#include <net/if.h>
#include <netdb.h>
#include <sys/ioctl.h>

#include "common.h"
#include "uuid.h"
#include "base64.h"
#include "wps.h"
#include "wps_i.h"
#include "wps_upnp.h"
#include "wps_upnp_i.h"


/*
 * UPnP allows a client ("control point") to send a server like us ("device")
 * a domain name for registration, and we are supposed to resolve it. This is
 * bad because, using the standard Linux library, we will stall the entire
 * hostapd waiting for resolution.
 *
 * The "correct" solution would be to use an event driven library for domain
 * name resolution such as "ares". However, this would increase code size
 * further. Since it is unlikely that we'll actually see such domain names, we
 * can just refuse to accept them.
 */
#define NO_DOMAIN_NAME_RESOLUTION 1  /* 1 to allow only dotted ip addresses */


/*
 * UPnP does not scale well. If we were in a room with thousands of control
 * points then potentially we could be expected to handle subscriptions for
 * each of them, which would exhaust our memory. So we must set a limit. In
 * practice we are unlikely to see more than one or two.
 */
#define MAX_SUBSCRIPTIONS 4    /* how many subscribing clients we handle */
#define MAX_ADDR_PER_SUBSCRIPTION 8


/* Write the current date/time per RFC */
void format_date(struct wpabuf *buf)
{
	const char *weekday_str = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
	const char *month_str = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0"
		"Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
	struct tm *date;
	time_t t;

	t = time(NULL);
	date = gmtime(&t);
	wpabuf_printf(buf, "%s, %02d %s %d %02d:%02d:%02d GMT",
		      &weekday_str[date->tm_wday * 4], date->tm_mday,
		      &month_str[date->tm_mon * 4], date->tm_year + 1900,
		      date->tm_hour, date->tm_min, date->tm_sec);
}


/***************************************************************************
 * UUIDs (unique identifiers)
 *
 * These are supposed to be unique in all the world.
 * Sometimes permanent ones are used, sometimes temporary ones
 * based on random numbers... there are different rules for valid content
 * of different types.
 * Each uuid is 16 bytes long.
 **************************************************************************/

/* uuid_make -- construct a random UUID
 * The UPnP documents don't seem to offer any guidelines as to which method to
 * use for constructing UUIDs for subscriptions. Presumably any method from
 * rfc4122 is good enough; I've chosen random number method.
 */
static void uuid_make(u8 uuid[UUID_LEN])
{
	os_get_random(uuid, UUID_LEN);

	/* Replace certain bits as specified in rfc4122 or X.667 */
	uuid[6] &= 0x0f; uuid[6] |= (4 << 4);   /* version 4 == random gen */
	uuid[8] &= 0x3f; uuid[8] |= 0x80;
}


/*
 * Subscriber address handling.
 * Since a subscriber may have an arbitrary number of addresses, we have to
 * add a bunch of code to handle them.
 *
 * Addresses are passed in text, and MAY be domain names instead of the (usual
 * and expected) dotted IP addresses. Resolving domain names consumes a lot of
 * resources. Worse, we are currently using the standard Linux getaddrinfo()
 * which will block the entire program until complete or timeout! The proper
 * solution would be to use the "ares" library or similar with more state
 * machine steps etc. or just disable domain name resolution by setting
 * NO_DOMAIN_NAME_RESOLUTION to 1 at top of this file.
 */

/* subscr_addr_delete -- delete single unlinked subscriber address
 * (be sure to unlink first if need be)
 */
static void subscr_addr_delete(struct subscr_addr *a)
{
	/*
	 * Note: do NOT free domain_and_port or path because they point to
	 * memory within the allocation of "a".
	 */
	os_free(a);
}


/* subscr_addr_unlink -- unlink subscriber address from linked list */
static void subscr_addr_unlink(struct subscription *s, struct subscr_addr *a)
{
	struct subscr_addr **listp = &s->addr_list;
	s->n_addr--;
	a->next->prev = a->prev;
	a->prev->next = a->next;
	if (*listp == a) {
		if (a == a->next) {
			/* last in queue */
			*listp = NULL;
			assert(s->n_addr == 0);
		} else {
			*listp = a->next;
		}
	}
}


/* subscr_addr_free_all -- unlink and delete list of subscriber addresses. */
static void subscr_addr_free_all(struct subscription *s)
{
	struct subscr_addr **listp = &s->addr_list;
	struct subscr_addr *a;
	while ((a = *listp) != NULL) {
		subscr_addr_unlink(s, a);
		subscr_addr_delete(a);
	}
}


/* subscr_addr_link -- add subscriber address to list of addresses */
static void subscr_addr_link(struct subscription *s, struct subscr_addr *a)
{
	struct subscr_addr **listp = &s->addr_list;
	s->n_addr++;
	if (*listp == NULL) {
		*listp = a->next = a->prev = a;
	} else {
		a->next = *listp;
		a->prev = (*listp)->prev;
		a->prev->next = a;
		a->next->prev = a;
	}
}


/* subscr_addr_add_url -- add address(es) for one url to subscription */
static void subscr_addr_add_url(struct subscription *s, const char *url)
{
	int alloc_len;
	char *scratch_mem = NULL;
	char *mem;
	char *domain_and_port;
	char *delim;
	char *path;
	char *domain;
	int port = 80;  /* port to send to (default is port 80) */
	struct addrinfo hints;
	struct addrinfo *result = NULL;
	struct addrinfo *rp;
	int rerr;
	struct subscr_addr *a = NULL;

	/* url MUST begin with http: */
	if (os_strncasecmp(url, "http://", 7))
		goto fail;
	url += 7;

	/* allocate memory for the extra stuff we need */
	alloc_len = (2 * (os_strlen(url) + 1));
	scratch_mem = os_zalloc(alloc_len);
	if (scratch_mem == NULL)
		goto fail;
	mem = scratch_mem;
	strcpy(mem, url);
	domain_and_port = mem;
	mem += 1 + os_strlen(mem);
	delim = os_strchr(domain_and_port, '/');
	if (delim) {
		*delim++ = 0;   /* null terminate domain and port */
		path = delim;
	} else {
		path = domain_and_port + os_strlen(domain_and_port);
	}
	domain = mem;
	strcpy(domain, domain_and_port);
	delim = strchr(domain, ':');
	if (delim) {
		*delim++ = 0;   /* null terminate domain */
		if (isdigit(*delim))
			port = atol(delim);
	}

	/*
	 * getaddrinfo does the right thing with dotted decimal notations, or
	 * will resolve domain names. Resolving domain names will unfortunately
	 * hang the entire program until it is resolved or it times out
	 * internal to getaddrinfo; fortunately we think that the use of actual
	 * domain names (vs. dotted decimal notations) should be uncommon.
	 */
	os_memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = AF_INET;      /* IPv4 */
	hints.ai_socktype = SOCK_STREAM;
#if NO_DOMAIN_NAME_RESOLUTION
	/* Suppress domain name resolutions that would halt
	 * the program for periods of time
	 */
	hints.ai_flags = AI_NUMERICHOST;
#else
	/* Allow domain name resolution. */
	hints.ai_flags = 0;
#endif
	hints.ai_protocol = 0;          /* Any protocol? */
	rerr = getaddrinfo(domain, NULL /* fill in port ourselves */,
			   &hints, &result);
	if (rerr) {
		wpa_printf(MSG_INFO, "WPS UPnP: Resolve error %d (%s) on: %s",
			   rerr, gai_strerror(rerr), domain);
		goto fail;
	}
	for (rp = result; rp; rp = rp->ai_next) {
		/* Limit no. of address to avoid denial of service attack */
		if (s->n_addr >= MAX_ADDR_PER_SUBSCRIPTION) {
			wpa_printf(MSG_INFO, "WPS UPnP: subscr_addr_add_url: "
				   "Ignoring excessive addresses");
			break;
		}

		a = os_zalloc(sizeof(*a) + alloc_len);
		if (a == NULL)
			continue;
		a->s = s;
		mem = (void *) (a + 1);
		a->domain_and_port = mem;
		strcpy(mem, domain_and_port);
		mem += 1 + strlen(mem);
		a->path = mem;
		if (path[0] != '/')
			*mem++ = '/';
		strcpy(mem, path);
		mem += 1 + strlen(mem);
		os_memcpy(&a->saddr, rp->ai_addr, sizeof(a->saddr));
		a->saddr.sin_port = htons(port);

		subscr_addr_link(s, a);
		a = NULL;       /* don't free it below */
	}

fail:
	if (result)
		freeaddrinfo(result);
	os_free(scratch_mem);
	os_free(a);
}


/* subscr_addr_list_create -- create list from urls in string.
 *      Each url is enclosed by angle brackets.
 */
static void subscr_addr_list_create(struct subscription *s,
				    const char *url_list)
{
	char *end;
	for (;;) {
		while (*url_list == ' ' || *url_list == '\t')
			url_list++;
		if (*url_list != '<')
			break;
		url_list++;
		end = os_strchr(url_list, '>');
		if (end == NULL)
			break;
		*end++ = 0;
		subscr_addr_add_url(s, url_list);
		url_list = end;
	}
}


int send_wpabuf(int fd, struct wpabuf *buf)
{
	wpa_printf(MSG_DEBUG, "WPS UPnP: Send %lu byte message",
		   (unsigned long) wpabuf_len(buf));
	errno = 0;
	if (write(fd, wpabuf_head(buf), wpabuf_len(buf)) !=
	    (int) wpabuf_len(buf)) {
		wpa_printf(MSG_ERROR, "WPS UPnP: Failed to send buffer: "
			   "errno=%d (%s)",
			   errno, strerror(errno));
		return -1;
	}

	return 0;
}


static void wpabuf_put_property(struct wpabuf *buf, const char *name,
				const char *value)
{
	wpabuf_put_str(buf, "<e:property>");
	wpabuf_printf(buf, "<%s>", name);
	if (value)
		wpabuf_put_str(buf, value);
	wpabuf_printf(buf, "</%s>", name);
	wpabuf_put_str(buf, "</e:property>\n");
}


/**
 * upnp_wps_device_send_event - Queue event messages for subscribers
 * @sm: WPS UPnP state machine from upnp_wps_device_init()
 *
 * This function queues the last WLANEvent to be sent for all currently
 * subscribed UPnP control points. sm->wlanevent must have been set with the
 * encoded data before calling this function.
 */
static void upnp_wps_device_send_event(struct upnp_wps_device_sm *sm)
{
	/* Enqueue event message for all subscribers */
	struct wpabuf *buf; /* holds event message */
	int buf_size = 0;
	struct subscription *s;
	/* Actually, utf-8 is the default, but it doesn't hurt to specify it */
	const char *format_head =
		"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
		"<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">\n";
	const char *format_tail = "</e:propertyset>\n";

	if (sm->subscriptions == NULL) {
		/* optimize */
		return;
	}

	/* Determine buffer size needed first */
	buf_size += os_strlen(format_head);
	buf_size += 50 + 2 * os_strlen("WLANEvent");
	if (sm->wlanevent)
		buf_size += os_strlen(sm->wlanevent);
	buf_size += os_strlen(format_tail);

	buf = wpabuf_alloc(buf_size);
	if (buf == NULL)
		return;
	wpabuf_put_str(buf, format_head);
	wpabuf_put_property(buf, "WLANEvent", sm->wlanevent);
	wpabuf_put_str(buf, format_tail);

	wpa_printf(MSG_MSGDUMP, "WPS UPnP: WLANEvent message:\n%s",
		   (char *) wpabuf_head(buf));

	s = sm->subscriptions;
	do {
		if (event_add(s, buf)) {
			struct subscription *s_old = s;
			wpa_printf(MSG_INFO, "WPS UPnP: Dropping "
				   "subscriber due to event backlog");
			s = s_old->next;
			subscription_unlink(s_old);
			subscription_destroy(s_old);
		} else {
			s = s->next;
		}
	} while (s != sm->subscriptions);

	wpabuf_free(buf);
}


/*
 * Event subscription (subscriber machines register with us to receive event
 * messages).
 * This is the result of an incoming HTTP over TCP SUBSCRIBE request.
 */

/* subscription_unlink -- remove from the active list */
void subscription_unlink(struct subscription *s)
{
	struct upnp_wps_device_sm *sm = s->sm;

	if (s->next == s) {
		/* only one? */
		sm->subscriptions = NULL;
	} else  {
		if (sm->subscriptions == s)
			sm->subscriptions = s->next;
		s->next->prev = s->prev;
		s->prev->next = s->next;
	}
	sm->n_subscriptions--;
}


/* subscription_link_to_end -- link to end of active list
 * (should have high expiry time!)
 */
static void subscription_link_to_end(struct subscription *s)
{
	struct upnp_wps_device_sm *sm = s->sm;

	if (sm->subscriptions) {
		s->next = sm->subscriptions;
		s->prev = s->next->prev;
		s->prev->next = s;
		s->next->prev = s;
	} else {
		sm->subscriptions = s->next = s->prev = s;
	}
	sm->n_subscriptions++;
}


/* subscription_destroy -- destroy an unlinked subscription
 * Be sure to unlink first if necessary.
 */
void subscription_destroy(struct subscription *s)
{
	wpa_printf(MSG_DEBUG, "WPS UPnP: Destroy subscription %p", s);
	if (s->addr_list)
		subscr_addr_free_all(s);
	event_delete_all(s);
	os_free(s);
}


/* subscription_list_age -- remove expired subscriptions */
static void subscription_list_age(struct upnp_wps_device_sm *sm, time_t now)
{
	struct subscription *s;
	while ((s = sm->subscriptions) != NULL && s->timeout_time < now) {
		wpa_printf(MSG_DEBUG, "WPS UPnP: Removing aged subscription");
		subscription_unlink(s);
		subscription_destroy(s);
	}
}


/* subscription_find -- return existing subscription matching uuid, if any
 * returns NULL if not found
 */
struct subscription * subscription_find(struct upnp_wps_device_sm *sm,
					const u8 uuid[UUID_LEN])
{
	struct subscription *s0 = sm->subscriptions;
	struct subscription *s = s0;

	if (s0 == NULL)
		return NULL;
	do {
		if (os_memcmp(s->uuid, uuid, UUID_LEN) == 0)
			return s; /* Found match */
		s = s->next;
	} while (s != s0);

	return NULL;
}


/* subscription_first_event -- send format/queue event that is automatically
 * sent on a new subscription.
 */
static int subscription_first_event(struct subscription *s)
{
	/*
	 * Actually, utf-8 is the default, but it doesn't hurt to specify it.
	 *
	 * APStatus is apparently a bit set,
	 * 0x1 = configuration change (but is always set?)
	 * 0x10 = ap is locked
	 *
	 * Per UPnP spec, we send out the last value of each variable, even
	 * for WLANEvent, whatever it was.
	 */
	char *wlan_event;
	struct wpabuf *buf;
	int ap_status = 1;      /* TODO: add 0x10 if access point is locked */
	const char *head =
		"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
		"<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">\n";
	const char *tail = "</e:propertyset>\n";
	char txt[10];

	wlan_event = s->sm->wlanevent;
	if (wlan_event == NULL || *wlan_event == '\0') {
		wpa_printf(MSG_DEBUG, "WPS UPnP: WLANEvent not known for "
			   "initial event message");
		wlan_event = "";
	}
	buf = wpabuf_alloc(500 + os_strlen(wlan_event));
	if (buf == NULL)
		return 1;

	wpabuf_put_str(buf, head);
	wpabuf_put_property(buf, "STAStatus", "1");
	os_snprintf(txt, sizeof(txt), "%d", ap_status);
	wpabuf_put_property(buf, "APStatus", txt);
	if (*wlan_event)
		wpabuf_put_property(buf, "WLANEvent", wlan_event);
	wpabuf_put_str(buf, tail);

	if (event_add(s, buf)) {
		wpabuf_free(buf);
		return 1;
	}
	wpabuf_free(buf);

	return 0;
}


/**
 * subscription_start - Rremember a UPnP control point to send events to.
 * @sm: WPS UPnP state machine from upnp_wps_device_init()
 * @callback_urls: malloc' mem given to the subscription
 * Returns: %NULL on error, or pointer to new subscription structure.
 */
struct subscription * subscription_start(struct upnp_wps_device_sm *sm,
					 char *callback_urls)
{
	struct subscription *s;
	time_t now = time(NULL);
	time_t expire = now + UPNP_SUBSCRIBE_SEC;

	/* Get rid of expired subscriptions so we have room */
	subscription_list_age(sm, now);

	/* If too many subscriptions, remove oldest */
	if (sm->n_subscriptions >= MAX_SUBSCRIPTIONS) {
		s = sm->subscriptions;
		wpa_printf(MSG_INFO, "WPS UPnP: Too many subscriptions, "
			   "trashing oldest");
		subscription_unlink(s);
		subscription_destroy(s);
	}

	s = os_zalloc(sizeof(*s));
	if (s == NULL)
		return NULL;

	s->sm = sm;
	s->timeout_time = expire;
	uuid_make(s->uuid);
	subscr_addr_list_create(s, callback_urls);
	/* Add to end of list, since it has the highest expiration time */
	subscription_link_to_end(s);
	/* Queue up immediate event message (our last event)
	 * as required by UPnP spec.
	 */
	if (subscription_first_event(s)) {
		wpa_printf(MSG_INFO, "WPS UPnP: Dropping subscriber due to "
			   "event backlog");
		subscription_unlink(s);
		subscription_destroy(s);
		return NULL;
	}
	wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription %p started with %s",
		   s, callback_urls);
	os_free(callback_urls);
	/* Schedule sending this */
	event_send_all_later(sm);
	return s;
}


/* subscription_renew -- find subscription and reset timeout */
struct subscription * subscription_renew(struct upnp_wps_device_sm *sm,
					 const u8 uuid[UUID_LEN])
{
	time_t now = time(NULL);
	time_t expire = now + UPNP_SUBSCRIBE_SEC;
	struct subscription *s = subscription_find(sm, uuid);
	if (s == NULL)
		return NULL;
	wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription renewed");
	subscription_unlink(s);
	s->timeout_time = expire;
	/* add back to end of list, since it now has highest expiry */
	subscription_link_to_end(s);
	return s;
}


/**
 * upnp_wps_device_send_wlan_event - Event notification
 * @sm: WPS UPnP state machine from upnp_wps_device_init()
 * @from_mac_addr: Source (Enrollee) MAC address for the event
 * @ev_type: Event type
 * @msg: Event data
 * Returns: 0 on success, -1 on failure
 *
 * Tell external Registrars (UPnP control points) that something happened. In
 * particular, events include WPS messages from clients that are proxied to
 * external Registrars.
 */
int upnp_wps_device_send_wlan_event(struct upnp_wps_device_sm *sm,
				    const u8 from_mac_addr[ETH_ALEN],
				    enum upnp_wps_wlanevent_type ev_type,
				    const struct wpabuf *msg)
{
	int ret = -1;
	char type[2];
	const u8 *mac = from_mac_addr;
	char mac_text[18];
	u8 *raw = NULL;
	size_t raw_len;
	char *val;
	size_t val_len;
	int pos = 0;

	if (!sm)
		goto fail;

	os_snprintf(type, sizeof(type), "%1u", ev_type);

	raw_len = 1 + 17 + (msg ? wpabuf_len(msg) : 0);
	raw = os_zalloc(raw_len);
	if (!raw)
		goto fail;

	*(raw + pos) = (u8) ev_type;
	pos += 1;
	os_snprintf(mac_text, sizeof(mac_text), MACSTR, MAC2STR(mac));
	wpa_printf(MSG_DEBUG, "WPS UPnP: Proxying WLANEvent from %s",
		   mac_text);
	os_memcpy(raw + pos, mac_text, 17);
	pos += 17;
	if (msg) {
		os_memcpy(raw + pos, wpabuf_head(msg), wpabuf_len(msg));
		pos += wpabuf_len(msg);
	}
	raw_len = pos;

	val = (char *) base64_encode(raw, raw_len, &val_len);
	if (val == NULL)
		goto fail;

	os_free(sm->wlanevent);
	sm->wlanevent = val;
	upnp_wps_device_send_event(sm);

	ret = 0;

fail:
	os_free(raw);

	return ret;
}


/**
 * get_netif_info - Get hw and IP addresses for network device
 * @net_if: Selected network interface name
 * @ip_addr: Buffer for returning IP address in network byte order
 * @ip_addr_text: Buffer for returning a pointer to allocated IP address text
 * @mac: Buffer for returning MAC address
 * @mac_addr_text: Buffer for returning allocated MAC address text
 * Returns: 0 on success, -1 on failure
 */
static int get_netif_info(const char *net_if, unsigned *ip_addr,
			  char **ip_addr_text, u8 mac[ETH_ALEN],
			  char **mac_addr_text)
{
	struct ifreq req;
	int sock = -1;
	struct sockaddr_in *addr;
	struct in_addr in_addr;

	*ip_addr_text = os_zalloc(16);
	*mac_addr_text = os_zalloc(18);
	if (*ip_addr_text == NULL || *mac_addr_text == NULL)
		goto fail;

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0)
		goto fail;

	os_strlcpy(req.ifr_name, net_if, sizeof(req.ifr_name));
	if (ioctl(sock, SIOCGIFADDR, &req) < 0) {
		wpa_printf(MSG_ERROR, "WPS UPnP: SIOCGIFADDR failed: %d (%s)",
			   errno, strerror(errno));
		goto fail;
	}
	addr = (void *) &req.ifr_addr;
	*ip_addr = addr->sin_addr.s_addr;
	in_addr.s_addr = *ip_addr;
	os_snprintf(*ip_addr_text, 16, "%s", inet_ntoa(in_addr));

	os_strlcpy(req.ifr_name, net_if, sizeof(req.ifr_name));
	if (ioctl(sock, SIOCGIFHWADDR, &req) < 0) {
		wpa_printf(MSG_ERROR, "WPS UPnP: SIOCGIFHWADDR failed: "
			   "%d (%s)", errno, strerror(errno));
		goto fail;
	}
	os_memcpy(mac, req.ifr_addr.sa_data, 6);
	os_snprintf(*mac_addr_text, 18, MACSTR, MAC2STR(req.ifr_addr.sa_data));

	close(sock);
	return 0;

fail:
	if (sock >= 0)
		close(sock);
	os_free(*ip_addr_text);
	*ip_addr_text = NULL;
	os_free(*mac_addr_text);
	*mac_addr_text = NULL;
	return -1;
}


/**
 * upnp_wps_device_stop - Stop WPS UPnP operations on an interface
 * @sm: WPS UPnP state machine from upnp_wps_device_init()
 */
void upnp_wps_device_stop(struct upnp_wps_device_sm *sm)
{
	if (!sm || !sm->started)
		return;

	wpa_printf(MSG_DEBUG, "WPS UPnP: Stop device");
	web_listener_stop(sm);
	while (sm->web_connections)
		web_connection_stop(sm->web_connections);
	while (sm->msearch_replies)
		msearchreply_state_machine_stop(sm->msearch_replies);
	while (sm->subscriptions)  {
		struct subscription *s = sm->subscriptions;
		subscription_unlink(s);
		subscription_destroy(s);
	}

	advertisement_state_machine_stop(sm);
	/* TODO: send byebye notifications */

	event_send_stop_all(sm);
	os_free(sm->wlanevent);
	sm->wlanevent = NULL;
	os_free(sm->net_if);
	sm->net_if = NULL;
	os_free(sm->mac_addr_text);
	sm->mac_addr_text = NULL;
	os_free(sm->ip_addr_text);
	sm->ip_addr_text = NULL;
	if (sm->multicast_sd >= 0)
		close(sm->multicast_sd);
	sm->multicast_sd = -1;
	ssdp_listener_stop(sm);

	sm->started = 0;
}


/**
 * upnp_wps_device_start - Start WPS UPnP operations on an interface
 * @sm: WPS UPnP state machine from upnp_wps_device_init()
 * @net_if: Selected network interface name
 * Returns: 0 on success, -1 on failure
 */
int upnp_wps_device_start(struct upnp_wps_device_sm *sm, char *net_if)
{
	if (!sm || !net_if)
		return -1;

	if (sm->started)
		upnp_wps_device_stop(sm);

	sm->net_if = strdup(net_if);
	sm->multicast_sd = -1;
	sm->ssdp_sd = -1;
	sm->started = 1;
	sm->advertise_count = 0;

	/* Fix up linux multicast handling */
	if (add_ssdp_network(net_if))
		goto fail;

	/* Determine which IP and mac address we're using */
	if (get_netif_info(net_if,
			   &sm->ip_addr, &sm->ip_addr_text,
			   sm->mac_addr, &sm->mac_addr_text)) {
		wpa_printf(MSG_INFO, "WPS UPnP: Could not get IP/MAC address "
			   "for %s. Does it have IP address?", net_if);
		goto fail;
	}

	/* Listen for incoming TCP connections so that others
	 * can fetch our "xml files" from us.
	 */
	if (web_listener_start(sm))
		goto fail;

	/* Set up for receiving discovery (UDP) packets */
	if (ssdp_listener_start(sm))
		goto fail;

	/* Set up for sending multicast */
	if (ssdp_open_multicast(sm) < 0)
		goto fail;

	/*
	 * Broadcast NOTIFY messages to let the world know we exist.
	 * This is done via a state machine since the messages should not be
	 * all sent out at once.
	 */
	if (advertisement_state_machine_start(sm))
		goto fail;

	return 0;

fail:
	upnp_wps_device_stop(sm);
	return -1;
}


/**
 * upnp_wps_device_deinit - Deinitialize WPS UPnP
 * @sm: WPS UPnP state machine from upnp_wps_device_init()
 */
void upnp_wps_device_deinit(struct upnp_wps_device_sm *sm)
{
	if (!sm)
		return;

	upnp_wps_device_stop(sm);

	if (sm->peer.wps)
		wps_deinit(sm->peer.wps);
	os_free(sm->root_dir);
	os_free(sm->desc_url);
	os_free(sm->ctx);
	os_free(sm);
}


/**
 * upnp_wps_device_init - Initialize WPS UPnP
 * @ctx: callback table; we must eventually free it
 * @wps: Pointer to longterm WPS context
 * @priv: External context data that will be used in callbacks
 * Returns: WPS UPnP state or %NULL on failure
 */
struct upnp_wps_device_sm *
upnp_wps_device_init(struct upnp_wps_device_ctx *ctx, struct wps_context *wps,
		     void *priv)
{
	struct upnp_wps_device_sm *sm;

	sm = os_zalloc(sizeof(*sm));
	if (!sm) {
		wpa_printf(MSG_ERROR, "WPS UPnP: upnp_wps_device_init failed");
		return NULL;
	}

	sm->ctx = ctx;
	sm->wps = wps;
	sm->priv = priv;

	return sm;
}


/**
 * upnp_wps_subscribers - Check whether there are any event subscribers
 * @sm: WPS UPnP state machine from upnp_wps_device_init()
 * Returns: 0 if no subscribers, 1 if subscribers
 */
int upnp_wps_subscribers(struct upnp_wps_device_sm *sm)
{
	return sm->subscriptions != NULL;
}