diff --git a/Page1.png b/Page1.png index c495615c..ff519e3c 100644 Binary files a/Page1.png and b/Page1.png differ diff --git a/Page2.png b/Page2.png index a45ec602..d42242a8 100644 Binary files a/Page2.png and b/Page2.png differ diff --git a/Page3.png b/Page3.png index e13829d2..93051361 100644 Binary files a/Page3.png and b/Page3.png differ diff --git a/Page4.png b/Page4.png index 0dd26a6c..7544e911 100644 Binary files a/Page4.png and b/Page4.png differ diff --git a/Page6.png b/Page6.png index 9f9b4ad5..c7522cb6 100644 Binary files a/Page6.png and b/Page6.png differ diff --git a/Page7.png b/Page7.png new file mode 100644 index 00000000..fc32ed01 Binary files /dev/null and b/Page7.png differ diff --git a/Page8.png b/Page8.png new file mode 100644 index 00000000..fe4826b3 Binary files /dev/null and b/Page8.png differ diff --git a/README.md b/README.md index eb879c47..fef050a4 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Additionnaly: *Tools: --Use IDE to upload directly (latest version of board manager module generate one binary) -- to flash the htm files present in data directory you need to use another tool, installation and usage is explained here: http://arduino.esp8266.com/versions/1.6.5-1160-gef26c5f/doc/reference.html#file-system - +Once flashed you also can use the web updater to flash new FW in System Configuration Page *Connection --Connect GPIO0 to ground to be in update mode @@ -78,6 +78,8 @@ Baud rate: 9600 Web port:80 Data port: 8888 Web Page refresh: 3 secondes +User: admin +Password: admin These are the pages defined using template: Home page : @@ -94,6 +96,10 @@ Printer Status Page for more than 64K SPIFFS, fancy one:
Extra Settings Page, for web UI and for printer:
+Change password Page: +
+Login Page: +
the template files are stored on SPIFFS:
and uploaded using [IDE](http://arduino.esp8266.com/versions/1.6.5-1160-gef26c5f/doc/reference.html#file-system) @@ -110,6 +116,14 @@ Currently, I tested on ESP01 using 64K SPIFFS ( please use data directory conten *SSDP : on Station and AP mode (done) *Captive portal : on AP mode only (not yet functionnal) +##Basic Authentification +Can be disabled in FW +default user: admin +default password: admin + +#OTA support +Currently only web update is supported not telnet one + ##Commands/msg from/to serial(not fully implemented): *from module to printer by serial communication -M117 [Message], Error/status message from module (done) diff --git a/esp8266/config.cpp b/esp8266/config.cpp index db636506..849bafb8 100644 --- a/esp8266/config.cpp +++ b/esp8266/config.cpp @@ -176,6 +176,7 @@ bool CONFIG::reset_config() if(!CONFIG::write_buffer(EP_XY_FEEDRATE,(const byte *)&DEFAULT_XY_FEEDRATE,INTEGER_LENGTH))return false; if(!CONFIG::write_buffer(EP_Z_FEEDRATE,(const byte *)&DEFAULT_Z_FEEDRATE,INTEGER_LENGTH))return false; if(!CONFIG::write_buffer(EP_E_FEEDRATE,(const byte *)&DEFAULT_E_FEEDRATE,INTEGER_LENGTH))return false; + if(!CONFIG::write_string(EP_ADMIN_PWD,FPSTR(DEFAULT_ADMIN)))return false; return true; } diff --git a/esp8266/config.h b/esp8266/config.h index f6b4633f..4124fa69 100644 --- a/esp8266/config.h +++ b/esp8266/config.h @@ -29,6 +29,9 @@ //CAPTIVE_PORTAL_FEATURE: In SoftAP redirect all unknow call to main page #define CAPTIVE_PORTAL_FEATURE +//AUTHENTICATION_FEATURE: protect pages by login password +#define AUTHENTICATION_FEATURE + #ifndef CONFIG_h #define CONFIG_h @@ -38,7 +41,7 @@ extern "C" { #include "user_interface.h" } //version and sources location -#define FW_VERSION "0.4" +#define FW_VERSION "0.5" #define REPOSITORY "https://github.com/luc-github/ESP8266" @@ -73,6 +76,7 @@ extern "C" { #define EP_XY_FEEDRATE 164//4 bytes = int #define EP_Z_FEEDRATE 168//4 bytes = int #define EP_E_FEEDRATE 172//4 bytes = int +#define EP_ADMIN_PWD 176//21 bytes 20+1 = string ; warning does not support multibyte char like chinese @@ -99,6 +103,7 @@ const int DEFAULT_DATA_PORT = 8888; const int DEFAULT_XY_FEEDRATE=1000; const int DEFAULT_Z_FEEDRATE =100; const int DEFAULT_E_FEEDRATE=400; +const char DEFAULT_ADMIN [] PROGMEM = "admin"; //sizes #define EEPROM_SIZE 256 //max is 512 @@ -106,6 +111,8 @@ const int DEFAULT_E_FEEDRATE=400; #define MIN_SSID_LENGTH 1 #define MAX_PASSWORD_LENGTH 64 #define MIN_PASSWORD_LENGTH 8 +#define MAX_ADMIN_PASSWORD_LENGTH 16 +#define MIN_ADMIN_PASSWORD_LENGTH 1 #define IP_LENGTH 4 #define INTEGER_LENGTH 4 #define MAX_HOSTNAME_LENGTH 32 diff --git a/esp8266/data/css.inc b/esp8266/data/css.inc index f4aff0c9..fbb98a17 100644 --- a/esp8266/data/css.inc +++ b/esp8266/data/css.inc @@ -14,7 +14,7 @@ th{text-align:left;} @media (min-width:1200px){.container{width:1170px;}} .nav{ width:100%; color:#cccccc;padding-left:10;padding-right:10;list-style:none;background-color:#333333;border-radius:6px ;margin-bottom:20px;} a{position:relative;display:block;padding:10px 15px;text-decoration:none;color:#cccccc;} - .active{color:#ffffff;background-color:#000000;} +.active{color:#ffffff;background-color:#000000;} .active a,a:hover,a:focus{color:#FFFFFF;} .panel{margin-bottom:20px;background-color:#ffffff;border:1px solid #dddddd;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05);} .panel-body{padding:15px;} @@ -46,3 +46,5 @@ caption{padding-top:8px;padding-bottom:8px;color:#777777;text-align:left;} .btn-danger:focus,.btn-danger:active,.btn-danger:hover,.btn-danger.focus,.btn-danger.active,.btn-danger.hover{color: #ffffff;background-color:#c9302c;border-color:#761c19;} .btnimg {cursor:hand; border-radius:6px ;;border:1px solid #FFFFFF;} .btnimg:hover{background-color:#F0F0F0;border-color:#00FFFF;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;} +.btnroundimg {cursor:hand; border-radius:30px;} +.btnroundimg:hover{background-color:#F0F0F0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;} diff --git a/esp8266/data/header.inc b/esp8266/data/header.inc index 1fbdee0c..5d90c9c0 100644 --- a/esp8266/data/header.inc +++ b/esp8266/data/header.inc @@ -11,12 +11,19 @@ $INCLUDE[css.inc]$
- - - - + + + + + + diff --git a/esp8266/data/login.tpl b/esp8266/data/login.tpl new file mode 100644 index 00000000..705e079b --- /dev/null +++ b/esp8266/data/login.tpl @@ -0,0 +1,21 @@ +$INCLUDE[header.inc]$ +
+
Log in
+
+
+

+ +
+

+
+ +
+ + +
+
+$INCLUDE[footer.inc]$ diff --git a/esp8266/data/password.tpl b/esp8266/data/password.tpl new file mode 100644 index 00000000..9ecfcff7 --- /dev/null +++ b/esp8266/data/password.tpl @@ -0,0 +1,111 @@ +$INCLUDE[header.inc]$ +
+
Change Password
+
+
+

+
+

+
+ +
+ + +
+
+ +$INCLUDE[footer.inc]$ diff --git a/esp8266/data/system.tpl b/esp8266/data/system.tpl index 8fe99276..81cd3b6b 100644 --- a/esp8266/data/system.tpl +++ b/esp8266/data/system.tpl @@ -1,5 +1,16 @@ $INCLUDE[header.inc]$ -
+ +
System
@@ -20,11 +31,74 @@ $SLEEP_MODE_OPTIONS_LIST$ -
+
+
+
Firmware Update
+
+
+ + + +
+
+ + + $INCLUDE[footer.inc]$ diff --git a/esp8266/esp8266.ino b/esp8266/esp8266.ino index a5e59fea..7083b355 100644 --- a/esp8266/esp8266.ino +++ b/esp8266/esp8266.ino @@ -110,6 +110,11 @@ void setup() { //start interfaces web_interface = new WEBINTERFACE_CLASS(wifi_config.iweb_port); data_server = new WiFiServer (wifi_config.idata_port); + //here the list of headers to be recorded + const char * headerkeys[] = {"Cookie"} ; + size_t headerkeyssize = sizeof(headerkeys)/sizeof(char*); + //ask server to track these headers + web_interface->WebServer.collectHeaders(headerkeys, headerkeyssize ); web_interface->WebServer.begin(); data_server->begin(); data_server->setNoDelay(true); diff --git a/esp8266/webinterface.cpp b/esp8266/webinterface.cpp index d5843a88..508d50f2 100644 --- a/esp8266/webinterface.cpp +++ b/esp8266/webinterface.cpp @@ -36,6 +36,14 @@ extern "C" { #ifdef SSDP_FEATURE #include #endif + +#define MAX_AUTH_IP 10 +#define UPLOAD_STATUS_NONE 0 +#define UPLOAD_STATUS_FAILED 1 +#define UPLOAD_STATUS_CANCELLED 2 +#define UPLOAD_STATUS_SUCCESSFUL 3 +#define UPLOAD_STATUS_ONGOING 4 + const char PAGE_404 [] PROGMEM ="\n\nRedirecting... \n\n\n
Unknown page - you will be redirected...\n

\nif not redirected, click here\n

\n\n\n\n
\n\n\n\n"; const char PAGE_RESTART [] PROGMEM ="\n\nRestarting... \n\n\n
Restarting, please wait....\n
\n\n
\n\n\n\n"; const char RESTARTCMD [] PROGMEM =""; @@ -182,6 +190,16 @@ const char KEY_Z_FEEDRATE_STATUS [] PROGMEM = "$Z_FEEDRATE_STATUS$"; const char KEY_E_FEEDRATE_STATUS [] PROGMEM = "$E_FEEDRATE_STATUS$"; const char VALUE_SETTINGS [] PROGMEM = "Extra Settings"; const char KEY_REFRESH_PAGE_STATUS [] PROGMEM = "$REFRESH_PAGE_STATUS$"; +const char KEY_DISCONNECT_VISIBILITY [] PROGMEM = "$DISCONNECT_VISIBILITY$"; +const char VALUE_LOGIN [] PROGMEM = "Login page"; +const char KEY_USER_STATUS [] PROGMEM = "$USER_STATUS$"; +const char KEY_USER_PASSWORD_STATUS [] PROGMEM = "$USER_PASSWORD_STATUS$"; +const char KEY_USER_PASSWORD_STATUS2 [] PROGMEM = "$USER_PASSWORD_STATUS2$"; +const char KEY_USER [] PROGMEM = "$USER$"; +const char KEY_USER_PASSWORD [] PROGMEM = "$USER_PASSWORD$"; +const char KEY_USER_PASSWORD2 [] PROGMEM = "$USER_PASSWORD2$"; +const char KEY_RETURN [] PROGMEM = "$RETURN$"; +const char VALUE_CHANGE_PASSWORD [] PROGMEM = "Change Password"; bool WEBINTERFACE_CLASS::isHostnameValid(const char * hostname) { //limited size @@ -225,6 +243,20 @@ bool WEBINTERFACE_CLASS::isPasswordValid(const char * password) return true; } +bool WEBINTERFACE_CLASS::isAdminPasswordValid(const char * password) +{ + char c; + //limited size + if ((strlen(password)>MAX_ADMIN_PASSWORD_LENGTH)|| (strlen(password)is_authenticated())ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + else ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); //Free Mem, put at the end to reflect situation KeysList.add(FPSTR(KEY_FREE_MEM)); ValuesList.add(intTostr(system_get_free_heap_size())); @@ -833,6 +870,12 @@ void handle_web_interface_configSys() const __FlashStringHelper *smodemdisplaylist[]={FPSTR(VALUE_NONE),FPSTR(VALUE_LIGHT),FPSTR(VALUE_MODEM),FPSTR(VALUE_MODEM)}; STORESTRINGS_CLASS KeysList ; STORESTRINGS_CLASS ValuesList ; + if (!web_interface->is_authenticated()) + { + String header = "HTTP/1.1 301 OK\r\nLocation: /LOGIN?return=CONFIGSYS\r\nCache-Control: no-cache\r\n\r\n"; + web_interface->WebServer.sendContent(header); + return; + } //Free Mem, put at the end to reflect situation KeysList.add(FPSTR(KEY_FREE_MEM)); ValuesList.add(intTostr(system_get_free_heap_size())); @@ -1067,6 +1110,195 @@ void handle_web_interface_configSys() ValuesList.clear(); } +void handle_password() +{ + String stmp,smsg; + String sPassword,sPassword2; + bool msg_alert_error=false; + bool msg_alert_success=false; + int ipos; + STORESTRINGS_CLASS KeysList ; + STORESTRINGS_CLASS ValuesList ; + if (!web_interface->is_authenticated()) + { + String header = "HTTP/1.1 301 OK\r\nLocation: /LOGIN?return=PASSWORD\r\nCache-Control: no-cache\r\n\r\n"; + web_interface->WebServer.sendContent(header); + return; + } + //Free Mem, put at the end to reflect situation + KeysList.add(FPSTR(KEY_FREE_MEM)); + ValuesList.add(intTostr(system_get_free_heap_size())); + //IP + stmp=FPSTR(KEY_IP); + KeysList.add(stmp); + if (wifi_get_opmode()==WIFI_STA ) stmp=WiFi.localIP().toString(); + else stmp=WiFi.softAPIP().toString(); + ValuesList.add(stmp); + //Web address = ip + port + KeysList.add(FPSTR(KEY_WEB_ADDRESS)); + if (wifi_config.iweb_port!=80) + { + stmp+=":"; + stmp+=intTostr(wifi_config.iweb_port); + } + ValuesList.add(stmp); + //mode + if (wifi_get_opmode()==WIFI_STA ) + { + KeysList.add(FPSTR(KEY_MODE)); + ValuesList.add(FPSTR(VALUE_STA)); + } + else + { + if (wifi_get_opmode()==WIFI_AP ) + { + KeysList.add(FPSTR(KEY_MODE)); + ValuesList.add(FPSTR(VALUE_AP)); + } + else + { + KeysList.add(FPSTR(KEY_MODE)); + ValuesList.add(FPSTR(VALUE_AP_STA)); + } + } + //FW Version + KeysList.add(FPSTR(KEY_FW_VER)); + ValuesList.add(FPSTR(VALUE_FW_VERSION)); + //page title + KeysList.add(FPSTR(KEY_PAGE_TITLE)); + ValuesList.add(FPSTR(VALUE_CHANGE_PASSWORD)); + //tpl file name with extension + KeysList.add(FPSTR(KEY_FILE_NAME)); + ValuesList.add("password.tpl"); + //tpl file name without extension + KeysList.add(FPSTR(KEY_SHORT_FILE_NAME)); + ValuesList.add("password"); + //menu item + KeysList.add(FPSTR(KEY_MENU_AP)); + ValuesList.add(FPSTR(VALUE_ACTIVE)); + + //check is it is a submission or a display + smsg=""; + if (web_interface->WebServer.hasArg("SUBMIT")) + { //is there a correct list of values? + if (web_interface->WebServer.hasArg("PASSWORD") && web_interface->WebServer.hasArg("PASSWORD2")) + { + //Password + web_interface->urldecode(sPassword,web_interface->WebServer.arg("PASSWORD").c_str()); + web_interface->urldecode(sPassword2,web_interface->WebServer.arg("PASSWORD2").c_str()); + if (!web_interface->isAdminPasswordValid(sPassword.c_str()) ) + { + msg_alert_error=true; + smsg+="Error : Incorrect password
"; + KeysList.add(FPSTR(KEY_USER_PASSWORD_STATUS)); + ValuesList.add(FPSTR(VALUE_HAS_ERROR)); + } + if (sPassword!=sPassword2) + { + msg_alert_error=true; + smsg+="Error : Passwords do not match
"; + KeysList.add(FPSTR(KEY_USER_PASSWORD_STATUS2)); + ValuesList.add(FPSTR(VALUE_HAS_ERROR)); + } + + } + else + { + msg_alert_error=true; + smsg="Error : Missing data"; + } + + //if no error apply the change + if (msg_alert_error==false) + { + //save + if(!CONFIG::write_string(EP_ADMIN_PWD,sPassword.c_str())) + { + msg_alert_error=true; + smsg="Error : Cannot write to EEPROM"; + } + else + { + msg_alert_success=true; + smsg="Changes saved to EEPROM"; + } + } + } + + else //no submit need to get data from EEPROM + { + //password + sPassword=""; + sPassword2=""; + } + //Display values + //password + KeysList.add(FPSTR(KEY_USER_PASSWORD)); + ValuesList.add(sPassword); + KeysList.add(FPSTR(KEY_USER_PASSWORD2)); + ValuesList.add(sPassword2); + +if (msg_alert_error) + { + KeysList.add(FPSTR(KEY_ERROR_MSG)); + ValuesList.add(smsg); + KeysList.add(FPSTR(KEY_SUCCESS_MSG)); + ValuesList.add(""); + KeysList.add(FPSTR(KEY_ERROR_MSG_VISIBILITY )); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + KeysList.add(FPSTR(KEY_SUCCESS_MSG_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + KeysList.add(FPSTR(KEY_SUBMIT_BUTTON_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + KeysList.add(FPSTR(KEY_SERVICE_PAGE)); + ValuesList.add(""); + } + else if (msg_alert_success) + { + KeysList.add(FPSTR(KEY_ERROR_MSG)); + ValuesList.add(""); + KeysList.add(FPSTR(KEY_SUCCESS_MSG)); + ValuesList.add(smsg); + KeysList.add(FPSTR(KEY_ERROR_MSG_VISIBILITY )); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + KeysList.add(FPSTR(KEY_SUCCESS_MSG_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + KeysList.add(FPSTR(KEY_SUBMIT_BUTTON_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + KeysList.add(FPSTR(KEY_SERVICE_PAGE)); + ValuesList.add(""); + //Add all green + KeysList.add(FPSTR(KEY_USER_PASSWORD_STATUS)); + ValuesList.add(FPSTR(VALUE_HAS_SUCCESS)); + KeysList.add(FPSTR(KEY_USER_PASSWORD_STATUS2)); + ValuesList.add(FPSTR(VALUE_HAS_SUCCESS)); + } + + else + + { + KeysList.add(FPSTR(KEY_ERROR_MSG)); + ValuesList.add(""); + KeysList.add(FPSTR(KEY_SUCCESS_MSG)); + ValuesList.add(""); + KeysList.add(FPSTR(KEY_ERROR_MSG_VISIBILITY )); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + KeysList.add(FPSTR(KEY_SUCCESS_MSG_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + KeysList.add(FPSTR(KEY_SUBMIT_BUTTON_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + KeysList.add(FPSTR(KEY_SERVICE_PAGE)); + ValuesList.add(""); + } + //process the template file and provide list of variables + if(KeysList.size()==ValuesList.size()) //Sanity check + processTemplate("/password.tpl", KeysList , ValuesList); + //need to clean to speed up memory recovery + KeysList.clear(); + ValuesList.clear(); +} + + void handle_web_interface_configAP() { String stmp,smsg; @@ -1088,6 +1320,12 @@ void handle_web_interface_configAP() const __FlashStringHelper * iauthdisplaylist []={FPSTR(VALUE_NONE),FPSTR(VALUE_WPA),FPSTR(VALUE_WPA2),FPSTR(VALUE_WPAWPA2),FPSTR(VALUE_MAX),FPSTR(VALUE_MAX)}; STORESTRINGS_CLASS KeysList ; STORESTRINGS_CLASS ValuesList ; + if (!web_interface->is_authenticated()) + { + String header = "HTTP/1.1 301 OK\r\nLocation: /LOGIN?return=CONFIGAP\r\nCache-Control: no-cache\r\n\r\n"; + web_interface->WebServer.sendContent(header); + return; + } //Free Mem, put at the end to reflect situation KeysList.add(FPSTR(KEY_FREE_MEM)); ValuesList.add(intTostr(system_get_free_heap_size())); @@ -1471,7 +1709,6 @@ if (msg_alert_error) ValuesList.clear(); } - void handle_web_interface_configSTA() { String stmp,smsg; @@ -1489,6 +1726,12 @@ void handle_web_interface_configSTA() const __FlashStringHelper * inetworkdisplaylist []={FPSTR(VALUE_11B),FPSTR(VALUE_11G),FPSTR(VALUE_11N),FPSTR(VALUE_11B)}; STORESTRINGS_CLASS KeysList ; STORESTRINGS_CLASS ValuesList ; + if (!web_interface->is_authenticated()) + { + String header = "HTTP/1.1 301 OK\r\nLocation: /LOGIN?return=CONFIGSTA\r\nCache-Control: no-cache\r\n\r\n"; + web_interface->WebServer.sendContent(header); + return; + } //Free Mem, put at the end to reflect situation KeysList.add(FPSTR(KEY_FREE_MEM)); ValuesList.add(intTostr(system_get_free_heap_size())); @@ -1861,7 +2104,6 @@ if (msg_alert_error) ValuesList.clear(); } - void handle_web_interface_printer() { String stmp,smsg; @@ -1869,6 +2111,12 @@ void handle_web_interface_printer() bool msg_alert_success=false; STORESTRINGS_CLASS KeysList ; STORESTRINGS_CLASS ValuesList ; + if (!web_interface->is_authenticated()) + { + String header = "HTTP/1.1 301 OK\r\nLocation: /LOGIN?return=PRINTER\r\nCache-Control: no-cache\r\n\r\n"; + web_interface->WebServer.sendContent(header); + return; + } //Free Mem, put at the end to reflect situation KeysList.add(FPSTR(KEY_FREE_MEM)); ValuesList.add(intTostr(system_get_free_heap_size())); @@ -1964,6 +2212,12 @@ void handle_web_settings() int ixy_feedrate,iz_feedrate,ie_feedrate; STORESTRINGS_CLASS KeysList ; STORESTRINGS_CLASS ValuesList ; + if (!web_interface->is_authenticated()) + { + String header = "HTTP/1.1 301 OK\r\nLocation: /LOGIN?return=SETTINGS\r\nCache-Control: no-cache\r\n\r\n"; + web_interface->WebServer.sendContent(header); + return; + } //Free Mem, put at the end to reflect situation KeysList.add(FPSTR(KEY_FREE_MEM)); ValuesList.add(intTostr(system_get_free_heap_size())); @@ -2163,10 +2417,9 @@ void handle_web_settings() ValuesList.clear(); } - - void handle_web_interface_status() { + web_interface->is_authenticated(); Serial.println("M114"); int tagpos,tagpos2; String buffer2send; @@ -2340,8 +2593,9 @@ String getContentType(String filename){ else if(filename.endsWith(".txt")) return "text/plain"; return "application/octet-stream"; } -void handleFileUpload(){ - if(web_interface->WebServer.uri() != "/FILES") return; + +void SPIFFSFileupload() +{ HTTPUpload& upload = (web_interface->WebServer).upload(); if(upload.status == UPLOAD_FILE_START){ String filename = upload.filename; @@ -2359,7 +2613,53 @@ void handleFileUpload(){ else Serial.println("Cannot open file"); } +void WebUpdateUpload() +{ + HTTPUpload& upload = (web_interface->WebServer).upload(); + if(upload.status == UPLOAD_FILE_START){ + Serial.println("M117 Update Firmware"); + web_interface->_upload_status= UPLOAD_STATUS_ONGOING; + WiFiUDP::stopAll(); + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + if(!Update.begin(maxSketchSpace)){//start with max available size + } + } else if(upload.status == UPLOAD_FILE_WRITE){ + web_interface->_upload_status= UPLOAD_STATUS_ONGOING; + if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){ + } + } else if(upload.status == UPLOAD_FILE_END){ + if(Update.end(true)){ //true to set the size to the current progress + //Now Reboot + web_interface->_upload_status=UPLOAD_STATUS_SUCCESSFUL; + } + } else if(upload.status == UPLOAD_FILE_ABORTED){ + Update.end(); + web_interface->_upload_status=UPLOAD_STATUS_CANCELLED; + } + yield(); +} + +void handleFileUpload(){ + if(web_interface->WebServer.uri() == "/FILES") SPIFFSFileupload(); + if(web_interface->WebServer.uri() == "/UPDATE") WebUpdateUpload(); +} + +void handleUpdate(){ + web_interface->is_authenticated(); + String jsonfile = "{\"status\":\"" ; + jsonfile+=intTostr(web_interface->_upload_status); + jsonfile+="\"}"; + //send status + web_interface->WebServer.send(200, "application/json", jsonfile); + //if success restart + if (web_interface->_upload_status==UPLOAD_STATUS_SUCCESSFUL)web_interface->restartmodule=true; +} + void handleFileList() { + if (!web_interface->is_authenticated()) + { + return; + } String path = "/"; String status="Ok"; if(web_interface->WebServer.hasArg("action")) { @@ -2409,6 +2709,10 @@ void handleFileList() { } void handleSDFileList() { + if (!web_interface->is_authenticated()) + { + return; + } String jsonfile = "["; for (int i=0;ifileslist.size();i++) { @@ -2421,11 +2725,16 @@ void handleSDFileList() { web_interface->WebServer.send(200, "application/json", jsonfile); } - //do a redirect to avoid to many query //and handle not registred path void handle_not_found() { + if (!web_interface->is_authenticated()) + { + String header = "HTTP/1.1 301 OK\r\nLocation: /\r\nCache-Control: no-cache\r\n\r\n"; + web_interface->WebServer.sendContent(header); + return; + } String path = web_interface->WebServer.uri(); String contentType = getContentType(path); String pathWithGz = path + ".gz"; @@ -2513,6 +2822,193 @@ else } } +void handle_login() +{ + String stmp,smsg; + String sReturn; + String sUser,sPassword; + bool msg_alert_error=false; + bool msg_alert_success=false; + STORESTRINGS_CLASS KeysList ; + STORESTRINGS_CLASS ValuesList ; + + if (web_interface->WebServer.hasArg("DISCONNECT")){ + String header = "HTTP/1.1 301 OK\r\nSet-Cookie: ESPSESSIONID=0\r\nLocation: /LOGIN\r\nCache-Control: no-cache\r\n\r\n"; + web_interface->WebServer.sendContent(header); + return; + } + + //check is it is a submission or a display + smsg=""; + if (web_interface->WebServer.hasArg("return")) web_interface->urldecode(sReturn,web_interface->WebServer.arg("return").c_str()); + if (web_interface->WebServer.hasArg("SUBMIT")) + { //is there a correct list of values? + if ( web_interface->WebServer.hasArg("PASSWORD")&& web_interface->WebServer.hasArg("USER")) + { + //USER + web_interface->urldecode(sUser,web_interface->WebServer.arg("USER").c_str()); + #ifdef AUTHENTICATION_FEATURE + if (sUser!="admin") + { + msg_alert_error=true; + smsg+="Error : Incorrect User
"; + KeysList.add(FPSTR(KEY_USER_STATUS)); + ValuesList.add(FPSTR(VALUE_HAS_ERROR)); + } + //Password + web_interface->urldecode(sPassword,web_interface->WebServer.arg("PASSWORD").c_str()); + String scurrentPassword; + + if (!CONFIG::read_string(EP_ADMIN_PWD, scurrentPassword , MAX_ADMIN_PASSWORD_LENGTH) )scurrentPassword=FPSTR(DEFAULT_ADMIN); + if (strcmp(sPassword.c_str(),scurrentPassword.c_str())!=0) + { + msg_alert_error=true; + smsg+="Error : Incorrect password
"; + KeysList.add(FPSTR(KEY_USER_PASSWORD_STATUS)); + ValuesList.add(FPSTR(VALUE_HAS_ERROR)); + } + #endif + } + else + { + msg_alert_error=true; + smsg="Error : Missing data"; + } + + //if no error login is ok + if (msg_alert_error==false) + { + #ifdef AUTHENTICATION_FEATURE + auth_ip * current_auth = new auth_ip; + current_auth->ip=web_interface->WebServer.client().remoteIP(); + strcpy(current_auth->sessionID,web_interface->create_session_ID()); + current_auth->last_time=millis(); + if (web_interface->AddAuthIP(current_auth)) + { + String header = "HTTP/1.1 301 OK\r\nSet-Cookie: ESPSESSIONID="; + header+=current_auth->sessionID; + header+="\r\nLocation: /"; + header+=sReturn; + header+="\r\nCache-Control: no-cache\r\n\r\n"; + web_interface->WebServer.sendContent(header); + return; + } + else + { + delete current_auth; + msg_alert_error=true; + smsg="Error : Too many connections"; + } + #endif + } + } + + else //no submit need to get data from EEPROM + { + sUser=String(); + //password + sPassword=String(); + } + + //Display values + KeysList.add(FPSTR(KEY_RETURN)); + ValuesList.add(sReturn); + //Free Mem, put at the end to reflect situation + KeysList.add(FPSTR(KEY_FREE_MEM)); + ValuesList.add(intTostr(system_get_free_heap_size())); + KeysList.add(FPSTR(KEY_DISCONNECT_VISIBILITY)); + if (web_interface->is_authenticated())ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + else ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + //IP + stmp=FPSTR(KEY_IP); + KeysList.add(stmp); + if (wifi_get_opmode()==WIFI_STA ) stmp=WiFi.localIP().toString(); + else stmp=WiFi.softAPIP().toString(); + ValuesList.add(stmp); + //Web address = ip + port + KeysList.add(FPSTR(KEY_WEB_ADDRESS)); + if (wifi_config.iweb_port!=80) + { + stmp+=":"; + stmp+=intTostr(wifi_config.iweb_port); + } + ValuesList.add(stmp); + //mode + if (wifi_get_opmode()==WIFI_STA ) + { + KeysList.add(FPSTR(KEY_MODE)); + ValuesList.add(FPSTR(VALUE_STA)); + } + else + { + if (wifi_get_opmode()==WIFI_AP ) + { + KeysList.add(FPSTR(KEY_MODE)); + ValuesList.add(FPSTR(VALUE_AP)); + } + else + { + KeysList.add(FPSTR(KEY_MODE)); + ValuesList.add(FPSTR(VALUE_AP_STA)); + } + } + //FW Version + KeysList.add(FPSTR(KEY_FW_VER)); + ValuesList.add(FPSTR(VALUE_FW_VERSION)); + //page title + KeysList.add(FPSTR(KEY_PAGE_TITLE)); + ValuesList.add(FPSTR(VALUE_LOGIN)); + //tpl file name with extension + KeysList.add(FPSTR(KEY_FILE_NAME)); + ValuesList.add("login.tpl"); + //tpl file name without extension + KeysList.add(FPSTR(KEY_SHORT_FILE_NAME)); + ValuesList.add("login"); + //User + KeysList.add(FPSTR(KEY_USER)); + ValuesList.add(sUser); + + //password + KeysList.add(FPSTR(KEY_USER_PASSWORD)); + ValuesList.add(sPassword); + +if (msg_alert_error) + { + KeysList.add(FPSTR(KEY_ERROR_MSG)); + ValuesList.add(smsg); + KeysList.add(FPSTR(KEY_SUCCESS_MSG)); + ValuesList.add(""); + KeysList.add(FPSTR(KEY_ERROR_MSG_VISIBILITY )); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + KeysList.add(FPSTR(KEY_SUCCESS_MSG_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + KeysList.add(FPSTR(KEY_SUBMIT_BUTTON_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + KeysList.add(FPSTR(KEY_SERVICE_PAGE)); + ValuesList.add(""); + } +else +{ + KeysList.add(FPSTR(KEY_ERROR_MSG)); + ValuesList.add(""); + KeysList.add(FPSTR(KEY_SUCCESS_MSG)); + ValuesList.add(""); + KeysList.add(FPSTR(KEY_ERROR_MSG_VISIBILITY )); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + KeysList.add(FPSTR(KEY_SUCCESS_MSG_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + KeysList.add(FPSTR(KEY_SUBMIT_BUTTON_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + KeysList.add(FPSTR(KEY_SERVICE_PAGE)); + ValuesList.add(""); + } + //process the template file and provide list of variables + if(KeysList.size()==ValuesList.size()) //Sanity check + processTemplate("/login.tpl", KeysList , ValuesList); + //need to clean to speed up memory recovery + KeysList.clear(); + ValuesList.clear(); +} void handle_restart() { if (SPIFFS.exists("/restart.tpl")) @@ -2581,18 +3077,22 @@ void handle_restart() void handle_web_command() { + if (!web_interface->is_authenticated()) + { + return; + } //check we have proper parameter -if (web_interface->WebServer.hasArg("COM")) - { - String scmd; - //decode command - web_interface->urldecode(scmd,web_interface->WebServer.arg("COM").c_str()); - scmd.trim(); - //send command to serial - Serial.println(scmd); - //give an ack - we need to be polite, right ? - web_interface->WebServer.send(200,"text/plain","Ok"); - } + if (web_interface->WebServer.hasArg("COM")) + { + String scmd; + //decode command + web_interface->urldecode(scmd,web_interface->WebServer.arg("COM").c_str()); + scmd.trim(); + //send command to serial + Serial.println(scmd); + //give an ack - we need to be polite, right ? + web_interface->WebServer.send(200,"text/plain","Ok"); + } } @@ -2648,8 +3148,11 @@ WEBINTERFACE_CLASS::WEBINTERFACE_CLASS (int port):WebServer(port) WebServer.on("/PRINTER",HTTP_ANY, handle_web_interface_printer); WebServer.on("/CMD",HTTP_ANY, handle_web_command); WebServer.on("/RESTART",HTTP_GET, handle_restart); + WebServer.on("/UPDATE",HTTP_ANY, handleUpdate); WebServer.on("/FILES", HTTP_ANY, handleFileList); WebServer.on("/SDFILES", HTTP_ANY, handleSDFileList); + WebServer.on("/LOGIN", HTTP_ANY, handle_login); + WebServer.on("/PASSWORD", HTTP_ANY, handle_password); WebServer.onFileUpload(handleFileUpload); //Captive portal Feature #ifdef CAPTIVE_PORTAL_FEATURE @@ -2676,6 +3179,9 @@ WEBINTERFACE_CLASS::WEBINTERFACE_CLASS (int port):WebServer(port) fileslist.setlenght(30);//12 for filename + space + size fileslist.setsize(70); // 70 files to limite to 2K fsUploadFile=(fs::File)0; + _head=NULL; + _nb_ip=0; + _upload_status=UPLOAD_STATUS_NONE; } //Destructor WEBINTERFACE_CLASS::~WEBINTERFACE_CLASS() @@ -2684,7 +3190,103 @@ WEBINTERFACE_CLASS::~WEBINTERFACE_CLASS() error_msg.clear(); status_msg.clear(); fileslist.clear(); + while (_head) + { + auth_ip * current = _head; + _head=_head->_next; + delete current; + } + _nb_ip=0; +} +//Session ID based on IP and time using 16 char +char * WEBINTERFACE_CLASS::create_session_ID(){ +static char sessionID[17]; +//reset SESSIONID +for (int i=0;i<17;i++)sessionID[i]='\0'; +//get time +uint32_t now = millis(); +//get remote IP +IPAddress remoteIP=WebServer.client().remoteIP(); +//generate SESSIONID +if (0>sprintf(sessionID,"%02X%02X%02X%02X%02X%02X%02X%02X",remoteIP[0],remoteIP[1],remoteIP[2],remoteIP[3],(uint8_t) ((now >> 0) & 0xff),(uint8_t) ((now >> 8) & 0xff),(uint8_t) ((now >> 16) & 0xff),(uint8_t) ((now >> 24) & 0xff)))strcpy(sessionID,"NONE"); +return sessionID; +} +//check authentification +bool WEBINTERFACE_CLASS::is_authenticated() +{ +#ifdef AUTHENTICATION_FEATURE + if (WebServer.hasHeader("Cookie")){ + String cookie = WebServer.header("Cookie"); + int pos = cookie.indexOf("ESPSESSIONID="); + if (pos!= -1) { + int pos2 = cookie.indexOf(";",pos); + String sessionID = cookie.substring(pos+strlen("ESPSESSIONID="),pos2); + IPAddress ip = WebServer.client().remoteIP(); + //check if cookie can be reset and clean table in same time + return ResetAuthIP(ip,sessionID.c_str()); + } + } + return false; +#else +return true; +#endif } +//add the information in the linked list if possible +bool WEBINTERFACE_CLASS::AddAuthIP(auth_ip * item) +{ + if (_nb_ip>MAX_AUTH_IP) return false; + item->_next=_head; + _head=item; + _nb_ip++; + return true; +} + +bool WEBINTERFACE_CLASS::ResetAuthIP(IPAddress ip,const char * sessionID) +{ + bool done=false; + auth_ip * current = _head; + auth_ip * previous = NULL; + //get time + uint32_t now = millis(); + while (current) + { + if ((millis()-current->last_time)>400000) + { + //remove + if (current==_head) + { + _head=current->_next; + _nb_ip--; + delete current; + current=_head; + } + else + { + previous->_next=current->_next; + _nb_ip--; + delete current; + current=previous->_next; + } + } + else + { + if (ip==current->ip) + { + if (strcmp(sessionID,current->sessionID)==0) + { + //reset time + current->last_time=millis(); + return true; + } + } + previous = current; + current=current->_next; + } + + + } + return done; +} WEBINTERFACE_CLASS * web_interface; diff --git a/esp8266/webinterface.h b/esp8266/webinterface.h index 2a6b34a0..93da1b9b 100644 --- a/esp8266/webinterface.h +++ b/esp8266/webinterface.h @@ -29,6 +29,13 @@ #define MAX_EXTRUDERS 4 +struct auth_ip{ + IPAddress ip; + char sessionID[17]; + uint32_t last_time; + auth_ip * _next; + }; + class WEBINTERFACE_CLASS { public: @@ -39,6 +46,7 @@ class WEBINTERFACE_CLASS void urldecode( String & dst, const char *src); bool isSSIDValid(const char * ssid); bool isPasswordValid(const char * password); + bool isAdminPasswordValid(const char * password); bool isHostnameValid(const char * hostname); bool isIPValid(const char * IP); String answer4M105; @@ -51,8 +59,15 @@ class WEBINTERFACE_CLASS STORESTRINGS_CLASS info_msg; STORESTRINGS_CLASS status_msg; bool restartmodule; + char * create_session_ID(); + bool is_authenticated(); + bool AddAuthIP(auth_ip * item); + bool ResetAuthIP(IPAddress ip,const char * sessionID); + uint8_t _upload_status; private: - + auth_ip * _head; + uint8_t _nb_ip; + }; extern WEBINTERFACE_CLASS * web_interface; diff --git a/page5.png b/page5.png index c3fe3b49..6db7bb5b 100644 Binary files a/page5.png and b/page5.png differ