432 lines
15 KiB
C++
432 lines
15 KiB
C++
/*********************************************************************************
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2020-2023 Gregg E. Berman
|
|
*
|
|
* https://github.com/HomeSpan/HomeSpan
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* 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
|
|
* AUTHORS 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 IN THE
|
|
* SOFTWARE.
|
|
*
|
|
********************************************************************************/
|
|
|
|
#include <DNSServer.h>
|
|
|
|
#include "Network.h"
|
|
#include "HomeSpan.h"
|
|
#include "Utils.h"
|
|
|
|
using namespace Utils;
|
|
|
|
///////////////////////////////
|
|
|
|
void Network::scan(){
|
|
|
|
int n=WiFi.scanNetworks();
|
|
|
|
free(ssidList);
|
|
ssidList=(char **)calloc(n,sizeof(char *));
|
|
numSSID=0;
|
|
|
|
for(int i=0;i<n;i++){
|
|
boolean found=false;
|
|
for(int j=0;j<numSSID;j++){
|
|
if(!strcmp(WiFi.SSID(i).c_str(),ssidList[j]))
|
|
found=true;
|
|
}
|
|
if(!found){
|
|
ssidList[numSSID]=(char *)calloc(WiFi.SSID(i).length()+1,sizeof(char));
|
|
sprintf(ssidList[numSSID],"%s",WiFi.SSID(i).c_str());
|
|
numSSID++;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
///////////////////////////////
|
|
|
|
void Network::serialConfigure(){
|
|
|
|
wifiData.ssid[0]='\0';
|
|
wifiData.pwd[0]='\0';
|
|
|
|
Serial.print("*** WiFi Setup - Scanning for Networks...\n\n");
|
|
|
|
scan(); // scan for networks
|
|
|
|
for(int i=0;i<numSSID;i++){
|
|
Serial.print(" ");
|
|
Serial.print(i+1);
|
|
Serial.print(") ");
|
|
Serial.print(ssidList[i]);
|
|
Serial.print("\n");
|
|
}
|
|
|
|
while(!strlen(wifiData.ssid)){
|
|
Serial.print("\n>>> WiFi SSID: ");
|
|
readSerial(wifiData.ssid,MAX_SSID);
|
|
if(atoi(wifiData.ssid)>0 && atoi(wifiData.ssid)<=numSSID){
|
|
strcpy(wifiData.ssid,ssidList[atoi(wifiData.ssid)-1]);
|
|
}
|
|
Serial.print(wifiData.ssid);
|
|
Serial.print("\n");
|
|
}
|
|
|
|
while(!strlen(wifiData.pwd)){
|
|
Serial.print(">>> WiFi PASS: ");
|
|
readSerial(wifiData.pwd,MAX_PWD);
|
|
Serial.print(mask(wifiData.pwd,2));
|
|
Serial.print("\n");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
///////////////////////////////
|
|
|
|
boolean Network::allowedCode(char *s){
|
|
return(
|
|
strcmp(s,"00000000") && strcmp(s,"11111111") && strcmp(s,"22222222") && strcmp(s,"33333333") &&
|
|
strcmp(s,"44444444") && strcmp(s,"55555555") && strcmp(s,"66666666") && strcmp(s,"77777777") &&
|
|
strcmp(s,"88888888") && strcmp(s,"99999999") && strcmp(s,"12345678") && strcmp(s,"87654321"));
|
|
}
|
|
|
|
///////////////////////////////
|
|
|
|
void Network::apConfigure(){
|
|
|
|
Serial.print("*** Starting Access Point: ");
|
|
Serial.print(apSSID);
|
|
Serial.print(" / ");
|
|
Serial.print(apPassword);
|
|
Serial.print("\n");
|
|
|
|
STATUS_UPDATE(start(LED_AP_STARTED),HS_AP_STARTED)
|
|
|
|
Serial.print("\nScanning for Networks...\n\n");
|
|
|
|
scan(); // scan for networks
|
|
|
|
for(int i=0;i<numSSID;i++){
|
|
Serial.print(" ");
|
|
Serial.print(i+1);
|
|
Serial.print(") ");
|
|
Serial.print(ssidList[i]);
|
|
Serial.print("\n");
|
|
}
|
|
|
|
WiFiServer apServer(80);
|
|
client=0;
|
|
|
|
TempBuffer <uint8_t> tempBuffer(MAX_HTTP+1);
|
|
uint8_t *httpBuf=tempBuffer.buf;
|
|
|
|
const byte DNS_PORT = 53;
|
|
DNSServer dnsServer;
|
|
IPAddress apIP(192, 168, 4, 1);
|
|
|
|
WiFi.mode(WIFI_AP);
|
|
WiFi.softAP(apSSID,apPassword); // start access point
|
|
dnsServer.start(DNS_PORT, "*", apIP); // start DNS server that resolves every request to the address of this device
|
|
apServer.begin();
|
|
|
|
alarmTimeOut=millis()+lifetime; // Access Point will shut down when alarmTimeOut is reached
|
|
apStatus=0; // status will be "timed out" unless changed
|
|
|
|
Serial.print("\nReady.\n");
|
|
|
|
while(1){ // loop until we get timed out (which will be accelerated if save/cancel selected)
|
|
|
|
if(homeSpan.controlButton && homeSpan.controlButton->triggered(9999,3000)){
|
|
Serial.print("\n*** Access Point Terminated. Restarting...\n\n");
|
|
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
|
|
homeSpan.controlButton->wait();
|
|
homeSpan.reboot();
|
|
}
|
|
|
|
if(millis()>alarmTimeOut){
|
|
WiFi.softAPdisconnect(true); // terminate connections and shut down captive access point
|
|
delay(100);
|
|
if(apStatus==1){
|
|
Serial.print("\n*** Access Point: Exiting and Saving Settings\n\n");
|
|
return;
|
|
} else {
|
|
if(apStatus==0){
|
|
Serial.print("\n*** Access Point: Timed Out (");
|
|
Serial.print(lifetime/1000);
|
|
Serial.print(" seconds).");
|
|
} else {
|
|
Serial.print("\n*** Access Point: Configuration Cancelled.");
|
|
}
|
|
Serial.print(" Restarting...\n\n");
|
|
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
|
|
homeSpan.reboot();
|
|
}
|
|
}
|
|
|
|
dnsServer.processNextRequest();
|
|
|
|
if(client=apServer.available()){ // found a new HTTP client
|
|
LOG2("=======================================\n");
|
|
LOG1("** Access Point Client Connected: (");
|
|
LOG1(millis()/1000);
|
|
LOG1(" sec) ");
|
|
LOG1(client.remoteIP());
|
|
LOG1("\n");
|
|
LOG2("\n");
|
|
delay(50); // pause to allow data buffer to begin to populate
|
|
}
|
|
|
|
if(client && client.available()){ // if connection exists and data is available
|
|
|
|
LOG2("<<<<<<<<< ");
|
|
LOG2(client.remoteIP());
|
|
LOG2(" <<<<<<<<<\n");
|
|
|
|
int nBytes=client.read(httpBuf,MAX_HTTP+1); // read all available bytes up to maximum allowed+1
|
|
|
|
if(nBytes>MAX_HTTP){ // exceeded maximum number of bytes allowed
|
|
badRequestError();
|
|
Serial.print("\n*** ERROR: Exceeded maximum HTTP message length\n\n");
|
|
continue;
|
|
}
|
|
|
|
httpBuf[nBytes]='\0'; // add null character to enable string functions
|
|
char *body=(char *)httpBuf; // char pointer to start of HTTP Body
|
|
char *p; // char pointer used for searches
|
|
|
|
if(!(p=strstr((char *)httpBuf,"\r\n\r\n"))){
|
|
badRequestError();
|
|
Serial.print("\n*** ERROR: Malformed HTTP request (can't find blank line indicating end of BODY)\n\n");
|
|
continue;
|
|
}
|
|
|
|
*p='\0'; // null-terminate end of HTTP Body to faciliate additional string processing
|
|
uint8_t *content=(uint8_t *)p+4; // byte pointer to start of optional HTTP Content
|
|
int cLen=0; // length of optional HTTP Content
|
|
|
|
if((p=strstr(body,"Content-Length: "))) // Content-Length is specified
|
|
cLen=atoi(p+16);
|
|
if(nBytes!=strlen(body)+4+cLen){
|
|
badRequestError();
|
|
Serial.print("\n*** ERROR: Malformed HTTP request (Content-Length plus Body Length does not equal total number of bytes read)\n\n");
|
|
continue;
|
|
}
|
|
|
|
LOG2(body);
|
|
LOG2("\n------------ END BODY! ------------\n");
|
|
|
|
content[cLen]='\0'; // add a trailing null on end of any contents, which should always be text-based
|
|
|
|
processRequest(body, (char *)content); // process request
|
|
|
|
LOG2("\n");
|
|
|
|
} // process Client
|
|
|
|
} // while 1
|
|
|
|
}
|
|
|
|
///////////////////////////////
|
|
|
|
void Network::processRequest(char *body, char *formData){
|
|
|
|
String responseHead="HTTP/1.1 200 OK\r\nContent-type: text/html\r\n";
|
|
|
|
String responseBody="<html><head><style>"
|
|
"p{font-size:300%; margin:1em}"
|
|
"label{font-size:300%; margin:1em}"
|
|
"input{font-size:250%; margin:1em}"
|
|
"button{font-size:250%; margin:1em}"
|
|
"</style></head>"
|
|
"<body style=\"background-color:lightyellow;\">"
|
|
"<center><p><b>HomeSpan Setup</b></p></center>";
|
|
|
|
if(!strncmp(body,"POST /configure ",16) && // POST CONFIGURE
|
|
strstr(body,"Content-Type: application/x-www-form-urlencoded")){ // check that content is from a form
|
|
|
|
LOG2(formData); // print form data
|
|
LOG2("\n------------ END DATA! ------------\n");
|
|
|
|
LOG1("In Post Configure...\n");
|
|
|
|
getFormValue(formData,"network",wifiData.ssid,MAX_SSID);
|
|
getFormValue(formData,"pwd",wifiData.pwd,MAX_PWD);
|
|
|
|
STATUS_UPDATE(start(LED_WIFI_CONNECTING),HS_WIFI_CONNECTING)
|
|
|
|
responseBody+="<meta http-equiv = \"refresh\" content = \"" + String(waitTime) + "; url = /wifi-status\" />"
|
|
"<p>Initiating WiFi connection to:</p><p><b>" + String(wifiData.ssid) + "</p>";
|
|
|
|
WiFi.begin(wifiData.ssid,wifiData.pwd);
|
|
|
|
} else
|
|
|
|
if(!strncmp(body,"POST /save ",11)){ // GET SAVE
|
|
getFormValue(formData,"code",setupCode,8);
|
|
|
|
if(allowedCode(setupCode)){
|
|
responseBody+="<p><b>Settings saved!</b></p><p>Restarting HomeSpan.</p><p>Closing window...</p>";
|
|
alarmTimeOut=millis()+2000;
|
|
apStatus=1;
|
|
|
|
} else {
|
|
responseBody+="<meta http-equiv = \"refresh\" content = \"4; url = /wifi-status\" />"
|
|
"<p><b>Disallowed Setup Code - too simple!</b></p><p>Returning to configuration page...</p>";
|
|
}
|
|
|
|
} else
|
|
|
|
if(!strncmp(body,"GET /cancel ",12)){ // GET CANCEL
|
|
responseBody+="<p><b>Configuration Canceled!</b></p><p>Restarting HomeSpan.</p><p>Closing window...</p>";
|
|
alarmTimeOut=millis()+2000;
|
|
apStatus=-1;
|
|
} else
|
|
|
|
if(!strncmp(body,"GET /wifi-status ",17)){ // GET WIFI-STATUS
|
|
|
|
LOG1("In Get WiFi Status...\n");
|
|
|
|
if(WiFi.status()!=WL_CONNECTED){
|
|
waitTime+=2;
|
|
if(waitTime==12)
|
|
waitTime=2;
|
|
responseHead+="Refresh: " + String(waitTime) + "\r\n";
|
|
responseBody+="<p>Re-initiating connection to:</p><p><b>" + String(wifiData.ssid) + "</b></p>";
|
|
responseBody+="<p>(waiting " + String(waitTime) + " seconds to check for response)</p>";
|
|
responseBody+="<p>Access Point termination in " + String((alarmTimeOut-millis())/1000) + " seconds.</p>";
|
|
responseBody+="<center><button onclick=\"document.location='/hotspot-detect.html'\">Cancel</button></center>";
|
|
WiFi.begin(wifiData.ssid,wifiData.pwd);
|
|
|
|
} else {
|
|
|
|
STATUS_UPDATE(start(LED_AP_CONNECTED),HS_AP_CONNECTED)
|
|
|
|
responseBody+="<p>SUCCESS! Connected to:</p><p><b>" + String(wifiData.ssid) + "</b></p>";
|
|
responseBody+="<p>You may enter new 8-digit Setup Code below, or leave blank to retain existing code.</p>";
|
|
|
|
responseBody+="<form action=\"/save\" method=\"post\">"
|
|
"<label for=\"code\">Setup Code:</label>"
|
|
"<center><input size=\"32\" type=\"tel\" id=\"code\" name=\"code\" placeholder=\"12345678\" pattern=\"[0-9]{8}\" maxlength=8></center>"
|
|
"<center><input style=\"font-size:300%\" type=\"submit\" value=\"SAVE Settings\"></center>"
|
|
"</form>";
|
|
|
|
responseBody+="<center><button style=\"font-size:300%\" onclick=\"document.location='/cancel'\">CANCEL Configuration</button></center>";
|
|
}
|
|
|
|
} else
|
|
|
|
if(!strstr(body,"wispr") && !strncmp(body,"GET /hotspot-detect.html ",25)){ // GET LANDING-PAGE, but only if request does NOT contain "wispr" user agent
|
|
|
|
LOG1("In Landing Page...\n");
|
|
|
|
STATUS_UPDATE(start(LED_AP_CONNECTED),HS_AP_CONNECTED)
|
|
waitTime=2;
|
|
|
|
responseBody+="<p>Welcome to HomeSpan! This page allows you to configure the above HomeSpan device to connect to your WiFi network.</p>"
|
|
"<p>The LED on this device should be <em>double-blinking</em> during this configuration.</p>"
|
|
"<form action=\"/configure\" method=\"post\">"
|
|
"<label for=\"ssid\">WiFi Network:</label>"
|
|
"<center><input size=\"32\" list=\"network\" name=\"network\" placeholder=\"Choose or Type\" required maxlength=" + String(MAX_SSID) + "></center>"
|
|
"<datalist id=\"network\">";
|
|
|
|
for(int i=0;i<numSSID;i++)
|
|
responseBody+="<option value=\"" + String(ssidList[i]) + "\">" + String(ssidList[i]) + "</option>";
|
|
|
|
responseBody+="</datalist><br><br>"
|
|
"<label for=\"pwd\">WiFi Password:</label>"
|
|
"<center><input size=\"32\" type=\"password\" id=\"pwd\" name=\"pwd\" required maxlength=" + String(MAX_PWD) + "></center>"
|
|
"<br><br>";
|
|
|
|
responseBody+="<center><input style=\"font-size:300%\" type=\"submit\" value=\"SUBMIT\"></center>"
|
|
"</form>";
|
|
|
|
responseBody+="<center><button style=\"font-size:300%\" onclick=\"document.location='/cancel'\">CANCEL Configuration</button></center>";
|
|
|
|
}
|
|
|
|
responseHead+="\r\n"; // add blank line between reponse header and body
|
|
responseBody+="</body></html>"; // close out body and html tags
|
|
|
|
LOG2("\n>>>>>>>>>> ");
|
|
LOG2(client.remoteIP());
|
|
LOG2(" >>>>>>>>>>\n");
|
|
LOG2(responseHead);
|
|
LOG2(responseBody);
|
|
LOG2("\n");
|
|
client.print(responseHead);
|
|
client.print(responseBody);
|
|
LOG2("------------ SENT! --------------\n");
|
|
|
|
} // processRequest
|
|
|
|
//////////////////////////////////////
|
|
|
|
int Network::getFormValue(char *formData, const char *tag, char *value, int maxSize){
|
|
|
|
char *s=strstr(formData,tag); // find start of tag
|
|
|
|
if(!s) // if not found, return -1
|
|
return(-1);
|
|
|
|
char *v=index(s,'='); // find '='
|
|
|
|
if(!v) // if not found, return -1 (this should not happen)
|
|
return(-1);
|
|
|
|
v++; // point to begining of value
|
|
int len=0; // track length of value
|
|
|
|
while(*v!='\0' && *v!='&' && len<maxSize){ // copy the value until null, '&', or maxSize is reached
|
|
if(*v=='%'){ // this is an escaped character of form %XX
|
|
v++;
|
|
sscanf(v,"%2x",(unsigned int *)value++);
|
|
v+=2;
|
|
} else {
|
|
*value++=(*v=='+'?' ':*v); // HTML Forms use '+' for spaces (and '+' signs are escaped)
|
|
v++;
|
|
}
|
|
len++;
|
|
}
|
|
|
|
*value='\0'; // add terminating null
|
|
return(len);
|
|
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
|
|
int Network::badRequestError(){
|
|
|
|
char s[]="HTTP/1.1 400 Bad Request\r\n\r\n";
|
|
LOG2("\n>>>>>>>>>> ");
|
|
LOG2(client.remoteIP());
|
|
LOG2(" >>>>>>>>>>\n");
|
|
LOG2(s);
|
|
client.print(s);
|
|
LOG2("------------ SENT! --------------\n");
|
|
|
|
delay(1);
|
|
client.stop();
|
|
|
|
return(-1);
|
|
}
|
|
|
|
//////////////////////////////////////
|