WPS: Convert struct subscr_addr to use dl_list
[libeap.git] / src / wps / wps_upnp_web.c
1 /*
2  * UPnP WPS Device - Web connections
3  * Copyright (c) 2000-2003 Intel Corporation
4  * Copyright (c) 2006-2007 Sony Corporation
5  * Copyright (c) 2008-2009 Atheros Communications
6  * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
7  *
8  * See wps_upnp.c for more details on licensing and code history.
9  */
10
11 #include "includes.h"
12
13 #include "common.h"
14 #include "base64.h"
15 #include "uuid.h"
16 #include "httpread.h"
17 #include "http_server.h"
18 #include "wps_i.h"
19 #include "wps_upnp.h"
20 #include "wps_upnp_i.h"
21 #include "upnp_xml.h"
22
23 /***************************************************************************
24  * Web connections (we serve pages of info about ourselves, handle
25  * requests, etc. etc.).
26  **************************************************************************/
27
28 #define WEB_CONNECTION_TIMEOUT_SEC 30   /* Drop web connection after t.o. */
29 #define WEB_CONNECTION_MAX_READ 8000    /* Max we'll read for TCP request */
30 #define MAX_WEB_CONNECTIONS 10          /* max simultaneous web connects */
31
32
33 static const char *urn_wfawlanconfig =
34         "urn:schemas-wifialliance-org:service:WFAWLANConfig:1";
35 static const char *http_server_hdr =
36         "Server: unspecified, UPnP/1.0, unspecified\r\n";
37 static const char *http_connection_close =
38         "Connection: close\r\n";
39
40 /*
41  * "Files" that we serve via HTTP. The format of these files is given by
42  * WFA WPS specifications. Extra white space has been removed to save space.
43  */
44
45 static const char wps_scpd_xml[] =
46 "<?xml version=\"1.0\"?>\n"
47 "<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">\n"
48 "<specVersion><major>1</major><minor>0</minor></specVersion>\n"
49 "<actionList>\n"
50 "<action>\n"
51 "<name>GetDeviceInfo</name>\n"
52 "<argumentList>\n"
53 "<argument>\n"
54 "<name>NewDeviceInfo</name>\n"
55 "<direction>out</direction>\n"
56 "<relatedStateVariable>DeviceInfo</relatedStateVariable>\n"
57 "</argument>\n"
58 "</argumentList>\n"
59 "</action>\n"
60 "<action>\n"
61 "<name>PutMessage</name>\n"
62 "<argumentList>\n"
63 "<argument>\n"
64 "<name>NewInMessage</name>\n"
65 "<direction>in</direction>\n"
66 "<relatedStateVariable>InMessage</relatedStateVariable>\n"
67 "</argument>\n"
68 "<argument>\n"
69 "<name>NewOutMessage</name>\n"
70 "<direction>out</direction>\n"
71 "<relatedStateVariable>OutMessage</relatedStateVariable>\n"
72 "</argument>\n"
73 "</argumentList>\n"
74 "</action>\n"
75 "<action>\n"
76 "<name>PutWLANResponse</name>\n"
77 "<argumentList>\n"
78 "<argument>\n"
79 "<name>NewMessage</name>\n"
80 "<direction>in</direction>\n"
81 "<relatedStateVariable>Message</relatedStateVariable>\n"
82 "</argument>\n"
83 "<argument>\n"
84 "<name>NewWLANEventType</name>\n"
85 "<direction>in</direction>\n"
86 "<relatedStateVariable>WLANEventType</relatedStateVariable>\n"
87 "</argument>\n"
88 "<argument>\n"
89 "<name>NewWLANEventMAC</name>\n"
90 "<direction>in</direction>\n"
91 "<relatedStateVariable>WLANEventMAC</relatedStateVariable>\n"
92 "</argument>\n"
93 "</argumentList>\n"
94 "</action>\n"
95 "<action>\n"
96 "<name>SetSelectedRegistrar</name>\n"
97 "<argumentList>\n"
98 "<argument>\n"
99 "<name>NewMessage</name>\n"
100 "<direction>in</direction>\n"
101 "<relatedStateVariable>Message</relatedStateVariable>\n"
102 "</argument>\n"
103 "</argumentList>\n"
104 "</action>\n"
105 "</actionList>\n"
106 "<serviceStateTable>\n"
107 "<stateVariable sendEvents=\"no\">\n"
108 "<name>Message</name>\n"
109 "<dataType>bin.base64</dataType>\n"
110 "</stateVariable>\n"
111 "<stateVariable sendEvents=\"no\">\n"
112 "<name>InMessage</name>\n"
113 "<dataType>bin.base64</dataType>\n"
114 "</stateVariable>\n"
115 "<stateVariable sendEvents=\"no\">\n"
116 "<name>OutMessage</name>\n"
117 "<dataType>bin.base64</dataType>\n"
118 "</stateVariable>\n"
119 "<stateVariable sendEvents=\"no\">\n"
120 "<name>DeviceInfo</name>\n"
121 "<dataType>bin.base64</dataType>\n"
122 "</stateVariable>\n"
123 "<stateVariable sendEvents=\"yes\">\n"
124 "<name>APStatus</name>\n"
125 "<dataType>ui1</dataType>\n"
126 "</stateVariable>\n"
127 "<stateVariable sendEvents=\"yes\">\n"
128 "<name>STAStatus</name>\n"
129 "<dataType>ui1</dataType>\n"
130 "</stateVariable>\n"
131 "<stateVariable sendEvents=\"yes\">\n"
132 "<name>WLANEvent</name>\n"
133 "<dataType>bin.base64</dataType>\n"
134 "</stateVariable>\n"
135 "<stateVariable sendEvents=\"no\">\n"
136 "<name>WLANEventType</name>\n"
137 "<dataType>ui1</dataType>\n"
138 "</stateVariable>\n"
139 "<stateVariable sendEvents=\"no\">\n"
140 "<name>WLANEventMAC</name>\n"
141 "<dataType>string</dataType>\n"
142 "</stateVariable>\n"
143 "<stateVariable sendEvents=\"no\">\n"
144 "<name>WLANResponse</name>\n"
145 "<dataType>bin.base64</dataType>\n"
146 "</stateVariable>\n"
147 "</serviceStateTable>\n"
148 "</scpd>\n"
149 ;
150
151
152 static const char *wps_device_xml_prefix =
153         "<?xml version=\"1.0\"?>\n"
154         "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n"
155         "<specVersion>\n"
156         "<major>1</major>\n"
157         "<minor>0</minor>\n"
158         "</specVersion>\n"
159         "<device>\n"
160         "<deviceType>urn:schemas-wifialliance-org:device:WFADevice:1"
161         "</deviceType>\n";
162
163 static const char *wps_device_xml_postfix =
164         "<serviceList>\n"
165         "<service>\n"
166         "<serviceType>urn:schemas-wifialliance-org:service:WFAWLANConfig:1"
167         "</serviceType>\n"
168         "<serviceId>urn:wifialliance-org:serviceId:WFAWLANConfig1</serviceId>"
169         "\n"
170         "<SCPDURL>" UPNP_WPS_SCPD_XML_FILE "</SCPDURL>\n"
171         "<controlURL>" UPNP_WPS_DEVICE_CONTROL_FILE "</controlURL>\n"
172         "<eventSubURL>" UPNP_WPS_DEVICE_EVENT_FILE "</eventSubURL>\n"
173         "</service>\n"
174         "</serviceList>\n"
175         "</device>\n"
176         "</root>\n";
177
178
179 /* format_wps_device_xml -- produce content of "file" wps_device.xml
180  * (UPNP_WPS_DEVICE_XML_FILE)
181  */
182 static void format_wps_device_xml(struct upnp_wps_device_sm *sm,
183                                   struct wpabuf *buf)
184 {
185         const char *s;
186         char uuid_string[80];
187
188         wpabuf_put_str(buf, wps_device_xml_prefix);
189
190         /*
191          * Add required fields with default values if not configured. Add
192          * optional and recommended fields only if configured.
193          */
194         s = sm->wps->friendly_name;
195         s = ((s && *s) ? s : "WPS Access Point");
196         xml_add_tagged_data(buf, "friendlyName", s);
197
198         s = sm->wps->dev.manufacturer;
199         s = ((s && *s) ? s : "");
200         xml_add_tagged_data(buf, "manufacturer", s);
201
202         if (sm->wps->manufacturer_url)
203                 xml_add_tagged_data(buf, "manufacturerURL",
204                                     sm->wps->manufacturer_url);
205
206         if (sm->wps->model_description)
207                 xml_add_tagged_data(buf, "modelDescription",
208                                     sm->wps->model_description);
209
210         s = sm->wps->dev.model_name;
211         s = ((s && *s) ? s : "");
212         xml_add_tagged_data(buf, "modelName", s);
213
214         if (sm->wps->dev.model_number)
215                 xml_add_tagged_data(buf, "modelNumber",
216                                     sm->wps->dev.model_number);
217
218         if (sm->wps->model_url)
219                 xml_add_tagged_data(buf, "modelURL", sm->wps->model_url);
220
221         if (sm->wps->dev.serial_number)
222                 xml_add_tagged_data(buf, "serialNumber",
223                                     sm->wps->dev.serial_number);
224
225         uuid_bin2str(sm->wps->uuid, uuid_string, sizeof(uuid_string));
226         s = uuid_string;
227         /* Need "uuid:" prefix, thus we can't use xml_add_tagged_data()
228          * easily...
229          */
230         wpabuf_put_str(buf, "<UDN>uuid:");
231         xml_data_encode(buf, s, os_strlen(s));
232         wpabuf_put_str(buf, "</UDN>\n");
233
234         if (sm->wps->upc)
235                 xml_add_tagged_data(buf, "UPC", sm->wps->upc);
236
237         wpabuf_put_str(buf, wps_device_xml_postfix);
238 }
239
240
241 static void http_put_reply_code(struct wpabuf *buf, enum http_reply_code code)
242 {
243         wpabuf_put_str(buf, "HTTP/1.1 ");
244         switch (code) {
245         case HTTP_OK:
246                 wpabuf_put_str(buf, "200 OK\r\n");
247                 break;
248         case HTTP_BAD_REQUEST:
249                 wpabuf_put_str(buf, "400 Bad request\r\n");
250                 break;
251         case HTTP_PRECONDITION_FAILED:
252                 wpabuf_put_str(buf, "412 Precondition failed\r\n");
253                 break;
254         case HTTP_UNIMPLEMENTED:
255                 wpabuf_put_str(buf, "501 Unimplemented\r\n");
256                 break;
257         case HTTP_INTERNAL_SERVER_ERROR:
258         default:
259                 wpabuf_put_str(buf, "500 Internal server error\r\n");
260                 break;
261         }
262 }
263
264
265 static void http_put_date(struct wpabuf *buf)
266 {
267         wpabuf_put_str(buf, "Date: ");
268         format_date(buf);
269         wpabuf_put_str(buf, "\r\n");
270 }
271
272
273 static void http_put_empty(struct wpabuf *buf, enum http_reply_code code)
274 {
275         http_put_reply_code(buf, code);
276         wpabuf_put_str(buf, http_server_hdr);
277         wpabuf_put_str(buf, http_connection_close);
278         wpabuf_put_str(buf, "Content-Length: 0\r\n"
279                        "\r\n");
280 }
281
282
283 /* Given that we have received a header w/ GET, act upon it
284  *
285  * Format of GET (case-insensitive):
286  *
287  * First line must be:
288  *      GET /<file> HTTP/1.1
289  * Since we don't do anything fancy we just ignore other lines.
290  *
291  * Our response (if no error) which includes only required lines is:
292  * HTTP/1.1 200 OK
293  * Connection: close
294  * Content-Type: text/xml
295  * Date: <rfc1123-date>
296  *
297  * Header lines must end with \r\n
298  * Per RFC 2616, content-length: is not required but connection:close
299  * would appear to be required (given that we will be closing it!).
300  */
301 static void web_connection_parse_get(struct upnp_wps_device_sm *sm,
302                                      struct http_request *hreq, char *filename)
303 {
304         struct wpabuf *buf; /* output buffer, allocated */
305         char *put_length_here;
306         char *body_start;
307         enum {
308                 GET_DEVICE_XML_FILE,
309                 GET_SCPD_XML_FILE
310         } req;
311         size_t extra_len = 0;
312         int body_length;
313         char len_buf[10];
314
315         /*
316          * It is not required that filenames be case insensitive but it is
317          * allowed and cannot hurt here.
318          */
319         if (filename == NULL)
320                 filename = "(null)"; /* just in case */
321         if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) {
322                 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML");
323                 req = GET_DEVICE_XML_FILE;
324                 extra_len = 3000;
325                 if (sm->wps->friendly_name)
326                         extra_len += os_strlen(sm->wps->friendly_name);
327                 if (sm->wps->manufacturer_url)
328                         extra_len += os_strlen(sm->wps->manufacturer_url);
329                 if (sm->wps->model_description)
330                         extra_len += os_strlen(sm->wps->model_description);
331                 if (sm->wps->model_url)
332                         extra_len += os_strlen(sm->wps->model_url);
333                 if (sm->wps->upc)
334                         extra_len += os_strlen(sm->wps->upc);
335         } else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) {
336                 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for SCPD XML");
337                 req = GET_SCPD_XML_FILE;
338                 extra_len = os_strlen(wps_scpd_xml);
339         } else {
340                 /* File not found */
341                 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET file not found: %s",
342                            filename);
343                 buf = wpabuf_alloc(200);
344                 if (buf == NULL) {
345                         http_request_deinit(hreq);
346                         return;
347                 }
348                 wpabuf_put_str(buf,
349                                "HTTP/1.1 404 Not Found\r\n"
350                                "Connection: close\r\n");
351
352                 http_put_date(buf);
353
354                 /* terminating empty line */
355                 wpabuf_put_str(buf, "\r\n");
356
357                 goto send_buf;
358         }
359
360         buf = wpabuf_alloc(1000 + extra_len);
361         if (buf == NULL) {
362                 http_request_deinit(hreq);
363                 return;
364         }
365
366         wpabuf_put_str(buf,
367                        "HTTP/1.1 200 OK\r\n"
368                        "Content-Type: text/xml; charset=\"utf-8\"\r\n");
369         wpabuf_put_str(buf, "Server: Unspecified, UPnP/1.0, Unspecified\r\n");
370         wpabuf_put_str(buf, "Connection: close\r\n");
371         wpabuf_put_str(buf, "Content-Length: ");
372         /*
373          * We will paste the length in later, leaving some extra whitespace.
374          * HTTP code is supposed to be tolerant of extra whitespace.
375          */
376         put_length_here = wpabuf_put(buf, 0);
377         wpabuf_put_str(buf, "        \r\n");
378
379         http_put_date(buf);
380
381         /* terminating empty line */
382         wpabuf_put_str(buf, "\r\n");
383
384         body_start = wpabuf_put(buf, 0);
385
386         switch (req) {
387         case GET_DEVICE_XML_FILE:
388                 format_wps_device_xml(sm, buf);
389                 break;
390         case GET_SCPD_XML_FILE:
391                 wpabuf_put_str(buf, wps_scpd_xml);
392                 break;
393         }
394
395         /* Now patch in the content length at the end */
396         body_length = (char *) wpabuf_put(buf, 0) - body_start;
397         os_snprintf(len_buf, 10, "%d", body_length);
398         os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
399
400 send_buf:
401         http_request_send_and_deinit(hreq, buf);
402 }
403
404
405 static enum http_reply_code
406 web_process_get_device_info(struct upnp_wps_device_sm *sm,
407                             struct wpabuf **reply, const char **replyname)
408 {
409         static const char *name = "NewDeviceInfo";
410         struct wps_config cfg;
411         struct upnp_wps_peer *peer = &sm->peer;
412
413         wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo");
414
415         /*
416          * Request for DeviceInfo, i.e., M1 TLVs. This is a start of WPS
417          * registration over UPnP with the AP acting as an Enrollee. It should
418          * be noted that this is frequently used just to get the device data,
419          * i.e., there may not be any intent to actually complete the
420          * registration.
421          */
422
423         if (peer->wps)
424                 wps_deinit(peer->wps);
425
426         os_memset(&cfg, 0, sizeof(cfg));
427         cfg.wps = sm->wps;
428         cfg.pin = (u8 *) sm->ctx->ap_pin;
429         cfg.pin_len = os_strlen(sm->ctx->ap_pin);
430         peer->wps = wps_init(&cfg);
431         if (peer->wps) {
432                 enum wsc_op_code op_code;
433                 *reply = wps_get_msg(peer->wps, &op_code);
434                 if (*reply == NULL) {
435                         wps_deinit(peer->wps);
436                         peer->wps = NULL;
437                 }
438         } else
439                 *reply = NULL;
440         if (*reply == NULL) {
441                 wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo");
442                 return HTTP_INTERNAL_SERVER_ERROR;
443         }
444         *replyname = name;
445         return HTTP_OK;
446 }
447
448
449 static enum http_reply_code
450 web_process_put_message(struct upnp_wps_device_sm *sm, char *data,
451                         struct wpabuf **reply, const char **replyname)
452 {
453         struct wpabuf *msg;
454         static const char *name = "NewOutMessage";
455         enum http_reply_code ret;
456         enum wps_process_res res;
457         enum wsc_op_code op_code;
458
459         /*
460          * PutMessage is used by external UPnP-based Registrar to perform WPS
461          * operation with the access point itself; as compared with
462          * PutWLANResponse which is for proxying.
463          */
464         wpa_printf(MSG_DEBUG, "WPS UPnP: PutMessage");
465         msg = xml_get_base64_item(data, "NewInMessage", &ret);
466         if (msg == NULL)
467                 return ret;
468         res = wps_process_msg(sm->peer.wps, WSC_UPnP, msg);
469         if (res == WPS_FAILURE)
470                 *reply = NULL;
471         else
472                 *reply = wps_get_msg(sm->peer.wps, &op_code);
473         wpabuf_free(msg);
474         if (*reply == NULL)
475                 return HTTP_INTERNAL_SERVER_ERROR;
476         *replyname = name;
477         return HTTP_OK;
478 }
479
480
481 static enum http_reply_code
482 web_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data,
483                               struct wpabuf **reply, const char **replyname)
484 {
485         struct wpabuf *msg;
486         enum http_reply_code ret;
487         u8 macaddr[ETH_ALEN];
488         int ev_type;
489         int type;
490         char *val;
491
492         /*
493          * External UPnP-based Registrar is passing us a message to be proxied
494          * over to a Wi-Fi -based client of ours.
495          */
496
497         wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse");
498         msg = xml_get_base64_item(data, "NewMessage", &ret);
499         if (msg == NULL)
500                 return ret;
501         val = xml_get_first_item(data, "NewWLANEventType");
502         if (val == NULL) {
503                 wpabuf_free(msg);
504                 return UPNP_ARG_VALUE_INVALID;
505         }
506         ev_type = atol(val);
507         os_free(val);
508         val = xml_get_first_item(data, "NewWLANEventMAC");
509         if (val == NULL || hwaddr_aton(val, macaddr)) {
510                 wpabuf_free(msg);
511                 os_free(val);
512                 return UPNP_ARG_VALUE_INVALID;
513         }
514         os_free(val);
515         if (ev_type == UPNP_WPS_WLANEVENT_TYPE_EAP) {
516                 struct wps_parse_attr attr;
517                 if (wps_parse_msg(msg, &attr) < 0 ||
518                     attr.msg_type == NULL)
519                         type = -1;
520                 else
521                         type = *attr.msg_type;
522                 wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type);
523         } else
524                 type = -1;
525         if (!sm->ctx->rx_req_put_wlan_response ||
526             sm->ctx->rx_req_put_wlan_response(sm->priv, ev_type, macaddr, msg,
527                                               type)) {
528                 wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->"
529                            "rx_req_put_wlan_response");
530                 wpabuf_free(msg);
531                 return HTTP_INTERNAL_SERVER_ERROR;
532         }
533         wpabuf_free(msg);
534         *replyname = NULL;
535         *reply = NULL;
536         return HTTP_OK;
537 }
538
539
540 static int find_er_addr(struct subscription *s, struct sockaddr_in *cli)
541 {
542         struct subscr_addr *a;
543
544         dl_list_for_each(a, &s->addr_list, struct subscr_addr, list) {
545                 if (cli->sin_addr.s_addr == a->saddr.sin_addr.s_addr)
546                         return 1;
547         }
548         return 0;
549 }
550
551
552 static struct subscription * find_er(struct upnp_wps_device_sm *sm,
553                                      struct sockaddr_in *cli)
554 {
555         struct subscription *s;
556
557         s = sm->subscriptions;
558         while (s) {
559                 if (find_er_addr(s, cli))
560                         return s;
561                 s = s->next;
562                 if (s == sm->subscriptions)
563                         break;
564         }
565
566         return NULL;
567 }
568
569
570 static enum http_reply_code
571 web_process_set_selected_registrar(struct upnp_wps_device_sm *sm,
572                                    struct sockaddr_in *cli, char *data,
573                                    struct wpabuf **reply,
574                                    const char **replyname)
575 {
576         struct wpabuf *msg;
577         enum http_reply_code ret;
578         struct subscription *s;
579
580         wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar");
581         s = find_er(sm, cli);
582         if (s == NULL) {
583                 wpa_printf(MSG_DEBUG, "WPS UPnP: Ignore SetSelectedRegistrar "
584                            "from unknown ER");
585                 return UPNP_ACTION_FAILED;
586         }
587         msg = xml_get_base64_item(data, "NewMessage", &ret);
588         if (msg == NULL)
589                 return ret;
590         if (upnp_er_set_selected_registrar(sm->wps->registrar, s, msg)) {
591                 wpabuf_free(msg);
592                 return HTTP_INTERNAL_SERVER_ERROR;
593         }
594         wpabuf_free(msg);
595         *replyname = NULL;
596         *reply = NULL;
597         return HTTP_OK;
598 }
599
600
601 static const char *soap_prefix =
602         "<?xml version=\"1.0\"?>\n"
603         "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
604         "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n"
605         "<s:Body>\n";
606 static const char *soap_postfix =
607         "</s:Body>\n</s:Envelope>\n";
608
609 static const char *soap_error_prefix =
610         "<s:Fault>\n"
611         "<faultcode>s:Client</faultcode>\n"
612         "<faultstring>UPnPError</faultstring>\n"
613         "<detail>\n"
614         "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n";
615 static const char *soap_error_postfix =
616         "<errorDescription>Error</errorDescription>\n"
617         "</UPnPError>\n"
618         "</detail>\n"
619         "</s:Fault>\n";
620
621 static void web_connection_send_reply(struct http_request *req,
622                                       enum http_reply_code ret,
623                                       const char *action, int action_len,
624                                       const struct wpabuf *reply,
625                                       const char *replyname)
626 {
627         struct wpabuf *buf;
628         char *replydata;
629         char *put_length_here = NULL;
630         char *body_start = NULL;
631
632         if (reply) {
633                 size_t len;
634                 replydata = (char *) base64_encode(wpabuf_head(reply),
635                                                    wpabuf_len(reply), &len);
636         } else
637                 replydata = NULL;
638
639         /* Parameters of the response:
640          *      action(action_len) -- action we are responding to
641          *      replyname -- a name we need for the reply
642          *      replydata -- NULL or null-terminated string
643          */
644         buf = wpabuf_alloc(1000 + (replydata ? os_strlen(replydata) : 0U) +
645                            (action_len > 0 ? action_len * 2 : 0));
646         if (buf == NULL) {
647                 wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to "
648                            "POST");
649                 os_free(replydata);
650                 http_request_deinit(req);
651                 return;
652         }
653
654         /*
655          * Assuming we will be successful, put in the output header first.
656          * Note: we do not keep connections alive (and httpread does
657          * not support it)... therefore we must have Connection: close.
658          */
659         if (ret == HTTP_OK) {
660                 wpabuf_put_str(buf,
661                                "HTTP/1.1 200 OK\r\n"
662                                "Content-Type: text/xml; "
663                                "charset=\"utf-8\"\r\n");
664         } else {
665                 wpabuf_printf(buf, "HTTP/1.1 %d Error\r\n", ret);
666         }
667         wpabuf_put_str(buf, http_connection_close);
668
669         wpabuf_put_str(buf, "Content-Length: ");
670         /*
671          * We will paste the length in later, leaving some extra whitespace.
672          * HTTP code is supposed to be tolerant of extra whitespace.
673          */
674         put_length_here = wpabuf_put(buf, 0);
675         wpabuf_put_str(buf, "        \r\n");
676
677         http_put_date(buf);
678
679         /* terminating empty line */
680         wpabuf_put_str(buf, "\r\n");
681
682         body_start = wpabuf_put(buf, 0);
683
684         if (ret == HTTP_OK) {
685                 wpabuf_put_str(buf, soap_prefix);
686                 wpabuf_put_str(buf, "<u:");
687                 wpabuf_put_data(buf, action, action_len);
688                 wpabuf_put_str(buf, "Response xmlns:u=\"");
689                 wpabuf_put_str(buf, urn_wfawlanconfig);
690                 wpabuf_put_str(buf, "\">\n");
691                 if (replydata && replyname) {
692                         /* TODO: might possibly need to escape part of reply
693                          * data? ...
694                          * probably not, unlikely to have ampersand(&) or left
695                          * angle bracket (<) in it...
696                          */
697                         wpabuf_printf(buf, "<%s>", replyname);
698                         wpabuf_put_str(buf, replydata);
699                         wpabuf_printf(buf, "</%s>\n", replyname);
700                 }
701                 wpabuf_put_str(buf, "</u:");
702                 wpabuf_put_data(buf, action, action_len);
703                 wpabuf_put_str(buf, "Response>\n");
704                 wpabuf_put_str(buf, soap_postfix);
705         } else {
706                 /* Error case */
707                 wpabuf_put_str(buf, soap_prefix);
708                 wpabuf_put_str(buf, soap_error_prefix);
709                 wpabuf_printf(buf, "<errorCode>%d</errorCode>\n", ret);
710                 wpabuf_put_str(buf, soap_error_postfix);
711                 wpabuf_put_str(buf, soap_postfix);
712         }
713         os_free(replydata);
714
715         /* Now patch in the content length at the end */
716         if (body_start && put_length_here) {
717                 int body_length = (char *) wpabuf_put(buf, 0) - body_start;
718                 char len_buf[10];
719                 os_snprintf(len_buf, sizeof(len_buf), "%d", body_length);
720                 os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
721         }
722
723         http_request_send_and_deinit(req, buf);
724 }
725
726
727 static const char * web_get_action(struct http_request *req,
728                                    size_t *action_len)
729 {
730         const char *match;
731         int match_len;
732         char *b;
733         char *action;
734
735         *action_len = 0;
736         /* The SOAPAction line of the header tells us what we want to do */
737         b = http_request_get_hdr_line(req, "SOAPAction:");
738         if (b == NULL)
739                 return NULL;
740         if (*b == '"')
741                 b++;
742         else
743                 return NULL;
744         match = urn_wfawlanconfig;
745         match_len = os_strlen(urn_wfawlanconfig) - 1;
746         if (os_strncasecmp(b, match, match_len))
747                 return NULL;
748         b += match_len;
749         /* skip over version */
750         while (isgraph(*b) && *b != '#')
751                 b++;
752         if (*b != '#')
753                 return NULL;
754         b++;
755         /* Following the sharp(#) should be the action and a double quote */
756         action = b;
757         while (isgraph(*b) && *b != '"')
758                 b++;
759         if (*b != '"')
760                 return NULL;
761         *action_len = b - action;
762         return action;
763 }
764
765
766 /* Given that we have received a header w/ POST, act upon it
767  *
768  * Format of POST (case-insensitive):
769  *
770  * First line must be:
771  *      POST /<file> HTTP/1.1
772  * Since we don't do anything fancy we just ignore other lines.
773  *
774  * Our response (if no error) which includes only required lines is:
775  * HTTP/1.1 200 OK
776  * Connection: close
777  * Content-Type: text/xml
778  * Date: <rfc1123-date>
779  *
780  * Header lines must end with \r\n
781  * Per RFC 2616, content-length: is not required but connection:close
782  * would appear to be required (given that we will be closing it!).
783  */
784 static void web_connection_parse_post(struct upnp_wps_device_sm *sm,
785                                       struct sockaddr_in *cli,
786                                       struct http_request *req,
787                                       const char *filename)
788 {
789         enum http_reply_code ret;
790         char *data = http_request_get_data(req); /* body of http msg */
791         const char *action = NULL;
792         size_t action_len = 0;
793         const char *replyname = NULL; /* argument name for the reply */
794         struct wpabuf *reply = NULL; /* data for the reply */
795
796         if (os_strcasecmp(filename, UPNP_WPS_DEVICE_CONTROL_FILE)) {
797                 wpa_printf(MSG_INFO, "WPS UPnP: Invalid POST filename %s",
798                            filename);
799                 ret = HTTP_NOT_FOUND;
800                 goto bad;
801         }
802
803         ret = UPNP_INVALID_ACTION;
804         action = web_get_action(req, &action_len);
805         if (action == NULL)
806                 goto bad;
807
808         if (!os_strncasecmp("GetDeviceInfo", action, action_len))
809                 ret = web_process_get_device_info(sm, &reply, &replyname);
810         else if (!os_strncasecmp("PutMessage", action, action_len))
811                 ret = web_process_put_message(sm, data, &reply, &replyname);
812         else if (!os_strncasecmp("PutWLANResponse", action, action_len))
813                 ret = web_process_put_wlan_response(sm, data, &reply,
814                                                     &replyname);
815         else if (!os_strncasecmp("SetSelectedRegistrar", action, action_len))
816                 ret = web_process_set_selected_registrar(sm, cli, data, &reply,
817                                                          &replyname);
818         else
819                 wpa_printf(MSG_INFO, "WPS UPnP: Unknown POST type");
820
821 bad:
822         if (ret != HTTP_OK)
823                 wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret);
824         web_connection_send_reply(req, ret, action, action_len, reply,
825                                   replyname);
826         wpabuf_free(reply);
827 }
828
829
830 /* Given that we have received a header w/ SUBSCRIBE, act upon it
831  *
832  * Format of SUBSCRIBE (case-insensitive):
833  *
834  * First line must be:
835  *      SUBSCRIBE /wps_event HTTP/1.1
836  *
837  * Our response (if no error) which includes only required lines is:
838  * HTTP/1.1 200 OK
839  * Server: xx, UPnP/1.0, xx
840  * SID: uuid:xxxxxxxxx
841  * Timeout: Second-<n>
842  * Content-Length: 0
843  * Date: xxxx
844  *
845  * Header lines must end with \r\n
846  * Per RFC 2616, content-length: is not required but connection:close
847  * would appear to be required (given that we will be closing it!).
848  */
849 static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
850                                            struct http_request *req,
851                                            const char *filename)
852 {
853         struct wpabuf *buf;
854         char *b;
855         char *hdr = http_request_get_hdr(req);
856         char *h;
857         char *match;
858         int match_len;
859         char *end;
860         int len;
861         int got_nt = 0;
862         u8 uuid[UUID_LEN];
863         int got_uuid = 0;
864         char *callback_urls = NULL;
865         struct subscription *s = NULL;
866         enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
867
868         buf = wpabuf_alloc(1000);
869         if (buf == NULL) {
870                 http_request_deinit(req);
871                 return;
872         }
873
874         /* Parse/validate headers */
875         h = hdr;
876         /* First line: SUBSCRIBE /wps_event HTTP/1.1
877          * has already been parsed.
878          */
879         if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
880                 ret = HTTP_PRECONDITION_FAILED;
881                 goto error;
882         }
883         wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event");
884         end = os_strchr(h, '\n');
885
886         for (; end != NULL; h = end + 1) {
887                 /* Option line by option line */
888                 h = end + 1;
889                 end = os_strchr(h, '\n');
890                 if (end == NULL)
891                         break; /* no unterminated lines allowed */
892
893                 /* NT assures that it is our type of subscription;
894                  * not used for a renewl.
895                  **/
896                 match = "NT:";
897                 match_len = os_strlen(match);
898                 if (os_strncasecmp(h, match, match_len) == 0) {
899                         h += match_len;
900                         while (*h == ' ' || *h == '\t')
901                                 h++;
902                         match = "upnp:event";
903                         match_len = os_strlen(match);
904                         if (os_strncasecmp(h, match, match_len) != 0) {
905                                 ret = HTTP_BAD_REQUEST;
906                                 goto error;
907                         }
908                         got_nt = 1;
909                         continue;
910                 }
911                 /* HOST should refer to us */
912 #if 0
913                 match = "HOST:";
914                 match_len = os_strlen(match);
915                 if (os_strncasecmp(h, match, match_len) == 0) {
916                         h += match_len;
917                         while (*h == ' ' || *h == '\t')
918                                 h++;
919                         .....
920                 }
921 #endif
922                 /* CALLBACK gives one or more URLs for NOTIFYs
923                  * to be sent as a result of the subscription.
924                  * Each URL is enclosed in angle brackets.
925                  */
926                 match = "CALLBACK:";
927                 match_len = os_strlen(match);
928                 if (os_strncasecmp(h, match, match_len) == 0) {
929                         h += match_len;
930                         while (*h == ' ' || *h == '\t')
931                                 h++;
932                         len = end - h;
933                         os_free(callback_urls);
934                         callback_urls = os_malloc(len + 1);
935                         if (callback_urls == NULL) {
936                                 ret = HTTP_INTERNAL_SERVER_ERROR;
937                                 goto error;
938                         }
939                         os_memcpy(callback_urls, h, len);
940                         callback_urls[len] = 0;
941                         continue;
942                 }
943                 /* SID is only for renewal */
944                 match = "SID:";
945                 match_len = os_strlen(match);
946                 if (os_strncasecmp(h, match, match_len) == 0) {
947                         h += match_len;
948                         while (*h == ' ' || *h == '\t')
949                                 h++;
950                         match = "uuid:";
951                         match_len = os_strlen(match);
952                         if (os_strncasecmp(h, match, match_len) != 0) {
953                                 ret = HTTP_BAD_REQUEST;
954                                 goto error;
955                         }
956                         h += match_len;
957                         while (*h == ' ' || *h == '\t')
958                                 h++;
959                         if (uuid_str2bin(h, uuid)) {
960                                 ret = HTTP_BAD_REQUEST;
961                                 goto error;
962                         }
963                         got_uuid = 1;
964                         continue;
965                 }
966                 /* TIMEOUT is requested timeout, but apparently we can
967                  * just ignore this.
968                  */
969         }
970
971         if (got_uuid) {
972                 /* renewal */
973                 if (callback_urls) {
974                         ret = HTTP_BAD_REQUEST;
975                         goto error;
976                 }
977                 s = subscription_renew(sm, uuid);
978                 if (s == NULL) {
979                         ret = HTTP_PRECONDITION_FAILED;
980                         goto error;
981                 }
982         } else if (callback_urls) {
983                 if (!got_nt) {
984                         ret = HTTP_PRECONDITION_FAILED;
985                         goto error;
986                 }
987                 s = subscription_start(sm, callback_urls);
988                 if (s == NULL) {
989                         ret = HTTP_INTERNAL_SERVER_ERROR;
990                         goto error;
991                 }
992         } else {
993                 ret = HTTP_PRECONDITION_FAILED;
994                 goto error;
995         }
996
997         /* success */
998         http_put_reply_code(buf, HTTP_OK);
999         wpabuf_put_str(buf, http_server_hdr);
1000         wpabuf_put_str(buf, http_connection_close);
1001         wpabuf_put_str(buf, "Content-Length: 0\r\n");
1002         wpabuf_put_str(buf, "SID: uuid:");
1003         /* subscription id */
1004         b = wpabuf_put(buf, 0);
1005         uuid_bin2str(s->uuid, b, 80);
1006         wpabuf_put(buf, os_strlen(b));
1007         wpabuf_put_str(buf, "\r\n");
1008         wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC);
1009         http_put_date(buf);
1010         /* And empty line to terminate header: */
1011         wpabuf_put_str(buf, "\r\n");
1012
1013         os_free(callback_urls);
1014         http_request_send_and_deinit(req, buf);
1015         return;
1016
1017 error:
1018         /* Per UPnP spec:
1019         * Errors
1020         * Incompatible headers
1021         *   400 Bad Request. If SID header and one of NT or CALLBACK headers
1022         *     are present, the publisher must respond with HTTP error
1023         *     400 Bad Request.
1024         * Missing or invalid CALLBACK
1025         *   412 Precondition Failed. If CALLBACK header is missing or does not
1026         *     contain a valid HTTP URL, the publisher must respond with HTTP
1027         *     error 412 Precondition Failed.
1028         * Invalid NT
1029         *   412 Precondition Failed. If NT header does not equal upnp:event,
1030         *     the publisher must respond with HTTP error 412 Precondition
1031         *     Failed.
1032         * [For resubscription, use 412 if unknown uuid].
1033         * Unable to accept subscription
1034         *   5xx. If a publisher is not able to accept a subscription (such as
1035         *     due to insufficient resources), it must respond with a
1036         *     HTTP 500-series error code.
1037         *   599 Too many subscriptions (not a standard HTTP error)
1038         */
1039         http_put_empty(buf, ret);
1040         http_request_send_and_deinit(req, buf);
1041         os_free(callback_urls);
1042 }
1043
1044
1045 /* Given that we have received a header w/ UNSUBSCRIBE, act upon it
1046  *
1047  * Format of UNSUBSCRIBE (case-insensitive):
1048  *
1049  * First line must be:
1050  *      UNSUBSCRIBE /wps_event HTTP/1.1
1051  *
1052  * Our response (if no error) which includes only required lines is:
1053  * HTTP/1.1 200 OK
1054  * Content-Length: 0
1055  *
1056  * Header lines must end with \r\n
1057  * Per RFC 2616, content-length: is not required but connection:close
1058  * would appear to be required (given that we will be closing it!).
1059  */
1060 static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm,
1061                                              struct http_request *req,
1062                                              const char *filename)
1063 {
1064         struct wpabuf *buf;
1065         char *hdr = http_request_get_hdr(req);
1066         char *h;
1067         char *match;
1068         int match_len;
1069         char *end;
1070         u8 uuid[UUID_LEN];
1071         int got_uuid = 0;
1072         struct subscription *s = NULL;
1073         enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
1074
1075         /* Parse/validate headers */
1076         h = hdr;
1077         /* First line: UNSUBSCRIBE /wps_event HTTP/1.1
1078          * has already been parsed.
1079          */
1080         if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
1081                 ret = HTTP_PRECONDITION_FAILED;
1082                 goto send_msg;
1083         }
1084         wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event");
1085         end = os_strchr(h, '\n');
1086
1087         for (; end != NULL; h = end + 1) {
1088                 /* Option line by option line */
1089                 h = end + 1;
1090                 end = os_strchr(h, '\n');
1091                 if (end == NULL)
1092                         break; /* no unterminated lines allowed */
1093
1094                 /* HOST should refer to us */
1095 #if 0
1096                 match = "HOST:";
1097                 match_len = os_strlen(match);
1098                 if (os_strncasecmp(h, match, match_len) == 0) {
1099                         h += match_len;
1100                         while (*h == ' ' || *h == '\t')
1101                                 h++;
1102                         .....
1103                 }
1104 #endif
1105                 /* SID is only for renewal */
1106                 match = "SID:";
1107                 match_len = os_strlen(match);
1108                 if (os_strncasecmp(h, match, match_len) == 0) {
1109                         h += match_len;
1110                         while (*h == ' ' || *h == '\t')
1111                                 h++;
1112                         match = "uuid:";
1113                         match_len = os_strlen(match);
1114                         if (os_strncasecmp(h, match, match_len) != 0) {
1115                                 ret = HTTP_BAD_REQUEST;
1116                                 goto send_msg;
1117                         }
1118                         h += match_len;
1119                         while (*h == ' ' || *h == '\t')
1120                                 h++;
1121                         if (uuid_str2bin(h, uuid)) {
1122                                 ret = HTTP_BAD_REQUEST;
1123                                 goto send_msg;
1124                         }
1125                         got_uuid = 1;
1126                         continue;
1127                 }
1128         }
1129
1130         if (got_uuid) {
1131                 s = subscription_find(sm, uuid);
1132                 if (s) {
1133                         struct subscr_addr *sa;
1134                         sa = dl_list_first(&s->addr_list, struct subscr_addr,
1135                                            list);
1136                         wpa_printf(MSG_DEBUG, "WPS UPnP: Unsubscribing %p %s",
1137                                    s, (sa && sa->domain_and_port) ?
1138                                    sa->domain_and_port : "-null-");
1139                         subscription_unlink(s);
1140                         subscription_destroy(s);
1141                 }
1142         } else {
1143                 wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not "
1144                            "found)");
1145                 ret = HTTP_PRECONDITION_FAILED;
1146                 goto send_msg;
1147         }
1148
1149         ret = HTTP_OK;
1150
1151 send_msg:
1152         buf = wpabuf_alloc(200);
1153         if (buf == NULL) {
1154                 http_request_deinit(req);
1155                 return;
1156         }
1157         http_put_empty(buf, ret);
1158         http_request_send_and_deinit(req, buf);
1159 }
1160
1161
1162 /* Send error in response to unknown requests */
1163 static void web_connection_unimplemented(struct http_request *req)
1164 {
1165         struct wpabuf *buf;
1166         buf = wpabuf_alloc(200);
1167         if (buf == NULL) {
1168                 http_request_deinit(req);
1169                 return;
1170         }
1171         http_put_empty(buf, HTTP_UNIMPLEMENTED);
1172         http_request_send_and_deinit(req, buf);
1173 }
1174
1175
1176
1177 /* Called when we have gotten an apparently valid http request.
1178  */
1179 static void web_connection_check_data(void *ctx, struct http_request *req)
1180 {
1181         struct upnp_wps_device_sm *sm = ctx;
1182         enum httpread_hdr_type htype = http_request_get_type(req);
1183         char *filename = http_request_get_uri(req);
1184         struct sockaddr_in *cli = http_request_get_cli_addr(req);
1185
1186         if (!filename) {
1187                 wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI");
1188                 http_request_deinit(req);
1189                 return;
1190         }
1191         /* Trim leading slashes from filename */
1192         while (*filename == '/')
1193                 filename++;
1194
1195         wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d from %s:%d",
1196                    htype, inet_ntoa(cli->sin_addr), htons(cli->sin_port));
1197
1198         switch (htype) {
1199         case HTTPREAD_HDR_TYPE_GET:
1200                 web_connection_parse_get(sm, req, filename);
1201                 break;
1202         case HTTPREAD_HDR_TYPE_POST:
1203                 web_connection_parse_post(sm, cli, req, filename);
1204                 break;
1205         case HTTPREAD_HDR_TYPE_SUBSCRIBE:
1206                 web_connection_parse_subscribe(sm, req, filename);
1207                 break;
1208         case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
1209                 web_connection_parse_unsubscribe(sm, req, filename);
1210                 break;
1211
1212                 /* We are not required to support M-POST; just plain
1213                  * POST is supposed to work, so we only support that.
1214                  * If for some reason we need to support M-POST, it is
1215                  * mostly the same as POST, with small differences.
1216                  */
1217         default:
1218                 /* Send 501 for anything else */
1219                 web_connection_unimplemented(req);
1220                 break;
1221         }
1222 }
1223
1224
1225 /*
1226  * Listening for web connections
1227  * We have a single TCP listening port, and hand off connections as we get
1228  * them.
1229  */
1230
1231 void web_listener_stop(struct upnp_wps_device_sm *sm)
1232 {
1233         http_server_deinit(sm->web_srv);
1234         sm->web_srv = NULL;
1235 }
1236
1237
1238 int web_listener_start(struct upnp_wps_device_sm *sm)
1239 {
1240         struct in_addr addr;
1241         addr.s_addr = sm->ip_addr;
1242         sm->web_srv = http_server_init(&addr, -1, web_connection_check_data,
1243                                        sm);
1244         if (sm->web_srv == NULL) {
1245                 web_listener_stop(sm);
1246                 return -1;
1247         }
1248         sm->web_port = http_server_get_port(sm->web_srv);
1249
1250         return 0;
1251 }