/********************************************************************************* * MIT License * * Copyright (c) 2020-2024 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 #include "Network.h" #include "HomeSpan.h" #include "Utils.h" using namespace Utils; /////////////////////////////// void Network::scan(){ int n=WiFi.scanNetworks(); free(ssidList); ssidList=(char **)HS_CALLOC(n,sizeof(char *)); numSSID=0; for(int i=0;i>> WiFi SSID: "); readSerial(wifiData.ssid,MAX_SSID); if(atoi(wifiData.ssid)>0 && atoi(wifiData.ssid)<=numSSID){ strcpy(wifiData.ssid,ssidList[atoi(wifiData.ssid)-1]); } LOG0("%s\n",wifiData.ssid); } while(!strlen(wifiData.pwd)){ LOG0(">>> WiFi PASS: "); readSerial(wifiData.pwd,MAX_PWD); LOG0("%s\n",mask(wifiData.pwd,2).c_str()); } 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(){ LOG0("*** Starting Access Point: %s / %s\n",apSSID,apPassword); STATUS_UPDATE(start(LED_AP_STARTED),HS_AP_STARTED) LOG0("\nScanning for Networks...\n\n"); scan(); // scan for networks for(int i=0;itriggered(9999,3000)){ LOG0("\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){ LOG0("\n*** Access Point: Exiting and Saving Settings\n\n"); return; } else { if(apStatus==0) LOG0("\n*** Access Point: Timed Out (%ld seconds).",lifetime/1000); else LOG0("\n*** Access Point: Configuration Cancelled."); LOG0(" 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 messageSize=client.available(); if(messageSize>MAX_HTTP){ // exceeded maximum number of bytes allowed badRequestError(); LOG0("\n*** ERROR: HTTP message of %d bytes exceeds maximum allowed (%d)\n\n",messageSize,MAX_HTTP); continue; } TempBuffer httpBuf(messageSize+1); // leave room for null character added below int nBytes=client.read(httpBuf,messageSize); // read all available bytes up to maximum allowed+1 if(nBytes!=messageSize || client.available()!=0){ badRequestError(); LOG0("\n*** ERROR: HTTP message not read correctly. Expected %d bytes, read %d bytes, %d bytes remaining\n\n",messageSize,nBytes,client.available()); continue; } httpBuf[nBytes]='\0'; // add null character to enable string functions char *body=(char *)httpBuf.get(); // char pointer to start of HTTP Body char *p; // char pointer used for searches if(!(p=strstr((char *)httpBuf.get(),"\r\n\r\n"))){ badRequestError(); LOG0("\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(); LOG0("\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="" "" "

HomeSpan Setup

"; 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+="" "

Initiating WiFi connection to:

" + String(wifiData.ssid) + "

"; WiFi.begin(wifiData.ssid,wifiData.pwd); } else if(!strncmp(body,"POST /save ",11)){ // GET SAVE getFormValue(formData,"code",setupCode,8); if(allowedCode(setupCode)){ responseBody+="

Settings saved!

Restarting HomeSpan.

Closing window...

"; alarmTimeOut=millis()+2000; apStatus=1; } else { responseBody+="" "

Disallowed Setup Code - too simple!

Returning to configuration page...

"; } } else if(!strncmp(body,"GET /cancel ",12)){ // GET CANCEL responseBody+="

Configuration Canceled!

Restarting HomeSpan.

Closing window...

"; 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+="

Re-initiating connection to:

" + String(wifiData.ssid) + "

"; responseBody+="

(waiting " + String(waitTime) + " seconds to check for response)

"; responseBody+="

Access Point termination in " + String((alarmTimeOut-millis())/1000) + " seconds.

"; responseBody+="
"; WiFi.begin(wifiData.ssid,wifiData.pwd); } else { STATUS_UPDATE(start(LED_AP_CONNECTED),HS_AP_CONNECTED) responseBody+="

SUCCESS! Connected to:

" + String(wifiData.ssid) + "

"; responseBody+="

You may enter new 8-digit Setup Code below, or leave blank to retain existing code.

"; responseBody+="
" "" "
" "
" "
"; responseBody+="
"; } } 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+="

Welcome to HomeSpan! This page allows you to configure the above HomeSpan device to connect to your WiFi network.

" "

The LED on this device should be double-blinking during this configuration.

" "
" "" "
" ""; for(int i=0;i" + String(ssidList[i]) + ""; responseBody+="

" "" "
" "

"; responseBody+="
" "
"; responseBody+="
"; } responseHead+="\r\n"; // add blank line between reponse header and body responseBody+=""; // 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>>>>>>>>> "); LOG2(client.remoteIP()); LOG2(" >>>>>>>>>>\n"); LOG2(s); client.print(s); LOG2("------------ SENT! --------------\n"); delay(1); client.stop(); return(-1); } //////////////////////////////////////