Reduce differences between our VKERNEL and VKERNEL64 configurations.
[dragonfly.git] / contrib / hostapd / src / wps / wps_upnp_web.c
blobb6374540e9a00d4154f82bcf7ce6178de92364e4
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>
8 * See wps_upnp.c for more details on licensing and code history.
9 */
11 #include "includes.h"
12 #include <fcntl.h>
14 #include "common.h"
15 #include "base64.h"
16 #include "eloop.h"
17 #include "uuid.h"
18 #include "httpread.h"
19 #include "wps_i.h"
20 #include "wps_upnp.h"
21 #include "wps_upnp_i.h"
23 /***************************************************************************
24 * Web connections (we serve pages of info about ourselves, handle
25 * requests, etc. etc.).
26 **************************************************************************/
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 */
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";
41 * Incoming web connections are recorded in this struct.
42 * A web connection is a TCP connection to us, the server;
43 * it is called a "web connection" because we use http and serve
44 * data that looks like web pages.
45 * State information is need to track the connection until we figure
46 * out what they want and what we want to do about it.
48 struct web_connection {
49 /* double linked list */
50 struct web_connection *next;
51 struct web_connection *prev;
52 struct upnp_wps_device_sm *sm; /* parent */
53 int sd; /* socket to read from */
54 struct sockaddr_in cli_addr;
55 int sd_registered; /* nonzero if we must cancel registration */
56 struct httpread *hread; /* state machine for reading socket */
57 int n_rcvd_data; /* how much data read so far */
58 int done; /* internal flag, set when we've finished */
63 * XML parsing and formatting
65 * XML is a markup language based on unicode; usually (and in our case,
66 * always!) based on utf-8. utf-8 uses a variable number of bytes per
67 * character. utf-8 has the advantage that all non-ASCII unicode characters are
68 * represented by sequences of non-ascii (high bit set) bytes, whereas ASCII
69 * characters are single ascii bytes, thus we can use typical text processing.
71 * (One other interesting thing about utf-8 is that it is possible to look at
72 * any random byte and determine if it is the first byte of a character as
73 * versus a continuation byte).
75 * The base syntax of XML uses a few ASCII punctionation characters; any
76 * characters that would appear in the payload data are rewritten using
77 * sequences, e.g., &amp; for ampersand(&) and &lt for left angle bracket (<).
78 * Five such escapes total (more can be defined but that does not apply to our
79 * case). Thus we can safely parse for angle brackets etc.
81 * XML describes tree structures of tagged data, with each element beginning
82 * with an opening tag <label> and ending with a closing tag </label> with
83 * matching label. (There is also a self-closing tag <label/> which is supposed
84 * to be equivalent to <label></label>, i.e., no payload, but we are unlikely
85 * to see it for our purpose).
87 * Actually the opening tags are a little more complicated because they can
88 * contain "attributes" after the label (delimited by ascii space or tab chars)
89 * of the form attribute_label="value" or attribute_label='value'; as it turns
90 * out we do not have to read any of these attributes, just ignore them.
92 * Labels are any sequence of chars other than space, tab, right angle bracket
93 * (and ?), but may have an inner structure of <namespace><colon><plain_label>.
94 * As it turns out, we can ignore the namespaces, in fact we can ignore the
95 * entire tree hierarchy, because the plain labels we are looking for will be
96 * unique (not in general, but for this application). We do however have to be
97 * careful to skip over the namespaces.
99 * In generating XML we have to be more careful, but that is easy because
100 * everything we do is pretty canned. The only real care to take is to escape
101 * any special chars in our payload.
105 * xml_next_tag - Advance to next tag
106 * @in: Input
107 * @out: OUT: start of tag just after '<'
108 * @out_tagname: OUT: start of name of tag, skipping namespace
109 * @end: OUT: one after tag
110 * Returns: 0 on success, 1 on failure
112 * A tag has form:
113 * <left angle bracket><...><right angle bracket>
114 * Within the angle brackets, there is an optional leading forward slash (which
115 * makes the tag an ending tag), then an optional leading label (followed by
116 * colon) and then the tag name itself.
118 * Note that angle brackets present in the original data must have been encoded
119 * as &lt; and &gt; so they will not trouble us.
121 static int xml_next_tag(char *in, char **out, char **out_tagname,
122 char **end)
124 while (*in && *in != '<')
125 in++;
126 if (*in != '<')
127 return 1;
128 *out = ++in;
129 if (*in == '/')
130 in++;
131 *out_tagname = in; /* maybe */
132 while (isalnum(*in) || *in == '-')
133 in++;
134 if (*in == ':')
135 *out_tagname = ++in;
136 while (*in && *in != '>')
137 in++;
138 if (*in != '>')
139 return 1;
140 *end = ++in;
141 return 0;
145 /* xml_data_encode -- format data for xml file, escaping special characters.
147 * Note that we assume we are using utf8 both as input and as output!
148 * In utf8, characters may be classed as follows:
149 * 0xxxxxxx(2) -- 1 byte ascii char
150 * 11xxxxxx(2) -- 1st byte of multi-byte char w/ unicode value >= 0x80
151 * 110xxxxx(2) -- 1st byte of 2 byte sequence (5 payload bits here)
152 * 1110xxxx(2) -- 1st byte of 3 byte sequence (4 payload bits here)
153 * 11110xxx(2) -- 1st byte of 4 byte sequence (3 payload bits here)
154 * 10xxxxxx(2) -- extension byte (6 payload bits per byte)
155 * Some values implied by the above are however illegal because they
156 * do not represent unicode chars or are not the shortest encoding.
157 * Actually, we can almost entirely ignore the above and just do
158 * text processing same as for ascii text.
160 * XML is written with arbitrary unicode characters, except that five
161 * characters have special meaning and so must be escaped where they
162 * appear in payload data... which we do here.
164 static void xml_data_encode(struct wpabuf *buf, const char *data, int len)
166 int i;
167 for (i = 0; i < len; i++) {
168 u8 c = ((u8 *) data)[i];
169 if (c == '<') {
170 wpabuf_put_str(buf, "&lt;");
171 continue;
173 if (c == '>') {
174 wpabuf_put_str(buf, "&gt;");
175 continue;
177 if (c == '&') {
178 wpabuf_put_str(buf, "&amp;");
179 continue;
181 if (c == '\'') {
182 wpabuf_put_str(buf, "&apos;");
183 continue;
185 if (c == '"') {
186 wpabuf_put_str(buf, "&quot;");
187 continue;
190 * We could try to represent control characters using the
191 * sequence: &#x; where x is replaced by a hex numeral, but not
192 * clear why we would do this.
194 wpabuf_put_u8(buf, c);
199 /* xml_add_tagged_data -- format tagged data as a new xml line.
201 * tag must not have any special chars.
202 * data may have special chars, which are escaped.
204 static void xml_add_tagged_data(struct wpabuf *buf, const char *tag,
205 const char *data)
207 wpabuf_printf(buf, "<%s>", tag);
208 xml_data_encode(buf, data, os_strlen(data));
209 wpabuf_printf(buf, "</%s>\n", tag);
213 /* A POST body looks something like (per upnp spec):
214 * <?xml version="1.0"?>
215 * <s:Envelope
216 * xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
217 * s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
218 * <s:Body>
219 * <u:actionName xmlns:u="urn:schemas-upnp-org:service:serviceType:v">
220 * <argumentName>in arg value</argumentName>
221 * other in args and their values go here, if any
222 * </u:actionName>
223 * </s:Body>
224 * </s:Envelope>
226 * where :
227 * s: might be some other namespace name followed by colon
228 * u: might be some other namespace name followed by colon
229 * actionName will be replaced according to action requested
230 * schema following actionName will be WFA scheme instead
231 * argumentName will be actual argument name
232 * (in arg value) will be actual argument value
234 static int
235 upnp_get_first_document_item(char *doc, const char *item, char **value)
237 const char *match = item;
238 int match_len = os_strlen(item);
239 char *tag;
240 char *tagname;
241 char *end;
243 *value = NULL; /* default, bad */
246 * This is crude: ignore any possible tag name conflicts and go right
247 * to the first tag of this name. This should be ok for the limited
248 * domain of UPnP messages.
250 for (;;) {
251 if (xml_next_tag(doc, &tag, &tagname, &end))
252 return 1;
253 doc = end;
254 if (!os_strncasecmp(tagname, match, match_len) &&
255 *tag != '/' &&
256 (tagname[match_len] == '>' ||
257 !isgraph(tagname[match_len]))) {
258 break;
261 end = doc;
262 while (*end && *end != '<')
263 end++;
264 *value = os_zalloc(1 + (end - doc));
265 if (*value == NULL)
266 return 1;
267 os_memcpy(*value, doc, end - doc);
268 return 0;
273 * "Files" that we serve via HTTP. The format of these files is given by
274 * WFA WPS specifications. Extra white space has been removed to save space.
277 static const char wps_scpd_xml[] =
278 "<?xml version=\"1.0\"?>\n"
279 "<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">\n"
280 "<specVersion><major>1</major><minor>0</minor></specVersion>\n"
281 "<actionList>\n"
282 "<action>\n"
283 "<name>GetDeviceInfo</name>\n"
284 "<argumentList>\n"
285 "<argument>\n"
286 "<name>NewDeviceInfo</name>\n"
287 "<direction>out</direction>\n"
288 "<relatedStateVariable>DeviceInfo</relatedStateVariable>\n"
289 "</argument>\n"
290 "</argumentList>\n"
291 "</action>\n"
292 "<action>\n"
293 "<name>PutMessage</name>\n"
294 "<argumentList>\n"
295 "<argument>\n"
296 "<name>NewInMessage</name>\n"
297 "<direction>in</direction>\n"
298 "<relatedStateVariable>InMessage</relatedStateVariable>\n"
299 "</argument>\n"
300 "<argument>\n"
301 "<name>NewOutMessage</name>\n"
302 "<direction>out</direction>\n"
303 "<relatedStateVariable>OutMessage</relatedStateVariable>\n"
304 "</argument>\n"
305 "</argumentList>\n"
306 "</action>\n"
307 "<action>\n"
308 "<name>GetAPSettings</name>\n"
309 "<argumentList>\n"
310 "<argument>\n"
311 "<name>NewMessage</name>\n"
312 "<direction>in</direction>\n"
313 "<relatedStateVariable>Message</relatedStateVariable>\n"
314 "</argument>\n"
315 "<argument>\n"
316 "<name>NewAPSettings</name>\n"
317 "<direction>out</direction>\n"
318 "<relatedStateVariable>APSettings</relatedStateVariable>\n"
319 "</argument>\n"
320 "</argumentList>\n"
321 "</action>\n"
322 "<action>\n"
323 "<name>SetAPSettings</name>\n"
324 "<argumentList>\n"
325 "<argument>\n"
326 "<name>APSettings</name>\n"
327 "<direction>in</direction>\n"
328 "<relatedStateVariable>APSettings</relatedStateVariable>\n"
329 "</argument>\n"
330 "</argumentList>\n"
331 "</action>\n"
332 "<action>\n"
333 "<name>DelAPSettings</name>\n"
334 "<argumentList>\n"
335 "<argument>\n"
336 "<name>NewAPSettings</name>\n"
337 "<direction>in</direction>\n"
338 "<relatedStateVariable>APSettings</relatedStateVariable>\n"
339 "</argument>\n"
340 "</argumentList>\n"
341 "</action>\n"
342 "<action>\n"
343 "<name>GetSTASettings</name>\n"
344 "<argumentList>\n"
345 "<argument>\n"
346 "<name>NewMessage</name>\n"
347 "<direction>in</direction>\n"
348 "<relatedStateVariable>Message</relatedStateVariable>\n"
349 "</argument>\n"
350 "<argument>\n"
351 "<name>NewSTASettings</name>\n"
352 "<direction>out</direction>\n"
353 "<relatedStateVariable>STASettings</relatedStateVariable>\n"
354 "</argument>\n"
355 "</argumentList>\n"
356 "</action>\n"
357 "<action>\n"
358 "<name>SetSTASettings</name>\n"
359 "<argumentList>\n"
360 "<argument>\n"
361 "<name>NewSTASettings</name>\n"
362 "<direction>out</direction>\n"
363 "<relatedStateVariable>STASettings</relatedStateVariable>\n"
364 "</argument>\n"
365 "</argumentList>\n"
366 "</action>\n"
367 "<action>\n"
368 "<name>DelSTASettings</name>\n"
369 "<argumentList>\n"
370 "<argument>\n"
371 "<name>NewSTASettings</name>\n"
372 "<direction>in</direction>\n"
373 "<relatedStateVariable>STASettings</relatedStateVariable>\n"
374 "</argument>\n"
375 "</argumentList>\n"
376 "</action>\n"
377 "<action>\n"
378 "<name>PutWLANResponse</name>\n"
379 "<argumentList>\n"
380 "<argument>\n"
381 "<name>NewMessage</name>\n"
382 "<direction>in</direction>\n"
383 "<relatedStateVariable>Message</relatedStateVariable>\n"
384 "</argument>\n"
385 "<argument>\n"
386 "<name>NewWLANEventType</name>\n"
387 "<direction>in</direction>\n"
388 "<relatedStateVariable>WLANEventType</relatedStateVariable>\n"
389 "</argument>\n"
390 "<argument>\n"
391 "<name>NewWLANEventMAC</name>\n"
392 "<direction>in</direction>\n"
393 "<relatedStateVariable>WLANEventMAC</relatedStateVariable>\n"
394 "</argument>\n"
395 "</argumentList>\n"
396 "</action>\n"
397 "<action>\n"
398 "<name>SetSelectedRegistrar</name>\n"
399 "<argumentList>\n"
400 "<argument>\n"
401 "<name>NewMessage</name>\n"
402 "<direction>in</direction>\n"
403 "<relatedStateVariable>Message</relatedStateVariable>\n"
404 "</argument>\n"
405 "</argumentList>\n"
406 "</action>\n"
407 "<action>\n"
408 "<name>RebootAP</name>\n"
409 "<argumentList>\n"
410 "<argument>\n"
411 "<name>NewAPSettings</name>\n"
412 "<direction>in</direction>\n"
413 "<relatedStateVariable>APSettings</relatedStateVariable>\n"
414 "</argument>\n"
415 "</argumentList>\n"
416 "</action>\n"
417 "<action>\n"
418 "<name>ResetAP</name>\n"
419 "<argumentList>\n"
420 "<argument>\n"
421 "<name>NewMessage</name>\n"
422 "<direction>in</direction>\n"
423 "<relatedStateVariable>Message</relatedStateVariable>\n"
424 "</argument>\n"
425 "</argumentList>\n"
426 "</action>\n"
427 "<action>\n"
428 "<name>RebootSTA</name>\n"
429 "<argumentList>\n"
430 "<argument>\n"
431 "<name>NewSTASettings</name>\n"
432 "<direction>in</direction>\n"
433 "<relatedStateVariable>APSettings</relatedStateVariable>\n"
434 "</argument>\n"
435 "</argumentList>\n"
436 "</action>\n"
437 "<action>\n"
438 "<name>ResetSTA</name>\n"
439 "<argumentList>\n"
440 "<argument>\n"
441 "<name>NewMessage</name>\n"
442 "<direction>in</direction>\n"
443 "<relatedStateVariable>Message</relatedStateVariable>\n"
444 "</argument>\n"
445 "</argumentList>\n"
446 "</action>\n"
447 "</actionList>\n"
448 "<serviceStateTable>\n"
449 "<stateVariable sendEvents=\"no\">\n"
450 "<name>Message</name>\n"
451 "<dataType>bin.base64</dataType>\n"
452 "</stateVariable>\n"
453 "<stateVariable sendEvents=\"no\">\n"
454 "<name>InMessage</name>\n"
455 "<dataType>bin.base64</dataType>\n"
456 "</stateVariable>\n"
457 "<stateVariable sendEvents=\"no\">\n"
458 "<name>OutMessage</name>\n"
459 "<dataType>bin.base64</dataType>\n"
460 "</stateVariable>\n"
461 "<stateVariable sendEvents=\"no\">\n"
462 "<name>DeviceInfo</name>\n"
463 "<dataType>bin.base64</dataType>\n"
464 "</stateVariable>\n"
465 "<stateVariable sendEvents=\"no\">\n"
466 "<name>APSettings</name>\n"
467 "<dataType>bin.base64</dataType>\n"
468 "</stateVariable>\n"
469 "<stateVariable sendEvents=\"yes\">\n"
470 "<name>APStatus</name>\n"
471 "<dataType>ui1</dataType>\n"
472 "</stateVariable>\n"
473 "<stateVariable sendEvents=\"no\">\n"
474 "<name>STASettings</name>\n"
475 "<dataType>bin.base64</dataType>\n"
476 "</stateVariable>\n"
477 "<stateVariable sendEvents=\"yes\">\n"
478 "<name>STAStatus</name>\n"
479 "<dataType>ui1</dataType>\n"
480 "</stateVariable>\n"
481 "<stateVariable sendEvents=\"yes\">\n"
482 "<name>WLANEvent</name>\n"
483 "<dataType>bin.base64</dataType>\n"
484 "</stateVariable>\n"
485 "<stateVariable sendEvents=\"no\">\n"
486 "<name>WLANEventType</name>\n"
487 "<dataType>ui1</dataType>\n"
488 "</stateVariable>\n"
489 "<stateVariable sendEvents=\"no\">\n"
490 "<name>WLANEventMAC</name>\n"
491 "<dataType>string</dataType>\n"
492 "</stateVariable>\n"
493 "<stateVariable sendEvents=\"no\">\n"
494 "<name>WLANResponse</name>\n"
495 "<dataType>bin.base64</dataType>\n"
496 "</stateVariable>\n"
497 "</serviceStateTable>\n"
498 "</scpd>\n"
502 static const char *wps_device_xml_prefix =
503 "<?xml version=\"1.0\"?>\n"
504 "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n"
505 "<specVersion>\n"
506 "<major>1</major>\n"
507 "<minor>0</minor>\n"
508 "</specVersion>\n"
509 "<device>\n"
510 "<deviceType>urn:schemas-wifialliance-org:device:WFADevice:1"
511 "</deviceType>\n";
513 static const char *wps_device_xml_postfix =
514 "<serviceList>\n"
515 "<service>\n"
516 "<serviceType>urn:schemas-wifialliance-org:service:WFAWLANConfig:1"
517 "</serviceType>\n"
518 "<serviceId>urn:wifialliance-org:serviceId:WFAWLANConfig1</serviceId>"
519 "\n"
520 "<SCPDURL>" UPNP_WPS_SCPD_XML_FILE "</SCPDURL>\n"
521 "<controlURL>" UPNP_WPS_DEVICE_CONTROL_FILE "</controlURL>\n"
522 "<eventSubURL>" UPNP_WPS_DEVICE_EVENT_FILE "</eventSubURL>\n"
523 "</service>\n"
524 "</serviceList>\n"
525 "</device>\n"
526 "</root>\n";
529 /* format_wps_device_xml -- produce content of "file" wps_device.xml
530 * (UPNP_WPS_DEVICE_XML_FILE)
532 static void format_wps_device_xml(struct upnp_wps_device_sm *sm,
533 struct wpabuf *buf)
535 const char *s;
536 char uuid_string[80];
538 wpabuf_put_str(buf, wps_device_xml_prefix);
541 * Add required fields with default values if not configured. Add
542 * optional and recommended fields only if configured.
544 s = sm->wps->friendly_name;
545 s = ((s && *s) ? s : "WPS Access Point");
546 xml_add_tagged_data(buf, "friendlyName", s);
548 s = sm->wps->dev.manufacturer;
549 s = ((s && *s) ? s : "");
550 xml_add_tagged_data(buf, "manufacturer", s);
552 if (sm->wps->manufacturer_url)
553 xml_add_tagged_data(buf, "manufacturerURL",
554 sm->wps->manufacturer_url);
556 if (sm->wps->model_description)
557 xml_add_tagged_data(buf, "modelDescription",
558 sm->wps->model_description);
560 s = sm->wps->dev.model_name;
561 s = ((s && *s) ? s : "");
562 xml_add_tagged_data(buf, "modelName", s);
564 if (sm->wps->dev.model_number)
565 xml_add_tagged_data(buf, "modelNumber",
566 sm->wps->dev.model_number);
568 if (sm->wps->model_url)
569 xml_add_tagged_data(buf, "modelURL", sm->wps->model_url);
571 if (sm->wps->dev.serial_number)
572 xml_add_tagged_data(buf, "serialNumber",
573 sm->wps->dev.serial_number);
575 uuid_bin2str(sm->wps->uuid, uuid_string, sizeof(uuid_string));
576 s = uuid_string;
577 /* Need "uuid:" prefix, thus we can't use xml_add_tagged_data()
578 * easily...
580 wpabuf_put_str(buf, "<UDN>uuid:");
581 xml_data_encode(buf, s, os_strlen(s));
582 wpabuf_put_str(buf, "</UDN>\n");
584 if (sm->wps->upc)
585 xml_add_tagged_data(buf, "UPC", sm->wps->upc);
587 wpabuf_put_str(buf, wps_device_xml_postfix);
591 void web_connection_stop(struct web_connection *c)
593 struct upnp_wps_device_sm *sm = c->sm;
595 httpread_destroy(c->hread);
596 c->hread = NULL;
597 close(c->sd);
598 c->sd = -1;
599 if (c->next == c) {
600 sm->web_connections = NULL;
601 } else {
602 if (sm->web_connections == c)
603 sm->web_connections = c->next;
604 c->next->prev = c->prev;
605 c->prev->next = c->next;
607 os_free(c);
608 sm->n_web_connections--;
612 static void http_put_reply_code(struct wpabuf *buf, enum http_reply_code code)
614 wpabuf_put_str(buf, "HTTP/1.1 ");
615 switch (code) {
616 case HTTP_OK:
617 wpabuf_put_str(buf, "200 OK\r\n");
618 break;
619 case HTTP_BAD_REQUEST:
620 wpabuf_put_str(buf, "400 Bad request\r\n");
621 break;
622 case HTTP_PRECONDITION_FAILED:
623 wpabuf_put_str(buf, "412 Precondition failed\r\n");
624 break;
625 case HTTP_UNIMPLEMENTED:
626 wpabuf_put_str(buf, "501 Unimplemented\r\n");
627 break;
628 case HTTP_INTERNAL_SERVER_ERROR:
629 default:
630 wpabuf_put_str(buf, "500 Internal server error\r\n");
631 break;
636 static void http_put_date(struct wpabuf *buf)
638 wpabuf_put_str(buf, "Date: ");
639 format_date(buf);
640 wpabuf_put_str(buf, "\r\n");
644 static void http_put_empty(struct wpabuf *buf, enum http_reply_code code)
646 http_put_reply_code(buf, code);
647 wpabuf_put_str(buf, http_server_hdr);
648 wpabuf_put_str(buf, http_connection_close);
649 wpabuf_put_str(buf, "Content-Length: 0\r\n"
650 "\r\n");
654 /* Given that we have received a header w/ GET, act upon it
656 * Format of GET (case-insensitive):
658 * First line must be:
659 * GET /<file> HTTP/1.1
660 * Since we don't do anything fancy we just ignore other lines.
662 * Our response (if no error) which includes only required lines is:
663 * HTTP/1.1 200 OK
664 * Connection: close
665 * Content-Type: text/xml
666 * Date: <rfc1123-date>
668 * Header lines must end with \r\n
669 * Per RFC 2616, content-length: is not required but connection:close
670 * would appear to be required (given that we will be closing it!).
672 static void web_connection_parse_get(struct web_connection *c, char *filename)
674 struct upnp_wps_device_sm *sm = c->sm;
675 struct wpabuf *buf; /* output buffer, allocated */
676 char *put_length_here;
677 char *body_start;
678 enum {
679 GET_DEVICE_XML_FILE,
680 GET_SCPD_XML_FILE
681 } req;
682 size_t extra_len = 0;
683 int body_length;
684 char len_buf[10];
687 * It is not required that filenames be case insensitive but it is
688 * allowed and cannot hurt here.
690 if (filename == NULL)
691 filename = "(null)"; /* just in case */
692 if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) {
693 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML");
694 req = GET_DEVICE_XML_FILE;
695 extra_len = 3000;
696 if (sm->wps->friendly_name)
697 extra_len += os_strlen(sm->wps->friendly_name);
698 if (sm->wps->manufacturer_url)
699 extra_len += os_strlen(sm->wps->manufacturer_url);
700 if (sm->wps->model_description)
701 extra_len += os_strlen(sm->wps->model_description);
702 if (sm->wps->model_url)
703 extra_len += os_strlen(sm->wps->model_url);
704 if (sm->wps->upc)
705 extra_len += os_strlen(sm->wps->upc);
706 } else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) {
707 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for SCPD XML");
708 req = GET_SCPD_XML_FILE;
709 extra_len = os_strlen(wps_scpd_xml);
710 } else {
711 /* File not found */
712 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET file not found: %s",
713 filename);
714 buf = wpabuf_alloc(200);
715 if (buf == NULL)
716 return;
717 wpabuf_put_str(buf,
718 "HTTP/1.1 404 Not Found\r\n"
719 "Connection: close\r\n");
721 http_put_date(buf);
723 /* terminating empty line */
724 wpabuf_put_str(buf, "\r\n");
726 goto send_buf;
729 buf = wpabuf_alloc(1000 + extra_len);
730 if (buf == NULL)
731 return;
733 wpabuf_put_str(buf,
734 "HTTP/1.1 200 OK\r\n"
735 "Content-Type: text/xml; charset=\"utf-8\"\r\n");
736 wpabuf_put_str(buf, "Server: Unspecified, UPnP/1.0, Unspecified\r\n");
737 wpabuf_put_str(buf, "Connection: close\r\n");
738 wpabuf_put_str(buf, "Content-Length: ");
740 * We will paste the length in later, leaving some extra whitespace.
741 * HTTP code is supposed to be tolerant of extra whitespace.
743 put_length_here = wpabuf_put(buf, 0);
744 wpabuf_put_str(buf, " \r\n");
746 http_put_date(buf);
748 /* terminating empty line */
749 wpabuf_put_str(buf, "\r\n");
751 body_start = wpabuf_put(buf, 0);
753 switch (req) {
754 case GET_DEVICE_XML_FILE:
755 format_wps_device_xml(sm, buf);
756 break;
757 case GET_SCPD_XML_FILE:
758 wpabuf_put_str(buf, wps_scpd_xml);
759 break;
762 /* Now patch in the content length at the end */
763 body_length = (char *) wpabuf_put(buf, 0) - body_start;
764 os_snprintf(len_buf, 10, "%d", body_length);
765 os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
767 send_buf:
768 send_wpabuf(c->sd, buf);
769 wpabuf_free(buf);
773 static struct wpabuf * web_get_item(char *data, const char *name,
774 enum http_reply_code *ret)
776 char *msg;
777 struct wpabuf *buf;
778 unsigned char *decoded;
779 size_t len;
781 if (upnp_get_first_document_item(data, name, &msg)) {
782 *ret = UPNP_ARG_VALUE_INVALID;
783 return NULL;
786 decoded = base64_decode((unsigned char *) msg, os_strlen(msg), &len);
787 os_free(msg);
788 if (decoded == NULL) {
789 *ret = UPNP_OUT_OF_MEMORY;
790 return NULL;
793 buf = wpabuf_alloc_ext_data(decoded, len);
794 if (buf == NULL) {
795 os_free(decoded);
796 *ret = UPNP_OUT_OF_MEMORY;
797 return NULL;
799 return buf;
803 static enum http_reply_code
804 web_process_get_device_info(struct upnp_wps_device_sm *sm,
805 struct wpabuf **reply, const char **replyname)
807 static const char *name = "NewDeviceInfo";
809 wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo");
810 if (sm->ctx->rx_req_get_device_info == NULL)
811 return HTTP_INTERNAL_SERVER_ERROR;
812 *reply = sm->ctx->rx_req_get_device_info(sm->priv, &sm->peer);
813 if (*reply == NULL) {
814 wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo");
815 return HTTP_INTERNAL_SERVER_ERROR;
817 *replyname = name;
818 return HTTP_OK;
822 static enum http_reply_code
823 web_process_put_message(struct upnp_wps_device_sm *sm, char *data,
824 struct wpabuf **reply, const char **replyname)
826 struct wpabuf *msg;
827 static const char *name = "NewOutMessage";
828 enum http_reply_code ret;
831 * PutMessage is used by external UPnP-based Registrar to perform WPS
832 * operation with the access point itself; as compared with
833 * PutWLANResponse which is for proxying.
835 wpa_printf(MSG_DEBUG, "WPS UPnP: PutMessage");
836 if (sm->ctx->rx_req_put_message == NULL)
837 return HTTP_INTERNAL_SERVER_ERROR;
838 msg = web_get_item(data, "NewInMessage", &ret);
839 if (msg == NULL)
840 return ret;
841 *reply = sm->ctx->rx_req_put_message(sm->priv, &sm->peer, msg);
842 wpabuf_free(msg);
843 if (*reply == NULL)
844 return HTTP_INTERNAL_SERVER_ERROR;
845 *replyname = name;
846 return HTTP_OK;
850 static enum http_reply_code
851 web_process_get_ap_settings(struct upnp_wps_device_sm *sm, char *data,
852 struct wpabuf **reply, const char **replyname)
854 struct wpabuf *msg;
855 static const char *name = "NewAPSettings";
856 enum http_reply_code ret;
858 wpa_printf(MSG_DEBUG, "WPS UPnP: GetAPSettings");
859 if (sm->ctx->rx_req_get_ap_settings == NULL)
860 return HTTP_INTERNAL_SERVER_ERROR;
861 msg = web_get_item(data, "NewMessage", &ret);
862 if (msg == NULL)
863 return ret;
864 *reply = sm->ctx->rx_req_get_ap_settings(sm->priv, msg);
865 wpabuf_free(msg);
866 if (*reply == NULL)
867 return HTTP_INTERNAL_SERVER_ERROR;
868 *replyname = name;
869 return HTTP_OK;
873 static enum http_reply_code
874 web_process_set_ap_settings(struct upnp_wps_device_sm *sm, char *data,
875 struct wpabuf **reply, const char **replyname)
877 struct wpabuf *msg;
878 enum http_reply_code ret;
880 wpa_printf(MSG_DEBUG, "WPS UPnP: SetAPSettings");
881 msg = web_get_item(data, "NewAPSettings", &ret);
882 if (msg == NULL)
883 return ret;
884 if (!sm->ctx->rx_req_set_ap_settings ||
885 sm->ctx->rx_req_set_ap_settings(sm->priv, msg)) {
886 wpabuf_free(msg);
887 return HTTP_INTERNAL_SERVER_ERROR;
889 wpabuf_free(msg);
890 *replyname = NULL;
891 *reply = NULL;
892 return HTTP_OK;
896 static enum http_reply_code
897 web_process_del_ap_settings(struct upnp_wps_device_sm *sm, char *data,
898 struct wpabuf **reply, const char **replyname)
900 struct wpabuf *msg;
901 enum http_reply_code ret;
903 wpa_printf(MSG_DEBUG, "WPS UPnP: DelAPSettings");
904 msg = web_get_item(data, "NewAPSettings", &ret);
905 if (msg == NULL)
906 return ret;
907 if (!sm->ctx->rx_req_del_ap_settings ||
908 sm->ctx->rx_req_del_ap_settings(sm->priv, msg)) {
909 wpabuf_free(msg);
910 return HTTP_INTERNAL_SERVER_ERROR;
912 wpabuf_free(msg);
913 *replyname = NULL;
914 *reply = NULL;
915 return HTTP_OK;
919 static enum http_reply_code
920 web_process_get_sta_settings(struct upnp_wps_device_sm *sm, char *data,
921 struct wpabuf **reply, const char **replyname)
923 struct wpabuf *msg;
924 static const char *name = "NewSTASettings";
925 enum http_reply_code ret;
927 wpa_printf(MSG_DEBUG, "WPS UPnP: GetSTASettings");
928 if (sm->ctx->rx_req_get_sta_settings == NULL)
929 return HTTP_INTERNAL_SERVER_ERROR;
930 msg = web_get_item(data, "NewMessage", &ret);
931 if (msg == NULL)
932 return ret;
933 *reply = sm->ctx->rx_req_get_sta_settings(sm->priv, msg);
934 wpabuf_free(msg);
935 if (*reply == NULL)
936 return HTTP_INTERNAL_SERVER_ERROR;
937 *replyname = name;
938 return HTTP_OK;
942 static enum http_reply_code
943 web_process_set_sta_settings(struct upnp_wps_device_sm *sm, char *data,
944 struct wpabuf **reply, const char **replyname)
946 struct wpabuf *msg;
947 enum http_reply_code ret;
949 wpa_printf(MSG_DEBUG, "WPS UPnP: SetSTASettings");
950 msg = web_get_item(data, "NewSTASettings", &ret);
951 if (msg == NULL)
952 return ret;
953 if (!sm->ctx->rx_req_set_sta_settings ||
954 sm->ctx->rx_req_set_sta_settings(sm->priv, msg)) {
955 wpabuf_free(msg);
956 return HTTP_INTERNAL_SERVER_ERROR;
958 wpabuf_free(msg);
959 *replyname = NULL;
960 *reply = NULL;
961 return HTTP_OK;
965 static enum http_reply_code
966 web_process_del_sta_settings(struct upnp_wps_device_sm *sm, char *data,
967 struct wpabuf **reply, const char **replyname)
969 struct wpabuf *msg;
970 enum http_reply_code ret;
972 wpa_printf(MSG_DEBUG, "WPS UPnP: DelSTASettings");
973 msg = web_get_item(data, "NewSTASettings", &ret);
974 if (msg == NULL)
975 return ret;
976 if (!sm->ctx->rx_req_del_sta_settings ||
977 sm->ctx->rx_req_del_sta_settings(sm->priv, msg)) {
978 wpabuf_free(msg);
979 return HTTP_INTERNAL_SERVER_ERROR;
981 wpabuf_free(msg);
982 *replyname = NULL;
983 *reply = NULL;
984 return HTTP_OK;
988 static enum http_reply_code
989 web_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data,
990 struct wpabuf **reply, const char **replyname)
992 struct wpabuf *msg;
993 enum http_reply_code ret;
994 u8 macaddr[ETH_ALEN];
995 int ev_type;
996 int type;
997 char *val;
1000 * External UPnP-based Registrar is passing us a message to be proxied
1001 * over to a Wi-Fi -based client of ours.
1004 wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse");
1005 msg = web_get_item(data, "NewMessage", &ret);
1006 if (msg == NULL)
1007 return ret;
1008 if (upnp_get_first_document_item(data, "NewWLANEventType", &val)) {
1009 wpabuf_free(msg);
1010 return UPNP_ARG_VALUE_INVALID;
1012 ev_type = atol(val);
1013 os_free(val);
1014 val = NULL;
1015 if (upnp_get_first_document_item(data, "NewWLANEventMAC", &val) ||
1016 hwaddr_aton(val, macaddr)) {
1017 wpabuf_free(msg);
1018 os_free(val);
1019 return UPNP_ARG_VALUE_INVALID;
1021 os_free(val);
1022 if (ev_type == UPNP_WPS_WLANEVENT_TYPE_EAP) {
1023 struct wps_parse_attr attr;
1024 if (wps_parse_msg(msg, &attr) < 0 ||
1025 attr.msg_type == NULL)
1026 type = -1;
1027 else
1028 type = *attr.msg_type;
1029 wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type);
1030 } else
1031 type = -1;
1032 if (!sm->ctx->rx_req_put_wlan_response ||
1033 sm->ctx->rx_req_put_wlan_response(sm->priv, ev_type, macaddr, msg,
1034 type)) {
1035 wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->"
1036 "rx_req_put_wlan_response");
1037 wpabuf_free(msg);
1038 return HTTP_INTERNAL_SERVER_ERROR;
1040 wpabuf_free(msg);
1041 *replyname = NULL;
1042 *reply = NULL;
1043 return HTTP_OK;
1047 static enum http_reply_code
1048 web_process_set_selected_registrar(struct upnp_wps_device_sm *sm, char *data,
1049 struct wpabuf **reply,
1050 const char **replyname)
1052 struct wpabuf *msg;
1053 enum http_reply_code ret;
1055 wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar");
1056 msg = web_get_item(data, "NewMessage", &ret);
1057 if (msg == NULL)
1058 return ret;
1059 if (!sm->ctx->rx_req_set_selected_registrar ||
1060 sm->ctx->rx_req_set_selected_registrar(sm->priv, msg)) {
1061 wpabuf_free(msg);
1062 return HTTP_INTERNAL_SERVER_ERROR;
1064 wpabuf_free(msg);
1065 *replyname = NULL;
1066 *reply = NULL;
1067 return HTTP_OK;
1071 static enum http_reply_code
1072 web_process_reboot_ap(struct upnp_wps_device_sm *sm, char *data,
1073 struct wpabuf **reply, const char **replyname)
1075 struct wpabuf *msg;
1076 enum http_reply_code ret;
1078 wpa_printf(MSG_DEBUG, "WPS UPnP: RebootAP");
1079 msg = web_get_item(data, "NewAPSettings", &ret);
1080 if (msg == NULL)
1081 return ret;
1082 if (!sm->ctx->rx_req_reboot_ap ||
1083 sm->ctx->rx_req_reboot_ap(sm->priv, msg)) {
1084 wpabuf_free(msg);
1085 return HTTP_INTERNAL_SERVER_ERROR;
1087 wpabuf_free(msg);
1088 *replyname = NULL;
1089 *reply = NULL;
1090 return HTTP_OK;
1094 static enum http_reply_code
1095 web_process_reset_ap(struct upnp_wps_device_sm *sm, char *data,
1096 struct wpabuf **reply, const char **replyname)
1098 struct wpabuf *msg;
1099 enum http_reply_code ret;
1101 wpa_printf(MSG_DEBUG, "WPS UPnP: ResetAP");
1102 msg = web_get_item(data, "NewMessage", &ret);
1103 if (msg == NULL)
1104 return ret;
1105 if (!sm->ctx->rx_req_reset_ap ||
1106 sm->ctx->rx_req_reset_ap(sm->priv, msg)) {
1107 wpabuf_free(msg);
1108 return HTTP_INTERNAL_SERVER_ERROR;
1110 wpabuf_free(msg);
1111 *replyname = NULL;
1112 *reply = NULL;
1113 return HTTP_OK;
1117 static enum http_reply_code
1118 web_process_reboot_sta(struct upnp_wps_device_sm *sm, char *data,
1119 struct wpabuf **reply, const char **replyname)
1121 struct wpabuf *msg;
1122 enum http_reply_code ret;
1124 wpa_printf(MSG_DEBUG, "WPS UPnP: RebootSTA");
1125 msg = web_get_item(data, "NewSTASettings", &ret);
1126 if (msg == NULL)
1127 return ret;
1128 if (!sm->ctx->rx_req_reboot_sta ||
1129 sm->ctx->rx_req_reboot_sta(sm->priv, msg)) {
1130 wpabuf_free(msg);
1131 return HTTP_INTERNAL_SERVER_ERROR;
1133 wpabuf_free(msg);
1134 *replyname = NULL;
1135 *reply = NULL;
1136 return HTTP_OK;
1140 static enum http_reply_code
1141 web_process_reset_sta(struct upnp_wps_device_sm *sm, char *data,
1142 struct wpabuf **reply, const char **replyname)
1144 struct wpabuf *msg;
1145 enum http_reply_code ret;
1147 wpa_printf(MSG_DEBUG, "WPS UPnP: ResetSTA");
1148 msg = web_get_item(data, "NewMessage", &ret);
1149 if (msg == NULL)
1150 return ret;
1151 if (!sm->ctx->rx_req_reset_sta ||
1152 sm->ctx->rx_req_reset_sta(sm->priv, msg)) {
1153 wpabuf_free(msg);
1154 return HTTP_INTERNAL_SERVER_ERROR;
1156 wpabuf_free(msg);
1157 *replyname = NULL;
1158 *reply = NULL;
1159 return HTTP_OK;
1163 static const char *soap_prefix =
1164 "<?xml version=\"1.0\"?>\n"
1165 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1166 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n"
1167 "<s:Body>\n";
1168 static const char *soap_postfix =
1169 "</s:Body>\n</s:Envelope>\n";
1171 static const char *soap_error_prefix =
1172 "<s:Fault>\n"
1173 "<faultcode>s:Client</faultcode>\n"
1174 "<faultstring>UPnPError</faultstring>\n"
1175 "<detail>\n"
1176 "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n";
1177 static const char *soap_error_postfix =
1178 "<errorDescription>Error</errorDescription>\n"
1179 "</UPnPError>\n"
1180 "</detail>\n"
1181 "</s:Fault>\n";
1183 static void web_connection_send_reply(struct web_connection *c,
1184 enum http_reply_code ret,
1185 const char *action, int action_len,
1186 const struct wpabuf *reply,
1187 const char *replyname)
1189 struct wpabuf *buf;
1190 char *replydata;
1191 char *put_length_here = NULL;
1192 char *body_start = NULL;
1194 if (reply) {
1195 size_t len;
1196 replydata = (char *) base64_encode(wpabuf_head(reply),
1197 wpabuf_len(reply), &len);
1198 } else
1199 replydata = NULL;
1201 /* Parameters of the response:
1202 * action(action_len) -- action we are responding to
1203 * replyname -- a name we need for the reply
1204 * replydata -- NULL or null-terminated string
1206 buf = wpabuf_alloc(1000 + (replydata ? os_strlen(replydata) : 0U) +
1207 (action_len > 0 ? action_len * 2 : 0));
1208 if (buf == NULL) {
1209 wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to "
1210 "POST");
1211 wpabuf_free(buf);
1212 os_free(replydata);
1213 return;
1217 * Assuming we will be successful, put in the output header first.
1218 * Note: we do not keep connections alive (and httpread does
1219 * not support it)... therefore we must have Connection: close.
1221 if (ret == HTTP_OK) {
1222 wpabuf_put_str(buf,
1223 "HTTP/1.1 200 OK\r\n"
1224 "Content-Type: text/xml; "
1225 "charset=\"utf-8\"\r\n");
1226 } else {
1227 wpabuf_printf(buf, "HTTP/1.1 %d Error\r\n", ret);
1229 wpabuf_put_str(buf, http_connection_close);
1231 wpabuf_put_str(buf, "Content-Length: ");
1233 * We will paste the length in later, leaving some extra whitespace.
1234 * HTTP code is supposed to be tolerant of extra whitespace.
1236 put_length_here = wpabuf_put(buf, 0);
1237 wpabuf_put_str(buf, " \r\n");
1239 http_put_date(buf);
1241 /* terminating empty line */
1242 wpabuf_put_str(buf, "\r\n");
1244 body_start = wpabuf_put(buf, 0);
1246 if (ret == HTTP_OK) {
1247 wpabuf_put_str(buf, soap_prefix);
1248 wpabuf_put_str(buf, "<u:");
1249 wpabuf_put_data(buf, action, action_len);
1250 wpabuf_put_str(buf, "Response xmlns:u=\"");
1251 wpabuf_put_str(buf, urn_wfawlanconfig);
1252 wpabuf_put_str(buf, "\">\n");
1253 if (replydata && replyname) {
1254 /* TODO: might possibly need to escape part of reply
1255 * data? ...
1256 * probably not, unlikely to have ampersand(&) or left
1257 * angle bracket (<) in it...
1259 wpabuf_printf(buf, "<%s>", replyname);
1260 wpabuf_put_str(buf, replydata);
1261 wpabuf_printf(buf, "</%s>\n", replyname);
1263 wpabuf_put_str(buf, "</u:");
1264 wpabuf_put_data(buf, action, action_len);
1265 wpabuf_put_str(buf, "Response>\n");
1266 wpabuf_put_str(buf, soap_postfix);
1267 } else {
1268 /* Error case */
1269 wpabuf_put_str(buf, soap_prefix);
1270 wpabuf_put_str(buf, soap_error_prefix);
1271 wpabuf_printf(buf, "<errorCode>%d</errorCode>\n", ret);
1272 wpabuf_put_str(buf, soap_error_postfix);
1273 wpabuf_put_str(buf, soap_postfix);
1275 os_free(replydata);
1277 /* Now patch in the content length at the end */
1278 if (body_start && put_length_here) {
1279 int body_length = (char *) wpabuf_put(buf, 0) - body_start;
1280 char len_buf[10];
1281 os_snprintf(len_buf, sizeof(len_buf), "%d", body_length);
1282 os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
1285 send_wpabuf(c->sd, buf);
1286 wpabuf_free(buf);
1290 static const char * web_get_action(struct web_connection *c,
1291 const char *filename, size_t *action_len)
1293 const char *match;
1294 int match_len;
1295 char *b;
1296 char *action;
1298 *action_len = 0;
1299 if (os_strcasecmp(filename, UPNP_WPS_DEVICE_CONTROL_FILE)) {
1300 wpa_printf(MSG_INFO, "WPS UPnP: Invalid POST filename %s",
1301 filename);
1302 return NULL;
1304 /* The SOAPAction line of the header tells us what we want to do */
1305 b = httpread_hdr_line_get(c->hread, "SOAPAction:");
1306 if (b == NULL)
1307 return NULL;
1308 if (*b == '"')
1309 b++;
1310 else
1311 return NULL;
1312 match = urn_wfawlanconfig;
1313 match_len = os_strlen(urn_wfawlanconfig) - 1;
1314 if (os_strncasecmp(b, match, match_len))
1315 return NULL;
1316 b += match_len;
1317 /* skip over version */
1318 while (isgraph(*b) && *b != '#')
1319 b++;
1320 if (*b != '#')
1321 return NULL;
1322 b++;
1323 /* Following the sharp(#) should be the action and a double quote */
1324 action = b;
1325 while (isgraph(*b) && *b != '"')
1326 b++;
1327 if (*b != '"')
1328 return NULL;
1329 *action_len = b - action;
1330 return action;
1334 /* Given that we have received a header w/ POST, act upon it
1336 * Format of POST (case-insensitive):
1338 * First line must be:
1339 * POST /<file> HTTP/1.1
1340 * Since we don't do anything fancy we just ignore other lines.
1342 * Our response (if no error) which includes only required lines is:
1343 * HTTP/1.1 200 OK
1344 * Connection: close
1345 * Content-Type: text/xml
1346 * Date: <rfc1123-date>
1348 * Header lines must end with \r\n
1349 * Per RFC 2616, content-length: is not required but connection:close
1350 * would appear to be required (given that we will be closing it!).
1352 static void web_connection_parse_post(struct web_connection *c,
1353 const char *filename)
1355 enum http_reply_code ret;
1356 struct upnp_wps_device_sm *sm = c->sm;
1357 char *data = httpread_data_get(c->hread); /* body of http msg */
1358 const char *action;
1359 size_t action_len;
1360 const char *replyname = NULL; /* argument name for the reply */
1361 struct wpabuf *reply = NULL; /* data for the reply */
1363 ret = UPNP_INVALID_ACTION;
1364 action = web_get_action(c, filename, &action_len);
1365 if (action == NULL)
1366 goto bad;
1369 * There are quite a few possible actions. Although we appear to
1370 * support them all here, not all of them are necessarily supported by
1371 * callbacks at higher levels.
1373 if (!os_strncasecmp("GetDeviceInfo", action, action_len))
1374 ret = web_process_get_device_info(sm, &reply, &replyname);
1375 else if (!os_strncasecmp("PutMessage", action, action_len))
1376 ret = web_process_put_message(sm, data, &reply, &replyname);
1377 else if (!os_strncasecmp("GetAPSettings", action, action_len))
1378 ret = web_process_get_ap_settings(sm, data, &reply,
1379 &replyname);
1380 else if (!os_strncasecmp("SetAPSettings", action, action_len))
1381 ret = web_process_set_ap_settings(sm, data, &reply,
1382 &replyname);
1383 else if (!os_strncasecmp("DelAPSettings", action, action_len))
1384 ret = web_process_del_ap_settings(sm, data, &reply,
1385 &replyname);
1386 else if (!os_strncasecmp("GetSTASettings", action, action_len))
1387 ret = web_process_get_sta_settings(sm, data, &reply,
1388 &replyname);
1389 else if (!os_strncasecmp("SetSTASettings", action, action_len))
1390 ret = web_process_set_sta_settings(sm, data, &reply,
1391 &replyname);
1392 else if (!os_strncasecmp("DelSTASettings", action, action_len))
1393 ret = web_process_del_sta_settings(sm, data, &reply,
1394 &replyname);
1395 else if (!os_strncasecmp("PutWLANResponse", action, action_len))
1396 ret = web_process_put_wlan_response(sm, data, &reply,
1397 &replyname);
1398 else if (!os_strncasecmp("SetSelectedRegistrar", action, action_len))
1399 ret = web_process_set_selected_registrar(sm, data, &reply,
1400 &replyname);
1401 else if (!os_strncasecmp("RebootAP", action, action_len))
1402 ret = web_process_reboot_ap(sm, data, &reply, &replyname);
1403 else if (!os_strncasecmp("ResetAP", action, action_len))
1404 ret = web_process_reset_ap(sm, data, &reply, &replyname);
1405 else if (!os_strncasecmp("RebootSTA", action, action_len))
1406 ret = web_process_reboot_sta(sm, data, &reply, &replyname);
1407 else if (!os_strncasecmp("ResetSTA", action, action_len))
1408 ret = web_process_reset_sta(sm, data, &reply, &replyname);
1409 else
1410 wpa_printf(MSG_INFO, "WPS UPnP: Unknown POST type");
1412 bad:
1413 if (ret != HTTP_OK)
1414 wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret);
1415 web_connection_send_reply(c, ret, action, action_len, reply,
1416 replyname);
1417 wpabuf_free(reply);
1421 /* Given that we have received a header w/ SUBSCRIBE, act upon it
1423 * Format of SUBSCRIBE (case-insensitive):
1425 * First line must be:
1426 * SUBSCRIBE /wps_event HTTP/1.1
1428 * Our response (if no error) which includes only required lines is:
1429 * HTTP/1.1 200 OK
1430 * Server: xx, UPnP/1.0, xx
1431 * SID: uuid:xxxxxxxxx
1432 * Timeout: Second-<n>
1433 * Content-Length: 0
1434 * Date: xxxx
1436 * Header lines must end with \r\n
1437 * Per RFC 2616, content-length: is not required but connection:close
1438 * would appear to be required (given that we will be closing it!).
1440 static void web_connection_parse_subscribe(struct web_connection *c,
1441 const char *filename)
1443 struct upnp_wps_device_sm *sm = c->sm;
1444 struct wpabuf *buf;
1445 char *b;
1446 char *hdr = httpread_hdr_get(c->hread);
1447 char *h;
1448 char *match;
1449 int match_len;
1450 char *end;
1451 int len;
1452 int got_nt = 0;
1453 u8 uuid[UUID_LEN];
1454 int got_uuid = 0;
1455 char *callback_urls = NULL;
1456 struct subscription *s = NULL;
1457 enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
1459 buf = wpabuf_alloc(1000);
1460 if (buf == NULL)
1461 return;
1463 /* Parse/validate headers */
1464 h = hdr;
1465 /* First line: SUBSCRIBE /wps_event HTTP/1.1
1466 * has already been parsed.
1468 if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
1469 ret = HTTP_PRECONDITION_FAILED;
1470 goto error;
1472 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event");
1473 end = os_strchr(h, '\n');
1475 for (; end != NULL; h = end + 1) {
1476 /* Option line by option line */
1477 h = end + 1;
1478 end = os_strchr(h, '\n');
1479 if (end == NULL)
1480 break; /* no unterminated lines allowed */
1482 /* NT assures that it is our type of subscription;
1483 * not used for a renewl.
1485 match = "NT:";
1486 match_len = os_strlen(match);
1487 if (os_strncasecmp(h, match, match_len) == 0) {
1488 h += match_len;
1489 while (*h == ' ' || *h == '\t')
1490 h++;
1491 match = "upnp:event";
1492 match_len = os_strlen(match);
1493 if (os_strncasecmp(h, match, match_len) != 0) {
1494 ret = HTTP_BAD_REQUEST;
1495 goto error;
1497 got_nt = 1;
1498 continue;
1500 /* HOST should refer to us */
1501 #if 0
1502 match = "HOST:";
1503 match_len = os_strlen(match);
1504 if (os_strncasecmp(h, match, match_len) == 0) {
1505 h += match_len;
1506 while (*h == ' ' || *h == '\t')
1507 h++;
1508 .....
1510 #endif
1511 /* CALLBACK gives one or more URLs for NOTIFYs
1512 * to be sent as a result of the subscription.
1513 * Each URL is enclosed in angle brackets.
1515 match = "CALLBACK:";
1516 match_len = os_strlen(match);
1517 if (os_strncasecmp(h, match, match_len) == 0) {
1518 h += match_len;
1519 while (*h == ' ' || *h == '\t')
1520 h++;
1521 len = end - h;
1522 os_free(callback_urls);
1523 callback_urls = os_malloc(len + 1);
1524 if (callback_urls == NULL) {
1525 ret = HTTP_INTERNAL_SERVER_ERROR;
1526 goto error;
1528 os_memcpy(callback_urls, h, len);
1529 callback_urls[len] = 0;
1530 continue;
1532 /* SID is only for renewal */
1533 match = "SID:";
1534 match_len = os_strlen(match);
1535 if (os_strncasecmp(h, match, match_len) == 0) {
1536 h += match_len;
1537 while (*h == ' ' || *h == '\t')
1538 h++;
1539 match = "uuid:";
1540 match_len = os_strlen(match);
1541 if (os_strncasecmp(h, match, match_len) != 0) {
1542 ret = HTTP_BAD_REQUEST;
1543 goto error;
1545 h += match_len;
1546 while (*h == ' ' || *h == '\t')
1547 h++;
1548 if (uuid_str2bin(h, uuid)) {
1549 ret = HTTP_BAD_REQUEST;
1550 goto error;
1552 got_uuid = 1;
1553 continue;
1555 /* TIMEOUT is requested timeout, but apparently we can
1556 * just ignore this.
1560 if (got_uuid) {
1561 /* renewal */
1562 if (callback_urls) {
1563 ret = HTTP_BAD_REQUEST;
1564 goto error;
1566 s = subscription_renew(sm, uuid);
1567 if (s == NULL) {
1568 ret = HTTP_PRECONDITION_FAILED;
1569 goto error;
1571 } else if (callback_urls) {
1572 if (!got_nt) {
1573 ret = HTTP_PRECONDITION_FAILED;
1574 goto error;
1576 s = subscription_start(sm, callback_urls);
1577 if (s == NULL) {
1578 ret = HTTP_INTERNAL_SERVER_ERROR;
1579 goto error;
1581 } else {
1582 ret = HTTP_PRECONDITION_FAILED;
1583 goto error;
1586 /* success */
1587 http_put_reply_code(buf, HTTP_OK);
1588 wpabuf_put_str(buf, http_server_hdr);
1589 wpabuf_put_str(buf, http_connection_close);
1590 wpabuf_put_str(buf, "Content-Length: 0\r\n");
1591 wpabuf_put_str(buf, "SID: uuid:");
1592 /* subscription id */
1593 b = wpabuf_put(buf, 0);
1594 uuid_bin2str(s->uuid, b, 80);
1595 wpabuf_put(buf, os_strlen(b));
1596 wpabuf_put_str(buf, "\r\n");
1597 wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC);
1598 http_put_date(buf);
1599 /* And empty line to terminate header: */
1600 wpabuf_put_str(buf, "\r\n");
1602 send_wpabuf(c->sd, buf);
1603 wpabuf_free(buf);
1604 os_free(callback_urls);
1605 return;
1607 error:
1608 /* Per UPnP spec:
1609 * Errors
1610 * Incompatible headers
1611 * 400 Bad Request. If SID header and one of NT or CALLBACK headers
1612 * are present, the publisher must respond with HTTP error
1613 * 400 Bad Request.
1614 * Missing or invalid CALLBACK
1615 * 412 Precondition Failed. If CALLBACK header is missing or does not
1616 * contain a valid HTTP URL, the publisher must respond with HTTP
1617 * error 412 Precondition Failed.
1618 * Invalid NT
1619 * 412 Precondition Failed. If NT header does not equal upnp:event,
1620 * the publisher must respond with HTTP error 412 Precondition
1621 * Failed.
1622 * [For resubscription, use 412 if unknown uuid].
1623 * Unable to accept subscription
1624 * 5xx. If a publisher is not able to accept a subscription (such as
1625 * due to insufficient resources), it must respond with a
1626 * HTTP 500-series error code.
1627 * 599 Too many subscriptions (not a standard HTTP error)
1629 http_put_empty(buf, ret);
1630 send_wpabuf(c->sd, buf);
1631 wpabuf_free(buf);
1632 os_free(callback_urls);
1636 /* Given that we have received a header w/ UNSUBSCRIBE, act upon it
1638 * Format of UNSUBSCRIBE (case-insensitive):
1640 * First line must be:
1641 * UNSUBSCRIBE /wps_event HTTP/1.1
1643 * Our response (if no error) which includes only required lines is:
1644 * HTTP/1.1 200 OK
1645 * Content-Length: 0
1647 * Header lines must end with \r\n
1648 * Per RFC 2616, content-length: is not required but connection:close
1649 * would appear to be required (given that we will be closing it!).
1651 static void web_connection_parse_unsubscribe(struct web_connection *c,
1652 const char *filename)
1654 struct upnp_wps_device_sm *sm = c->sm;
1655 struct wpabuf *buf;
1656 char *hdr = httpread_hdr_get(c->hread);
1657 char *h;
1658 char *match;
1659 int match_len;
1660 char *end;
1661 u8 uuid[UUID_LEN];
1662 int got_uuid = 0;
1663 struct subscription *s = NULL;
1664 enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
1666 /* Parse/validate headers */
1667 h = hdr;
1668 /* First line: UNSUBSCRIBE /wps_event HTTP/1.1
1669 * has already been parsed.
1671 if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
1672 ret = HTTP_PRECONDITION_FAILED;
1673 goto send_msg;
1675 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event");
1676 end = os_strchr(h, '\n');
1678 for (; end != NULL; h = end + 1) {
1679 /* Option line by option line */
1680 h = end + 1;
1681 end = os_strchr(h, '\n');
1682 if (end == NULL)
1683 break; /* no unterminated lines allowed */
1685 /* HOST should refer to us */
1686 #if 0
1687 match = "HOST:";
1688 match_len = os_strlen(match);
1689 if (os_strncasecmp(h, match, match_len) == 0) {
1690 h += match_len;
1691 while (*h == ' ' || *h == '\t')
1692 h++;
1693 .....
1695 #endif
1696 /* SID is only for renewal */
1697 match = "SID:";
1698 match_len = os_strlen(match);
1699 if (os_strncasecmp(h, match, match_len) == 0) {
1700 h += match_len;
1701 while (*h == ' ' || *h == '\t')
1702 h++;
1703 match = "uuid:";
1704 match_len = os_strlen(match);
1705 if (os_strncasecmp(h, match, match_len) != 0) {
1706 ret = HTTP_BAD_REQUEST;
1707 goto send_msg;
1709 h += match_len;
1710 while (*h == ' ' || *h == '\t')
1711 h++;
1712 if (uuid_str2bin(h, uuid)) {
1713 ret = HTTP_BAD_REQUEST;
1714 goto send_msg;
1716 got_uuid = 1;
1717 continue;
1721 if (got_uuid) {
1722 s = subscription_find(sm, uuid);
1723 if (s) {
1724 wpa_printf(MSG_DEBUG, "WPS UPnP: Unsubscribing %p %s",
1726 (s && s->addr_list &&
1727 s->addr_list->domain_and_port) ?
1728 s->addr_list->domain_and_port : "-null-");
1729 subscription_unlink(s);
1730 subscription_destroy(s);
1732 } else {
1733 wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not "
1734 "found)");
1735 ret = HTTP_PRECONDITION_FAILED;
1736 goto send_msg;
1739 ret = HTTP_OK;
1741 send_msg:
1742 buf = wpabuf_alloc(200);
1743 if (buf == NULL)
1744 return;
1745 http_put_empty(buf, ret);
1746 send_wpabuf(c->sd, buf);
1747 wpabuf_free(buf);
1751 /* Send error in response to unknown requests */
1752 static void web_connection_unimplemented(struct web_connection *c)
1754 struct wpabuf *buf;
1755 buf = wpabuf_alloc(200);
1756 if (buf == NULL)
1757 return;
1758 http_put_empty(buf, HTTP_UNIMPLEMENTED);
1759 send_wpabuf(c->sd, buf);
1760 wpabuf_free(buf);
1765 /* Called when we have gotten an apparently valid http request.
1767 static void web_connection_check_data(struct web_connection *c)
1769 struct httpread *hread = c->hread;
1770 enum httpread_hdr_type htype = httpread_hdr_type_get(hread);
1771 /* char *data = httpread_data_get(hread); */
1772 char *filename = httpread_uri_get(hread);
1774 c->done = 1;
1775 if (!filename) {
1776 wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI");
1777 return;
1779 /* Trim leading slashes from filename */
1780 while (*filename == '/')
1781 filename++;
1783 wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d from %s:%d",
1784 htype, inet_ntoa(c->cli_addr.sin_addr),
1785 htons(c->cli_addr.sin_port));
1787 switch (htype) {
1788 case HTTPREAD_HDR_TYPE_GET:
1789 web_connection_parse_get(c, filename);
1790 break;
1791 case HTTPREAD_HDR_TYPE_POST:
1792 web_connection_parse_post(c, filename);
1793 break;
1794 case HTTPREAD_HDR_TYPE_SUBSCRIBE:
1795 web_connection_parse_subscribe(c, filename);
1796 break;
1797 case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
1798 web_connection_parse_unsubscribe(c, filename);
1799 break;
1800 /* We are not required to support M-POST; just plain
1801 * POST is supposed to work, so we only support that.
1802 * If for some reason we need to support M-POST, it is
1803 * mostly the same as POST, with small differences.
1805 default:
1806 /* Send 501 for anything else */
1807 web_connection_unimplemented(c);
1808 break;
1814 /* called back when we have gotten request */
1815 static void web_connection_got_file_handler(struct httpread *handle,
1816 void *cookie,
1817 enum httpread_event en)
1819 struct web_connection *c = cookie;
1821 if (en == HTTPREAD_EVENT_FILE_READY)
1822 web_connection_check_data(c);
1823 web_connection_stop(c);
1827 /* web_connection_start - Start web connection
1828 * @sm: WPS UPnP state machine from upnp_wps_device_init()
1829 * @sd: Socket descriptor
1830 * @addr: Client address
1832 * The socket descriptor sd is handed over for ownership by the WPS UPnP
1833 * state machine.
1835 static void web_connection_start(struct upnp_wps_device_sm *sm,
1836 int sd, struct sockaddr_in *addr)
1838 struct web_connection *c = NULL;
1840 /* if too many connections, bail */
1841 if (sm->n_web_connections >= MAX_WEB_CONNECTIONS) {
1842 close(sd);
1843 return;
1846 c = os_zalloc(sizeof(*c));
1847 if (c == NULL)
1848 return;
1849 os_memcpy(&c->cli_addr, addr, sizeof(c->cli_addr));
1850 c->sm = sm;
1851 c->sd = sd;
1852 #if 0
1854 * Setting non-blocking should not be necessary for read, and can mess
1855 * up sending where blocking might be better.
1857 if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0)
1858 break;
1859 #endif
1860 c->hread = httpread_create(c->sd, web_connection_got_file_handler,
1861 c /* cookie */,
1862 WEB_CONNECTION_MAX_READ,
1863 WEB_CONNECTION_TIMEOUT_SEC);
1864 if (c->hread == NULL)
1865 goto fail;
1866 if (sm->web_connections) {
1867 c->next = sm->web_connections;
1868 c->prev = c->next->prev;
1869 c->prev->next = c;
1870 c->next->prev = c;
1871 } else {
1872 sm->web_connections = c->next = c->prev = c;
1874 sm->n_web_connections++;
1875 return;
1877 fail:
1878 if (c)
1879 web_connection_stop(c);
1884 * Listening for web connections
1885 * We have a single TCP listening port, and hand off connections as we get
1886 * them.
1889 void web_listener_stop(struct upnp_wps_device_sm *sm)
1891 if (sm->web_sd_registered) {
1892 sm->web_sd_registered = 0;
1893 eloop_unregister_sock(sm->web_sd, EVENT_TYPE_READ);
1895 if (sm->web_sd >= 0)
1896 close(sm->web_sd);
1897 sm->web_sd = -1;
1901 static void web_listener_handler(int sd, void *eloop_ctx, void *sock_ctx)
1903 struct sockaddr_in addr;
1904 socklen_t addr_len = sizeof(addr);
1905 struct upnp_wps_device_sm *sm = sock_ctx;
1906 int new_sd;
1908 /* Create state for new connection */
1909 /* Remember so we can cancel if need be */
1910 new_sd = accept(sm->web_sd, (struct sockaddr *) &addr, &addr_len);
1911 if (new_sd < 0) {
1912 wpa_printf(MSG_ERROR, "WPS UPnP: web listener accept "
1913 "errno=%d (%s) web_sd=%d",
1914 errno, strerror(errno), sm->web_sd);
1915 return;
1917 web_connection_start(sm, new_sd, &addr);
1921 int web_listener_start(struct upnp_wps_device_sm *sm)
1923 struct sockaddr_in addr;
1924 int port;
1926 sm->web_sd = socket(AF_INET, SOCK_STREAM, 0);
1927 if (sm->web_sd < 0)
1928 goto fail;
1929 if (fcntl(sm->web_sd, F_SETFL, O_NONBLOCK) != 0)
1930 goto fail;
1931 port = 49152; /* first non-reserved port */
1932 for (;;) {
1933 os_memset(&addr, 0, sizeof(addr));
1934 addr.sin_family = AF_INET;
1935 addr.sin_addr.s_addr = sm->ip_addr;
1936 addr.sin_port = htons(port);
1937 if (bind(sm->web_sd, (struct sockaddr *) &addr,
1938 sizeof(addr)) == 0)
1939 break;
1940 if (errno == EADDRINUSE) {
1941 /* search for unused port */
1942 if (++port == 65535)
1943 goto fail;
1944 continue;
1946 goto fail;
1948 if (listen(sm->web_sd, 10 /* max backlog */) != 0)
1949 goto fail;
1950 if (fcntl(sm->web_sd, F_SETFL, O_NONBLOCK) != 0)
1951 goto fail;
1952 if (eloop_register_sock(sm->web_sd, EVENT_TYPE_READ,
1953 web_listener_handler, NULL, sm))
1954 goto fail;
1955 sm->web_sd_registered = 1;
1956 sm->web_port = port;
1958 return 0;
1960 fail:
1961 /* Error */
1962 web_listener_stop(sm);
1963 return -1;