From 8b0a1ae3621fb74a94d171385e235afaec36a748 Mon Sep 17 00:00:00 2001 From: Our Air Quality Date: Sun, 13 Aug 2017 15:16:50 +1000 Subject: [PATCH] Add Wificfg Uses the sysparam store to save the wifi configuration. Adds a basic http server for configuration. --- examples/wificfg/.gitignore | 1 + examples/wificfg/FreeRTOSConfig.h | 7 + examples/wificfg/Makefile | 5 + examples/wificfg/content/index.html | 18 + examples/wificfg/local.mk | 1 + examples/wificfg/wificfg.c | 94 + extras/wificfg/component.mk | 10 + extras/wificfg/content/challenge.html | 30 + extras/wificfg/content/favicon.ico | 11 + extras/wificfg/content/script.js | 8 + extras/wificfg/content/style.css | 19 + extras/wificfg/content/tasks.html | 18 + extras/wificfg/content/wificfg/ap.html | 90 + extras/wificfg/content/wificfg/index.html | 52 + extras/wificfg/content/wificfg/sta.html | 68 + extras/wificfg/wificfg.c | 2027 +++++++++++++++++++++ extras/wificfg/wificfg.h | 137 ++ 17 files changed, 2596 insertions(+) create mode 100644 examples/wificfg/.gitignore create mode 100644 examples/wificfg/FreeRTOSConfig.h create mode 100644 examples/wificfg/Makefile create mode 100644 examples/wificfg/content/index.html create mode 100644 examples/wificfg/local.mk create mode 100644 examples/wificfg/wificfg.c create mode 100644 extras/wificfg/component.mk create mode 100644 extras/wificfg/content/challenge.html create mode 100644 extras/wificfg/content/favicon.ico create mode 100644 extras/wificfg/content/script.js create mode 100644 extras/wificfg/content/style.css create mode 100644 extras/wificfg/content/tasks.html create mode 100644 extras/wificfg/content/wificfg/ap.html create mode 100644 extras/wificfg/content/wificfg/index.html create mode 100644 extras/wificfg/content/wificfg/sta.html create mode 100644 extras/wificfg/wificfg.c create mode 100644 extras/wificfg/wificfg.h diff --git a/examples/wificfg/.gitignore b/examples/wificfg/.gitignore new file mode 100644 index 0000000..e067149 --- /dev/null +++ b/examples/wificfg/.gitignore @@ -0,0 +1 @@ +!local.mk diff --git a/examples/wificfg/FreeRTOSConfig.h b/examples/wificfg/FreeRTOSConfig.h new file mode 100644 index 0000000..baf25a4 --- /dev/null +++ b/examples/wificfg/FreeRTOSConfig.h @@ -0,0 +1,7 @@ +#define configUSE_TRACE_FACILITY 1 +#define configGENERATE_RUN_TIME_STATS 1 +#define portGET_RUN_TIME_COUNTER_VALUE() (RTC.COUNTER) +#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() {} + +/* Use the defaults for everything else */ +#include_next diff --git a/examples/wificfg/Makefile b/examples/wificfg/Makefile new file mode 100644 index 0000000..2cbb996 --- /dev/null +++ b/examples/wificfg/Makefile @@ -0,0 +1,5 @@ +# Makefile for wificfg example +PROGRAM=wificfg +EXTRA_COMPONENTS=extras/dhcpserver extras/wificfg + +include ../../common.mk diff --git a/examples/wificfg/content/index.html b/examples/wificfg/content/index.html new file mode 100644 index 0000000..5512b7d --- /dev/null +++ b/examples/wificfg/content/index.html @@ -0,0 +1,18 @@ +"" +"" +"" +"" +"", +"" +"" +"" +"" +"", +"" diff --git a/examples/wificfg/local.mk b/examples/wificfg/local.mk new file mode 100644 index 0000000..c6c3129 --- /dev/null +++ b/examples/wificfg/local.mk @@ -0,0 +1 @@ +FLASH_SIZE ?= 32 diff --git a/examples/wificfg/wificfg.c b/examples/wificfg/wificfg.c new file mode 100644 index 0000000..a90a9da --- /dev/null +++ b/examples/wificfg/wificfg.c @@ -0,0 +1,94 @@ +/* + * Example Wifi configuration via an access point. + * + * Copyright (C) 2016 OurAirQuality.org + * + * Licensed under the Apache License, Version 2.0, January 2004 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.apache.org/licenses/ + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS WITH THE SOFTWARE. + * + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "lwip/sockets.h" + +#include "wificfg/wificfg.h" + +#include "sysparam.h" + +static const char http_success_header[] = "HTTP/1.1 200 \r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "Cache-Control: no-store\r\n" + "Transfer-Encoding: chunked\r\n" + "Connection: close\r\n" + "\r\n"; +static const char *http_index_content[] = { +#include "content/index.html" +}; + +static int handle_index(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (wificfg_write_string(s, http_success_header) < 0) return -1; + + if (method != HTTP_METHOD_HEAD) { + if (wificfg_write_string_chunk(s, http_index_content[0], buf, len) < 0) return -1; + if (wificfg_write_html_title(s, buf, len, "Home") < 0) return -1; + if (wificfg_write_string_chunk(s, http_index_content[1], buf, len) < 0) return -1; + + socklen_t addr_len; + struct sockaddr addr; + addr_len = sizeof(addr); + getpeername(s, (struct sockaddr*)&addr, &addr_len); + + if (wificfg_write_string_chunk(s, "
", buf, len) < 0) return -1; + if (addr.sa_family == AF_INET) { + struct sockaddr_in *sa = (struct sockaddr_in *)&addr; + if (wificfg_write_string_chunk(s, "
Peer address
", buf, len) < 0) return -1; + snprintf(buf, len, "
" IPSTR " : %d
", + IP2STR((ip4_addr_t *)&sa->sin_addr.s_addr), ntohs(sa->sin_port)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, "
", buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, http_index_content[2], buf, len) < 0) return -1; + if (wificfg_write_chunk_end(s) < 0) return -1; + } + return 0; +} + +static const wificfg_dispatch dispatch_list[] = { + {"/", HTTP_METHOD_GET, handle_index, false}, + {"/index.html", HTTP_METHOD_GET, handle_index, false}, + {NULL, HTTP_METHOD_ANY, NULL} +}; + +void user_init(void) +{ + uart_set_baud(0, 115200); + printf("SDK version:%s\n", sdk_system_get_sdk_version()); + + sdk_wifi_set_sleep_type(WIFI_SLEEP_MODEM); + + wificfg_init(80, dispatch_list); +} diff --git a/extras/wificfg/component.mk b/extras/wificfg/component.mk new file mode 100644 index 0000000..5412c52 --- /dev/null +++ b/extras/wificfg/component.mk @@ -0,0 +1,10 @@ +# Component makefile for extras/wificfg + +# Expected anyone using wificfg includes it as 'wificfg/wificfg.h' +INC_DIRS += $(wificfg_ROOT).. + +# args for passing into compile rule generation +wificfg_INC_DIR = +wificfg_SRC_DIR = $(wificfg_ROOT) + +$(eval $(call component_compile_rules,wificfg)) diff --git a/extras/wificfg/content/challenge.html b/extras/wificfg/content/challenge.html new file mode 100644 index 0000000..568ead2 --- /dev/null +++ b/extras/wificfg/content/challenge.html @@ -0,0 +1,30 @@ +"" +"" +"" +"" +"", +"" +"" +"" +"" +"" +"
" +"
" +"Unlock the configuration interface" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"" diff --git a/extras/wificfg/content/favicon.ico b/extras/wificfg/content/favicon.ico new file mode 100644 index 0000000..93f7af2 --- /dev/null +++ b/extras/wificfg/content/favicon.ico @@ -0,0 +1,11 @@ +"HTTP/1.1 200 \r\n" +"Content-Type: image/svg+xml\r\n" +"Cache-Control: max-age=900\r\n" +"Transfer-Encoding: chunked\r\n" +"Connection: close\r\n" +"\r\n", +"" +"" +"" +"" +"" diff --git a/extras/wificfg/content/script.js b/extras/wificfg/content/script.js new file mode 100644 index 0000000..68064c5 --- /dev/null +++ b/extras/wificfg/content/script.js @@ -0,0 +1,8 @@ +"HTTP/1.1 200 \r\n" +"Content-Type: text/javascript\r\n" +"Cache-Control: max-age=900\r\n" +"Transfer-Encoding: chunked\r\n" +"Connection: close\r\n" +"\r\n", +"function myFunction() { var x = document.getElementById(\"myTopnav\");" +"if (x.className === \"topnav\") { x.className += \" responsive\"; } else { x.className = \"topnav\"; } }" diff --git a/extras/wificfg/content/style.css b/extras/wificfg/content/style.css new file mode 100644 index 0000000..9155940 --- /dev/null +++ b/extras/wificfg/content/style.css @@ -0,0 +1,19 @@ +"HTTP/1.1 200 \r\n" +"Content-Type: text/css\r\n" +"Cache-Control: max-age=900\r\n" +"Transfer-Encoding: chunked\r\n" +"\r\n", +".dlh dd,h1{font-weight:300}.dlh{font-size:0;text-align:center}" +".dlh dd,.dlh dt{width:48%;width:calc(50% - 10px);margin:8px 0;display:inline-block;font-size:16px;vertical-align:middle}" +".dlh dt{text-align:right;padding-right:10px}" +".dlh dd{font-size:18px;text-align:left;padding-left:10px}" +"ul.topnav{list-style-type:none;margin:0;padding:0;overflow:hidden;background-color:#bbb}" +"ul.topnav li{float:left}" +"ul.topnav li a{display:inline-block;color:#444;text-align:center;padding:14px 16px;text-decoration:none;transition:.3s;font-size:17px}" +"ul.topnav li a:hover{background-color:#ddd}ul.topnav li.icon{display:none}" +"@media screen and (max-width:680px){ul.topnav li:not(.active){display:none}ul.topnav li.icon{float:right;display:inline-block}ul.topnav.responsive{position:relative}ul.topnav.responsive li.icon{position:absolute;right:0;top:0}ul.topnav.responsive li{float:none;display:inline}ul.topnav.responsive li a{display:block;text-align:left}}" +"html{min-height:100%}" +"body{background:#d0e4f7;background:-moz-linear-gradient(top, #d0e4f7 0%, #73b1e7 24%, #0a77d5 50%, #539fe1 79%, #87bcea 100%);background:-webkit-linear-gradient(top, #d0e4f7 0%,#73b1e7 24%,#0a77d5 50%,#539fe1 79%,#87bcea 100%);background:linear-gradient(to bottom, #d0e4f7 0%,#73b1e7 24%,#0a77d5 50%,#539fe1 79%,#87bcea 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#d0e4f7', endColorstr='#87bcea',GradientType=0)}" +"body{font-family:helvetica,arial,sans-serif;font-size:16px}" +"h1{font-size:26px}" +"p{font-size:14px}" diff --git a/extras/wificfg/content/tasks.html b/extras/wificfg/content/tasks.html new file mode 100644 index 0000000..d084ea7 --- /dev/null +++ b/extras/wificfg/content/tasks.html @@ -0,0 +1,18 @@ +"" +"" +"" +"" +"", +"" +"" +"" +"" +"", +"" diff --git a/extras/wificfg/content/wificfg/ap.html b/extras/wificfg/content/wificfg/ap.html new file mode 100644 index 0000000..8dbb730 --- /dev/null +++ b/extras/wificfg/content/wificfg/ap.html @@ -0,0 +1,90 @@ +"" +"" +"" +"" +"", +"" +"" +"" +"" +"" +"
" +"
" +"WiFi Access Point configuration" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
 
" +"
" +"" +"
" +"" diff --git a/extras/wificfg/content/wificfg/index.html b/extras/wificfg/content/wificfg/index.html new file mode 100644 index 0000000..cb81712 --- /dev/null +++ b/extras/wificfg/content/wificfg/index.html @@ -0,0 +1,52 @@ +"" +"" +"" +"" +"", +"" +"" +"" +"" +"" +"

WiFi Status

" +"
", +"
" +"
" +"
" +"
" +"Lock the configuration interface" +"

These WiFi configuration pages can be disabled for security on a shared network. If a password is supplied then they can be unlocked. Warning: if no password is supplied then it will not be possible to unlock these pages via this interface.

" +"
" +"
" +"
" +"
" +"" +"
" +"
" +"
" +"
" +"
" +"Restart device" +"

A restart is necessary for some changes to take effect.

" +"
" +"
" +"
" +"
" +"Erase configuration" +"

Erases the device configuration stored in the flash memory and restarts the device. " +"This might be useful to clear stored passwords and private configuration information." +"

" +"
" +"
" +"
", +"" diff --git a/extras/wificfg/content/wificfg/sta.html b/extras/wificfg/content/wificfg/sta.html new file mode 100644 index 0000000..65bfe9d --- /dev/null +++ b/extras/wificfg/content/wificfg/sta.html @@ -0,0 +1,68 @@ +"" +"" +"" +"" +"", +"" +"" +"" +"" +"" +"
" +"
" +"WiFi Station configuration" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
" +"
 
" +"
" +"" +"
" +"" diff --git a/extras/wificfg/wificfg.c b/extras/wificfg/wificfg.c new file mode 100644 index 0000000..bde84a7 --- /dev/null +++ b/extras/wificfg/wificfg.c @@ -0,0 +1,2027 @@ +/* + * WiFi configuration via a simple web server. + * + * Copyright (C) 2016 OurAirQuality.org + * + * Licensed under the Apache License, Version 2.0, January 2004 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.apache.org/licenses/ + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS WITH THE SOFTWARE. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include "lwip/netdb.h" +#include "lwip/dns.h" + +#include "wificfg.h" +#include "sysparam.h" + +char *wificfg_default_ssid = "EOR_%02X%02X%02X"; +char *wificfg_default_password = "esp-open-rtos"; +char *wificfg_default_hostname = "eor-%02x%02x%02x"; + +/* The http task stack allocates a single buffer to do much of it's work. */ +#define HTTP_BUFFER_SIZE 54 + +/* + * Read a line terminated by "\r\n" or "\n" to be robust. Used to read the http + * status line and headers. On success returns the number of characters read, + * which might be more that the available buffer size 'len'. Excess characters + * in a line are discarded as a protection against excessively long lines. On + * failure -1 is returned. The character case is lowered to give a canonical + * case for easier comparision. The buffer is null terminated on success, even + * if truncated. + */ +static int read_crlf_line(int s, char *buf, size_t len) +{ + size_t num = 0; + + do { + char c; + int r = read(s, &c, 1); + + /* Expecting a known terminator so fail on EOF. */ + if (r <= 0) + return -1; + + if (c == '\n') + break; + + /* Remove a trailing '\r', and many unexpected characters. */ + if (c < 0x20 || c > 0x7e) + continue; + + if (num < len) + buf[num] = tolower((unsigned char)c); + + num++; + } while(1); + + /* Null terminate. */ + buf[num >= len ? len - 1 : num] = 0; + + return num; +} + +int wificfg_form_name_value(int s, bool *valp, size_t *rem, char *buf, size_t len) +{ + size_t num = 0; + + do { + if (*rem == 0) + break; + + char c; + int r = read(s, &c, 1); + + /* Expecting a known number of characters so fail on EOF. */ + if (r <= 0) return -1; + + (*rem)--; + + if (valp && c == '=') { + *valp = true; + break; + } + + if (c == '&') { + if (valp) + *valp = false; + break; + } + + if (num < len) + buf[num] = c; + + num++; + } while(1); + + /* Null terminate. */ + buf[num >= len ? len - 1 : num] = 0; + + return num; +} + +void wificfg_form_url_decode(char *string) +{ + char *src = string; + char *src_end = string + strlen(string); + char *dst = string; + + while (src < src_end) { + char c = *src++; + if (c == '+') { + c = ' '; + } else if (c == '%' && src < src_end - 1) { + unsigned char c1 = src[0]; + unsigned char c2 = src[1]; + if (isxdigit(c1) && isxdigit(c2)) { + c1 = tolower(c1); + int d1 = (c1 >= 'a' && c1 <= 'z') ? c1 - 'a' + 10 : c1 - '0'; + c2 = tolower(c2); + int d2 = (c2 >= 'a' && c2 <= 'z') ? c2 - 'a' + 10 : c2 - '0'; + *dst++ = (d1 << 4) + d2; + src += 2; + continue; + } + } + *dst++ = c; + } + + *dst = 0; +} + +/* HTML escaping. */ +void wificfg_html_escape(char *string, char *buf, size_t len) +{ + size_t i; + size_t out = 0; + + for (i = 0, out = 0; out < len - 1; ) { + char c = string[i++]; + if (!c) + break; + + if (c == '&') { + if (out >= len - 5) + break; + buf[out] = '&'; + buf[out + 1] = 'a'; + buf[out + 2] = 'm'; + buf[out + 3] = 'p'; + buf[out + 4] = ';'; + out += 5; + continue; + } + if (c == '"') { + if (out >= len - 6) + break; + buf[out] = '&'; + buf[out + 1] = 'q'; + buf[out + 2] = 'u'; + buf[out + 3] = 'o'; + buf[out + 4] = 't'; + buf[out + 5] = ';'; + out += 6; + continue; + } + if (c == '<') { + if (out >= len - 4) + break; + buf[out] = '&'; + buf[out + 1] = 'l'; + buf[out + 2] = 't'; + buf[out + 3] = ';'; + out += 4; + continue; + } + if (c == '>') { + if (out >= len - 4) + break; + buf[out] = '&'; + buf[out + 1] = 'g'; + buf[out + 2] = 't'; + buf[out + 3] = ';'; + out += 4; + continue; + } + + buf[out++] = c; + } + + buf[out] = 0; +} + +/* Various keywords are interned as they are read. */ + +static const struct { + const char *str; + wificfg_method method; +} method_table[] = { + {"get", HTTP_METHOD_GET}, + {"post", HTTP_METHOD_POST}, + {"head", HTTP_METHOD_HEAD} +}; + +static wificfg_method intern_http_method(char *str) +{ + int i; + for (i = 0; i < sizeof(method_table) / sizeof(method_table[0]); i++) { + if (!strcmp(str, method_table[i].str)) + return method_table[i].method; + } + return HTTP_METHOD_OTHER; +} + +/* + * The web server recognizes only these header names. Other headers are ignored. + */ +typedef enum { + HTTP_HEADER_HOST, + HTTP_HEADER_CONTENT_LENGTH, + HTTP_HEADER_CONTENT_TYPE, + HTTP_HEADER_CONNECTION, + HTTP_HEADER_OTHER +} http_header; + +static const struct { + const char *str; + http_header name; +} http_header_table[] = { + {"host", HTTP_HEADER_HOST}, + {"content-length", HTTP_HEADER_CONTENT_LENGTH}, + {"content-type", HTTP_HEADER_CONTENT_TYPE}, + {"connection", HTTP_HEADER_CONNECTION} +}; + +static http_header intern_http_header(char *str) +{ + int i; + for (i = 0; i < sizeof(http_header_table) / sizeof(http_header_table[0]); i++) { + if (!strcmp(str, http_header_table[i].str)) + return http_header_table[i].name; + } + return HTTP_HEADER_OTHER; +} + + +static const struct { + const char *str; + wificfg_content_type type; +} content_type_table[] = { + {"application/x-www-form-urlencoded", HTTP_CONTENT_TYPE_WWW_FORM_URLENCODED} +}; + +static wificfg_content_type intern_http_content_type(char *str) +{ + int i; + for (i = 0; i < sizeof(content_type_table) / sizeof(content_type_table[0]); i++) { + if (!strcmp(str, content_type_table[i].str)) + return content_type_table[i].type; + } + return HTTP_CONTENT_TYPE_OTHER; +} + +static char *skip_whitespace(char *string) +{ + while (isspace((unsigned char)*string)) string++; + return string; +} + +static char *skip_to_whitespace(char *string) +{ + do { + unsigned char c = *string; + if (!c || isspace(c)) + break; + string++; + } while (1); + + return string; +} + +int wificfg_write_string(int s, const char *str) +{ + int res = write(s, str, strlen(str)); + return res; +} + +int wificfg_write_string_chunk(int s, const char *str, char *buf, size_t len) +{ + size_t str_len = strlen(str); + + if (str_len == 0) { + /* Can not be encoded, would be EOF. */ + return 0; + } + + if (str_len + 6 < len) { + /* Can fit the chunk in the buffer. */ + memmove(buf + 4, str, str_len); + size_t start = 1; + if (str_len < 10) { + buf[1] = '0' + str_len; + } else if (str_len < 16) { + buf[1] = 'a' + str_len - 10; + } else { + uint32_t digit0 = str_len >> 4; + if (digit0 < 10) { + buf[0] = '0' + digit0; + } else { + buf[0] = 'a' + digit0 - 10; + } + uint32_t digit1 = str_len & 0xf; + if (digit1 < 10) { + buf[1] = '0' + digit1; + } else { + buf[1] = 'a' + digit1 - 10; + } + start = 0; + } + buf[2] = '\r'; + buf[3] = '\n'; + buf[4 + str_len] = '\r'; + buf[4 + str_len + 1] = '\n'; + return write(s, buf + start, 4 - start + str_len + 2); + } + + /* Else too big for the buffer. */ + char size_buf[8]; + size_t size_len = snprintf(size_buf, sizeof(size_buf), "%x\r\n", str_len); + int res = write(s, size_buf, size_len); + if (res != size_len) { + return res; + } + res = write(s, str, str_len); + if (res != str_len) { + return res; + } + return write(s, size_buf + size_len - 2, 2); +} + +int wificfg_write_chunk_end(int s) +{ + return wificfg_write_string(s, "0\r\n\r\n"); +} + +typedef enum { + FORM_NAME_CFG_ENABLE, + FORM_NAME_CFG_PASSWORD, + FORM_NAME_HOSTNAME, + FORM_NAME_STA_ENABLE, + FORM_NAME_STA_DISABLED_RESTARTS, + FORM_NAME_STA_SSID, + FORM_NAME_STA_PASSWORD, + FORM_NAME_STA_DHCP, + FORM_NAME_STA_IP_ADDR, + FORM_NAME_STA_NETMASK, + FORM_NAME_STA_GATEWAY, + FORM_NAME_AP_ENABLE, + FORM_NAME_AP_DISABLE_IF_STA, + FORM_NAME_AP_DISABLED_RESTARTS, + FORM_NAME_AP_SSID, + FORM_NAME_AP_PASSWORD, + FORM_NAME_AP_SSID_HIDDEN, + FORM_NAME_AP_CHANNEL, + FORM_NAME_AP_AUTHMODE, + FORM_NAME_AP_MAX_CONN, + FORM_NAME_AP_BEACON_INTERVAL, + FORM_NAME_AP_IP_ADDR, + FORM_NAME_AP_NETMASK, + FORM_NAME_AP_DHCP_LEASES, + FORM_NAME_AP_DNS, + FORM_NAME_DONE, + FORM_NAME_NONE +} form_name; + +static const struct { + const char *str; + form_name name; +} form_name_table[] = { + {"cfg_enable", FORM_NAME_CFG_ENABLE}, + {"cfg_password", FORM_NAME_CFG_PASSWORD}, + {"hostname", FORM_NAME_HOSTNAME}, + {"sta_enable", FORM_NAME_STA_ENABLE}, + {"sta_disabled_restarts", FORM_NAME_STA_DISABLED_RESTARTS}, + {"sta_ssid", FORM_NAME_STA_SSID}, + {"sta_dhcp", FORM_NAME_STA_DHCP}, + {"sta_password", FORM_NAME_STA_PASSWORD}, + {"sta_ip_addr", FORM_NAME_STA_IP_ADDR}, + {"sta_netmask", FORM_NAME_STA_NETMASK}, + {"sta_gateway", FORM_NAME_STA_GATEWAY}, + {"ap_enable", FORM_NAME_AP_ENABLE}, + {"ap_disable_if_sta", FORM_NAME_AP_DISABLE_IF_STA}, + {"ap_disabled_restarts", FORM_NAME_AP_DISABLED_RESTARTS}, + {"ap_ssid", FORM_NAME_AP_SSID}, + {"ap_password", FORM_NAME_AP_PASSWORD}, + {"ap_ssid_hidden", FORM_NAME_AP_SSID_HIDDEN}, + {"ap_channel", FORM_NAME_AP_CHANNEL}, + {"ap_authmode", FORM_NAME_AP_AUTHMODE}, + {"ap_max_conn", FORM_NAME_AP_MAX_CONN}, + {"ap_beacon_interval", FORM_NAME_AP_BEACON_INTERVAL}, + {"ap_ip_addr", FORM_NAME_AP_IP_ADDR}, + {"ap_netmask", FORM_NAME_AP_NETMASK}, + {"ap_dhcp_leases", FORM_NAME_AP_DHCP_LEASES}, + {"ap_dns", FORM_NAME_AP_DNS}, + {"done", FORM_NAME_DONE} +}; + +static form_name intern_form_name(char *str) +{ + int i; + for (i = 0; i < sizeof(form_name_table) / sizeof(form_name_table[0]); i++) { + if (!strcmp(str, form_name_table[i].str)) + return form_name_table[i].name; + } + return FORM_NAME_NONE; +} + + +static const char *http_favicon[] = { +#include "content/favicon.ico" +}; + +static int handle_favicon(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (wificfg_write_string(s, http_favicon[0]) < 0) return -1; + + if (method != HTTP_METHOD_HEAD) { + if (wificfg_write_string_chunk(s, http_favicon[1], buf, len) < 0) return -1; + if (wificfg_write_chunk_end(s) < 0) return -1; + } + return 0; +} + +// .value-lg{font-size:24px}.label-extra{display:block;font-style:italic;font-size:13px} +// devo: "Cache-Control: no-store\r\n" +static const char *http_style[] = { +#include "content/style.css" +}; + + +static int handle_style(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (wificfg_write_string(s, http_style[0]) < 0) return -1; + + if (method != HTTP_METHOD_HEAD) { + if (wificfg_write_string_chunk(s, http_style[1], buf, len) < 0) return -1; + if (wificfg_write_chunk_end(s) < 0) return -1; + } + return 0; +} + +static const char *http_script[] = { +#include "content/script.js" +}; + +static int handle_script(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (wificfg_write_string(s, http_script[0]) < 0) return -1; + + if (method != HTTP_METHOD_HEAD) { + if (wificfg_write_string_chunk(s, http_script[1], buf, len) < 0) return -1; + if (wificfg_write_chunk_end(s) < 0) return -1; + } + return 0; +} + + +static const char http_success_header[] = "HTTP/1.1 200 \r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "Cache-Control: no-store\r\n" + "Transfer-Encoding: chunked\r\n" + "Connection: close\r\n" + "\r\n"; + +static const char http_redirect_header[] = "HTTP/1.1 302 \r\n" + "Location: /wificfg/\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n" + "\r\n"; + +static int handle_wificfg_redirect(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + return wificfg_write_string(s, http_redirect_header); +} + +static int handle_ipaddr_redirect(int s, char *buf, size_t len) +{ + if (wificfg_write_string(s, "HTTP/1.1 302 \r\nLocation: http://") < 0) return -1; + + struct sockaddr addr; + socklen_t addr_len = sizeof(addr); + getsockname(s, &addr, &addr_len); + struct sockaddr_in *sa = (struct sockaddr_in *)&addr; + snprintf(buf, len, "" IPSTR "/\r\n", IP2STR((ip4_addr_t *)&sa->sin_addr.s_addr)); + if (wificfg_write_string(s, buf) < 0) return -1;; + /* Always close here - expect a new connection. */ + return wificfg_write_string(s, "Content-Length: 0\r\n" + "Connection: close\r\n" + "\r\n"); +} + +int wificfg_write_html_title(int s, char *buf, size_t len, const char *str) +{ + /* Use the hostname or AP SSID as the title prefix. */ + char *hostname = NULL; + sysparam_get_string("hostname", &hostname); + if (!hostname) { + sysparam_get_string("wifi_ap_ssid", &hostname); + } + if (hostname) { + wificfg_html_escape(hostname, buf, len); + free(hostname); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + if (str) { + if (wificfg_write_string_chunk(s, " ", buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, str, buf, len) < 0) return -1; + } + } + + return 0; +} + +static const char *http_wificfg_content[] = { +#include "content/wificfg/index.html" +}; + +static int handle_wificfg_index(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (wificfg_write_string(s, http_success_header) < 0) return -1; + + if (method != HTTP_METHOD_HEAD) { + if (wificfg_write_string_chunk(s, http_wificfg_content[0], buf, len) < 0) return -1; + if (wificfg_write_html_title(s, buf, len, "Wifi Config") < 0) return -1; + if (wificfg_write_string_chunk(s, http_wificfg_content[1], buf, len) < 0) return -1; + + char *hostname = NULL; + sysparam_get_string("hostname", &hostname); + if (hostname) { + if (wificfg_write_string_chunk(s, "
Hostname
", buf, len) < 0) { + free(hostname); + return -1; + } + wificfg_html_escape(hostname, buf, len); + free(hostname); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, "
", buf, len) < 0) return -1; + } + + uint32_t chip_id = sdk_system_get_chip_id(); + snprintf(buf, len, "
Chip ID
%08x
", chip_id); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + snprintf(buf, len, "
Uptime
%u seconds
", + xTaskGetTickCount() * portTICK_PERIOD_MS / 1000); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + snprintf(buf, len, "
Free heap
%u bytes
", (int)xPortGetFreeHeapSize()); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + snprintf(buf, len, "
Flash ID
0x%08x
", sdk_spi_flash_get_id()); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + snprintf(buf, len, "
Flash size
%u KiB
", sdk_flashchip.chip_size >> 10); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + if (wificfg_write_string_chunk(s, "
LwIP version
" LWIP_VERSION_STRING "
", buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, "
FreeRTOS version
" tskKERNEL_VERSION_NUMBER "
", buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, "
Newlib version
" _NEWLIB_VERSION "
", buf, len) < 0) return -1; + + enum sdk_sleep_type sleep_type = sdk_wifi_get_sleep_type(); + char *sleep_type_str = "??"; + switch (sleep_type) { + case WIFI_SLEEP_NONE: + sleep_type_str = "None"; + break; + case WIFI_SLEEP_LIGHT: + sleep_type_str = "Light"; + break; + case WIFI_SLEEP_MODEM: + sleep_type_str = "Modem"; + break; + default: + break; + } + snprintf(buf, len, "
WiFi sleep type
%s
", sleep_type_str); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + uint8_t opmode = sdk_wifi_get_opmode(); + const char *opmode_str = "??"; + switch (opmode) { + case NULL_MODE: + opmode_str = "Null"; + break; + case STATION_MODE: + opmode_str = "Station"; + break; + case SOFTAP_MODE: + opmode_str = "SoftAP"; + break; + case STATIONAP_MODE: + opmode_str = "StationAP"; + break; + default: + break; + } + snprintf(buf, len, "
OpMode
%s
", opmode_str); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + if (opmode > NULL_MODE) { + snprintf(buf, len, "
WiFi channel
%u
", sdk_wifi_get_channel()); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + const char *phy_mode_str = "??"; + switch (sdk_wifi_get_phy_mode()) { + case PHY_MODE_11B: + phy_mode_str = "11b"; + break; + case PHY_MODE_11G: + phy_mode_str = "11g"; + break; + case PHY_MODE_11N: + phy_mode_str = "11n"; + break; + default: + break; + } + snprintf(buf, len, "
WiFi physical mode
%s
", phy_mode_str); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (opmode == STATION_MODE || opmode == STATIONAP_MODE) { + uint8_t hwaddr[6]; + if (sdk_wifi_get_macaddr(STATION_IF, hwaddr)) { + if (wificfg_write_string_chunk(s, "
Station MAC address
", buf, len) < 0) return -1; + snprintf(buf, len, "
" MACSTR "
", MAC2STR(hwaddr)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + struct ip_info info; + if (sdk_wifi_get_ip_info(STATION_IF, &info)) { + if (wificfg_write_string_chunk(s, "
Station IP address
", buf, len) < 0) return -1; + snprintf(buf, len, "
" IPSTR "
", IP2STR(&info.ip)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, "
Station netmask
", buf, len) < 0) return -1; + snprintf(buf, len, "
" IPSTR "
", IP2STR(&info.netmask)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, "
Station gateway
", buf, len) < 0) return -1; + snprintf(buf, len, "
" IPSTR "
", IP2STR(&info.gw)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + } + + if (opmode == SOFTAP_MODE || opmode == STATIONAP_MODE) { + uint8_t hwaddr[6]; + if (sdk_wifi_get_macaddr(SOFTAP_IF, hwaddr)) { + if (wificfg_write_string_chunk(s, "
AP MAC address
", buf, len) < 0) return -1; + snprintf(buf, len, "
" MACSTR "
", MAC2STR(hwaddr)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + struct ip_info info; + if (sdk_wifi_get_ip_info(SOFTAP_IF, &info)) { + if (wificfg_write_string_chunk(s, "
AP IP address
", buf, len) < 0) return -1; + snprintf(buf, len, "
" IPSTR "
", IP2STR(&info.ip)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, "
AP netmask
", buf, len) < 0) return -1; + snprintf(buf, len, "
" IPSTR "
", IP2STR(&info.netmask)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, "
AP gateway
", buf, len) < 0) return -1; + snprintf(buf, len, "
" IPSTR "
", IP2STR(&info.gw)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + } + + struct sockaddr addr; + socklen_t addr_len = sizeof(addr); + getpeername(s, (struct sockaddr*)&addr, &addr_len); + + if (addr.sa_family == AF_INET) { + struct sockaddr_in *sa = (struct sockaddr_in *)&addr; + if (wificfg_write_string_chunk(s, "
Peer address
", buf, len) < 0) return -1; + snprintf(buf, len, "
" IPSTR " : %u
", + IP2STR((ip4_addr_t *)&sa->sin_addr.s_addr), ntohs(sa->sin_port)); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_wificfg_content[2], buf, len) < 0) return -1; + + char *password = NULL; + sysparam_get_string("cfg_password", &password); + if (password) { + wificfg_html_escape(password, buf, len); + free(password); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_wificfg_content[3], buf, len) < 0) return -1; + + if (wificfg_write_chunk_end(s) < 0) return -1; + } + return 0; +} + +static int handle_wificfg_index_post(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (content_type != HTTP_CONTENT_TYPE_WWW_FORM_URLENCODED) { + return wificfg_write_string(s, "HTTP/1.1 400 \r\n" + "Content-Type: text/html\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n\r\n"); + } + + size_t rem = content_length; + bool valp = false; + + while (rem > 0) { + int r = wificfg_form_name_value(s, &valp, &rem, buf, len); + + if (r < 0) { + break; + } + + wificfg_form_url_decode(buf); + + form_name name = intern_form_name(buf); + + if (valp) { + int r = wificfg_form_name_value(s, NULL, &rem, buf, len); + if (r < 0) { + break; + } + + wificfg_form_url_decode(buf); + + switch (name) { + case FORM_NAME_CFG_ENABLE: { + uint8_t enable = strtoul(buf, NULL, 10) != 0; + sysparam_set_int8("cfg_enable", enable); + break; + } + case FORM_NAME_CFG_PASSWORD: + sysparam_set_string("cfg_password", buf); + break; + default: + break; + } + } + } + + return wificfg_write_string(s, http_redirect_header); +} + +static const char *http_wifi_station_content[] = { +#include "content/wificfg/sta.html" +}; + +static int handle_wifi_station(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (wificfg_write_string(s, http_success_header) < 0) return -1; + + if (method != HTTP_METHOD_HEAD) { + if (wificfg_write_string_chunk(s, http_wifi_station_content[0], buf, len) < 0) return -1; + if (wificfg_write_html_title(s, buf, len, "Wifi station") < 0) return -1; + if (wificfg_write_string_chunk(s, http_wifi_station_content[1], buf, len) < 0) return -1; + + int8_t wifi_sta_enable = 1; + sysparam_get_int8("wifi_sta_enable", &wifi_sta_enable); + if (wifi_sta_enable && wificfg_write_string_chunk(s, "checked", buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, http_wifi_station_content[2], buf, len) < 0) return -1; + + int8_t wifi_sta_disabled_restarts = 0; + sysparam_get_int8("wifi_sta_disabled_restarts", &wifi_sta_disabled_restarts); + snprintf(buf, len, "%u", wifi_sta_disabled_restarts); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, http_wifi_station_content[3], buf, len) < 0) return -1; + + char *wifi_sta_ssid = NULL; + sysparam_get_string("wifi_sta_ssid", &wifi_sta_ssid); + if (wifi_sta_ssid) { + wificfg_html_escape(wifi_sta_ssid, buf, len); + free(wifi_sta_ssid); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_wifi_station_content[4], buf, len) < 0) return -1; + + char *wifi_sta_password = NULL; + sysparam_get_string("wifi_sta_password", &wifi_sta_password); + if (wifi_sta_password) { + wificfg_html_escape(wifi_sta_password, buf, len); + free(wifi_sta_password); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_wifi_station_content[5], buf, len) < 0) return -1; + + char *hostname = NULL; + sysparam_get_string("hostname", &hostname); + if (hostname) { + wificfg_html_escape(hostname, buf, len); + free(hostname); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_wifi_station_content[6], buf, len) < 0) return -1; + + int8_t wifi_sta_dhcp = 1; + sysparam_get_int8("wifi_sta_dhcp", &wifi_sta_dhcp); + if (wifi_sta_dhcp && wificfg_write_string_chunk(s, "checked", buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, http_wifi_station_content[7], buf, len) < 0) return -1; + if (!wifi_sta_dhcp && wificfg_write_string_chunk(s, "checked", buf, len) < 0) return -1; + + if (wificfg_write_string_chunk(s, http_wifi_station_content[8], buf, len) < 0) return -1; + + char *wifi_sta_ip_addr = NULL; + sysparam_get_string("wifi_sta_ip_addr", &wifi_sta_ip_addr); + if (wifi_sta_ip_addr) { + wificfg_html_escape(wifi_sta_ip_addr, buf, len); + free(wifi_sta_ip_addr); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_wifi_station_content[9], buf, len) < 0) return -1; + + char *wifi_sta_netmask = NULL; + sysparam_get_string("wifi_sta_netmask", &wifi_sta_netmask); + if (wifi_sta_netmask) { + wificfg_html_escape(wifi_sta_netmask, buf, len); + free(wifi_sta_netmask); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_wifi_station_content[10], buf, len) < 0) return -1; + + char *wifi_sta_gateway = NULL; + sysparam_get_string("wifi_sta_gateway", &wifi_sta_gateway); + if (wifi_sta_gateway) { + wificfg_html_escape(wifi_sta_gateway, buf, len); + free(wifi_sta_gateway); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_wifi_station_content[11], buf, len) < 0) return -1; + + if (wificfg_write_chunk_end(s) < 0) return -1; + } + return 0; +} + +static int handle_wifi_station_post(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (content_type != HTTP_CONTENT_TYPE_WWW_FORM_URLENCODED) { + return wificfg_write_string(s, "HTTP/1.1 400 \r\n" + "Content-Type: text/html\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n\r\n"); + } + + size_t rem = content_length; + bool valp = false; + + /* Delay committing some values until all have been read. */ + bool done = false; + uint8_t sta_enable = 0; + + while (rem > 0) { + int r = wificfg_form_name_value(s, &valp, &rem, buf, len); + + if (r < 0) { + break; + } + + wificfg_form_url_decode(buf); + + form_name name = intern_form_name(buf); + + if (valp) { + int r = wificfg_form_name_value(s, NULL, &rem, buf, len); + if (r < 0) { + break; + } + + wificfg_form_url_decode(buf); + + switch (name) { + case FORM_NAME_STA_ENABLE: { + sta_enable = strtoul(buf, NULL, 10) != 0; + break; + } + case FORM_NAME_STA_DISABLED_RESTARTS: { + uint32_t restarts = strtoul(buf, NULL, 10); + if (restarts <= 255) + sysparam_set_int8("wifi_sta_disabled_restarts", restarts); + break; + } + case FORM_NAME_STA_SSID: + sysparam_set_string("wifi_sta_ssid", buf); + break; + case FORM_NAME_STA_PASSWORD: + sysparam_set_string("wifi_sta_password", buf); + break; + case FORM_NAME_HOSTNAME: + sysparam_set_string("hostname", buf); + break; + case FORM_NAME_STA_DHCP: { + uint8_t enable = strtoul(buf, NULL, 10) != 0; + sysparam_set_int8("wifi_sta_dhcp", enable); + break; + } + case FORM_NAME_STA_IP_ADDR: + sysparam_set_string("wifi_sta_ip_addr", buf); + break; + case FORM_NAME_STA_NETMASK: + sysparam_set_string("wifi_sta_netmask", buf); + break; + case FORM_NAME_STA_GATEWAY: + sysparam_set_string("wifi_sta_gateway", buf); + break; + case FORM_NAME_DONE: + done = true; + break; + default: + break; + } + } + } + + if (done) { + sysparam_set_int8("wifi_sta_enable", sta_enable); + } + + return wificfg_write_string(s, http_redirect_header); +} + +static const char *http_wifi_ap_content[] = { +#include "content/wificfg/ap.html" +}; + +static int handle_wifi_ap(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (wificfg_write_string(s, http_success_header) < 0) return -1; + + if (method != HTTP_METHOD_HEAD) { + if (wificfg_write_string_chunk(s, http_wifi_ap_content[0], buf, len) < 0) return -1; + if (wificfg_write_html_title(s, buf, len, "Wifi access point") < 0) return -1; + if (wificfg_write_string_chunk(s, http_wifi_ap_content[1], buf, len) < 0) return -1; + + int8_t wifi_ap_enable = 1; + sysparam_get_int8("wifi_ap_enable", &wifi_ap_enable); + if (wifi_ap_enable && wificfg_write_string_chunk(s, "checked", buf, len) < 0) return -1; + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[2], buf, len) < 0) return -1; + + int8_t wifi_ap_disable_if_sta = 1; + sysparam_get_int8("wifi_ap_disable_if_sta", &wifi_ap_disable_if_sta); + if (wifi_ap_disable_if_sta && wificfg_write_string_chunk(s, "checked", buf, len) < 0) return -1; + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[3], buf, len) < 0) return -1; + + int8_t wifi_ap_disabled_restarts = 0; + sysparam_get_int8("wifi_ap_disabled_restarts", &wifi_ap_disabled_restarts); + snprintf(buf, len, "%u", wifi_ap_disabled_restarts); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[4], buf, len) < 0) return -1; + + char *wifi_ap_ssid = NULL; + sysparam_get_string("wifi_ap_ssid", &wifi_ap_ssid); + if (wifi_ap_ssid) { + wificfg_html_escape(wifi_ap_ssid, buf, len); + free(wifi_ap_ssid); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[5], buf, len) < 0) return -1; + + char *wifi_ap_password = NULL; + sysparam_get_string("wifi_ap_password", &wifi_ap_password); + if (wifi_ap_password) { + wificfg_html_escape(wifi_ap_password, buf, len); + free(wifi_ap_password); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[6], buf, len) < 0) return -1; + + int8_t wifi_ap_ssid_hidden = 0; + sysparam_get_int8("wifi_ap_ssid_hidden", &wifi_ap_ssid_hidden); + if (wifi_ap_ssid_hidden && wificfg_write_string_chunk(s, "checked", buf, len) < 0) return -1; + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[7], buf, len) < 0) return -1; + + int8_t wifi_ap_channel = 6; + sysparam_get_int8("wifi_ap_channel", &wifi_ap_channel); + snprintf(buf, len, "%u", wifi_ap_channel); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[8], buf, len) < 0) return -1; + + int8_t wifi_ap_authmode = 4; + sysparam_get_int8("wifi_ap_authmode", &wifi_ap_authmode); + if (wifi_ap_authmode == 0 && wificfg_write_string_chunk(s, " selected", buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, http_wifi_ap_content[9], buf, len) < 0) return -1; + if (wifi_ap_authmode == 1 && wificfg_write_string_chunk(s, " selected", buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, http_wifi_ap_content[10], buf, len) < 0) return -1; + if (wifi_ap_authmode == 2 && wificfg_write_string_chunk(s, " selected", buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, http_wifi_ap_content[11], buf, len) < 0) return -1; + if (wifi_ap_authmode == 3 && wificfg_write_string_chunk(s, " selected", buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, http_wifi_ap_content[12], buf, len) < 0) return -1; + if (wifi_ap_authmode == 4 && wificfg_write_string_chunk(s, " selected", buf, len) < 0) return -1; + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[13], buf, len) < 0) return -1; + + int8_t wifi_ap_max_conn = 3; + sysparam_get_int8("wifi_ap_max_conn", &wifi_ap_max_conn); + snprintf(buf, len, "%u", wifi_ap_max_conn); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[14], buf, len) < 0) return -1; + + int32_t wifi_ap_beacon_interval = 100; + sysparam_get_int32("wifi_ap_beacon_interval", &wifi_ap_beacon_interval); + snprintf(buf, len, "%u", wifi_ap_beacon_interval); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[15], buf, len) < 0) return -1; + + char *wifi_ap_ip_addr = NULL; + sysparam_get_string("wifi_ap_ip_addr", &wifi_ap_ip_addr); + if (wifi_ap_ip_addr) { + wificfg_html_escape(wifi_ap_ip_addr, buf, len); + free(wifi_ap_ip_addr); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[16], buf, len) < 0) return -1; + + char *wifi_ap_netmask = NULL; + sysparam_get_string("wifi_ap_netmask", &wifi_ap_netmask); + if (wifi_ap_netmask) { + wificfg_html_escape(wifi_ap_netmask, buf, len); + free(wifi_ap_netmask); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[17], buf, len) < 0) return -1; + + int8_t wifi_ap_dhcp_leases = 4; + sysparam_get_int8("wifi_ap_dhcp_leases", &wifi_ap_dhcp_leases); + snprintf(buf, len, "%u", wifi_ap_dhcp_leases); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) return -1; + + if (wificfg_write_string_chunk(s, http_wifi_ap_content[18], buf, len) < 0) return -1; + + int8_t wifi_ap_dns = 1; + sysparam_get_int8("wifi_ap_dns", &wifi_ap_dns); + if (wifi_ap_dns && wificfg_write_string_chunk(s, "checked", buf, len) < 0) return -1; + if (wificfg_write_string_chunk(s, http_wifi_ap_content[19], buf, len) < 0) return -1; + + if (wificfg_write_chunk_end(s) < 0) return -1; + } + return 0; +} + +static int handle_wifi_ap_post(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (content_type != HTTP_CONTENT_TYPE_WWW_FORM_URLENCODED) { + return wificfg_write_string(s, "HTTP/1.1 400 \r\n" + "Content-Type: text/html\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n\r\n"); + } + + size_t rem = content_length; + bool valp = false; + + /* Delay committing some values until all have been read. */ + bool done = false; + uint8_t ap_enable = 0; + uint8_t ap_disable_if_sta = 0; + uint8_t ssid_hidden = 0; + uint8_t dns_enable = 0; + + while (rem > 0) { + int r = wificfg_form_name_value(s, &valp, &rem, buf, len); + + if (r < 0) { + break; + } + + wificfg_form_url_decode(buf); + + form_name name = intern_form_name(buf); + + if (valp) { + int r = wificfg_form_name_value(s, NULL, &rem, buf, len); + if (r < 0) { + break; + } + + wificfg_form_url_decode(buf); + + switch (name) { + case FORM_NAME_AP_ENABLE: { + ap_enable = strtoul(buf, NULL, 10) != 0; + break; + } + case FORM_NAME_AP_DISABLE_IF_STA: { + ap_disable_if_sta = strtoul(buf, NULL, 10) != 0; + break; + } + case FORM_NAME_AP_DISABLED_RESTARTS: { + uint32_t restarts = strtoul(buf, NULL, 10); + if (restarts <= 255) + sysparam_set_int8("wifi_ap_disabled_restarts", restarts); + break; + } + case FORM_NAME_AP_SSID: + sysparam_set_string("wifi_ap_ssid", buf); + break; + case FORM_NAME_AP_PASSWORD: + sysparam_set_string("wifi_ap_password", buf); + break; + case FORM_NAME_AP_SSID_HIDDEN: { + ssid_hidden = strtoul(buf, NULL, 10) != 0; + break; + } + case FORM_NAME_AP_CHANNEL: { + uint32_t channel = strtoul(buf, NULL, 10); + if (channel >= 1 && channel <= 14) + sysparam_set_int8("wifi_ap_channel", channel); + break; + } + case FORM_NAME_AP_AUTHMODE: { + uint32_t mode = strtoul(buf, NULL, 10); + if (mode >= 0 && mode <= 5) + sysparam_set_int8("wifi_ap_authmode", mode); + break; + } + case FORM_NAME_AP_MAX_CONN: { + uint32_t max_conn = strtoul(buf, NULL, 10); + if (max_conn >= 0 && max_conn <= 8) + sysparam_set_int8("wifi_ap_max_conn", max_conn); + break; + } + case FORM_NAME_AP_BEACON_INTERVAL: { + uint32_t interval = strtoul(buf, NULL, 10); + if (interval >= 0 && interval <= 10000) + sysparam_set_int32("wifi_ap_beacon_interval", interval); + break; + } + case FORM_NAME_AP_IP_ADDR: + sysparam_set_string("wifi_ap_ip_addr", buf); + break; + case FORM_NAME_AP_NETMASK: + sysparam_set_string("wifi_ap_netmask", buf); + break; + case FORM_NAME_AP_DHCP_LEASES: { + uint32_t leases = strtoul(buf, NULL, 10); + if (leases >= 0 && leases <= 16) + sysparam_set_int8("wifi_ap_dhcp_leases", leases); + break; + } + case FORM_NAME_AP_DNS: { + dns_enable = strtoul(buf, NULL, 10) != 0; + break; + } + case FORM_NAME_DONE: + done = true; + break; + default: + break; + } + } + } + + if (done) { + sysparam_set_int8("wifi_ap_enable", ap_enable); + sysparam_set_int8("wifi_ap_disable_if_sta", ap_disable_if_sta); + sysparam_set_int8("wifi_ap_ssid_hidden", ssid_hidden); + sysparam_set_int8("wifi_ap_dns", dns_enable); + } + + return wificfg_write_string(s, http_redirect_header); +} + +static bool got_sta_connect = false; +void wificfg_got_sta_connect() +{ + /* Only process this once, to not continue adjusting the settings. */ + if (got_sta_connect) { + return; + } + got_sta_connect = true; + + /* Skip if AP not even enabled. */ + int8_t wifi_ap_enable = 1; + sysparam_get_int8("wifi_ap_enable", &wifi_ap_enable); + if (!wifi_ap_enable) { + return; + } + + int8_t wifi_ap_disable_if_sta = 1; + sysparam_get_int8("wifi_ap_disable_if_sta", &wifi_ap_disable_if_sta); + + if (wifi_ap_disable_if_sta) { + int8_t wifi_ap_disabled_restarts = 0; + sysparam_get_int8("wifi_ap_disabled_restarts", &wifi_ap_disabled_restarts); + if (wifi_ap_disabled_restarts == 0) { + sysparam_set_int8("wifi_ap_disabled_restarts", 1); + } + } +} + +static int handle_restart_post(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + wificfg_write_string(s, http_redirect_header); + close(s); + vTaskDelay(2000 / portTICK_PERIOD_MS); + sdk_system_restart(); + return 0; +} + +static int handle_erase_post(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + wificfg_write_string(s, http_redirect_header); + close(s); + vTaskDelay(2000 / portTICK_PERIOD_MS); + + /* + * Erase the area starting from the sysparams to the end of the flash. + * Configuration information may be in the sdk parameter area too, which is + * in these sectors. + */ + uint32_t num_sectors = 5 + DEFAULT_SYSPARAM_SECTORS; + uint32_t start = sdk_flashchip.chip_size - num_sectors * sdk_flashchip.sector_size; + uint32_t i; + vPortEnterCritical(); + for (i = 0; i < num_sectors; i++) { + spiflash_erase_sector(start + i * sdk_flashchip.sector_size); + } + sdk_system_restart(); + return 0; +} + +/* Minimal not-found response. */ +static const char not_found_header[] = "HTTP/1.1 404 \r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "Cache-Control: no-store\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n" + "\r\n"; + +static const char *http_wificfg_challenge_content[] = { +#include "content/challenge.html" +}; + +static int handle_wificfg_challenge(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (wificfg_write_string(s, http_success_header) < 0) return -1; + + if (method != HTTP_METHOD_HEAD) { + if (wificfg_write_string_chunk(s, http_wificfg_challenge_content[0], buf, len) < 0) return -1; + if (wificfg_write_html_title(s, buf, len, "Challenge") < 0) return -1; + if (wificfg_write_string_chunk(s, http_wificfg_challenge_content[1], buf, len) < 0) return -1; + if (wificfg_write_chunk_end(s) < 0) return -1; + } + return 0; +} + +static int handle_wificfg_challenge_post(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (content_type != HTTP_CONTENT_TYPE_WWW_FORM_URLENCODED) { + return wificfg_write_string(s, "HTTP/1.1 400 \r\n" + "Content-Type: text/html\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n\r\n"); + } + size_t rem = content_length; + bool valp = false; + + int8_t enable = 1; + sysparam_get_int8("cfg_enable", &enable); + char *password = NULL; + sysparam_get_string("cfg_password", &password); + + if (!enable && password && strlen(password)) { + while (rem > 0) { + int r = wificfg_form_name_value(s, &valp, &rem, buf, len); + + if (r < 0) { + break; + } + + wificfg_form_url_decode(buf); + + form_name name = intern_form_name(buf); + + if (valp) { + int r = wificfg_form_name_value(s, NULL, &rem, buf, len); + if (r < 0) { + break; + } + + wificfg_form_url_decode(buf); + + switch (name) { + case FORM_NAME_CFG_PASSWORD: + if (strcmp(password, buf) == 0) + sysparam_set_int8("cfg_enable", 1); + break; + default: + break; + } + } + } + } + + if (password) + free(password); + + return wificfg_write_string(s, http_redirect_header); +} + +#ifdef configUSE_TRACE_FACILITY +static const char *http_tasks_content[] = { +#include "content/tasks.html" +}; + +static int handle_tasks(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len) +{ + if (wificfg_write_string(s, http_success_header) < 0) return -1; + + if (method != HTTP_METHOD_HEAD) { + if (wificfg_write_string_chunk(s, http_tasks_content[0], buf, len) < 0) return -1; + if (wificfg_write_html_title(s, buf, len, "Tasks") < 0) return -1; + if (wificfg_write_string_chunk(s, http_tasks_content[1], buf, len) < 0) return -1; + int num_tasks = uxTaskGetNumberOfTasks(); + TaskStatus_t *task_status = pvPortMalloc(num_tasks * sizeof(TaskStatus_t)); + + if (task_status != NULL) { + int i; + + if (wificfg_write_string_chunk(s, "", buf, len) < 0) { + free(task_status); + return -1; + } + + /* Generate the (binary) data. */ + num_tasks = uxTaskGetSystemState(task_status, num_tasks, NULL); + + /* Create a human readable table from the binary data. */ + for(i = 0; i < num_tasks; i++) { + char cStatus; + switch(task_status[i].eCurrentState) { + case eRunning: cStatus = '*'; break; + case eReady: cStatus = 'R'; break; + case eBlocked: cStatus = 'B'; break; + case eSuspended: cStatus = 'S'; break; + case eDeleted: cStatus = 'D'; break; + default: cStatus = '?'; break; + } + + snprintf(buf, len, "", task_status[i].pcTaskName); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) { + free(task_status); + return -1; + } + snprintf(buf, len, "", + (unsigned int)task_status[i].xTaskNumber, + cStatus); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) { + free(task_status); + return -1; + } + snprintf(buf, len, "", + (unsigned int)task_status[i].uxCurrentPriority, + (unsigned int)task_status[i].uxBasePriority); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) { + free(task_status); + return -1; + } + snprintf(buf, len, "", + (unsigned int)task_status[i].ulRunTimeCounter, + (unsigned int)task_status[i].usStackHighWaterMark); + if (wificfg_write_string_chunk(s, buf, buf, len) < 0) { + free(task_status); + return -1; + } + } + + free(task_status); + + if (wificfg_write_string_chunk(s, "
Task nameTask numberStatusPriorityBase priorityRuntimeStack high-water
%s%u%c%u%u%u%u
", buf, len) < 0) return -1; + } + + if (wificfg_write_string_chunk(s, http_tasks_content[2], buf, len) < 0) return -1; + if (wificfg_write_chunk_end(s) < 0) return -1; + } + return 0; +} +#endif /* configUSE_TRACE_FACILITY */ + +static const wificfg_dispatch wificfg_dispatch_list[] = { + {"/favicon.ico", HTTP_METHOD_GET, handle_favicon, false}, + {"/style.css", HTTP_METHOD_GET, handle_style, false}, + {"/script.js", HTTP_METHOD_GET, handle_script, false}, + {"/", HTTP_METHOD_GET, handle_wificfg_redirect, false}, + {"/index.html", HTTP_METHOD_GET, handle_wificfg_redirect, false}, + {"/wificfg/", HTTP_METHOD_GET, handle_wificfg_index, true}, + {"/wificfg/", HTTP_METHOD_POST, handle_wificfg_index_post, true}, + {"/wificfg/sta.html", HTTP_METHOD_GET, handle_wifi_station, true}, + {"/wificfg/sta.html", HTTP_METHOD_POST, handle_wifi_station_post, true}, + {"/wificfg/ap.html", HTTP_METHOD_GET, handle_wifi_ap, true}, + {"/wificfg/ap.html", HTTP_METHOD_POST, handle_wifi_ap_post, true}, + {"/challenge.html", HTTP_METHOD_GET, handle_wificfg_challenge, false}, + {"/challenge.html", HTTP_METHOD_POST, handle_wificfg_challenge_post, false}, + {"/wificfg/restart.html", HTTP_METHOD_POST, handle_restart_post, true}, + {"/wificfg/erase.html", HTTP_METHOD_POST, handle_erase_post, true}, +#ifdef configUSE_TRACE_FACILITY + {"/tasks", HTTP_METHOD_GET, handle_tasks, false}, + {"/tasks.html", HTTP_METHOD_GET, handle_tasks, false}, +#endif /* configUSE_TRACE_FACILITY */ + {NULL, HTTP_METHOD_ANY, NULL} +}; + +static const wificfg_dispatch wificfg_challenge_dispatch = {"/challenge.html", HTTP_METHOD_GET, handle_wificfg_challenge, false}; + +typedef struct { + int32_t port; + /* + * Two dispatch lists. First is used for the config pages. Second + * can be used to extend the pages handled in app code. + */ + const wificfg_dispatch *wificfg_dispatch; + const wificfg_dispatch *dispatch; +} server_params; + +/* + * The http server uses a single thread to service all requests, one request at + * a time, to keep peak resource usage to a minimum. Keeping connections open + * would cause delays switching between connections. Thus it closes the + * connection after each response. + * + * To help avoid the resource usage of connections in the time-wait state, the + * server asks the client to initiate the connection close and waits a short + * period for it to do so before closing the connection itself. + * + * The response length is always well defined, either sending the content-length + * header or using chunk transfer encoding. Thus the client knows the end of + * responses without the server having to close the connection, and this allows + * the client to close the connection. + * + * Always closing the connection also allows the connection-close header to be + * statically bundled in with the response. + */ +static void server_task(void *pvParameters) +{ + server_params *params = pvParameters; + + struct sockaddr_in serv_addr; + int listenfd = socket(AF_INET, SOCK_STREAM, 0); + memset(&serv_addr, '0', sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); + serv_addr.sin_port = htons(params->port); + bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); + listen(listenfd, 2); + + for (;;) { + int s = accept(listenfd, (struct sockaddr *)NULL, (socklen_t *)NULL); + if (s >= 0) { + const struct timeval timeout = { 10, 0 }; /* 10 second timeout */ + setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); + + /* Buffer for reading the request and headers and the post method + * names and values. Also used for building dynamically generated + * responses. */ + char buf[HTTP_BUFFER_SIZE]; + + for (;;) { + /* Read the request line */ + int request_line_size = read_crlf_line(s, buf, sizeof(buf)); + if (request_line_size < 5) { + break; + } + + /* Parse the http method, path, and protocol version. */ + char *method_end = skip_to_whitespace(buf); + char *path_string = skip_whitespace(method_end); + *method_end = 0; + wificfg_method method = intern_http_method(buf); + char *path_end = skip_to_whitespace(path_string); + *path_end = 0; + + /* Dispatch to separate functions to handle the requests. */ + const wificfg_dispatch *match = NULL; + const wificfg_dispatch *dispatch; + + /* + * Check the optional application supplied dispatch table + * first to allow overriding the wifi config paths. + */ + if (params->dispatch) { + for (dispatch = params->dispatch; dispatch->path != NULL; dispatch++) { + if (strcmp(path_string, dispatch->path) == 0 && + (dispatch->method == HTTP_METHOD_ANY || + method == dispatch->method)) { + match = dispatch; + break; + } + } + } + + if (!match) { + for (dispatch = params->wificfg_dispatch; dispatch->path != NULL; dispatch++) { + if (strcmp(path_string, dispatch->path) == 0 && + (dispatch->method == HTTP_METHOD_ANY || + method == dispatch->method)) { + match = dispatch; + break; + } + } + } + + if (match && match->secure) { + /* A secure url so check if enabled. */ + int8_t enable = 1; + sysparam_get_int8("cfg_enable", &enable); + if (!enable) { + /* Is there a recovery password? */ + char *password = NULL; + sysparam_get_string("cfg_password", &password); + if (password && strlen(password) > 0) { + match = &wificfg_challenge_dispatch; + } else { + match = NULL; + } + if (password) + free(password); + } + } + + /* Read the headers, noting some of interest. */ + wificfg_content_type content_type = HTTP_CONTENT_TYPE_OTHER; + bool connection_close = false; + bool hostp = false; + uint32_t host = IPADDR_NONE; + long content_length = 0; + + for (;;) { + int header_length = read_crlf_line(s, buf, sizeof(buf)); + if (header_length <= 0) + break; + + char *name_end = buf; + for (; ; name_end++) { + char c = *name_end; + if (!c || c == ':') + break; + } + if (*name_end == ':') { + char *value = name_end + 1; + *name_end = 0; + http_header header = intern_http_header(buf); + value = skip_whitespace(value); + switch (header) { + case HTTP_HEADER_HOST: + hostp = true; + host = ipaddr_addr(value); + break; + case HTTP_HEADER_CONTENT_LENGTH: + content_length = strtoul(value, NULL, 10); + break; + case HTTP_HEADER_CONTENT_TYPE: + content_type = intern_http_content_type(value); + break; + case HTTP_HEADER_CONNECTION: + connection_close = strcmp(value, "close") == 0; + break; + default: + break; + } + } + } + + if (hostp && host == IPADDR_NONE) { + /* Redirect to an IP address. */ + handle_ipaddr_redirect(s, buf, sizeof(buf)); + /* Close the connection. */ + break; + } else if (match) { + if ((*match->handler)(s, method, content_length, content_type, buf, sizeof(buf)) < 0) break; + } else { + if (wificfg_write_string(s, not_found_header) < 0) break; + } + + /* + * At this point the client is expected to close the connection, + * so wait briefly for it to do so before giving up. While here + * consume any excess input to avoid a connection reset - this + * can happen if the handler aborted early. + */ + const struct timeval timeout1 = { 1, 0 }; /* 1 second timeout */ + setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout1, sizeof(timeout1)); + size_t len; + for (len = 0; len < 4096; len++) { + char c; + int res = read(s, &c, 1); + if (res != 1) break; + } + + if (connection_close) + break; + + /* Close anyway. */ + break; + } + + close(s); + + if (sdk_wifi_station_get_connect_status() == STATION_GOT_IP) { + wificfg_got_sta_connect(); + } + } + } +} + + +static void dns_task(void *pvParameters) +{ + char *wifi_ap_ip_addr = NULL; + sysparam_get_string("wifi_ap_ip_addr", &wifi_ap_ip_addr); + if (!wifi_ap_ip_addr) { + printf("dns: no ip address\n"); + vTaskDelete(NULL); + } + ip4_addr_t server_addr; + server_addr.addr = ipaddr_addr(wifi_ap_ip_addr); + + struct sockaddr_in serv_addr; + int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + memset(&serv_addr, '0', sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); + serv_addr.sin_port = htons(53); + bind(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); + + const struct ifreq ifreq0 = { "en0" }; + const struct ifreq ifreq1 = { "en1" }; + lwip_setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, + sdk_wifi_get_opmode() == STATIONAP_MODE ? &ifreq1 : &ifreq0, + sizeof(ifreq0)); + + for (;;) { + char buffer[96]; + struct sockaddr src_addr; + socklen_t src_addr_len = sizeof(src_addr); + size_t count = recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr*)&src_addr, &src_addr_len); + + /* Drop messages that are too large to send a response in the buffer */ + if (count > 0 && count <= sizeof(buffer) - 16 && src_addr.sa_family == AF_INET) { + size_t qname_len = strlen(buffer + 12) + 1; + uint32_t reply_len = 2 + 10 + qname_len + 16 + 4; + + char *head = buffer + 2; + *head++ = 0x80; // Flags + *head++ = 0x00; + *head++ = 0x00; // Q count + *head++ = 0x01; + *head++ = 0x00; // A count + *head++ = 0x01; + *head++ = 0x00; // Auth count + *head++ = 0x00; + *head++ = 0x00; // Add count + *head++ = 0x00; + head += qname_len; + *head++ = 0x00; // Q type + *head++ = 0x01; + *head++ = 0x00; // Q class + *head++ = 0x01; + *head++ = 0xC0; // LBL offs + *head++ = 0x0C; + *head++ = 0x00; // Type + *head++ = 0x01; + *head++ = 0x00; // Class + *head++ = 0x01; + *head++ = 0x00; // TTL + *head++ = 0x00; + *head++ = 0x00; + *head++ = 0x78; + *head++ = 0x00; // RD len + *head++ = 0x04; + *head++ = ip4_addr1(&server_addr); + *head++ = ip4_addr2(&server_addr); + *head++ = ip4_addr3(&server_addr); + *head++ = ip4_addr4(&server_addr); + + sendto(fd, buffer, reply_len, 0, &src_addr, src_addr_len); + } + } +} + + +void wificfg_init(uint32_t port, const wificfg_dispatch *dispatch) +{ + char *wifi_sta_ssid = NULL; + char *wifi_sta_password = NULL; + char *wifi_ap_ssid = NULL; + char *wifi_ap_password = NULL; + + uint32_t base_addr; + uint32_t num_sectors; + if (sysparam_get_info(&base_addr, &num_sectors) != SYSPARAM_OK) { + printf("Warning: WiFi config, sysparam not initialized\n"); + return; + } + + sysparam_get_string("wifi_ap_ssid", &wifi_ap_ssid); + sysparam_get_string("wifi_ap_password", &wifi_ap_password); + sysparam_get_string("wifi_sta_ssid", &wifi_sta_ssid); + sysparam_get_string("wifi_sta_password", &wifi_sta_password); + + int8_t wifi_sta_enable = 1; + int8_t wifi_ap_enable = 1; + sysparam_get_int8("wifi_sta_enable", &wifi_sta_enable); + sysparam_get_int8("wifi_ap_enable", &wifi_ap_enable); + + int8_t wifi_sta_disabled_restarts = 0; + sysparam_get_int8("wifi_sta_disabled_restarts", &wifi_sta_disabled_restarts); + if (wifi_sta_disabled_restarts > 0) { + wifi_sta_enable = 0; + wifi_sta_disabled_restarts--; + sysparam_set_int8("wifi_sta_disabled_restarts", wifi_sta_disabled_restarts); + } + + int8_t wifi_ap_disabled_restarts = 0; + sysparam_get_int8("wifi_ap_disabled_restarts", &wifi_ap_disabled_restarts); + if (wifi_ap_disabled_restarts > 0) { + wifi_ap_enable = 0; + wifi_ap_disabled_restarts--; + sysparam_set_int8("wifi_ap_disabled_restarts", wifi_ap_disabled_restarts); + } + + /* Validate the configuration. */ + + if (wifi_sta_enable && (!wifi_sta_ssid || !wifi_sta_password || + strlen(wifi_sta_ssid) < 1 || + strlen(wifi_sta_ssid) > 32 || + !wifi_sta_password || + strlen(wifi_sta_password) < 8 || + strlen(wifi_sta_password) >= 64)) { + wifi_sta_enable = 0; + } + + if (wifi_ap_enable) { + /* Default AP ssid and password. */ + if (!wifi_ap_ssid && wificfg_default_ssid) { + uint8_t macaddr[6]; + char ssid[32]; + sdk_wifi_get_macaddr(1, macaddr); + snprintf(ssid, sizeof(ssid), wificfg_default_ssid, macaddr[3], + macaddr[4], macaddr[5]); + sysparam_set_string("wifi_ap_ssid", ssid); + sysparam_get_string("wifi_ap_ssid", &wifi_ap_ssid); + + if (!wifi_ap_password && wificfg_default_password) { + sysparam_set_string("wifi_ap_password", wificfg_default_password); + sysparam_get_string("wifi_ap_password", &wifi_ap_password); + } + } + + /* If the ssid and password are not valid then disable the AP interface. */ + if (!wifi_ap_ssid || strlen(wifi_ap_ssid) < 1 || strlen(wifi_ap_ssid) >= 32 || + !wifi_ap_password || strlen(wifi_ap_ssid) < 8 || strlen(wifi_ap_password) >= 64) { + wifi_ap_enable = 0; + } + } + + int8_t wifi_mode = NULL_MODE; + if (wifi_sta_enable && wifi_ap_enable) + wifi_mode = STATIONAP_MODE; + else if (wifi_sta_enable) + wifi_mode = STATION_MODE; + else + wifi_mode = SOFTAP_MODE; + sdk_wifi_set_opmode(wifi_mode); + + if (wifi_sta_enable) { + /* Default a hostname. */ + char *hostname = NULL; + sysparam_get_string("hostname", &hostname); + if (!hostname && wificfg_default_hostname) { + uint8_t macaddr[6]; + char name[32]; + sdk_wifi_get_macaddr(1, macaddr); + snprintf(name, sizeof(name), wificfg_default_hostname, macaddr[3], + macaddr[4], macaddr[5]); + sysparam_set_string("hostname", name); + } + if (hostname) { + free(hostname); + } + + struct sdk_station_config config; + strcpy((char *)config.ssid, wifi_sta_ssid); + strcpy((char *)config.password, wifi_sta_password); + config.bssid_set = 0; + + int8_t wifi_sta_dhcp = 1; + sysparam_get_int8("wifi_sta_dhcp", &wifi_sta_dhcp); + + if (!wifi_sta_dhcp) { + char *wifi_sta_ip_addr = NULL; + char *wifi_sta_netmask = NULL; + char *wifi_sta_gateway = NULL; + sysparam_get_string("wifi_sta_ip_addr", &wifi_sta_ip_addr); + sysparam_get_string("wifi_sta_netmask", &wifi_sta_netmask); + sysparam_get_string("wifi_sta_gateway", &wifi_sta_gateway); + + if (wifi_sta_ip_addr && strlen(wifi_sta_ip_addr) > 4 && + wifi_sta_netmask && strlen(wifi_sta_netmask) > 4 && + wifi_sta_gateway && strlen(wifi_sta_gateway) > 4) { + sdk_wifi_station_dhcpc_stop(); + struct ip_info info; + memset(&info, 0x0, sizeof(info)); + info.ip.addr = ipaddr_addr(wifi_sta_ip_addr); + info.netmask.addr = ipaddr_addr(wifi_sta_netmask); + info.gw.addr = ipaddr_addr(wifi_sta_gateway); + sdk_wifi_set_ip_info(STATION_IF, &info); + } + if (wifi_sta_ip_addr) free(wifi_sta_ip_addr); + if (wifi_sta_netmask) free(wifi_sta_netmask); + if (wifi_sta_gateway) free(wifi_sta_gateway); + } + + sdk_wifi_station_set_config(&config); + } + + if (wifi_ap_enable) { + /* Read and validate paramenters. */ + int8_t wifi_ap_ssid_hidden = 0; + sysparam_get_int8("wifi_ap_ssid_hidden", &wifi_ap_ssid_hidden); + if (wifi_ap_ssid_hidden < 0 || wifi_ap_ssid_hidden > 1) + wifi_ap_ssid_hidden = 1; + + int8_t wifi_ap_channel = 6; + sysparam_get_int8("wifi_ap_channel", &wifi_ap_channel); + /* Hack, disallow channel 1 here as it is not working on multiple + * devices. It seems to only work in STATIONAP mode when connected, but + * and not if the station is still scanning. */ + if (wifi_ap_channel < 2) { + wifi_ap_channel = 2; + } + /* AU does not allow channels above 13, although 14 works. */ + if (wifi_ap_channel > 13) { + wifi_ap_channel = 13; + } +#if 0 + /* US does not allow channels above 11, although they work. */ + if (wifi_ap_channel > 11) { + wifi_ap_channel = 11; + } +#endif + if (wifi_ap_channel < 1 || wifi_ap_channel > 14) { + wifi_ap_channel = 6; + } + + int8_t wifi_ap_authmode = AUTH_WPA_WPA2_PSK; + sysparam_get_int8("wifi_ap_authmode", &wifi_ap_authmode); + if (wifi_ap_authmode < AUTH_OPEN || wifi_ap_authmode > AUTH_MAX) + wifi_ap_authmode = AUTH_WPA_WPA2_PSK; + + int8_t wifi_ap_max_conn = 3; + sysparam_get_int8("wifi_ap_max_conn", &wifi_ap_max_conn); + if (wifi_ap_max_conn < 1 || wifi_ap_max_conn > 8) + wifi_ap_max_conn = 3; + + int32_t wifi_ap_beacon_interval = 100; + sysparam_get_int32("wifi_ap_beacon_interval", &wifi_ap_beacon_interval); + if (wifi_ap_beacon_interval < 0 || wifi_ap_beacon_interval > 1000) + wifi_ap_beacon_interval = 100; + + /* Default AP IP address and netmask. */ + char *wifi_ap_ip_addr = NULL; + sysparam_get_string("wifi_ap_ip_addr", &wifi_ap_ip_addr); + if (!wifi_ap_ip_addr) { + sysparam_set_string("wifi_ap_ip_addr", "172.16.0.1"); + sysparam_get_string("wifi_ap_ip_addr", &wifi_ap_ip_addr); + } + char *wifi_ap_netmask = NULL; + sysparam_get_string("wifi_ap_netmask", &wifi_ap_netmask); + if (!wifi_ap_netmask) { + sysparam_set_string("wifi_ap_netmask", "255.255.0.0"); + sysparam_get_string("wifi_ap_netmask", &wifi_ap_netmask); + } + + if (strlen(wifi_ap_ip_addr) >= 7 && strlen(wifi_ap_netmask) >= 7) { + struct ip_info ap_ip; + ap_ip.ip.addr = ipaddr_addr(wifi_ap_ip_addr); + ap_ip.netmask.addr = ipaddr_addr(wifi_ap_netmask); + IP4_ADDR(&ap_ip.gw, 0, 0, 0, 0); + sdk_wifi_set_ip_info(1, &ap_ip); + + struct sdk_softap_config ap_config = { + .ssid_hidden = wifi_ap_ssid_hidden, + .channel = wifi_ap_channel, + .authmode = wifi_ap_authmode, + .max_connection = wifi_ap_max_conn, + .beacon_interval = wifi_ap_beacon_interval, + }; + strcpy((char *)ap_config.ssid, wifi_ap_ssid); + ap_config.ssid_len = strlen(wifi_ap_ssid); + strcpy((char *)ap_config.password, wifi_ap_password); + sdk_wifi_softap_set_config(&ap_config); + + int8_t wifi_ap_dhcp_leases = 4; + sysparam_get_int8("wifi_ap_dhcp_leases", &wifi_ap_dhcp_leases); + + if (wifi_ap_dhcp_leases) { + ip4_addr_t first_client_ip; + first_client_ip.addr = ap_ip.ip.addr + htonl(1); + + int8_t wifi_ap_dns = 1; + sysparam_get_int8("wifi_ap_dns", &wifi_ap_dns); + if (wifi_ap_dns < 0 || wifi_ap_dns > 1) + wifi_ap_dns = 1; + + dhcpserver_start(&first_client_ip, wifi_ap_dhcp_leases); + dhcpserver_set_router(&ap_ip.ip); + if (wifi_ap_dns) { + dhcpserver_set_dns(&ap_ip.ip); + xTaskCreate(dns_task, "WiFi Cfg DNS", 384, NULL, 2, NULL); + } + } + } + + free(wifi_ap_ip_addr); + free(wifi_ap_netmask); + } + + if (wifi_sta_ssid) free(wifi_sta_ssid); + if (wifi_sta_password) free(wifi_sta_password); + if (wifi_ap_ssid) free(wifi_ap_ssid); + if (wifi_ap_password) free(wifi_ap_password); + + server_params *params = malloc(sizeof(server_params)); + params->port = port; + params->wificfg_dispatch = wificfg_dispatch_list; + params->dispatch = dispatch; + + xTaskCreate(server_task, "WiFi Cfg HTTP", 464, params, 2, NULL); +} diff --git a/extras/wificfg/wificfg.h b/extras/wificfg/wificfg.h new file mode 100644 index 0000000..705b2f6 --- /dev/null +++ b/extras/wificfg/wificfg.h @@ -0,0 +1,137 @@ +/* + * WiFi configuration via a simple web server. + * + * Copyright (C) 2016 OurAirQuality.org + * + * Licensed under the Apache License, Version 2.0, January 2004 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.apache.org/licenses/ + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS WITH THE SOFTWARE. + * + */ + +#ifndef __WIFICFG_H__ +#define __WIFICFG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Printf format used to initialize a default AP ssid. It is passed the last + * three bytes of the mac address. This may be NULL to not default the ssid, + * but the AP network will not run without a ssid. + */ +extern char *wificfg_default_ssid; + +/* + * A default password for the AP interface. This may be NULL to not default the + * password, but the AP network will not run without a password. The minimum + * length is 8 characters. + */ +extern char *wificfg_default_password; + +/* + * A default hostname printf format string. This may be NULL to not default the + * hostname. + */ +extern char *wificfg_default_hostname; + +/* + * The web server parses the http method string in these enums. The ANY method + * is only use for dispatch. The method enum is passed to the handler functions. + */ +typedef enum { + HTTP_METHOD_GET, + HTTP_METHOD_POST, + HTTP_METHOD_HEAD, + HTTP_METHOD_OTHER, + HTTP_METHOD_ANY, +} wificfg_method; + +/* + * The web server parses these content-type header values. This is passed to the + * dispatch function. + */ +typedef enum { + HTTP_CONTENT_TYPE_WWW_FORM_URLENCODED, + HTTP_CONTENT_TYPE_OTHER +} wificfg_content_type; + +/* + * The function signature for the http server request handler functions. + * + * The buffer, with its length, is usable by the handler. + */ +typedef int (* wificfg_handler)(int s, wificfg_method method, + uint32_t content_length, + wificfg_content_type content_type, + char *buf, size_t len); + +typedef struct { + const char *path; + wificfg_method method; + wificfg_handler handler; + bool secure; +} wificfg_dispatch; + + +/* + * Start the Wifi Configuration http server task. The IP port number + * and a path dispatch list are needed. The dispatch list can not be + * stack allocated as it is passed to another task. + */ +void wificfg_init(uint32_t port, const wificfg_dispatch *dispatch); + +/* + * Support for reading a form name or value from the socket. The name or value + * is truncated to the buffer length. The number of characters read is limited + * to the remainder which is updated. The 'valp' flag is set if a value follows. + */ +int wificfg_form_name_value(int s, bool *valp, size_t *rem, char *buf, size_t len); + +/* Support for form url-encoding decoder. */ +void wificfg_form_url_decode(char *string); + +/* Support for html-escaping of form values. */ +void wificfg_html_escape(char *string, char *buf, size_t len); + +/* Support for writing a string in a response. */ +int wificfg_write_string(int s, const char *str); + +/* Support for writing a string in a response, with chunk transfer encoding. + * An optional buffer may be supplied to use to construct a chunk with the + * header and trailer, reducing the number of write() calls, and the str may be + * at the start of this buffer. + */ +int wificfg_write_string_chunk(int s, const char *str, char *buf, size_t len); + +/* Write a chunk transfer encoding end marker. */ +int wificfg_write_chunk_end(int s); + +/* Write a chunk offset 4 bytes into the buffer. */ +int wificfg_write_buffer_chunk(int s, char *buf); + +/* Write a html title meta data, using the hostname or AP SSI. */ +int wificfg_write_html_title(int s, char *buf, size_t len, const char *str); + +/* Callback to notify the wificfg logic that a station connection has been + * successfully established. It might use this to disable the AP interface after + * a restart. + */ +void wificfg_got_sta_connect(void); + +#ifdef __cplusplus +} +#endif + +#endif // __WIFICFG_H__