From 7a2fc442b61cb08a4ffbcdee0912fd56941e7d46 Mon Sep 17 00:00:00 2001 From: Andy Green <andy@warmcat.com> Date: Thu, 19 May 2016 15:28:31 +0800 Subject: [PATCH] protocol generic sessions Signed-off-by: Andy Green <andy@warmcat.com> --- CMakeLists.txt | 122 ++- README.generic-sessions.md | 383 ++++++++ appveyor.yml | 5 +- lib/context.c | 47 +- lib/lejp-conf.c | 36 +- lib/libwebsockets.c | 68 +- lib/libwebsockets.h | 47 + lib/output.c | 49 +- lib/private-libwebsockets.h | 2 + lib/server.c | 197 +++- .../generic-sessions/assets/admin-login.html | 5 + .../generic-sessions/assets/failed-login.html | 3 + plugins/generic-sessions/assets/index.html | 35 + .../generic-sessions/assets/lwsgs-logo.png | Bin 0 -> 9729 bytes plugins/generic-sessions/assets/lwsgs.js | 476 +++++++++ plugins/generic-sessions/assets/md5.min.js | 2 + .../assets/post-forgot-fail.html | 5 + .../assets/post-forgot-ok.html | 6 + .../assets/post-register-fail.html | 1 + .../assets/post-register-ok.html | 27 + .../assets/post-verify-fail.html | 20 + .../assets/post-verify-ok.html | 25 + plugins/generic-sessions/assets/seats.jpg | Bin 0 -> 122754 bytes .../assets/sent-forgot-fail.html | 5 + .../assets/sent-forgot-ok.html | 4 + .../assets/successful-login.html | 4 + plugins/generic-sessions/handlers.c | 598 ++++++++++++ plugins/generic-sessions/private-lwsgs.h | 161 ++++ .../protocol_generic_sessions.c | 901 ++++++++++++++++++ plugins/generic-sessions/utils.c | 450 +++++++++ plugins/protocol_lws_status.c | 1 + plugins/protocol_post_demo.c | 9 +- test-server/test-server-v2.0.c | 6 + test-server/test-server.h | 2 +- 34 files changed, 3598 insertions(+), 104 deletions(-) create mode 100644 README.generic-sessions.md create mode 100644 plugins/generic-sessions/assets/admin-login.html create mode 100644 plugins/generic-sessions/assets/failed-login.html create mode 100644 plugins/generic-sessions/assets/index.html create mode 100644 plugins/generic-sessions/assets/lwsgs-logo.png create mode 100644 plugins/generic-sessions/assets/lwsgs.js create mode 100644 plugins/generic-sessions/assets/md5.min.js create mode 100644 plugins/generic-sessions/assets/post-forgot-fail.html create mode 100644 plugins/generic-sessions/assets/post-forgot-ok.html create mode 100644 plugins/generic-sessions/assets/post-register-fail.html create mode 100644 plugins/generic-sessions/assets/post-register-ok.html create mode 100644 plugins/generic-sessions/assets/post-verify-fail.html create mode 100644 plugins/generic-sessions/assets/post-verify-ok.html create mode 100644 plugins/generic-sessions/assets/seats.jpg create mode 100644 plugins/generic-sessions/assets/sent-forgot-fail.html create mode 100644 plugins/generic-sessions/assets/sent-forgot-ok.html create mode 100644 plugins/generic-sessions/assets/successful-login.html create mode 100644 plugins/generic-sessions/handlers.c create mode 100644 plugins/generic-sessions/private-lwsgs.h create mode 100644 plugins/generic-sessions/protocol_generic_sessions.c create mode 100644 plugins/generic-sessions/utils.c diff --git a/CMakeLists.txt b/CMakeLists.txt index eceddacd..33642ab1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,8 +99,9 @@ option(LWS_WITH_ACCESS_LOG "Support generating Apache-compatible access logs" OF option(LWS_WITH_SERVER_STATUS "Support json + jscript server monitoring" OFF) option(LWS_WITH_LEJP "With the Lightweight JSON Parser" OFF) option(LWS_WITH_LEJP_CONF "With LEJP configuration parser as used by lwsws" OFF) +option(LWS_WITH_GENERIC_SESSIONS "With the Generic Sessions plugin" OFF) +option(LWS_WITH_SQLITE3 "Require SQLITE3 support" OFF) option(LWS_WITH_SMTP "Provide SMTP support" OFF) -option(LWS_WITH_STATEFUL_URLDECODE "Provide stateful URLDECODE apis" OFF) if (LWS_WITH_LWSWS) message(STATUS "LWS_WITH_LWSWS --> Enabling LWS_WITH_PLUGINS and LWS_WITH_LIBUV") @@ -112,10 +113,6 @@ if (LWS_WITH_LWSWS) set(LWS_WITH_LEJP_CONF 1) endif() -if (LWS_WITH_PLUGINS) - set(LWS_WITH_STATEFUL_URLDECODE 1) -endif() - if (LWS_WITH_PLUGINS AND NOT LWS_WITH_LIBUV) message(STATUS "LWS_WITH_PLUGINS --> Enabling LWS_WITH_LIBUV") set(LWS_WITH_LIBUV 1) @@ -126,6 +123,16 @@ message(STATUS "LWS_WITH_SMTP --> Enabling LWS_WITH_LIBUV") set(LWS_WITH_LIBUV 1) endif() +if (LWS_WITH_GENERIC_SESSIONS) + set(LWS_WITH_SQLITE3 1) + set(LWS_WITH_SMTP 1) +endif() + +if (LWS_WITH_SMTP AND NOT LWS_WITH_LIBUV) +message(STATUS "LWS_WITH_SMTP --> Enabling LWS_WITH_LIBUV") + set(LWS_WITH_LIBUV 1) +endif() + if (DEFINED YOTTA_WEBSOCKETS_VERSION_STRING) set(LWS_WITH_SHARED OFF) @@ -198,6 +205,9 @@ set( CACHE PATH "Path to the libev library") set(LWS_LIBEV_INCLUDE_DIRS CACHE PATH "Path to the libev include directory") set(LWS_LIBUV_LIBRARIES CACHE PATH "Path to the libuv library") set(LWS_LIBUV_INCLUDE_DIRS CACHE PATH "Path to the libuv include directory") +set(LWS_SQLITE3_LIBRARIES CACHE PATH "Path to the libuv library") +set(LWS_SQLITE3_INCLUDE_DIRS CACHE PATH "Path to the libuv include directory") + if (NOT LWS_WITH_SSL) set(LWS_WITHOUT_BUILTIN_SHA1 OFF) @@ -285,6 +295,15 @@ if (LWS_WITH_LIBUV) endif() endif() +if (LWS_WITH_SQLITE3) + if ("${LWS_SQLITE3_LIBRARIES}" STREQUAL "" OR "${LWS_SQLITE3_INCLUDE_DIRS}" STREQUAL "") + else() + set(SQLITE3_LIBRARIES ${LWS_SQLITE3_LIBRARIES}) + set(SQLITE3_INCLUDE_DIRS ${LWS_SQLITE3_INCLUDE_DIRS}) + set(SQLITE3_FOUND 1) + endif() +endif() + # FIXME: This must be runtime-only option. # The base dir where the test-apps look for the SSL certs. @@ -612,6 +631,17 @@ endif() if (WIN32) set(WIN32_HELPERS_PATH win32port/win32helpers) include_directories(${WIN32_HELPERS_PATH}) + + if (WIN32) + list(APPEND SOURCES + ${WIN32_HELPERS_PATH}/gettimeofday.c + ) + + list(APPEND HDR_PRIVATE + ${WIN32_HELPERS_PATH}/gettimeofday.h + ) + endif(WIN32) + else() # Unix. if (NOT LWS_WITHOUT_DAEMONIZE) @@ -870,6 +900,22 @@ if (LWS_WITH_LIBUV) include_directories("${LIBUV_INCLUDE_DIRS}") list(APPEND LIB_LIST ${LIBUV_LIBRARIES}) endif() + +if (LWS_WITH_SQLITE3) + if (NOT SQLITE3_FOUND) + find_path(SQLITE3_INCLUDE_DIRS NAMES sqlite3.h) + find_library(SQLITE3_LIBRARIES NAMES sqlite3) + if(SQLITE3_INCLUDE_DIRS AND SQLITE3_LIBRARIES) + set(SQLITE3_FOUND 1) + endif() + endif() + message("sqlite3 include dir: ${SQLITE3_INCLUDE_DIRS}") + message("sqlite3 libraries: ${SQLITE3_LIBRARIES}") + include_directories("${SQLITE3_INCLUDE_DIRS}") + list(APPEND LIB_LIST ${SQLITE3_LIBRARIES}) +endif() + + if (LWS_WITH_HTTP_PROXY) find_library(LIBHUBBUB_LIBRARIES NAMES libhubbub) list(APPEND LIB_LIST ${LIBHUBBUB_LIBRARIES} ) @@ -1204,10 +1250,19 @@ if (NOT LWS_WITHOUT_TESTAPPS) if (LWS_WITH_PLUGINS AND LWS_WITH_SHARED) - macro(create_plugin PLUGIN_NAME MAIN_SRC) + macro(create_plugin PLUGIN_NAME MAIN_SRC S2 S3) set(PLUGIN_SRCS ${MAIN_SRC}) + if ("${S2}" STREQUAL "") + else() + list(APPEND PLUGIN_SRCS ${S2}) + endif() + if ("${S3}" STREQUAL "") + else() + list(APPEND PLUGIN_SRCS ${S3}) + endif() + if (WIN32) list(APPEND PLUGIN_SRCS ${WIN32_HELPERS_PATH}/getopt.c @@ -1239,27 +1294,42 @@ if (NOT LWS_WITHOUT_TESTAPPS) # OUTPUT_NAME ${PLUGIN_NAME}) list(APPEND PLUGINS_LIST ${PLUGIN_NAME}) + endmacro() create_plugin(protocol_dumb_increment - "plugins/protocol_dumb_increment.c") + "plugins/protocol_dumb_increment.c" "" "") create_plugin(protocol_lws_mirror - "plugins/protocol_lws_mirror.c") + "plugins/protocol_lws_mirror.c" "" "") create_plugin(protocol_lws_status - "plugins/protocol_lws_status.c") + "plugins/protocol_lws_status.c" "" "") create_plugin(protocol_post_demo - "plugins/protocol_post_demo.c") + "plugins/protocol_post_demo.c" "" "") if (LWS_WITH_SERVER_STATUS) create_plugin(protocol_lws_server_status - "plugins/protocol_lws_server_status.c") + "plugins/protocol_lws_server_status.c" "" "") endif() if (NOT LWS_WITHOUT_CLIENT) create_plugin(protocol_client_loopback_test - "plugins/protocol_client_loopback_test.c") + "plugins/protocol_client_loopback_test.c" "" "") endif(NOT LWS_WITHOUT_CLIENT) +if (LWS_WITH_GENERIC_SESSIONS) + create_plugin(protocol_generic_sessions + "plugins/generic-sessions/protocol_generic_sessions.c" + "plugins/generic-sessions/utils.c" + "plugins/generic-sessions/handlers.c") + + if (WIN32) + target_link_libraries(protocol_generic_sessions ${LWS_SQLITE3_LIBRARIES}) + else() + target_link_libraries(protocol_generic_sessions sqlite3 ) + endif(WIN32) +endif(LWS_WITH_GENERIC_SESSIONS) + + endif(LWS_WITH_PLUGINS AND LWS_WITH_SHARED) # @@ -1460,6 +1530,31 @@ if (LWS_WITH_SERVER_STATUS) DESTINATION share/libwebsockets-test-server/server-status COMPONENT examples) endif() +if (LWS_WITH_GENERIC_SESSIONS) + install(FILES + plugins/generic-sessions/assets/lwsgs-logo.png + plugins/generic-sessions/assets/seats.jpg + plugins/generic-sessions/assets/failed-login.html + plugins/generic-sessions/assets/lwsgs.js + plugins/generic-sessions/assets/post-register-fail.html + plugins/generic-sessions/assets/post-register-ok.html + plugins/generic-sessions/assets/post-verify-ok.html + plugins/generic-sessions/assets/post-verify-fail.html + plugins/generic-sessions/assets/sent-forgot-ok.html + plugins/generic-sessions/assets/sent-forgot-fail.html + plugins/generic-sessions/assets/post-forgot-ok.html + plugins/generic-sessions/assets/post-forgot-fail.html + plugins/generic-sessions/assets/index.html + DESTINATION share/libwebsockets-test-server/generic-sessions + COMPONENT examples) + install(FILES plugins/generic-sessions/assets/successful-login.html + DESTINATION share/libwebsockets-test-server/generic-sessions/needauth + COMPONENT examples) + install(FILES plugins/generic-sessions/assets/admin-login.html + DESTINATION share/libwebsockets-test-server/generic-sessions/needadmin + COMPONENT examples) +endif() + endif() # Install the LibwebsocketsConfig.cmake and LibwebsocketsConfigVersion.cmake @@ -1531,7 +1626,8 @@ message(" LWS_WITH_SERVER_STATUS = ${LWS_WITH_SERVER_STATUS}") message(" LWS_WITH_LEJP = ${LWS_WITH_LEJP}") message(" LWS_WITH_LEJP_CONF = ${LWS_WITH_LEJP_CONF}") message(" LWS_WITH_SMTP = ${LWS_WITH_SMTP}") -message(" LWS_WITH_STATEFUL_URLDECODE = ${LWS_WITH_STATEFUL_URLDECODE}") +message(" LWS_WITH_GENERIC_SESSIONS = ${LWS_WITH_GENERIC_SESSIONS}") + message("---------------------------------------------------------------------") diff --git a/README.generic-sessions.md b/README.generic-sessions.md new file mode 100644 index 00000000..dab28734 --- /dev/null +++ b/README.generic-sessions.md @@ -0,0 +1,383 @@ +Generic Sessions Plugin +----------------------- + +Enabling for build +------------------ + +Enable at CMake with -DLWS_WITH_GENERIC_SESSIONS=1 + +This also needs sqlite3 (libsqlite3-dev or similar package) + + +Introduction +------------ + +The generic-sessions protocol plugin provides cookie-based login +authentication for lws web and ws connections. + +The plugin handles everything about generic account registration, +email verification, lost password, account deletion, and other generic account +management. + +Other code, in another eg, ws protocol handler, only needs very high-level +state information from generic-sessions, ie, which user the client is +authenticated as. Everything underneath is managed in generic-sessions. + + + - random 20-byte session id managed in a cookie + + - all information related to the session held at the server, nothing managed clientside + + - sqlite3 used at the server to manage active sessions and users + + - defaults to creating anonymous sessions with no user associated + + - admin account (with user-selectable username) is defined in config with a SHA-1 of the password; rest of the accounts are in sqlite3 + + - user account passwords stored as salted SHA-1 with additional confounder + only stored in the JSON config, not the database + + - login, logout, register account + email verification built-in with examples + + - in a mount, some file suffixes (ie, .js) can be associated with a protocol for the purposes of rewriting symbolnames. These are read-only copies of logged-in server state. + + - When your page fetches .js or other rewritten files from that mount, "$lwsgs_user" and so on are rewritten on the fly using chunked transfer encoding + + - Eliminates server-side scripting with a few rewritten symbols and + javascript on client side + + - 32-bit bitfield for authentication sectoring, mounts can provide a mask on the loggin-in session's associated server-side bitfield that must be set for access. + + - No code (just config) required for, eg, private URL namespace that requires login to access. + + +Integration to HTML +------------------- + +Only three steps are needed to integrate lwsgs in your HTML. + +1) lwsgs HTML UI is bundled with the javascript it uses in `lwsgs.js`, so +import that script file in your head section + +2) define an empty div of id "lwsgs" somewhere + +3) Call lwsgs_initial() in your page + +That's it. An example is below + + +``` +<html> + <head> + <script src="lwsgs.js"></script> + <style> + .body { font-size: 12 } + .gstitle { font-size: 18 } + </style> + </head> + <body style="background-image:url(seats.jpg)"> + <table style="width:100%;transition: max-height 2s;"> + <tr> + <td style="vertical-align:top;text-align:left;width=200px"> + <img src="lwsgs-logo.png"> + </td> + <td style="vertical-align:top;float:right"> + <div id=lwsgs style="text-align:right;background-color: rgba(255, 255, 255, 0.8);"></div> + </td> + </tr> + </table> + </form> + + <script>lwsgs_initial();</script> + + </body> +</html> +``` + +Overall Flow +------------ + +When the protocol is initialized, it gets per-vhost information from the config, such +as where the sqlite3 databases are to be stored. The admin username and sha-1 of the +admin password are also taken from here. + +In the mounts using protocol-generic-sessions, a cookie is maintained against any requests; if no cookie was active on the initial request a new session is +created with no attached user. + +So there should always be an active session after any transactions with the server. + +In the example html going to the mount /lwsgs loads a login / register page as the default. + +The <form> in the login page contains 'next url' hidden inputs that let the html 'program' where the form handler will go after a successful admin login, a successful user login and a failed login. + +After a successful login, the sqlite record at the server for the current session is updated to have the logged-in username associated with it. + + + +Configuration +------------- + +"auth-mask" defines the autorization sector bits that must be enabled on the session to gain access. + +"auth-mask" 0 is the default. + + - b0 is set if you are logged in as a user at all. + - b1 is set if you are logged in with the user configured to be admin + - b2 is set if the account has been verified (the account configured for admin is always verified) + +``` + { + # things in here can always be served + "mountpoint": "/lwsgs", + "origin": "file:///usr/share/libwebsockets-test-server/generic-sessions", + "origin": "callback://protocol-lws-messageboard", + "default": "generic-sessions-login-example.html", + "auth-mask": "0", + "interpret": { + ".js": "protocol-lws-messageboard" + } + }, { + # things in here can only be served if logged in as a user + "mountpoint": "/lwsgs/needauth", + "origin": "file:///usr/share/libwebsockets-test-server/generic-sessions/needauth", + "origin": "callback://protocol-lws-messageboard", + "default": "generic-sessions-login-example.html", + "auth-mask": "5", # logged in as a verified user + "interpret": { + ".js": "protocol-lws-messageboard" + } + }, { + # things in here can only be served if logged in as admin + "mountpoint": "/lwsgs/needadmin", + "origin": "file:///usr/share/libwebsockets-test-server/generic-sessions/needadmin", + "origin": "callback://protocol-lws-messageboard", + "default": "generic-sessions-login-example.html", + "auth-mask": "7", # b2 = verified (by email / or admin), b1 = admin, b0 = logged in with any user name + "interpret": { + ".js": "protocol-lws-messageboard" + } + } +``` + +Note that the name of the real application protocol that uses generic-sessions +is used, not generic-sessions itself. + +The vhost configures the storage dir, admin credentials and session cookie lifetimes: + +``` + "ws-protocols": [{ + "protocol-generic-sessions": { + "status": "ok", + "admin-user": "admin", + +# create the pw hash like this (for the example pw, "jipdocesExunt" ) +# $ echo -n "jipdocesExunt" | sha1sum +# 046ce9a9cca769e85798133be06ef30c9c0122c9 - +# +# Obviously ** change this password hash to a secret one before deploying ** +# + "admin-password-sha1": "046ce9a9cca769e85798133be06ef30c9c0122c9", + "session-db": "/var/www/sessions/lws.sqlite3", + "timeout-idle-secs": "600", + "timeout-anon-idle-secs": "1200", + "timeout-absolute-secs": "6000", +# the confounder is part of the salted password hashes. If this config +# file is in a 0700 root:root dir, an attacker with apache credentials +# will have to get the confounder out of the process image to even try +# to guess the password hashes. + "confounder": "Change to <=31 chars of junk", + + "email-from": "noreply@example.com", + "email-smtp-ip": "127.0.0.1", + "email-expire": "3600", + "email-helo": "myhost.com", + "email-contact-person": "Set Me <real-person@email.com>", + "email-confirm-url-base": "http://localhost:7681/lwsgs" + } +``` + +The email- related settings control generation of automatic emails for +registration and forgotten password. + + - `email-from`: The email address automatic emails are sent from + + - `email-smtp-ip`: Normally 127.0.0.1, if you have a suitable server on port + 25 on your lan you can use this instead here. + + - `email-expire`: Seconds that links sent in email will work before being + deleted + + - `email-helo`: HELO to use when communicating with your SMTP server + + - `email-contact-person`: mentioned in the automatic emails as a human who can + answer questions + + - `email-confirm-url-base`: the URL to start links with in the emails, so the + recipient can get back to the web server + +The real protocol that makes use of generic-sessions must also be listed and +any configuration it needs given + +``` + "protocol-lws-messageboard": { + "status": "ok", + "message-db": "/var/www/sessions/messageboard.sqlite3" + }, +``` +Notice the real application uses his own sqlite db, no details about how +generic-sessions works or how it stores data are available to it. + + +Password Confounder +------------------- + +You can also define a per-vhost confounder shown in the example above, used +when aggregating the password with the salt when it is hashed. Any attacker +will also need to get the confounder along with the database, which you can +make harder by making the config dir only eneterable / readable by root. + + +Preparing the db directory +-------------------------- + +You will have to prepare the db directory so it's suitable for the lwsws user to use, +that usually means apache, eg + +``` +# mkdir -p /var/www/sessions +# chown root:apache /var/www/sessions +# chmod 770 /var/www/sessions +``` + +Email configuration +------------------- + +lwsgs will can send emails by talking to an SMTP server on localhost:25. That +will usually be sendmail or postfix, you should confirm that works first by +itself using the `mail` application to send on it. + +lwsgs has been tested on stock Fedora sendmail and postfix. + + +Integration with another protocol +--------------------------------- + +lwsgs is designed to provide sessions and accounts in a standalone and generic way. + +But it's not useful by itself, there will always be the actual application who wants +to make use of generic-sessions features. + +The basic approach is the 'real' protocol handler (usually a plugin itself) +subclasses the generic-sessions plugin and calls through to it by default. + +The "real" protocol handler entirely deals with ws-related stuff itself, since +generic-sessions does not use ws. But for + + - LWS_CALLBACK_HTTP + - LWS_CALLBACK_HTTP_BODY + - LWS_CALLBACK_HTTP_BODY_COMPLETION + - LWS_CALLBACK_HTTP_DROP_PROTOCOL + +the "real" protocol handler checks if it recognizes the activity (eg, his own +POST form URL) and if not, passes stuff through to the generic-sessions protocol callback to handle it. To simplify matters the real protocol can just pass +through any unhandled messages to generic-sessions. + +The "real" protocol can get a pointer to generic-sessions protocol on the +same vhost using + +``` + vhd->gsp = lws_vhost_name_to_protocol(vhd->vh, "protocol-generic-sessions"); +``` + +The "real" protocol must also arrange generic-sessions per_session_data in his +own per-session allocation. To allow keeping generic-sessions opaque, the +real protocol must allocate that space at runtime, using the pss size +the generic-sessions protocol struct exposes + +``` +struct per_session_data__myapp { + void *pss_gs; +... + + pss->pss_gs = malloc(vhd->gsp->per_session_data_size); +``` + +The allocation reserved for generic-sessions is then used as user_space when +the real protocol calls through to the generic-sessions callback + +``` + vhd->gsp->callback(wsi, reason, &pss->pss_gs, in, len); +``` + +In that way the "real" protocol can subclass generic-sessions functionality. + + +To ease management of these secondary allocations, there are callbacks that +occur when a wsi binds to a protocol and when the binding is dropped. These +should be used to malloc and free and kind of per-connection +secondary allocations. + + +``` + case LWS_CALLBACK_HTTP_BIND_PROTOCOL: + if (!pss || pss->pss_gs) + break; + + pss->pss_gs = malloc(vhd->gsp->per_session_data_size); + if (!pss->pss_gs) + return -1; + + memset(pss->pss_gs, 0, vhd->gsp->per_session_data_size); + break; + + case LWS_CALLBACK_HTTP_DROP_PROTOCOL: + if (vhd->gsp->callback(wsi, reason, pss ? pss->pss_gs : NULL, in, len)) + return -1; + + if (pss->pss_gs) { + free(pss->pss_gs); + pss->pss_gs = NULL; + } + break; +``` + + +Getting session-specific information from another protocol +---------------------------------------------------------- + +At least at the time when someone tries to upgrade an http(s) connection to +ws(s) with your real protocol, it is necessary to confirm the cookie the http(s) +connection has with generic-sessions and find out his username and other info. + +Generic sessions lets another protocol check it again by calling his callback, +and lws itself provides a generic session info struct to pass the related data + +``` +struct lws_session_info { + char username[32]; + char email[100]; + char ip[72]; + unsigned int mask; + char session[42]; +}; +``` + +``` + struct lws_session_info sinfo; + ... + vhd->gsp->callback(wsi, LWS_CALLBACK_SESSION_INFO, + &pss->pss_gs, &sinfo, 0); +``` + +After the call to generic-sessions, the results can be + + - all the strings will be zero-length and .mask zero, there is no usable cookie + + - only .ip and .session are set: the cookie is OK but no user logged in + + - all the strings contain information about the logged-in user + +the real protocol can use this to reject attempts to open ws connections from +http connections that are not authenticated; afterwards there's no need to +check the ws connection auth status again. + diff --git a/appveyor.yml b/appveyor.yml index b5cb9afc..4bbc0f1a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ environment: matrix: - LWS_METHOD: lwsws - CMAKE_ARGS: -DLWS_WITH_LWSWS=1 -DLIBUV_INCLUDE_DIRS=C:\assets\libuv\include -DLIBUV_LIBRARIES=C:\assets\libuv\libuv.lib + CMAKE_ARGS: -DLWS_WITH_LWSWS=1 -DSQLITE3_INCLUDE_DIRS=C:\assets\sqlite3 -DSQLITE3_LIBRARIES=C:\assets\sqlite3\sqlite3.lib -DLIBUV_INCLUDE_DIRS=C:\assets\libuv\include -DLIBUV_LIBRARIES=C:\assets\libuv\libuv.lib - LWS_METHOD: default @@ -26,6 +26,9 @@ install: - Win32OpenSSL-1_0_2h.exe /silent /verysilent /sp- /suppressmsgboxes - appveyor DownloadFile https://libwebsockets.org:444/nsis-3.0rc1-setup.exe - cmd /c start /wait nsis-3.0rc1-setup.exe /S /D=C:\nsis + - appveyor DownloadFile https://libwebsockets.org:444/sqlite-dll-win32-x86-3130000.zip + - mkdir c:\assets\sqlite3 + - 7z x -oc:\assets\sqlite3 sqlite-dll-win32-x86-3130000.zip - SET PATH=C:\Program Files\NSIS\;C:\Program Files (x86)\NSIS\;c:\nsis;%PATH% build: diff --git a/lib/context.c b/lib/context.c index 0d3a41c1..d3c22d6b 100644 --- a/lib/context.c +++ b/lib/context.c @@ -67,8 +67,15 @@ lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, const struct lws_protocols while (n < vhost->count_protocols && &vhost->protocols[n] != prot) n++; - if (n == vhost->count_protocols) - return NULL; + if (n == vhost->count_protocols) { + n = 0; + while (n < vhost->count_protocols && + strcmp(vhost->protocols[n].name, prot->name)) + n++; + + if (n == vhost->count_protocols) + return NULL; + } vhost->protocol_vh_privs[n] = lws_zalloc(size); return vhost->protocol_vh_privs[n]; @@ -86,8 +93,15 @@ lws_protocol_vh_priv_get(struct lws_vhost *vhost, const struct lws_protocols *pr n++; if (n == vhost->count_protocols) { - lwsl_err("%s: unknown protocol %p\n", __func__, prot); - return NULL; + n = 0; + while (n < vhost->count_protocols && + strcmp(vhost->protocols[n].name, prot->name)) + n++; + + if (n == vhost->count_protocols) { + lwsl_err("%s: unknown protocol %p\n", __func__, prot); + return NULL; + } } return vhost->protocol_vh_privs[n]; @@ -165,15 +179,17 @@ lws_protocol_init(struct lws_context *context) * NOTE the wsi is all zeros except for the context, vh and * protocol ptrs so lws_get_context(wsi) etc can work */ - vh->protocols[n].callback(&wsi, + if (vh->protocols[n].callback(&wsi, LWS_CALLBACK_PROTOCOL_INIT, NULL, - (void *)pvo, 0); + (void *)pvo, 0)) + return 1; } vh = vh->vhost_next; } context->protocol_init_done = 1; + lws_finalize_startup(context); return 0; } @@ -287,12 +303,14 @@ lws_create_vhost(struct lws_context *context, struct lws_vhost *vh = lws_zalloc(sizeof(*vh)), **vh1 = &context->vhost_list; const struct lws_http_mount *mounts; + const struct lws_protocol_vhost_options *pvo; #ifdef LWS_WITH_PLUGINS struct lws_plugin *plugin = context->plugin_list; struct lws_protocols *lwsp; - int m, n, f = !info->pvo; + int m, f = !info->pvo; #endif char *p; + int n; if (!vh) return NULL; @@ -381,6 +399,21 @@ lws_create_vhost(struct lws_context *context, lwsl_notice(" mounting %s%s to %s\n", mount_protocols[mounts->origin_protocol], mounts->origin, mounts->mountpoint); + + /* convert interpreter protocol names to pointers */ + pvo = mounts->interpret; + while (pvo) { + for (n = 0; n < vh->count_protocols; n++) + if (!strcmp(pvo->value, vh->protocols[n].name)) { + ((struct lws_protocol_vhost_options *)pvo)->value = + (const char *)(long)n; + break; + } + if (n == vh->count_protocols) + lwsl_err("ignoring unknown interpret protocol %s\n", pvo->value); + pvo = pvo->next; + } + mounts = mounts->mount_next; } diff --git a/lib/lejp-conf.c b/lib/lejp-conf.c index 1d529a1f..91429a54 100644 --- a/lib/lejp-conf.c +++ b/lib/lejp-conf.c @@ -59,7 +59,9 @@ static const char * const paths_vhosts[] = { "vhosts[].access-log", "vhosts[].mounts[].mountpoint", "vhosts[].mounts[].origin", + "vhosts[].mounts[].protocol", "vhosts[].mounts[].default", + "vhosts[].mounts[].auth-mask", "vhosts[].mounts[].cgi-timeout", "vhosts[].mounts[].cgi-env[].*", "vhosts[].mounts[].cache-max-age", @@ -67,6 +69,7 @@ static const char * const paths_vhosts[] = { "vhosts[].mounts[].cache-revalidate", "vhosts[].mounts[].cache-intermediaries", "vhosts[].mounts[].extra-mimetypes.*", + "vhosts[].mounts[].interpret.*", "vhosts[].ws-protocols[].*.*", "vhosts[].ws-protocols[].*", "vhosts[].ws-protocols[]", @@ -94,7 +97,9 @@ enum lejp_vhost_paths { LEJPVP_ACCESS_LOG, LEJPVP_MOUNTPOINT, LEJPVP_ORIGIN, + LEJPVP_MOUNT_PROTOCOL, LEJPVP_DEFAULT, + LEJPVP_DEFAULT_AUTH_MASK, LEJPVP_CGI_TIMEOUT, LEJPVP_CGI_ENV, LEJPVP_MOUNT_CACHE_MAX_AGE, @@ -102,6 +107,7 @@ enum lejp_vhost_paths { LEJPVP_MOUNT_CACHE_REVALIDATE, LEJPVP_MOUNT_CACHE_INTERMEDIARIES, LEJPVP_MOUNT_EXTRA_MIMETYPES, + LEJPVP_MOUNT_INTERPRET, LEJPVP_PROTOCOL_NAME_OPT, LEJPVP_PROTOCOL_NAME, LEJPVP_PROTOCOL, @@ -154,6 +160,7 @@ struct jpargs { struct lws_protocol_vhost_options *pvo; struct lws_protocol_vhost_options *pvo_em; + struct lws_protocol_vhost_options *pvo_int; struct lws_http_mount m; const char **plugin_dirs; int count_plugin_dirs; @@ -363,8 +370,10 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) for (n = 0; n < ARRAY_SIZE(mount_protocols); n++) if (!strncmp(a->m.origin, mount_protocols[n], strlen(mount_protocols[n]))) { + lwsl_err("----%s\n", a->m.origin); m->origin_protocol = n; - m->origin = a->m.origin + strlen(mount_protocols[n]); + m->origin = a->m.origin + + strlen(mount_protocols[n]); break; } @@ -424,11 +433,18 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) a->m.mountpoint_len = (unsigned char)strlen(ctx->buf); break; case LEJPVP_ORIGIN: - a->m.origin = a->p; + if (!strncmp(ctx->buf, "callback://", 11)) + a->m.protocol = a->p + 11; + + if (!a->m.origin) + a->m.origin = a->p; break; case LEJPVP_DEFAULT: a->m.def = a->p; break; + case LEJPVP_DEFAULT_AUTH_MASK: + a->m.auth_mask = atoi(ctx->buf); + return 0; case LEJPVP_MOUNT_CACHE_MAX_AGE: a->m.cache_max_age = atoi(ctx->buf); return 0; @@ -505,6 +521,22 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) a->pvo_em->options = NULL; break; + case LEJPVP_MOUNT_INTERPRET: + a->pvo_int = lwsws_align(a); + a->p += sizeof(*a->pvo_int); + + n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p); + /* ie, enable this protocol, no options yet */ + a->pvo_int->next = a->m.interpret; + a->m.interpret = a->pvo_int; + a->pvo_int->name = a->p; + lwsl_notice(" adding interpret %s -> %s\n", a->p, + ctx->buf); + a->p += n; + a->pvo_int->value = a->p; + a->pvo_int->options = NULL; + break; + case LEJPVP_ENABLE_CLIENT_SSL: a->enable_client_ssl = arg_to_bool(ctx->buf); return 0; diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index cac78db5..f8d08fc8 100755 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -1735,39 +1735,6 @@ lws_socket_bind(struct lws_vhost *vhost, int sockfd, int port, static const char *hex = "0123456789ABCDEF"; -static int -urlencode(const char *in, int inlen, char *out, int outlen) -{ - char *start = out, *end = out + outlen; - - while (inlen-- && out < end - 4) { - if ((*in >= 'A' && *in <= 'Z') || - (*in >= 'a' && *in <= 'z') || - (*in >= '0' && *in <= '9') || - *in == '-' || - *in == '_' || - *in == '.' || - *in == '~') { - *out++ = *in++; - continue; - } - if (*in == ' ') { - *out++ = '+'; - in++; - continue; - } - *out++ = '%'; - *out++ = hex[(*in) >> 4]; - *out++ = hex[(*in++) & 15]; - } - *out = '\0'; - - if (out >= end - 4) - return -1; - - return out - start; -} - /** * lws_sql_purify() - like strncpy but with escaping for sql quotes * @@ -1787,7 +1754,7 @@ lws_sql_purify(char *escaped, const char *string, int len) while (*p && len-- > 2) { if (*p == '\'') { - *q++ = '\\'; + *q++ = '\''; *q++ = '\''; len --; p++; @@ -1964,6 +1931,39 @@ lws_is_cgi(struct lws *wsi) { #ifdef LWS_WITH_CGI +static int +urlencode(const char *in, int inlen, char *out, int outlen) +{ + char *start = out, *end = out + outlen; + + while (inlen-- && out < end - 4) { + if ((*in >= 'A' && *in <= 'Z') || + (*in >= 'a' && *in <= 'z') || + (*in >= '0' && *in <= '9') || + *in == '-' || + *in == '_' || + *in == '.' || + *in == '~') { + *out++ = *in++; + continue; + } + if (*in == ' ') { + *out++ = '+'; + in++; + continue; + } + *out++ = '%'; + *out++ = hex[(*in) >> 4]; + *out++ = hex[(*in++) & 15]; + } + *out = '\0'; + + if (out >= end - 4) + return -1; + + return out - start; +} + static struct lws * lws_create_basic_wsi(struct lws_context *context, int tsi) { diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index b6ec9bbc..11f6fffc 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -476,6 +476,12 @@ enum lws_callback_reasons { LWS_CALLBACK_COMPLETED_CLIENT_HTTP = 47, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ = 48, LWS_CALLBACK_HTTP_DROP_PROTOCOL = 49, + LWS_CALLBACK_CHECK_ACCESS_RIGHTS = 50, + LWS_CALLBACK_PROCESS_HTML = 51, + LWS_CALLBACK_ADD_HEADERS = 52, + LWS_CALLBACK_SESSION_INFO = 53, + + LWS_CALLBACK_GS_EVENT = 54, /****** add new things just above ---^ ******/ @@ -1338,6 +1344,14 @@ struct lws_protocols { * This is part of the ABI, don't needlessly break compatibility */ }; +struct lws_session_info { + char username[32]; + char email[100]; + char ip[72]; + unsigned int mask; + char session[42]; +}; + struct lws_process_html_args { char *p; int len; @@ -1362,6 +1376,33 @@ LWS_VISIBLE LWS_EXTERN int lws_chunked_html_process(struct lws_process_html_args *args, struct lws_process_html_state *s); +/* generic-sessions public api */ + +#define LWSGS_EMAIL_CONTENT_SIZE 16384 + +/* SHA-1 binary and hexified versions */ +typedef struct { unsigned char bin[20]; } lwsgw_hash_bin; +typedef struct { char id[41]; } lwsgw_hash; + +enum lwsgs_auth_bits { + LWSGS_AUTH_LOGGED_IN = 1, + LWSGS_AUTH_ADMIN = 2, + LWSGS_AUTH_VERIFIED = 4, + LWSGS_AUTH_FORGOT_FLOW = 8, +}; + +enum lws_gs_event { + LWSGSE_CREATED, + LWSGSE_DELETED +}; + +struct lws_gs_event_args { + enum lws_gs_event event; + const char *username; + const char *email; +}; + + enum lws_ext_options_types { EXTARG_NONE, EXTARG_DEC, @@ -1470,12 +1511,15 @@ struct lws_http_mount { const char *mountpoint; /* mountpoint in http pathspace, eg, "/" */ const char *origin; /* path to be mounted, eg, "/var/www/warmcat.com" */ const char *def; /* default target, eg, "index.html" */ + const char *protocol; /* "protocol-name" to handle mount */ const struct lws_protocol_vhost_options *cgienv; const struct lws_protocol_vhost_options *extra_mimetypes; + const struct lws_protocol_vhost_options *interpret; int cgi_timeout; int cache_max_age; + unsigned int auth_mask; unsigned int cache_reusable:1; unsigned int cache_revalidate:1; @@ -1737,6 +1781,9 @@ LWS_VISIBLE LWS_EXTERN int lws_init_vhost_client_ssl(const struct lws_context_creation_info *info, struct lws_vhost *vhost); +LWS_VISIBLE LWS_EXTERN const struct lws_protocols * +lws_vhost_name_to_protocol(struct lws_vhost *vh, const char *name); + /* deprecated: use lws_get_vhost() */ LWS_VISIBLE LWS_EXTERN struct lws_vhost * lws_vhost_get(struct lws *wsi) LWS_WARN_DEPRECATED; diff --git a/lib/output.c b/lib/output.c index f14a4a3f..be0c73c5 100644 --- a/lib/output.c +++ b/lib/output.c @@ -568,7 +568,9 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) { struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - unsigned long amount; + struct lws_process_html_args args; + unsigned long amount, poss; + unsigned char *p = pt->serv_buf; int n, m; while (wsi->http2_substream || !lws_send_pipe_choked(wsi)) { @@ -585,31 +587,58 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) if (wsi->u.http.filepos == wsi->u.http.filelen) goto all_sent; - if (lws_plat_file_read(wsi, wsi->u.http.fd, &amount, - pt->serv_buf, - context->pt_serv_buf_size) < 0) + poss = context->pt_serv_buf_size; + + if (wsi->sending_chunked) { + /* we need to drop the chunk size in here */ + p += 10; + /* allow for the chunk to grow by 128 in translation */ + poss -= 10 + 128; + } + + if (lws_plat_file_read(wsi, wsi->u.http.fd, &amount, p, poss) < 0) return -1; /* caller will close */ n = (int)amount; if (n) { lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, context->timeout_secs); - wsi->u.http.filepos += n; - m = lws_write(wsi, pt->serv_buf, n, + + if (wsi->sending_chunked) { + args.p = (char *)p; + args.len = n; + args.max_len = poss + 128; + args.final = wsi->u.http.filepos + n == + wsi->u.http.filelen; + if (user_callback_handle_rxflow( + wsi->vhost->protocols[(int)wsi->protocol_interpret_idx].callback, wsi, + LWS_CALLBACK_PROCESS_HTML, + wsi->user_space, &args, 0) < 0) + return -1; + n = args.len; + p = (unsigned char *)args.p; + } + + m = lws_write(wsi, p, n, wsi->u.http.filepos == wsi->u.http.filelen ? - LWS_WRITE_HTTP_FINAL : LWS_WRITE_HTTP); + LWS_WRITE_HTTP_FINAL : + LWS_WRITE_HTTP + ); if (m < 0) return -1; - if (m != n) + wsi->u.http.filepos += amount; + if (m != n) { /* adjust for what was not sent */ if (lws_plat_file_seek_cur(wsi, wsi->u.http.fd, m - n) == (unsigned long)-1) return -1; + } } all_sent: - if (!wsi->trunc_len && wsi->u.http.filepos == wsi->u.http.filelen) { + if (!wsi->trunc_len && + wsi->u.http.filepos == wsi->u.http.filelen) { wsi->state = LWSS_HTTP; /* we might be in keepalive, so close it off here */ lws_plat_file_close(wsi, wsi->u.http.fd); @@ -622,11 +651,11 @@ all_sent: LWS_CALLBACK_HTTP_FILE_COMPLETION, wsi->user_space, NULL, 0) < 0) return -1; + return 1; /* >0 indicates completed */ } } - lwsl_info("choked before able to send whole file (post)\n"); lws_callback_on_writable(wsi); return 0; /* indicates further processing must be done */ diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 5ce64131..12719fcb 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -1257,6 +1257,7 @@ struct lws { unsigned int cache_revalidate:1; unsigned int cache_intermediaries:1; unsigned int favoured_pollin:1; + unsigned int sending_chunked:1; #ifdef LWS_WITH_ACCESS_LOG unsigned int access_log_pending:1; #endif @@ -1295,6 +1296,7 @@ struct lws { char pending_timeout; /* enum pending_timeout */ char pps; /* enum lws_pending_protocol_send */ char tsi; /* thread service index we belong to */ + char protocol_interpret_idx; #ifdef LWS_WITH_CGI char cgi_channel; /* which of stdin/out/err */ char hdr_state; diff --git a/lib/server.c b/lib/server.c index 8aafdbf5..d2398e31 100644 --- a/lib/server.c +++ b/lib/server.c @@ -206,6 +206,27 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername) return NULL; } +/** + * lws_vhost_name_to_protocol() - get vhost's protocol object from its name + * + * @vh: vhost to search + * @name: protocol name + * + * Returns NULL or a pointer to the vhost's protocol of the requested name + */ + +LWS_VISIBLE LWS_EXTERN const struct lws_protocols * +lws_vhost_name_to_protocol(struct lws_vhost *vh, const char *name) +{ + int n; + + for (n = 0; n < vh->count_protocols; n++) + if (!strcmp(name, vh->protocols[n].name)) + return &vh->protocols[n]; + + return NULL; +} + static const char * get_mimetype(const char *file, const struct lws_http_mount *m) { @@ -271,11 +292,13 @@ static int lws_http_serve(struct lws *wsi, char *uri, const char *origin, const struct lws_http_mount *m) { + const struct lws_protocol_vhost_options *pvo = m->interpret; + struct lws_process_html_args args; const char *mimetype; #ifndef _WIN32_WCE struct stat st; #endif - char path[256], sym[256]; + char path[256], sym[512]; unsigned char *p = (unsigned char *)sym + 32 + LWS_PRE, *start = p; unsigned char *end = p + sizeof(sym) - 32 - LWS_PRE; #if !defined(WIN32) @@ -342,7 +365,7 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, return -1; n = lws_write(wsi, start, p - start, - LWS_WRITE_HTTP_HEADERS); + LWS_WRITE_HTTP_HEADERS); if (n != (p - start)) { lwsl_err("_write returned %d from %d\n", n, p - start); return -1; @@ -363,6 +386,43 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, goto bail; } + wsi->sending_chunked = 0; + + /* + * check if this is in the list of file suffixes to be interpreted by + * a protocol + */ + while (pvo) { + n = strlen(path); + if (n > (int)strlen(pvo->name) && + !strcmp(&path[n - strlen(pvo->name)], pvo->name)) { + wsi->sending_chunked = 1; + wsi->protocol_interpret_idx = (char)(long)pvo->value; + lwsl_info("want %s interpreted by %s\n", path, + wsi->vhost->protocols[(int)(long)(pvo->value)].name); + wsi->protocol = &wsi->vhost->protocols[(int)(long)(pvo->value)]; + if (lws_ensure_user_space(wsi)) + return -1; + break; + } + pvo = pvo->next; + } + + if (m->protocol) { + const struct lws_protocols *pp = lws_vhost_name_to_protocol( + wsi->vhost, m->protocol); + + wsi->protocol = pp; + if (lws_ensure_user_space(wsi)) + return -1; + args.p = (char *)p; + args.max_len = end - p; + if (pp->callback(wsi, LWS_CALLBACK_ADD_HEADERS, + wsi->user_space, &args, 0)) + return -1; + p = (unsigned char *)args.p; + } + n = lws_serve_http_file(wsi, path, mimetype, (char *)start, p - start); if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) @@ -381,6 +441,7 @@ lws_http_action(struct lws *wsi) enum http_connection_type connection_type; enum http_version request_version; char content_length_str[32]; + struct lws_process_html_args args; const struct lws_http_mount *hm, *hit = NULL; unsigned int n, count = 0; char http_version_str[10]; @@ -409,6 +470,9 @@ lws_http_action(struct lws *wsi) #endif }; #endif + static const char * const oprot[] = { + "http://", "https://" + }; /* it's not websocket.... shall we accept it as http? */ @@ -614,7 +678,8 @@ lws_http_action(struct lws *wsi) ) { if (hm->origin_protocol == LWSMPRO_CALLBACK || ((hm->origin_protocol == LWSMPRO_CGI || - lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) && + lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) || + hm->protocol) && hm->mountpoint_len > best)) { best = hm->mountpoint_len; hit = hm; @@ -628,6 +693,38 @@ lws_http_action(struct lws *wsi) lwsl_debug("*** hit %d %d %s\n", hit->mountpoint_len, hit->origin_protocol , hit->origin); + if (hit->protocol) { + const struct lws_protocols *pp = lws_vhost_name_to_protocol( + wsi->vhost, hit->protocol); + + if (!pp) { + lwsl_err("unknown protocol %s\n", hit->protocol); + return 1; + } + + wsi->protocol = pp; + if (lws_ensure_user_space(wsi)) { + lwsl_err("Unable to allocate user space\n"); + return 1; + } + } + lwsl_info("wsi %s protocol '%s'\n", uri_ptr, wsi->protocol->name); + + args.p = uri_ptr; + args.len = uri_len; + args.max_len = hit->auth_mask; + args.final = 0; /* used to signal callback dealt with it */ + + n = wsi->protocol->callback(wsi, LWS_CALLBACK_CHECK_ACCESS_RIGHTS, + wsi->user_space, &args, 0); + if (n) { + lws_return_http_status(wsi, HTTP_STATUS_UNAUTHORIZED, + NULL); + goto bail_nuke_ah; + } + if (args.final) /* callback completely handled it well */ + return 0; + /* * if we have a mountpoint like https://xxx.com/yyy * there is an implied / at the end for our purposes since @@ -649,12 +746,12 @@ lws_http_action(struct lws *wsi) (*s != '/' || (hit->origin_protocol == LWSMPRO_REDIR_HTTP || hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) && - (hit->origin_protocol != LWSMPRO_CGI && hit->origin_protocol != LWSMPRO_CALLBACK)) { + (hit->origin_protocol != LWSMPRO_CGI && + hit->origin_protocol != LWSMPRO_CALLBACK //&& + //hit->protocol == NULL + )) { unsigned char *start = pt->serv_buf + LWS_PRE, *p = start, *end = p + 512; - static const char *oprot[] = { - "http://", "https://" - }; lwsl_debug("Doing 301 '%s' org %s\n", s, hit->origin); @@ -662,13 +759,14 @@ lws_http_action(struct lws *wsi) goto bail_nuke_ah; /* > at start indicates deal with by redirect */ - if (hit->origin_protocol & 4) + if (hit->origin_protocol == LWSMPRO_REDIR_HTTP || + hit->origin_protocol == LWSMPRO_REDIR_HTTPS) n = snprintf((char *)end, 256, "%s%s", oprot[hit->origin_protocol & 1], hit->origin); else n = snprintf((char *)end, 256, - "https://%s/%s/", + "%s%s%s/", oprot[lws_is_ssl(wsi)], lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST), uri_ptr); @@ -686,32 +784,36 @@ lws_http_action(struct lws *wsi) * For the duration of this http transaction, bind us to the * associated protocol */ - if (hit->origin_protocol == LWSMPRO_CALLBACK) { - - for (n = 0; n < (unsigned int)wsi->vhost->count_protocols; n++) - if (!strcmp(wsi->vhost->protocols[n].name, - hit->origin)) { - if (wsi->protocol != &wsi->vhost->protocols[n]) - if (!wsi->user_space_externally_allocated) - lws_free_set_NULL(wsi->user_space); - wsi->protocol = &wsi->vhost->protocols[n]; - if (lws_ensure_user_space(wsi)) { - lwsl_err("Unable to allocate user space\n"); - - return 1; + if (hit->origin_protocol == LWSMPRO_CALLBACK || + (hit->protocol && lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))) { + if (! hit->protocol) { + for (n = 0; n < (unsigned int)wsi->vhost->count_protocols; n++) + if (!strcmp(wsi->vhost->protocols[n].name, + hit->origin)) { + + if (wsi->protocol != &wsi->vhost->protocols[n]) + if (!wsi->user_space_externally_allocated) + lws_free_set_NULL(wsi->user_space); + wsi->protocol = &wsi->vhost->protocols[n]; + if (lws_ensure_user_space(wsi)) { + lwsl_err("Unable to allocate user space\n"); + + return 1; + } + break; } - break; - } - if (n == wsi->vhost->count_protocols) { - n = -1; - lwsl_err("Unable to find plugin '%s'\n", - hit->origin); + if (n == wsi->vhost->count_protocols) { + n = -1; + lwsl_err("Unable to find plugin '%s'\n", + hit->origin); + } } - n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP, - wsi->user_space, uri_ptr, uri_len); + wsi->user_space, + uri_ptr + hit->mountpoint_len, + uri_len - hit->mountpoint_len); goto after; } @@ -765,17 +867,35 @@ lws_http_action(struct lws *wsi) wsi->cache_revalidate = hit->cache_revalidate; wsi->cache_intermediaries = hit->cache_intermediaries; + n = lws_http_serve(wsi, s, hit->origin, hit); if (n) { /* * lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); */ - n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP, + if (hit->protocol) { + const struct lws_protocols *pp = lws_vhost_name_to_protocol( + wsi->vhost, hit->protocol); + + wsi->protocol = pp; + if (lws_ensure_user_space(wsi)) { + lwsl_err("Unable to allocate user space\n"); + return 1; + } + + n = pp->callback(wsi, LWS_CALLBACK_HTTP, + wsi->user_space, + uri_ptr + hit->mountpoint_len, + uri_len - hit->mountpoint_len); + } else + n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP, wsi->user_space, uri_ptr, uri_len); } } else { /* deferred cleanup and reset to protocols[0] */ + lwsl_notice("no hit\n"); + if (wsi->protocol != &wsi->vhost->protocols[0]) if (!wsi->user_space_externally_allocated) lws_free_set_NULL(wsi->user_space); @@ -1283,6 +1403,7 @@ lws_http_transaction_completed(struct lws *wsi) wsi->state = LWSS_HTTP; wsi->mode = LWSCM_HTTP_SERVING; wsi->u.http.content_length = 0; + wsi->u.http.content_remain = 0; wsi->hdr_parsing_completed = 0; #ifdef LWS_WITH_ACCESS_LOG wsi->access_log.sent = 0; @@ -1813,8 +1934,16 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, (unsigned char *)content_type, strlen(content_type), &p, end)) return -1; - if (lws_add_http_header_content_length(wsi, wsi->u.http.filelen, &p, end)) - return -1; + + if (!wsi->sending_chunked) { + if (lws_add_http_header_content_length(wsi, wsi->u.http.filelen, &p, end)) + return -1; + } else { + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING, + (unsigned char *)"chunked", + 7, &p, end)) + return -1; + } if (wsi->cache_secs && wsi->cache_reuse) { if (wsi->cache_revalidate) { @@ -2277,8 +2406,6 @@ lws_urldecode_s_destroy(struct lws_urldecode_stateful *s) { int ret = 0; - lwsl_notice("%s\n", __func__); - if (s->state != US_IDLE) ret = -1; diff --git a/plugins/generic-sessions/assets/admin-login.html b/plugins/generic-sessions/assets/admin-login.html new file mode 100644 index 00000000..113df9cd --- /dev/null +++ b/plugins/generic-sessions/assets/admin-login.html @@ -0,0 +1,5 @@ +<html> +This is an example destination that will appear after successful Admin login. + +This URL cannot be served if you're not logged in as admin. +</html> diff --git a/plugins/generic-sessions/assets/failed-login.html b/plugins/generic-sessions/assets/failed-login.html new file mode 100644 index 00000000..9ab065b5 --- /dev/null +++ b/plugins/generic-sessions/assets/failed-login.html @@ -0,0 +1,3 @@ +<html> +This is an example destination that will appear after a failed login +</html> diff --git a/plugins/generic-sessions/assets/index.html b/plugins/generic-sessions/assets/index.html new file mode 100644 index 00000000..ea970eec --- /dev/null +++ b/plugins/generic-sessions/assets/index.html @@ -0,0 +1,35 @@ +<html> + <head> + <script src="lwsgs.js"></script> + <style> + .body { font-size: 12 } + .gstitle { font-size: 18 } + </style> + </head> + <body style="background-image:url(seats.jpg)"> + <table style="width:100%;transition: max-height 2s;"> + <tr> + <td style="vertical-align:top;text-align:left;width=200px"> + <img src="lwsgs-logo.png"> + </td> + <td style="vertical-align:top;float:right"> + <div id=lwsgs style="text-align:right;background-color: rgba(255, 255, 255, 0.8);"></div> + </td> + </tr> + <tr><td colspan=2> + <div id="nolog" style="display:none"> + Register / Login to see the messages + </div> + <div id="logged" style="display:none"> + Logged in + </div> + </td></tr> + </table> + </form> + <script>lwsgs_initial(); + document.getElementById("nolog").style.display = !!lwsgs_user ? "none" : "inline"; + document.getElementById("logged").style.display = !lwsgs_user ? "none" : "inline"; + </script> + </body> +</html> + diff --git a/plugins/generic-sessions/assets/lwsgs-logo.png b/plugins/generic-sessions/assets/lwsgs-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..723a124431189c21c340de517bd9b82cb35374d8 GIT binary patch literal 9729 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 By;T4J literal 0 HcmV?d00001 diff --git a/plugins/generic-sessions/assets/lwsgs.js b/plugins/generic-sessions/assets/lwsgs.js new file mode 100644 index 00000000..5362c9a9 --- /dev/null +++ b/plugins/generic-sessions/assets/lwsgs.js @@ -0,0 +1,476 @@ +<!-- lwsgs rewrites the below $vars $v $v into the correct values on the fly --> + +var lwsgs_user = "$lwsgs_user"; +var lwsgs_auth = "$lwsgs_auth"; +var lwsgs_email = "$lwsgs_email"; + +var lwsgs_html = '\ + <div id="dlogin" style="display:none"> \ + <form action="lwsgs-login" method="post"> \ + <input type="hidden" name="admin" value="needadmin/admin-login.html"> \ + <input type="hidden" name="good" value="index.html"> \ + <input type="hidden" name="bad" value="failed-login.html"> \ + <input type="hidden" name="forgot-good" value="sent-forgot-ok.html"> \ + <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\ + <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\ + <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\ + <table style="vertical-align:top;text-align:right"\ + <tr>\ + <td>User Name\ + <input type="text" size="10" id="username" name="username" oninput="lwsgs_update()" onchange="lwsgs_update()"></td>\ + <td>Password\ + <input type="password" id="password" size="10" name="password" oninput="lwsgs_update()" onchange="lwsgs_update()"><div id="pw1"></div></td>\ + </tr><tr>\ + <td colspan="2" style="text-align:center"><input type="submit" id="login" name="login" value="Login" style="margin: 4px; padding: 2px; font-weight=bold;">\ + <input type="submit" id="forgot" name="forgot" value="Forgot password" style="margin: 2px; padding: 2px">\ + <input type="button" onclick="lwsgs_open_registration()" value="Sign up" style="margin: 2px; padding: 2px"></td>\ + </tr>\ + </table>\ + </form>\ + </div>\ +\ + <div id="dlogout" style="display:none;text-align:right">\ + <form action="lwsgs-logout" method="post" style="text-align:right">\ + <input type="hidden" name="good" value="index.html">\ + <table style="vertical-align:top;text-align:right">\ + <tr><td><span id=grav></span></td>\ + <td style="text-align:center"><table><tr><td style="text-align:center">\ + <a href="#" onclick="lwsgs_select_change(); event.preventDefault();">\ + <span id="curuser"></span></a></td></tr><tr>\ + <td style="text-align:center"><input type="submit" name="logout" value="Logout" style="margin: 2px; padding: 2px"></td>\ + </tr></table></td></tr>\ + </table>\ + </form></div>\ +\ + <div id="dregister" style="display:none">\ + <form action="lwsgs-login" method="post">\ + <input type="hidden" name="admin" value="needadmin/admin-login.html">\ + <input type="hidden" name="good" value="successful-login.html">\ + <input type="hidden" name="bad" value="failed-login.html">\ + <input type="hidden" name="reg-good" value="post-register-ok.html">\ + <input type="hidden" name="reg-bad" value="post-register-fail.html">\ + <input type="hidden" name="forgot-good" value="sent-forgot-ok.html">\ + <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\ + <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\ + <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\ + <table style="vertical-align:top;text-align:left">\ + <tr>\ + <td colspan=2 align=center>\ + <span id="curuser"></span>\ + <script>\ + if (lwsgs_user)\ + document.getElementById("curuser").innerHTML = "currently logged in as " + lwsgs_san(lwsgs_user) + "</br>";\ + </script>\ + <b>Please enter your details to register</b>:\ + </td>\ + </tr>\ + <tr><td align=right>\ + User Name:</td>\ + <td><input type="text" size="10" id="rusername" name="username" oninput="lwsgs_rupdate(); lwsgs_check_user();"> <span id=uchk></span></td>\ + </tr>\ + <tr>\ + <td align=right>Password:</td>\ + <td><input type="password" size="10" id="rpassword" name="password" oninput="lwsgs_rupdate()"> <span id="rpw1"></span></td>\ + </tr>\ + <tr>\ + </tr>\ + <tr>\ + <td align=right><span id="pw2">Password (again):</span></td>\ + <td><input type="password" size="10" id="password2" name="password2" oninput="lwsgs_rupdate()"> <span id="match"></span></td>\ + </tr>\ + <tr>\ + <td align=right>Email:</td>\ + <td><input type="email" size="10" id="email" name="email"\ + placeholder="me@example.com" oninput="lwsgs_rupdate(); lwsgs_check_email(\'email\')"> <span id=echk></span></td>\ + </tr>\ + <tr>\ + <td colspan=2 align=center>\ +<input type="submit" id="register" name="register" value="Register" style="margin: 2px; padding: 2px">\ +<input type="submit" id="rforgot" name="forgot" value="Forgot Password" style="margin: 2px; padding: 2px;display: none">\ +<input type="button" id="cancel" name="cancel" value="Cancel" style="margin: 2px; padding: 2px;" onclick="lwsgs_cancel_registration()">\ + </td>\ + </tr>\ + </table>\ + </form>\ + </div>\ + \ + <div id="dchange" style="display:none">\ + <form action="lwsgs-change" method="post">\ + <input type="hidden" id="cusername" name="username">\ + <input type="hidden" name="admin" value="needadmin/admin-login.html">\ + <input type="hidden" name="good" value="index.html">\ + <input type="hidden" name="bad" value="failed-login.html">\ + <input type="hidden" name="reg-good" value="post-register-ok.html">\ + <input type="hidden" name="reg-bad" value="post-register-fail.html">\ + <input type="hidden" name="forgot-good" value="sent-forgot-ok.html">\ + <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\ + <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\ + <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\ + <table style="vertical-align:top;text-align:left">\ + <tr>\ + <td colspan=2 align=center>\ + <span id="ccuruser"></span>\ + <script>\ + if (lwsgs_user)\ + document.getElementById("ccuruser").innerHTML =\ + "<span class=\"gstitle\">Login settings for " +\ + lwsgs_san(lwsgs_user) + "</span></br>";\ + </script>\ + <b>Please enter your details to change</b>:\ + </td>\ + </tr>\ + <tr><td align=right id="ccurpw_name">\ + Current Password:</td>\ + <td><input type="password" size="10" id="ccurpw" name="curpw"\ + oninput="lwsgs_cupdate();"> <span id=cuchk></span></td>\ + </tr>\ + <tr>\ + <td align=right>Password:</td>\ + <td><input type="password" size="10" id="cpassword" name="password"\ + oninput="lwsgs_cupdate()"> <span id="cpw1"></span></td>\ + </tr>\ + <tr>\ + <td align=right><span id="pw2">Password (again)</span></td>\ + <td><input type="password" size="10" id="cpassword2" name="password2"\ + oninput="lwsgs_cupdate()"> <span id="cmatch"></span></td>\ + </tr>\ + <!-- not supported yet\ + <tr>\ + <td align=right id="cemail_name">Email:</td>\ + <td><input type="email" size="10" id="cemail" name="email"\ + placeholder="?" oninput="lwsgs_cupdate(); lwsgs_check_email(\'cemail\')">\ + <span id=cechk></span></td>\ + </tr> -->\ + <tr>\ + <td colspan=2 align=center>\ + <input type="submit" id="change" name="change"\ + value="Change" style="margin: 6px; padding: 6px">\ + <input type="submit" id="cforgot" name="forgot"\ + value="Forgot Password" style="margin: 6px; padding: 6px;display: none">\ + <input type="button" id="cancel" name="cancel"\ + value="Cancel" style="margin: 6px; padding: 6px;"\ + onclick="lwsgs_cancel_registration()">\ + </td>\ + </tr>\ + <tr>\ + <td colspan=2>\ + <input type="checkbox" id="showdel" name="showdel"\ + onchange="lwsgs_cupdate();"> Show Delete \ + <input type="submit" id="delete" name="delete" \ + value="Delete Account" style="margin: 6px; padding: 6px;display: none">\ + </td>\ + </tr>\ + </table>\ + </form>\ + </div>\ + \ + <div id="dadmin" style="display:none">\ + Admin settings TBD\ + </div>\ +'; + +/*-- this came from + -- https://raw.githubusercontent.com/blueimp/JavaScript-MD5/master/js/md5.min.js + -- under MIT license */ +!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t),e=(n>>16)+(t>>16)+(r>>16);return e<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[(r+64>>>9<<4)+14]=r;var e,i,a,h,d,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,h=v,d=m,l=o(l,g,v,m,n[e],7,-680876936),m=o(m,l,g,v,n[e+1],12,-389564586),v=o(v,m,l,g,n[e+2],17,606105819),g=o(g,v,m,l,n[e+3],22,-1044525330),l=o(l,g,v,m,n[e+4],7,-176418897),m=o(m,l,g,v,n[e+5],12,1200080426),v=o(v,m,l,g,n[e+6],17,-1473231341),g=o(g,v,m,l,n[e+7],22,-45705983),l=o(l,g,v,m,n[e+8],7,1770035416),m=o(m,l,g,v,n[e+9],12,-1958414417),v=o(v,m,l,g,n[e+10],17,-42063),g=o(g,v,m,l,n[e+11],22,-1990404162),l=o(l,g,v,m,n[e+12],7,1804603682),m=o(m,l,g,v,n[e+13],12,-40341101),v=o(v,m,l,g,n[e+14],17,-1502002290),g=o(g,v,m,l,n[e+15],22,1236535329),l=u(l,g,v,m,n[e+1],5,-165796510),m=u(m,l,g,v,n[e+6],9,-1069501632),v=u(v,m,l,g,n[e+11],14,643717713),g=u(g,v,m,l,n[e],20,-373897302),l=u(l,g,v,m,n[e+5],5,-701558691),m=u(m,l,g,v,n[e+10],9,38016083),v=u(v,m,l,g,n[e+15],14,-660478335),g=u(g,v,m,l,n[e+4],20,-405537848),l=u(l,g,v,m,n[e+9],5,568446438),m=u(m,l,g,v,n[e+14],9,-1019803690),v=u(v,m,l,g,n[e+3],14,-187363961),g=u(g,v,m,l,n[e+8],20,1163531501),l=u(l,g,v,m,n[e+13],5,-1444681467),m=u(m,l,g,v,n[e+2],9,-51403784),v=u(v,m,l,g,n[e+7],14,1735328473),g=u(g,v,m,l,n[e+12],20,-1926607734),l=c(l,g,v,m,n[e+5],4,-378558),m=c(m,l,g,v,n[e+8],11,-2022574463),v=c(v,m,l,g,n[e+11],16,1839030562),g=c(g,v,m,l,n[e+14],23,-35309556),l=c(l,g,v,m,n[e+1],4,-1530992060),m=c(m,l,g,v,n[e+4],11,1272893353),v=c(v,m,l,g,n[e+7],16,-155497632),g=c(g,v,m,l,n[e+10],23,-1094730640),l=c(l,g,v,m,n[e+13],4,681279174),m=c(m,l,g,v,n[e],11,-358537222),v=c(v,m,l,g,n[e+3],16,-722521979),g=c(g,v,m,l,n[e+6],23,76029189),l=c(l,g,v,m,n[e+9],4,-640364487),m=c(m,l,g,v,n[e+12],11,-421815835),v=c(v,m,l,g,n[e+15],16,530742520),g=c(g,v,m,l,n[e+2],23,-995338651),l=f(l,g,v,m,n[e],6,-198630844),m=f(m,l,g,v,n[e+7],10,1126891415),v=f(v,m,l,g,n[e+14],15,-1416354905),g=f(g,v,m,l,n[e+5],21,-57434055),l=f(l,g,v,m,n[e+12],6,1700485571),m=f(m,l,g,v,n[e+3],10,-1894986606),v=f(v,m,l,g,n[e+10],15,-1051523),g=f(g,v,m,l,n[e+1],21,-2054922799),l=f(l,g,v,m,n[e+8],6,1873313359),m=f(m,l,g,v,n[e+15],10,-30611744),v=f(v,m,l,g,n[e+6],15,-1560198380),g=f(g,v,m,l,n[e+13],21,1309151649),l=f(l,g,v,m,n[e+4],6,-145523070),m=f(m,l,g,v,n[e+11],10,-1120210379),v=f(v,m,l,g,n[e+2],15,718787259),g=f(g,v,m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,h),m=t(m,d);return[l,g,v,m]}function a(n){var t,r="";for(t=0;t<32*n.length;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function h(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;for(t=0;t<8*n.length;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function d(n){return a(i(h(n),8*n.length))}function l(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;16>r;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(h(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="0123456789abcdef",o="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),o+=e.charAt(t>>>4&15)+e.charAt(15&t);return o}function v(n){return unescape(encodeURIComponent(n))}function m(n){return d(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this); + +if (lwsgs_user.substring(0, 1) == "$") { + alert("lwsgs.js: lws generic sessions misconfigured and not providing vars"); +} +function lwsgs_san(s) +{ + if (s.search("<") != -1) + return "invalid string"; + + return s; +} + +function lwsgs_update() +{ + var en_login = 1, en_forgot = 1; + + if (document.getElementById('password').value.length && + document.getElementById('password').value.length < 8) + en_login = 0; + + if (!document.getElementById('username').value || + !document.getElementById('password').value) + en_login = 0; + + if (!document.getElementById('username').value || + document.getElementById('password').value) + en_forgot = 0; + + document.getElementById('login').disabled = !en_login; + document.getElementById('forgot').disabled = !en_forgot; + + if (lwsgs_user) + document.getElementById("curuser").innerHTML = lwsgs_san(lwsgs_user); + + if (lwsgs_user === "") + document.getElementById("dlogin").style.display = "inline"; + else + document.getElementById("dlogout").style.display = "inline"; + } + +function lwsgs_open_registration() +{ + document.getElementById("dadmin").style.display = "none"; + document.getElementById("dlogin").style.display = "none"; + document.getElementById("dlogout").style.display = "none"; + document.getElementById("dchange").style.display = "none"; + document.getElementById("dregister").style.display = "inline"; +} + +function lwsgs_cancel_registration() +{ + document.getElementById("dadmin").style.display = "none"; + document.getElementById("dregister").style.display = "none"; + document.getElementById("dchange").style.display = "none"; + + if (lwsgs_user === "") + document.getElementById("dlogin").style.display = "inline"; + else + document.getElementById("dlogout").style.display = "inline"; +} + +function lwsgs_select_change() +{ + document.getElementById("dlogin").style.display = "none"; + document.getElementById("dlogout").style.display = "none"; + document.getElementById("dregister").style.display = "none"; + if (lwsgs_auth & 2) { + document.getElementById("dadmin").style.display = "inline"; + document.getElementById("dchange").style.display = "none"; + } else { + document.getElementById("dadmin").style.display = "none"; + document.getElementById("dchange").style.display = "inline"; + } +} + +var lwsgs_user_check = '0'; +var lwsgs_email_check = '0'; + +function lwsgs_rupdate() +{ + var en_register = 1, en_forgot = 0; + + if (document.getElementById('rpassword').value == + document.getElementById('password2').value) { + if (document.getElementById('rpassword').value.length) + document.getElementById('match').innerHTML = + "<b style=\"color:green\">\u2713</b>"; + else + document.getElementById('match').innerHTML = ""; + document.getElementById('pw2').style = ""; + } else { + if (document.getElementById('password2').value || + document.getElementById('email').value) { // ie, he is filling in "register" path and cares + document.getElementById('match').innerHTML = + "<span style=\"color: red\">\u2718 <b>Passwords do not match</b></span>"; + } else + document.getElementById('match').innerHTML = + "<span style=\"color: gray\">\u2718 Passwords do not match</span>"; + + en_register = 0; + } + + if (document.getElementById('rpassword').value.length && + document.getElementById('rpassword').value.length < 8) { + en_register = 0; + document.getElementById('rpw1').innerHTML = "Need 8 chars"; + } else + if (document.getElementById('rpassword').value.length) + document.getElementById('rpw1').innerHTML = "<b style=\"color:green\">\u2713</b>"; + else + document.getElementById('rpw1').innerHTML = ""; + + if (!document.getElementById('rpassword').value || + !document.getElementById('password2').value || + !document.getElementById('rusername').value || + !document.getElementById('email').value || + lwsgs_email_check === '1'|| + lwsgs_user_check === '1') + en_register = 0; + + document.getElementById('register').disabled = !en_register; + document.getElementById('rpassword').disabled = lwsgs_user_check === '1'; + document.getElementById('password2').disabled = lwsgs_user_check === '1'; + document.getElementById('email').disabled = lwsgs_user_check === '1'; + + if (lwsgs_user_check === '0') { + if (document.getElementById('rusername').value) + document.getElementById('uchk').innerHTML = "<b style=\"color:green\">\u2713</b>"; + else + document.getElementById('uchk').innerHTML = ""; + } else { + document.getElementById('uchk').innerHTML = "<b style=\"color:red\">\u2718 Already registered</b>"; + en_forgot = 1; + } + + if (lwsgs_email_check === '0') { + if (document.getElementById('email').value) + document.getElementById('echk').innerHTML = "<b style=\"color:green\">\u2713</b>"; + else + document.getElementById('echk').innerHTML = ""; + } else { + document.getElementById('echk').innerHTML = "<b style=\"color:red\">\u2718 Already registered</b>"; + en_forgot = 1; + } + + if (en_forgot) + document.getElementById('rforgot').style.display = "inline"; + else + document.getElementById('rforgot').style.display = "none"; + + if (lwsgs_user_check === '1') + op = '0.5'; + else + op = '1.0'; + document.getElementById('rpassword').style.opacity = op; + document.getElementById('password2').style.opacity = op; + document.getElementById('email').style.opacity = op; + } + +function lwsgs_cupdate() +{ + var en_change = 1, en_forgot = 1, pwok = 1; + + if (lwsgs_auth & 8) { + document.getElementById('ccurpw').style.display = "none"; + document.getElementById('ccurpw_name').style.display = "none"; + } else { + if (!document.getElementById('ccurpw').value || + document.getElementById('ccurpw').value.length < 8) { + en_change = 0; + pwok = 0; + document.getElementById('cuchk').innerHTML = "<b style=\"color:red\">\u2718</b>"; + } else { + en_forgot = 0; + document.getElementById('cuchk').innerHTML = ""; + } + document.getElementById('ccurpw').style.display = "inline"; + document.getElementById('ccurpw_name').style.display = "inline"; + } + + if (document.getElementById('cpassword').value == + document.getElementById('cpassword2').value) { + if (document.getElementById('cpassword').value.length) + document.getElementById('cmatch').innerHTML = "<b style=\"color:green\">\u2713</b>"; + else + document.getElementById('cmatch').innerHTML = ""; + document.getElementById('pw2').style = ""; + } else { + if (document.getElementById('cpassword2').value //|| + //document.getElementById('cemail').value + ) { // ie, he is filling in "register" path and cares + document.getElementById('cmatch').innerHTML = + "<span style=\"color: red\">\u2718 <b>Passwords do not match</b></span>"; + } else + document.getElementById('cmatch').innerHTML = "<span style=\"color: gray\">\u2718 Passwords do not match</span>"; + + en_change = 0; + } + + if (document.getElementById('cpassword').value.length && + document.getElementById('cpassword').value.length < 8) { + en_change = 0; + document.getElementById('cpw1').innerHTML = "Need 8 chars"; + } else + if (document.getElementById('cpassword').value.length) + document.getElementById('cpw1').innerHTML = "<b style=\"color:green\">\u2713</b>"; + else + document.getElementById('cpw1').innerHTML = ""; + + if (!document.getElementById('cpassword').value || + !document.getElementById('cpassword2').value || + pwok == 0) + en_change = 0; + + if (document.getElementById('showdel').checked) + document.getElementById('delete').style.display = "inline"; + else + document.getElementById('delete').style.display = "none"; + + document.getElementById('change').disabled = !en_change; + document.getElementById('cpassword').disabled = pwok === 0; + document.getElementById('cpassword2').disabled = pwok === 0; + document.getElementById('showdel').disabled = pwok === 0; + document.getElementById('delete').disabled = pwok === 0; + //document.getElementById('cemail').disabled = pwok === 0; + + /* + if (lwsgs_auth & 8) { + document.getElementById('cemail').style.display = "none"; + document.getElementById('cemail_name').style.display = "none"; + } else { + document.getElementById('cemail').style.display = "inline"; + document.getElementById('cemail_name').style.display = "inline"; + if (lwsgs_email_check === '0' && + document.getElementById('cemail').value != lwsgs_email) { + if (document.getElementById('cemail').value) + document.getElementById('cechk').innerHTML = "<b style=\"color:green\">\u2713</b>"; + else + document.getElementById('cechk').innerHTML = ""; + } else { + document.getElementById('cechk').innerHTML = "<b style=\"color:red\">\u2718 Already registered</b>"; + en_forgot = 1; + } + } */ + + if (lwsgs_auth & 8) + en_forgot = 0; + + if (en_forgot) + document.getElementById('cforgot').style.display = "inline"; + else + document.getElementById('cforgot').style.display = "none"; + + if (pwok == 0) + op = '0.5'; + else + op = '1.0'; + document.getElementById('cpassword').style.opacity = op; + document.getElementById('cpassword2').style.opacity = op; + // document.getElementById('cemail').style.opacity = op; + } + +function lwsgs_check_user() +{ + var xmlHttp = new XMLHttpRequest(); + xmlHttp.onreadystatechange = function() { + if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { + lwsgs_user_check = xmlHttp.responseText; + lwsgs_rupdate(); + } + } + xmlHttp.open("GET", "lwsgs-check?username="+document.getElementById('rusername').value, true); + xmlHttp.send(null); +} + +function lwsgs_check_email(id) +{ + var xmlHttp = new XMLHttpRequest(); + xmlHttp.onreadystatechange = function() { + if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { + lwsgs_email_check = xmlHttp.responseText; + lwsgs_rupdate(); + } + } + xmlHttp.open("GET", "lwsgs-check?email="+document.getElementById(id).value, true); + xmlHttp.send(null); +} + +function lwsgs_initial() +{ + document.getElementById('lwsgs').innerHTML = lwsgs_html; + if (lwsgs_email) + document.getElementById('grav').innerHTML = + "<img src=\"https://www.gravatar.com/avatar/" + md5(lwsgs_email) + + "?d=identicon\">"; + //if (lwsgs_email) + //document.getElementById('cemail').placeholder = lwsgs_email; + document.getElementById('cusername').value = lwsgs_user; + lwsgs_update(); + lwsgs_cupdate(); +} diff --git a/plugins/generic-sessions/assets/md5.min.js b/plugins/generic-sessions/assets/md5.min.js new file mode 100644 index 00000000..4bd9de1e --- /dev/null +++ b/plugins/generic-sessions/assets/md5.min.js @@ -0,0 +1,2 @@ +!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t),e=(n>>16)+(t>>16)+(r>>16);return e<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[(r+64>>>9<<4)+14]=r;var e,i,a,h,d,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,h=v,d=m,l=o(l,g,v,m,n[e],7,-680876936),m=o(m,l,g,v,n[e+1],12,-389564586),v=o(v,m,l,g,n[e+2],17,606105819),g=o(g,v,m,l,n[e+3],22,-1044525330),l=o(l,g,v,m,n[e+4],7,-176418897),m=o(m,l,g,v,n[e+5],12,1200080426),v=o(v,m,l,g,n[e+6],17,-1473231341),g=o(g,v,m,l,n[e+7],22,-45705983),l=o(l,g,v,m,n[e+8],7,1770035416),m=o(m,l,g,v,n[e+9],12,-1958414417),v=o(v,m,l,g,n[e+10],17,-42063),g=o(g,v,m,l,n[e+11],22,-1990404162),l=o(l,g,v,m,n[e+12],7,1804603682),m=o(m,l,g,v,n[e+13],12,-40341101),v=o(v,m,l,g,n[e+14],17,-1502002290),g=o(g,v,m,l,n[e+15],22,1236535329),l=u(l,g,v,m,n[e+1],5,-165796510),m=u(m,l,g,v,n[e+6],9,-1069501632),v=u(v,m,l,g,n[e+11],14,643717713),g=u(g,v,m,l,n[e],20,-373897302),l=u(l,g,v,m,n[e+5],5,-701558691),m=u(m,l,g,v,n[e+10],9,38016083),v=u(v,m,l,g,n[e+15],14,-660478335),g=u(g,v,m,l,n[e+4],20,-405537848),l=u(l,g,v,m,n[e+9],5,568446438),m=u(m,l,g,v,n[e+14],9,-1019803690),v=u(v,m,l,g,n[e+3],14,-187363961),g=u(g,v,m,l,n[e+8],20,1163531501),l=u(l,g,v,m,n[e+13],5,-1444681467),m=u(m,l,g,v,n[e+2],9,-51403784),v=u(v,m,l,g,n[e+7],14,1735328473),g=u(g,v,m,l,n[e+12],20,-1926607734),l=c(l,g,v,m,n[e+5],4,-378558),m=c(m,l,g,v,n[e+8],11,-2022574463),v=c(v,m,l,g,n[e+11],16,1839030562),g=c(g,v,m,l,n[e+14],23,-35309556),l=c(l,g,v,m,n[e+1],4,-1530992060),m=c(m,l,g,v,n[e+4],11,1272893353),v=c(v,m,l,g,n[e+7],16,-155497632),g=c(g,v,m,l,n[e+10],23,-1094730640),l=c(l,g,v,m,n[e+13],4,681279174),m=c(m,l,g,v,n[e],11,-358537222),v=c(v,m,l,g,n[e+3],16,-722521979),g=c(g,v,m,l,n[e+6],23,76029189),l=c(l,g,v,m,n[e+9],4,-640364487),m=c(m,l,g,v,n[e+12],11,-421815835),v=c(v,m,l,g,n[e+15],16,530742520),g=c(g,v,m,l,n[e+2],23,-995338651),l=f(l,g,v,m,n[e],6,-198630844),m=f(m,l,g,v,n[e+7],10,1126891415),v=f(v,m,l,g,n[e+14],15,-1416354905),g=f(g,v,m,l,n[e+5],21,-57434055),l=f(l,g,v,m,n[e+12],6,1700485571),m=f(m,l,g,v,n[e+3],10,-1894986606),v=f(v,m,l,g,n[e+10],15,-1051523),g=f(g,v,m,l,n[e+1],21,-2054922799),l=f(l,g,v,m,n[e+8],6,1873313359),m=f(m,l,g,v,n[e+15],10,-30611744),v=f(v,m,l,g,n[e+6],15,-1560198380),g=f(g,v,m,l,n[e+13],21,1309151649),l=f(l,g,v,m,n[e+4],6,-145523070),m=f(m,l,g,v,n[e+11],10,-1120210379),v=f(v,m,l,g,n[e+2],15,718787259),g=f(g,v,m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,h),m=t(m,d);return[l,g,v,m]}function a(n){var t,r="";for(t=0;t<32*n.length;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function h(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;for(t=0;t<8*n.length;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function d(n){return a(i(h(n),8*n.length))}function l(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;16>r;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(h(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="0123456789abcdef",o="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),o+=e.charAt(t>>>4&15)+e.charAt(15&t);return o}function v(n){return unescape(encodeURIComponent(n))}function m(n){return d(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this); +//# sourceMappingURL=md5.min.js.map \ No newline at end of file diff --git a/plugins/generic-sessions/assets/post-forgot-fail.html b/plugins/generic-sessions/assets/post-forgot-fail.html new file mode 100644 index 00000000..ead3d13e --- /dev/null +++ b/plugins/generic-sessions/assets/post-forgot-fail.html @@ -0,0 +1,5 @@ +<html> +Sorry, something went wrong. + +Click <a href="../">here</a> to continue. +</html> diff --git a/plugins/generic-sessions/assets/post-forgot-ok.html b/plugins/generic-sessions/assets/post-forgot-ok.html new file mode 100644 index 00000000..3e8e9cf5 --- /dev/null +++ b/plugins/generic-sessions/assets/post-forgot-ok.html @@ -0,0 +1,6 @@ +<html> +This is a one-time password recovery login. + +Please click <a href="./">here</a> and click your username at the top to reset your password. +</html> + diff --git a/plugins/generic-sessions/assets/post-register-fail.html b/plugins/generic-sessions/assets/post-register-fail.html new file mode 100644 index 00000000..063c3c50 --- /dev/null +++ b/plugins/generic-sessions/assets/post-register-fail.html @@ -0,0 +1 @@ +Registration failed, sorry diff --git a/plugins/generic-sessions/assets/post-register-ok.html b/plugins/generic-sessions/assets/post-register-ok.html new file mode 100644 index 00000000..2d150358 --- /dev/null +++ b/plugins/generic-sessions/assets/post-register-ok.html @@ -0,0 +1,27 @@ +<html> + <head> + <script src="lwsgs.js"></script> + </head> + <body> + <table> + <tr> + <td colspan=2 align=center> + <img src="lwsgs-logo.png"> + </td> + </tr> + <tr> + <td> + Your registration as <span id="u"></span> is accepted,<br> + you will receive an email shortly with instructions<br> + to verify and enable the account for normal use.<br><br> + The link is only valid for an hour, after that if it has<br> + not been verified your account will be deleted. + </td> + </tr> + </table> + </body> + <script> + document.getElementById('u').innerHTML = "<b>" + lwsgs_san(lwsgs_user) + "</b>"; + </script> +</html> + diff --git a/plugins/generic-sessions/assets/post-verify-fail.html b/plugins/generic-sessions/assets/post-verify-fail.html new file mode 100644 index 00000000..d1d89ca5 --- /dev/null +++ b/plugins/generic-sessions/assets/post-verify-fail.html @@ -0,0 +1,20 @@ +<html> + <head> + <script src="lwsgs.js"></script> + </head> + <body> + <table> + <tr> + <td colspan=2 align=center> + <img src="lwsws-logo.png"> + </td> + </tr> + <tr> + <td> + Sorry, the link was invalid. + </td> + </tr> + </table> + </body> +</html> + diff --git a/plugins/generic-sessions/assets/post-verify-ok.html b/plugins/generic-sessions/assets/post-verify-ok.html new file mode 100644 index 00000000..e968f6a7 --- /dev/null +++ b/plugins/generic-sessions/assets/post-verify-ok.html @@ -0,0 +1,25 @@ +<html> + <head> + <script src="lwsgs.js"></script> + </head> + <body> + <table> + <tr> + <td colspan=2 align=center> + <img src="lwsgs-logo.png"> + </td> + </tr> + <tr> + <td> + Thanks for signing up, your registration as <span id="u"></span> is verified.<br> + <br> + Click <a href="/lwsgs">here</a> to continue. + </td> + </tr> + </table> + </body> + <script> + document.getElementById('u').innerHTML = "<b>" + san(lwsgs_user) + "</b>"; + </script> +</html> + diff --git a/plugins/generic-sessions/assets/seats.jpg b/plugins/generic-sessions/assets/seats.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5bed40d919872359f2fcc00a8430e7dcfabaab4a GIT binary patch literal 122754 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 Kf%~`m|2F}BW(*ww literal 0 HcmV?d00001 diff --git a/plugins/generic-sessions/assets/sent-forgot-fail.html b/plugins/generic-sessions/assets/sent-forgot-fail.html new file mode 100644 index 00000000..ead3d13e --- /dev/null +++ b/plugins/generic-sessions/assets/sent-forgot-fail.html @@ -0,0 +1,5 @@ +<html> +Sorry, something went wrong. + +Click <a href="../">here</a> to continue. +</html> diff --git a/plugins/generic-sessions/assets/sent-forgot-ok.html b/plugins/generic-sessions/assets/sent-forgot-ok.html new file mode 100644 index 00000000..83df7510 --- /dev/null +++ b/plugins/generic-sessions/assets/sent-forgot-ok.html @@ -0,0 +1,4 @@ +An email has been sent to your registered address. + +Please follow the instructions to reset your password. + diff --git a/plugins/generic-sessions/assets/successful-login.html b/plugins/generic-sessions/assets/successful-login.html new file mode 100644 index 00000000..dfc25cf7 --- /dev/null +++ b/plugins/generic-sessions/assets/successful-login.html @@ -0,0 +1,4 @@ +<html> +This is an example destination that will appear after successful non-Admin login +</html> + diff --git a/plugins/generic-sessions/handlers.c b/plugins/generic-sessions/handlers.c new file mode 100644 index 00000000..bd00c2b4 --- /dev/null +++ b/plugins/generic-sessions/handlers.c @@ -0,0 +1,598 @@ +/* + * ws protocol handler plugin for "generic sessions" + * + * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-lwsgs.h" + +/* handle account confirmation links */ + +int +lwsgs_handler_confirm(struct per_vhost_data__gs *vhd, struct lws *wsi, + struct per_session_data__gs *pss) +{ + char cookie[1024], s[256], esc[50]; + struct lws_gs_event_args a; + struct lwsgs_user u; + + if (lws_hdr_copy_fragment(wsi, cookie, sizeof(cookie), + WSI_TOKEN_HTTP_URI_ARGS, 0) < 0) + goto verf_fail; + + if (strncmp(cookie, "token=", 6)) + goto verf_fail; + + u.username[0] = '\0'; + snprintf(s, sizeof(s) - 1, + "select username,email,verified from users where token = '%s';", + lws_sql_purify(esc, &cookie[6], sizeof(esc) - 1)); + if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, &u, NULL) != + SQLITE_OK) { + lwsl_err("Unable to lookup token: %s\n", + sqlite3_errmsg(vhd->pdb)); + goto verf_fail; + } + + if (!u.username[0] || u.verified != 1) { + lwsl_notice("verify token doesn't map to unverified user\n"); + goto verf_fail; + } + + lwsl_notice("Verifying %s\n", u.username); + snprintf(s, sizeof(s) - 1, + "update users set verified=%d where username='%s';", + LWSGS_VERIFIED_ACCEPTED, + lws_sql_purify(esc, u.username, sizeof(esc) - 1)); + if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, &u, NULL) != + SQLITE_OK) { + lwsl_err("Unable to lookup token: %s\n", + sqlite3_errmsg(vhd->pdb)); + + goto verf_fail; + } + + lwsl_notice("deleting account\n"); + + a.event = LWSGSE_CREATED; + a.username = u.username; + a.email = u.email; + lws_callback_vhost_protocols(wsi, LWS_CALLBACK_GS_EVENT, &a, 0); + + snprintf(pss->onward, sizeof(pss->onward), + "%s/post-verify-ok.html", vhd->email_confirm_url); + + pss->login_expires = lws_now_secs() + vhd->timeout_absolute_secs; + + pss->delete_session.id[0] = '\0'; + lwsgs_get_sid_from_wsi(wsi, &pss->delete_session); + + /* we need to create a new, authorized session */ + + if (lwsgs_new_session_id(vhd, &pss->login_session, u.username, + pss->login_expires)) + goto verf_fail; + + lwsl_notice("Creating new session: %s, redir to %s\n", + pss->login_session.id, pss->onward); + + return 0; + +verf_fail: + pss->delete_session.id[0] = '\0'; + lwsgs_get_sid_from_wsi(wsi, &pss->delete_session); + pss->login_expires = 0; + + snprintf(pss->onward, sizeof(pss->onward), "%s/post-verify-fail.html", + vhd->email_confirm_url); + + return 1; +} + +/* handle forgot password confirmation links */ + +int +lwsgs_handler_forgot(struct per_vhost_data__gs *vhd, struct lws *wsi, + struct per_session_data__gs *pss) +{ + char cookie[1024], s[256], esc[50]; + struct lwsgs_user u; + const char *a; + + a = lws_get_urlarg_by_name(wsi, "token=", cookie, sizeof(cookie)); + if (!a) + goto forgot_fail; + + u.username[0] = '\0'; + snprintf(s, sizeof(s) - 1, + "select username,verified from users where verified=%d and " + "token = '%s' and token_time != 0;", + LWSGS_VERIFIED_ACCEPTED, + lws_sql_purify(esc, &cookie[6], sizeof(esc) - 1)); + if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, &u, NULL) != + SQLITE_OK) { + lwsl_err("Unable to lookup token: %s\n", + sqlite3_errmsg(vhd->pdb)); + + goto forgot_fail; + } + + if (!u.username[0]) { + puts(s); + lwsl_notice("forgot token doesn't map to verified user\n"); + goto forgot_fail; + } + + /* mark user as having validated forgot flow just now */ + + snprintf(s, sizeof(s) - 1, + "update users set token_time=0,last_forgot_validated=%lu " + "where username='%s';", + (unsigned long)lws_now_secs(), + lws_sql_purify(esc, u.username, sizeof(esc) - 1)); + + if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, &u, NULL) != + SQLITE_OK) { + lwsl_err("Unable to lookup token: %s\n", + sqlite3_errmsg(vhd->pdb)); + goto forgot_fail; + } + + a = lws_get_urlarg_by_name(wsi, "good=", cookie, sizeof(cookie)); + if (!a) + a = "broken-forget-post-good-url"; + + snprintf(pss->onward, sizeof(pss->onward), + "%s/%s", vhd->email_confirm_url, a); + + pss->login_expires = lws_now_secs() + vhd->timeout_absolute_secs; + + pss->delete_session.id[0] = '\0'; + lwsgs_get_sid_from_wsi(wsi, &pss->delete_session); + + /* we need to create a new, authorized session */ + if (lwsgs_new_session_id(vhd, &pss->login_session, + u.username, + pss->login_expires)) + goto forgot_fail; + + lwsl_notice("Creating new session: %s, redir to %s\n", + pss->login_session.id, pss->onward); + + return 0; + +forgot_fail: + pss->delete_session.id[0] = '\0'; + lwsgs_get_sid_from_wsi(wsi, &pss->delete_session); + pss->login_expires = 0; + + a = lws_get_urlarg_by_name(wsi, "bad=", cookie, sizeof(cookie)); + if (!a) + a = "broken-forget-post-bad-url"; + + snprintf(pss->onward, sizeof(pss->onward), "%s/%s", + vhd->email_confirm_url, a); + + return 1; +} + +/* support dynamic username / email checking */ + +int +lwsgs_handler_check(struct per_vhost_data__gs *vhd, + struct lws *wsi, struct per_session_data__gs *pss) +{ + static const char * const colname[] = { "username", "email" }; + char cookie[1024], s[256], esc[50], *pc; + unsigned char *p, *start, *end, buffer[LWS_PRE + 256]; + struct lwsgs_user u; + int n; + + /* + * either /check?email=xxx@yyy or: /check?username=xxx + * returns '0' if not already registered, else '1' + */ + + u.username[0] = '\0'; + if (lws_hdr_copy_fragment(wsi, cookie, sizeof(cookie), + WSI_TOKEN_HTTP_URI_ARGS, 0) < 0) + goto reply; + + n = !strncmp(cookie, "email=", 6); + pc = strchr(cookie, '='); + if (!pc) { + lwsl_notice("cookie has no =\n"); + goto reply; + } + pc++; + + /* admin user cannot be registered in user db */ + if (!strcmp(vhd->admin_user, pc)) { + u.username[0] = 'a'; + goto reply; + } + + snprintf(s, sizeof(s) - 1, + "select username, email from users where %s = '%s';", + colname[n], lws_sql_purify(esc, pc, sizeof(esc) - 1)); + if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, &u, NULL) != + SQLITE_OK) { + lwsl_err("Unable to lookup token: %s\n", + sqlite3_errmsg(vhd->pdb)); + goto reply; + } + +reply: + s[0] = '0' + !!u.username[0]; + p = buffer + LWS_PRE; + start = p; + end = p + sizeof(buffer) - LWS_PRE; + + if (lws_add_http_header_status(wsi, 200, &p, end)) + return -1; + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)"text/plain", 10, + &p, end)) + return -1; + + if (lws_add_http_header_content_length(wsi, 1, &p, end)) + return -1; + + if (lws_finalize_http_header(wsi, &p, end)) + return -1; + + n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); + if (n != (p - start)) { + lwsl_err("_write returned %d from %d\n", n, (p - start)); + return -1; + } + n = lws_write(wsi, (unsigned char *)s, 1, LWS_WRITE_HTTP); + if (n != 1) + return -1; + + return 0; +} + +/* handle forgot password confirmation links */ + +int +lwsgs_handler_change_password(struct per_vhost_data__gs *vhd, struct lws *wsi, + struct per_session_data__gs *pss) +{ + char s[256], esc[50], username[50]; + struct lwsgs_user u; + lwsgw_hash sid; + int n = 0; + + /* see if he's logged in */ + username[0] = '\0'; + if (!lwsgs_get_sid_from_wsi(wsi, &sid)) { + u.username[0] = '\0'; + if (!lwsgs_lookup_session(vhd, &sid, username, sizeof(username))) { + n = 1; /* yes, logged in */ + if (lwsgs_lookup_user(vhd, username, &u)) + return 1; + + /* did a forgot pw ? */ + if (u.last_forgot_validated > lws_now_secs() - 300) + n |= LWSGS_AUTH_FORGOT_FLOW; + } + } + + /* if he just did forgot pw flow, don't need old pw */ + if (!(n & (LWSGS_AUTH_FORGOT_FLOW | 1))) { + /* otherwise user:pass must be right */ + if (lwsgs_check_credentials(vhd, + lws_spa_get_string(pss->spa, FGS_USERNAME), + lws_spa_get_string(pss->spa, FGS_CURPW))) { + lwsl_notice("credentials bad\n"); + return 1; + } + + strcpy(u.username, lws_spa_get_string(pss->spa, FGS_USERNAME)); + } + + /* does he want to delete his account? */ + + if (lws_spa_get_length(pss->spa, FGS_DELETE)) { + struct lws_gs_event_args a; + + lwsl_notice("deleting account\n"); + + a.event = LWSGSE_DELETED; + a.username = u.username; + a.email = ""; + lws_callback_vhost_protocols(wsi, LWS_CALLBACK_GS_EVENT, &a, 0); + + snprintf(s, sizeof(s) - 1, + "delete from users where username='%s';" + "delete from sessions where username='%s';", + lws_sql_purify(esc, u.username, sizeof(esc) - 1), + lws_sql_purify(esc, u.username, sizeof(esc) - 1)); + goto sql; + } + + if (lwsgs_hash_password(vhd, lws_spa_get_string(pss->spa, FGS_PASSWORD), &u)) + return 1; + + lwsl_notice("updating password hash\n"); + + snprintf(s, sizeof(s) - 1, + "update users set pwhash='%s', pwsalt='%s', " + "last_forgot_validated=0 where username='%s';", + u.pwhash.id, u.pwsalt.id, + lws_sql_purify(esc, u.username, sizeof(esc) - 1)); + +sql: + if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("Unable to update pw hash: %s\n", + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + return 0; +} + +int +lwsgs_handler_forgot_pw_form(struct per_vhost_data__gs *vhd, + struct lws *wsi, + struct per_session_data__gs *pss) +{ + char s[LWSGS_EMAIL_CONTENT_SIZE]; + unsigned char buffer[LWS_PRE + LWSGS_EMAIL_CONTENT_SIZE]; + char esc[50], esc1[50], esc2[50], esc3[50], esc4[50]; + struct lwsgs_user u; + lwsgw_hash hash; + unsigned char sid_rand[20]; + int n; + + lwsl_notice("FORGOT %s %s\n", + lws_spa_get_string(pss->spa, FGS_USERNAME), + lws_spa_get_string(pss->spa, FGS_EMAIL)); + + if (!lws_spa_get_string(pss->spa, FGS_USERNAME) && + !lws_spa_get_string(pss->spa, FGS_EMAIL)) { + lwsl_err("Form must provide either " + "username or email\n"); + return -1; + } + + if (!lws_spa_get_string(pss->spa, FGS_FORGOT_GOOD) || + !lws_spa_get_string(pss->spa, FGS_FORGOT_BAD) || + !lws_spa_get_string(pss->spa, FGS_FORGOT_POST_GOOD) || + !lws_spa_get_string(pss->spa, FGS_FORGOT_POST_BAD)) { + lwsl_err("Form must provide reg-good " + "and reg-bad (and post-*)" + "targets\n"); + return -1; + } + + u.username[0] = '\0'; + if (lws_spa_get_string(pss->spa, FGS_USERNAME)) + snprintf(s, sizeof(s) - 1, + "select username,email " + "from users where username = '%s';", + lws_sql_purify(esc, lws_spa_get_string(pss->spa, FGS_USERNAME), + sizeof(esc) - 1)); + else + snprintf(s, sizeof(s) - 1, + "select username,email " + "from users where email = '%s';", + lws_sql_purify(esc, lws_spa_get_string(pss->spa, FGS_EMAIL), sizeof(esc) - 1)); + if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, &u, NULL) != + SQLITE_OK) { + lwsl_err("Unable to lookup token: %s\n", + sqlite3_errmsg(vhd->pdb)); + return 1; + } + if (!u.username[0]) { + lwsl_err("No match found %s\n", s); + return 1; + } + + lws_get_peer_simple(wsi, pss->ip, sizeof(pss->ip)); + if (lws_get_random(vhd->context, sid_rand, + sizeof(sid_rand)) != + sizeof(sid_rand)) { + lwsl_err("Problem getting random for token\n"); + return 1; + } + sha1_to_lwsgw_hash(sid_rand, &hash); + n = snprintf(s, sizeof(s), + "From: Forgot Password Assistant Noreply <%s>\n" + "To: %s <%s>\n" + "Subject: Password reset request\n" + "\n" + "Hello, %s\n\n" + "We received a password reset request from IP %s for this email,\n" + "to confirm you want to do that, please click the link below.\n\n", + lws_sql_purify(esc, vhd->email.email_from, sizeof(esc) - 1), + lws_sql_purify(esc1, u.username, sizeof(esc1) - 1), + lws_sql_purify(esc2, u.email, sizeof(esc2) - 1), + lws_sql_purify(esc3, u.username, sizeof(esc3) - 1), + lws_sql_purify(esc4, pss->ip, sizeof(esc4) - 1)); + snprintf(s + n, sizeof(s) -n, + "%s/lwsgs-forgot?token=%s" + "&good=%s" + "&bad=%s\n\n" + "If this request is unexpected, please ignore it and\n" + "no further action will be taken.\n\n" + "If you have any questions or concerns about this\n" + "automated email, you can contact a real person at\n" + "%s.\n" + "\n.\n", + vhd->email_confirm_url, hash.id, + lws_urlencode(esc1, + lws_spa_get_string(pss->spa, FGS_FORGOT_POST_GOOD), + sizeof(esc1) - 1), + lws_urlencode(esc3, + lws_spa_get_string(pss->spa, FGS_FORGOT_POST_BAD), + sizeof(esc3) - 1), + vhd->email_contact_person); + + snprintf((char *)buffer, sizeof(buffer) - 1, + "insert into email(username, content)" + " values ('%s', '%s');", + lws_sql_purify(esc, u.username, sizeof(esc) - 1), s); + if (sqlite3_exec(vhd->pdb, (char *)buffer, NULL, + NULL, NULL) != SQLITE_OK) { + lwsl_err("Unable to insert email: %s\n", + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + snprintf(s, sizeof(s) - 1, + "update users set token='%s',token_time='%ld' where username='%s';", + hash.id, (long)lws_now_secs(), + lws_sql_purify(esc, u.username, sizeof(esc) - 1)); + if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != + SQLITE_OK) { + lwsl_err("Unable to set token: %s\n", + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + return 0; +} + +int +lwsgs_handler_register_form(struct per_vhost_data__gs *vhd, + struct lws *wsi, + struct per_session_data__gs *pss) +{ + unsigned char buffer[LWS_PRE + LWSGS_EMAIL_CONTENT_SIZE]; + char esc[50], esc1[50], esc2[50], esc3[50], esc4[50]; + char s[LWSGS_EMAIL_CONTENT_SIZE]; + unsigned char sid_rand[20]; + struct lwsgs_user u; + lwsgw_hash hash; + + lwsl_notice("REGISTER %s %s %s\n", + lws_spa_get_string(pss->spa, FGS_USERNAME), + lws_spa_get_string(pss->spa, FGS_PASSWORD), + lws_spa_get_string(pss->spa, FGS_EMAIL)); + if (lwsgs_get_sid_from_wsi(wsi, + &pss->login_session)) + return 1; + + lws_get_peer_simple(wsi, pss->ip, sizeof(pss->ip)); + lwsl_notice("IP=%s\n", pss->ip); + + if (!lws_spa_get_string(pss->spa, FGS_REG_GOOD) || + !lws_spa_get_string(pss->spa, FGS_REG_BAD)) { + lwsl_info("Form must provide reg-good and reg-bad targets\n"); + return -1; + } + + /* admin user cannot be registered in user db */ + if (!strcmp(vhd->admin_user, + lws_spa_get_string(pss->spa, FGS_USERNAME))) + return 1; + + if (!lwsgs_lookup_user(vhd, + lws_spa_get_string(pss->spa, FGS_USERNAME), &u)) { + lwsl_notice("user %s already registered\n", + lws_spa_get_string(pss->spa, FGS_USERNAME)); + return 1; + } + + u.username[0] = '\0'; + snprintf(s, sizeof(s) - 1, "select username, email from users where email = '%s';", + lws_sql_purify(esc, lws_spa_get_string(pss->spa, FGS_EMAIL), + sizeof(esc) - 1)); + + if (sqlite3_exec(vhd->pdb, s, + lwsgs_lookup_callback_user, &u, NULL) != SQLITE_OK) { + lwsl_err("Unable to lookup token: %s\n", + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + if (u.username[0]) { + lwsl_notice("email %s already in use\n", + lws_spa_get_string(pss->spa, FGS_USERNAME)); + return 1; + } + + if (lwsgs_hash_password(vhd, lws_spa_get_string(pss->spa, FGS_PASSWORD), + &u)) { + lwsl_err("Password hash failed\n"); + return 1; + } + + if (lws_get_random(vhd->context, sid_rand, sizeof(sid_rand)) != + sizeof(sid_rand)) { + lwsl_err("Problem getting random for token\n"); + return 1; + } + sha1_to_lwsgw_hash(sid_rand, &hash); + + snprintf((char *)buffer, sizeof(buffer) - 1, + "insert into users(username," + " creation_time, ip, email, verified," + " pwhash, pwsalt, token, last_forgot_validated)" + " values ('%s', %lu, '%s', '%s', 0," + " '%s', '%s', '%s', 0);", + lws_sql_purify(esc, lws_spa_get_string(pss->spa, FGS_USERNAME), sizeof(esc) - 1), + (unsigned long)lws_now_secs(), + lws_sql_purify(esc1, pss->ip, sizeof(esc1) - 1), + lws_sql_purify(esc2, lws_spa_get_string(pss->spa, FGS_EMAIL), sizeof(esc2) - 1), + u.pwhash.id, u.pwsalt.id, hash.id); + + if (sqlite3_exec(vhd->pdb, (char *)buffer, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("Unable to insert user: %s\n", + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + snprintf(s, sizeof(s), + "From: Noreply <%s>\n" + "To: %s <%s>\n" + "Subject: Registration verification\n" + "\n" + "Hello, %s\n\n" + "We received a registration from IP %s using this email,\n" + "to confirm it is legitimate, please click the link below.\n\n" + "%s/lwsgs-confirm?token=%s\n\n" + "If this request is unexpected, please ignore it and\n" + "no further action will be taken.\n\n" + "If you have any questions or concerns about this\n" + "automated email, you can contact a real person at\n" + "%s.\n" + "\n.\n", + lws_sql_purify(esc, vhd->email.email_from, sizeof(esc) - 1), + lws_sql_purify(esc1, lws_spa_get_string(pss->spa, FGS_USERNAME), sizeof(esc1) - 1), + lws_sql_purify(esc2, lws_spa_get_string(pss->spa, FGS_EMAIL), sizeof(esc2) - 1), + lws_sql_purify(esc3, lws_spa_get_string(pss->spa, FGS_USERNAME), sizeof(esc3) - 1), + lws_sql_purify(esc4, pss->ip, sizeof(esc4) - 1), + vhd->email_confirm_url, hash.id, + vhd->email_contact_person); + + snprintf((char *)buffer, sizeof(buffer) - 1, + "insert into email(username, content) values ('%s', '%s');", + lws_sql_purify(esc, lws_spa_get_string(pss->spa, FGS_USERNAME), + sizeof(esc) - 1), s); + + if (sqlite3_exec(vhd->pdb, (char *)buffer, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("Unable to insert email: %s\n", + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + return 0; +} diff --git a/plugins/generic-sessions/private-lwsgs.h b/plugins/generic-sessions/private-lwsgs.h new file mode 100644 index 00000000..cb408ae8 --- /dev/null +++ b/plugins/generic-sessions/private-lwsgs.h @@ -0,0 +1,161 @@ +/* + * ws protocol handler plugin for "generic sessions" + * + * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#define LWS_DLL +#define LWS_INTERNAL +#include "../lib/libwebsockets.h" + +#include <sqlite3.h> +#include <string.h> + +#define LWSGS_VERIFIED_ACCEPTED 100 + +enum { + FGS_USERNAME, + FGS_PASSWORD, + FGS_PASSWORD2, + FGS_EMAIL, + FGS_REGISTER, + FGS_GOOD, + FGS_BAD, + FGS_REG_GOOD, + FGS_REG_BAD, + FGS_ADMIN, + FGS_FORGOT, + FGS_FORGOT_GOOD, + FGS_FORGOT_BAD, + FGS_FORGOT_POST_GOOD, + FGS_FORGOT_POST_BAD, + FGS_CHANGE, + FGS_CURPW, + FGS_DELETE, +}; + +struct lwsgs_user { + char username[32]; + char ip[16]; + lwsgw_hash pwhash; + lwsgw_hash pwsalt; + lwsgw_hash token; + time_t created; + time_t last_forgot_validated; + char email[100]; + int verified; +}; + +struct per_vhost_data__gs { + struct lws_email email; + struct lws_context *context; + char session_db[256]; + char admin_user[32]; + char confounder[32]; + char email_contact_person[128]; + char email_title[128]; + char email_template[128]; + char email_confirm_url[128]; + lwsgw_hash admin_password_sha1; + sqlite3 *pdb; + int timeout_idle_secs; + int timeout_absolute_secs; + int timeout_anon_absolute_secs; + int timeout_email_secs; + time_t last_session_expire; + struct lwsgs_user u; +}; + +struct per_session_data__gs { + struct lws_spa *spa; + lwsgw_hash login_session; + lwsgw_hash delete_session; + unsigned int login_expires; + char onward[256]; + char result[500 + LWS_PRE]; + char urldec[500 + LWS_PRE]; + int result_len; + char ip[46]; + struct lws_process_html_state phs; + int spos; + + unsigned int logging_out:1; +}; + +/* utils.c */ + +int +lwsgs_lookup_callback_user(void *priv, int cols, char **col_val, + char **col_name); +void +lwsgw_cookie_from_session(lwsgw_hash *sid, time_t expires, char **p, char *end); +int +lwsgs_get_sid_from_wsi(struct lws *wsi, lwsgw_hash *sid); +int +lwsgs_lookup_session(struct per_vhost_data__gs *vhd, + const lwsgw_hash *sid, char *username, int len); +int +lwsgs_get_auth_level(struct per_vhost_data__gs *vhd, + const char *username); +int +lwsgs_check_credentials(struct per_vhost_data__gs *vhd, + const char *username, const char *password); +void +sha1_to_lwsgw_hash(unsigned char *hash, lwsgw_hash *shash); +unsigned int +lwsgs_now_secs(void); +int +lwsgw_check_admin(struct per_vhost_data__gs *vhd, + const char *username, const char *password); +int +lwsgs_hash_password(struct per_vhost_data__gs *vhd, + const char *password, struct lwsgs_user *u); +int +lwsgs_new_session_id(struct per_vhost_data__gs *vhd, + lwsgw_hash *sid, const char *username, int exp); +int +lwsgs_lookup_user(struct per_vhost_data__gs *vhd, + const char *username, struct lwsgs_user *u); +int +lwsgw_update_session(struct per_vhost_data__gs *vhd, + lwsgw_hash *hash, const char *user); +int +lwsgw_expire_old_sessions(struct per_vhost_data__gs *vhd); + + +/* handlers.c */ + +int +lwsgs_handler_confirm(struct per_vhost_data__gs *vhd, struct lws *wsi, + struct per_session_data__gs *pss); +int +lwsgs_handler_forgot(struct per_vhost_data__gs *vhd, struct lws *wsi, + struct per_session_data__gs *pss); +int +lwsgs_handler_check(struct per_vhost_data__gs *vhd, struct lws *wsi, + struct per_session_data__gs *pss); +int +lwsgs_handler_change_password(struct per_vhost_data__gs *vhd, struct lws *wsi, + struct per_session_data__gs *pss); +int +lwsgs_handler_forgot_pw_form(struct per_vhost_data__gs *vhd, struct lws *wsi, + struct per_session_data__gs *pss); +int +lwsgs_handler_register_form(struct per_vhost_data__gs *vhd, struct lws *wsi, + struct per_session_data__gs *pss); + diff --git a/plugins/generic-sessions/protocol_generic_sessions.c b/plugins/generic-sessions/protocol_generic_sessions.c new file mode 100644 index 00000000..6bf5d2b2 --- /dev/null +++ b/plugins/generic-sessions/protocol_generic_sessions.c @@ -0,0 +1,901 @@ +/* + * ws protocol handler plugin for "generic sessions" + * + * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-lwsgs.h" + +/* keep changes in sync with the enum in lwsgs.h */ +static const char * const param_names[] = { + "username", + "password", + "password2", + "email", + "register", + "good", + "bad", + "reg-good", + "reg-bad", + "admin", + "forgot", + "forgot-good", + "forgot-bad", + "forgot-post-good", + "forgot-post-bad", + "change", + "curpw", + "delete" +}; + +struct lwsgs_fill_args { + char *buf; + int len; +}; + +static const struct lws_protocols protocols[]; + +static int +lwsgs_lookup_callback_email(void *priv, int cols, char **col_val, + char **col_name) +{ + struct lwsgs_fill_args *a = (struct lwsgs_fill_args *)priv; + int n; + + for (n = 0; n < cols; n++) { + if (!strcmp(col_name[n], "content")) { + strncpy(a->buf, col_val[n], a->len - 1); + a->buf[a->len - 1] = '\0'; + continue; + } + } + return 0; +} + +static int +lwsgs_email_cb_get_body(struct lws_email *email, char *buf, int len) +{ + struct per_vhost_data__gs *vhd = (struct per_vhost_data__gs *)email->data; + struct lwsgs_fill_args a; + char ss[150], esc[50]; + + a.buf = buf; + a.len = len; + + snprintf(ss, sizeof(ss) - 1, + "select content from email where username='%s';", + lws_sql_purify(esc, vhd->u.username, sizeof(esc) - 1)); + + strncpy(buf, "failed", len); + if (sqlite3_exec(vhd->pdb, ss, lwsgs_lookup_callback_email, &a, + NULL) != SQLITE_OK) { + lwsl_err("Unable to lookup email: %s\n", + sqlite3_errmsg(vhd->pdb)); + + return 1; + } + + return 0; +} + +static int +lwsgs_email_cb_sent(struct lws_email *email) +{ + struct per_vhost_data__gs *vhd = (struct per_vhost_data__gs *)email->data; + char s[200], esc[50]; + + /* mark the user as having sent the verification email */ + snprintf(s, sizeof(s) - 1, + "update users set verified=1 where username='%s' and verified==0;", + lws_sql_purify(esc, vhd->u.username, sizeof(esc) - 1)); + if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("%s: Unable to update user: %s\n", __func__, + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + snprintf(s, sizeof(s) - 1, + "delete from email where username='%s';", + lws_sql_purify(esc, vhd->u.username, sizeof(esc) - 1)); + if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("%s: Unable to delete email text: %s\n", __func__, + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + return 0; +} + +static int +lwsgs_email_cb_on_next(struct lws_email *email) +{ + struct per_vhost_data__gs *vhd = lws_container_of(email, + struct per_vhost_data__gs, email); + char s[LWSGS_EMAIL_CONTENT_SIZE], esc[50]; + time_t now = lws_now_secs(); + + /* + * users not verified in 24h get deleted + */ + snprintf(s, sizeof(s) - 1, "delete from users where ((verified != %d)" + " and (creation_time <= %lu));", LWSGS_VERIFIED_ACCEPTED, + (unsigned long)now - vhd->timeout_email_secs); + if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("Unable to expire users: %s\n", + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + snprintf(s, sizeof(s) - 1, "update users set token_time=0 where " + "(token_time <= %lu);", + (unsigned long)now - vhd->timeout_email_secs); + if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("Unable to expire users: %s\n", + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + vhd->u.username[0] = '\0'; + snprintf(s, sizeof(s) - 1, "select username from email limit 1;"); + if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, &vhd->u, + NULL) != SQLITE_OK) { + lwsl_err("Unable to lookup user: %s\n", sqlite3_errmsg(vhd->pdb)); + return 1; + } + + snprintf(s, sizeof(s) - 1, + "select username, creation_time, email, ip, verified, token" + " from users where username='%s' limit 1;", + lws_sql_purify(esc, vhd->u.username, sizeof(esc) - 1)); + if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, &vhd->u, + NULL) != SQLITE_OK) { + lwsl_err("Unable to lookup user: %s\n", + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + if (!vhd->u.username[0]) + /* + * nothing to do, we are idle and no suitable + * accounts waiting for verification. When a new user + * is added we will get kicked to try again. + */ + return 1; + + strncpy(email->email_to, vhd->u.email, sizeof(email->email_to) - 1); + + return 0; +} + + +struct lwsgs_subst_args +{ + struct per_session_data__gs *pss; + struct per_vhost_data__gs *vhd; + struct lws *wsi; +}; + +static const char * +lwsgs_subst(void *data, int index) +{ + struct lwsgs_subst_args *a = (struct lwsgs_subst_args *)data; + struct lwsgs_user u; + lwsgw_hash sid; + char esc[50], s[100]; + int n; + + a->pss->result[0] = '\0'; + u.email[0] = '\0'; + if (!lwsgs_get_sid_from_wsi(a->wsi, &sid)) { + if (lwsgs_lookup_session(a->vhd, &sid, a->pss->result, 31)) { + lwsl_notice("sid lookup for %s failed\n", sid.id); + a->pss->delete_session = sid; + return NULL; + } + snprintf(s, sizeof(s) - 1, "select username,email " + "from users where username = '%s';", + lws_sql_purify(esc, a->pss->result, sizeof(esc) - 1)); + if (sqlite3_exec(a->vhd->pdb, s, lwsgs_lookup_callback_user, + &u, NULL) != SQLITE_OK) { + lwsl_err("Unable to lookup token: %s\n", + sqlite3_errmsg(a->vhd->pdb)); + a->pss->delete_session = sid; + return NULL; + } + } else + lwsl_notice("no sid\n"); + + strncpy(a->pss->result + 32, u.email, 100); + + switch (index) { + case 0: + return a->pss->result; + + case 1: + n = lwsgs_get_auth_level(a->vhd, a->pss->result); + sprintf(a->pss->result, "%d", n); + return a->pss->result; + case 2: + return a->pss->result + 32; + } + + return NULL; +} + +static int +callback_generic_sessions(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct per_session_data__gs *pss = (struct per_session_data__gs *)user; + const struct lws_protocol_vhost_options *pvo; + struct per_vhost_data__gs *vhd = (struct per_vhost_data__gs *) + lws_protocol_vh_priv_get(lws_get_vhost(wsi), + &protocols[0]); + char cookie[1024], username[32], *pc = cookie; + unsigned char buffer[LWS_PRE + LWSGS_EMAIL_CONTENT_SIZE]; + struct lws_process_html_args *args; + struct lws_session_info *sinfo; + char s[LWSGS_EMAIL_CONTENT_SIZE]; + unsigned char *p, *start, *end; + sqlite3_stmt *sm; + lwsgw_hash sid; + const char *cp; + int n; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */ + + vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + &protocols[0], sizeof(struct per_vhost_data__gs)); + if (!vhd) + return 1; + vhd->context = lws_get_context(wsi); + + /* defaults */ + vhd->timeout_idle_secs = 600; + vhd->timeout_absolute_secs = 36000; + vhd->timeout_anon_absolute_secs = 1200; + vhd->timeout_email_secs = 24 * 3600; + strcpy(vhd->email.email_helo, "unconfigured.com"); + strcpy(vhd->email.email_from, "noreply@unconfigured.com"); + strcpy(vhd->email_title, "Registration Email from unconfigured"); + strcpy(vhd->email.email_smtp_ip, "127.0.0.1"); + + vhd->email.on_next = lwsgs_email_cb_on_next; + vhd->email.on_get_body = lwsgs_email_cb_get_body; + vhd->email.on_sent = lwsgs_email_cb_sent; + vhd->email.data = (void *)vhd; + + pvo = (const struct lws_protocol_vhost_options *)in; + while (pvo) { + if (!strcmp(pvo->name, "admin-user")) + strncpy(vhd->admin_user, pvo->value, + sizeof(vhd->admin_user) - 1); + if (!strcmp(pvo->name, "admin-password-sha1")) + strncpy(vhd->admin_password_sha1.id, pvo->value, + sizeof(vhd->admin_password_sha1.id) - 1); + if (!strcmp(pvo->name, "session-db")) + strncpy(vhd->session_db, pvo->value, + sizeof(vhd->session_db) - 1); + if (!strcmp(pvo->name, "confounder")) + strncpy(vhd->confounder, pvo->value, + sizeof(vhd->confounder) - 1); + if (!strcmp(pvo->name, "email-from")) + strncpy(vhd->email.email_from, pvo->value, + sizeof(vhd->email.email_from) - 1); + if (!strcmp(pvo->name, "email-helo")) + strncpy(vhd->email.email_helo, pvo->value, + sizeof(vhd->email.email_helo) - 1); + if (!strcmp(pvo->name, "email-template")) + strncpy(vhd->email_template, pvo->value, + sizeof(vhd->email_template) - 1); + if (!strcmp(pvo->name, "email-title")) + strncpy(vhd->email_title, pvo->value, + sizeof(vhd->email_title) - 1); + if (!strcmp(pvo->name, "email-contact-person")) + strncpy(vhd->email_contact_person, pvo->value, + sizeof(vhd->email_contact_person) - 1); + if (!strcmp(pvo->name, "email-confirm-url-base")) + strncpy(vhd->email_confirm_url, pvo->value, + sizeof(vhd->email_confirm_url) - 1); + if (!strcmp(pvo->name, "email-server-ip")) + strncpy(vhd->email.email_smtp_ip, pvo->value, + sizeof(vhd->email.email_smtp_ip) - 1); + + if (!strcmp(pvo->name, "timeout-idle-secs")) + vhd->timeout_idle_secs = atoi(pvo->value); + if (!strcmp(pvo->name, "timeout-absolute-secs")) + vhd->timeout_absolute_secs = atoi(pvo->value); + if (!strcmp(pvo->name, "timeout-anon-absolute-secs")) + vhd->timeout_anon_absolute_secs = atoi(pvo->value); + if (!strcmp(pvo->name, "email-expire")) + vhd->timeout_email_secs = atoi(pvo->value); + pvo = pvo->next; + } + if (!vhd->admin_user[0] || + !vhd->admin_password_sha1.id[0] || + !vhd->session_db[0]) { + lwsl_err("generic-sessions: " + "You must give \"admin-user\", " + "\"admin-password-sha1\", " + "and \"session_db\" per-vhost options\n"); + return 1; + } + + if (sqlite3_open_v2(vhd->session_db, &vhd->pdb, + SQLITE_OPEN_READWRITE | + SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) { + lwsl_err("Unable to open session db %s: %s\n", + vhd->session_db, sqlite3_errmsg(vhd->pdb)); + + return 1; + } + + if (sqlite3_prepare(vhd->pdb, + "create table if not exists sessions (" + " name char(40)," + " username varchar(32)," + " expire integer" + ");", + -1, &sm, NULL) != SQLITE_OK) { + lwsl_err("Unable to prepare session table init: %s\n", + sqlite3_errmsg(vhd->pdb)); + + return 1; + } + + if (sqlite3_step(sm) != SQLITE_DONE) { + lwsl_err("Unable to run session table init: %s\n", + sqlite3_errmsg(vhd->pdb)); + + return 1; + } + sqlite3_finalize(sm); + + if (sqlite3_exec(vhd->pdb, + "create table if not exists users (" + " username varchar(32)," + " creation_time integer," + " ip varchar(46)," + " email varchar(100)," + " pwhash varchar(42)," + " pwsalt varchar(42)," + " pwchange_time integer," + " token varchar(42)," + " verified integer," + " token_time integer," + " last_forgot_validated integer," + " primary key (username)" + ");", + NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("Unable to create user table: %s\n", + sqlite3_errmsg(vhd->pdb)); + + return 1; + } + + sprintf(s, "create table if not exists email (" + " username varchar(32)," + " content blob," + " primary key (username)" + ");"); + if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("Unable to create user table: %s\n", + sqlite3_errmsg(vhd->pdb)); + + return 1; + } + + lws_email_init(&vhd->email, lws_uv_getloop(vhd->context, 0), + LWSGS_EMAIL_CONTENT_SIZE); + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + if (vhd->pdb) { + sqlite3_close(vhd->pdb); + vhd->pdb = NULL; + } + lws_email_destroy(&vhd->email); + break; + + case LWS_CALLBACK_HTTP: + lwsl_info("LWS_CALLBACK_HTTP: %s\n", in); + + pss->login_session.id[0] = '\0'; + pss->phs.pos = 0; + strncpy(pss->onward, (char *)in, sizeof(pss->onward)); + + if (!strcmp((const char *)in, "/lwsgs-forgot")) { + lwsgs_handler_forgot(vhd, wsi, pss); + goto redirect_with_cookie; + } + + if (!strcmp((const char *)in, "/lwsgs-confirm")) { + lwsgs_handler_confirm(vhd, wsi, pss); + goto redirect_with_cookie; + } + if (!strcmp((const char *)in, "/lwsgs-check")) { + lwsgs_handler_check(vhd, wsi, pss); + goto try_to_reuse; + } + + if (!strcmp((const char *)in, "/lwsgs-login")) + break; + if (!strcmp((const char *)in, "/lwsgs-logout")) + break; + if (!strcmp((const char *)in, "/lwsgs-forgot")) + break; + if (!strcmp((const char *)in, "/lwsgs-change")) + break; + + /* if no legitimate url for GET, return 404 */ + + lwsl_err("http doing 404 on %s\n", in); + lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); + goto try_to_reuse; + + case LWS_CALLBACK_CHECK_ACCESS_RIGHTS: + n = 0; + username[0] = '\0'; + sid.id[0] = '\0'; + args = (struct lws_process_html_args *)in; + lwsl_debug("LWS_CALLBACK_CHECK_ACCESS_RIGHTS\n"); + if (!lwsgs_get_sid_from_wsi(wsi, &sid)) { + if (lwsgs_lookup_session(vhd, &sid, username, sizeof(username))) { + static const char * const oprot[] = { + "http://", "https://" + }; + lwsl_notice("session lookup for %s failed, probably expired\n", sid.id); + pss->delete_session = sid; + args->final = 1; /* signal we dealt with it */ + if (lws_hdr_copy(wsi, cookie, sizeof(cookie) - 1, + WSI_TOKEN_HOST) < 0) + return 1; + snprintf(pss->onward, sizeof(pss->onward) - 1, + "%s%s%s", oprot[lws_is_ssl(wsi)], + cookie, args->p); + lwsl_notice("redirecting to ourselves with cookie refresh\n"); + /* we need a redirect to ourselves, session cookie is expired */ + goto redirect_with_cookie; + } + } else + lwsl_notice("failed to get sid from wsi\n"); + + n = lwsgs_get_auth_level(vhd, username); + + if ((args->max_len & n) != args->max_len) { + lwsl_notice("Access rights fail 0x%X vs 0x%X (cookie %s)\n", + args->max_len, n, sid.id); + return 1; + } + lwsl_debug("Access rights OK\n"); + break; + + case LWS_CALLBACK_SESSION_INFO: + { + struct lwsgs_user u; + sinfo = (struct lws_session_info *)in; + sinfo->username[0] = '\0'; + sinfo->email[0] = '\0'; + sinfo->ip[0] = '\0'; + sinfo->session[0] = '\0'; + sinfo->mask = 0; + + sid.id[0] = '\0'; + lwsl_debug("LWS_CALLBACK_SESSION_INFO\n"); + if (lwsgs_get_sid_from_wsi(wsi, &sid)) + break; + if (lwsgs_lookup_session(vhd, &sid, username, sizeof(username))) + break; + + snprintf(s, sizeof(s) - 1, + "select username, email from users where username='%s';", + username); + if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, &u, NULL) != + SQLITE_OK) { + lwsl_err("Unable to lookup token: %s\n", + sqlite3_errmsg(vhd->pdb)); + break; + } + strncpy(sinfo->username, u.username, sizeof(sinfo->username)); + strncpy(sinfo->email, u.email, sizeof(sinfo->email)); + strncpy(sinfo->session, sid.id, sizeof(sinfo->session)); + sinfo->mask = lwsgs_get_auth_level(vhd, username); + lws_get_peer_simple(wsi, sinfo->ip, sizeof(sinfo->ip)); + } + + break; + + case LWS_CALLBACK_PROCESS_HTML: + + args = (struct lws_process_html_args *)in; + { + static const char * const vars[] = { + "$lwsgs_user", + "$lwsgs_auth", + "$lwsgs_email" + }; + struct lwsgs_subst_args a; + + a.vhd = vhd; + a.pss = pss; + a.wsi = wsi; + + pss->phs.vars = vars; + pss->phs.count_vars = ARRAY_SIZE(vars); + pss->phs.replace = lwsgs_subst; + pss->phs.data = &a; + + if (lws_chunked_html_process(args, &pss->phs)) + return -1; + } + break; + + case LWS_CALLBACK_HTTP_BODY: + if (len < 2) + break; + + if (!pss->spa) { + pss->spa = lws_spa_create(wsi, param_names, + ARRAY_SIZE(param_names), 1024, + NULL, NULL); + if (!pss->spa) + return -1; + } + + if (lws_spa_process(pss->spa, in, len)) { + lwsl_notice("spa process blew\n"); + return -1; + } + break; + + case LWS_CALLBACK_HTTP_BODY_COMPLETION: + + if (!pss->spa) + break; + + lwsl_info("LWS_CALLBACK_HTTP_BODY_COMPLETION: %s\n", pss->onward); + lws_spa_finalize(pss->spa); + + if (!strcmp((char *)pss->onward, "/lwsgs-change")) { + if (!lwsgs_handler_change_password(vhd, wsi, pss)) { + cp = lws_spa_get_string(pss->spa, FGS_GOOD); + goto pass; + } + + cp = lws_spa_get_string(pss->spa, FGS_BAD); + lwsl_notice("user/password no good %s\n", + lws_spa_get_string(pss->spa, FGS_USERNAME)); + strncpy(pss->onward, cp, sizeof(pss->onward) - 1); + pss->onward[sizeof(pss->onward) - 1] = '\0'; + goto completion_flow; + } + + if (!strcmp((char *)pss->onward, "/lwsgs-login")) { + if (lws_spa_get_string(pss->spa, FGS_FORGOT) && + lws_spa_get_string(pss->spa, FGS_FORGOT)[0]) { + if (lwsgs_handler_forgot_pw_form(vhd, wsi, pss)) { + n = FGS_FORGOT_BAD; + goto reg_done; + } + /* get the email monitor to take a look */ + lws_email_check(&vhd->email); + n = FGS_FORGOT_GOOD; + goto reg_done; + } + + if (!lws_spa_get_string(pss->spa, FGS_USERNAME) || + !lws_spa_get_string(pss->spa, FGS_PASSWORD)) { + lwsl_notice("username '%s' or pw '%s' missing\n", + lws_spa_get_string(pss->spa, FGS_USERNAME), + lws_spa_get_string(pss->spa, FGS_PASSWORD)); + return -1; + } + + if (lws_spa_get_string(pss->spa, FGS_REGISTER) && + lws_spa_get_string(pss->spa, FGS_REGISTER)[0]) { + + if (lwsgs_handler_register_form(vhd, wsi, pss)) + n = FGS_REG_BAD; + else { + n = FGS_REG_GOOD; + + /* get the email monitor to take a look */ + lws_email_check(&vhd->email); + } +reg_done: + strncpy(pss->onward, lws_spa_get_string(pss->spa, n), + sizeof(pss->onward) - 1); + pss->onward[sizeof(pss->onward) - 1] = '\0'; + pss->login_expires = 0; + pss->logging_out = 1; + goto completion_flow; + } + + /* we have the username and password... check if admin */ + if (lwsgw_check_admin(vhd, lws_spa_get_string(pss->spa, FGS_USERNAME), + lws_spa_get_string(pss->spa, FGS_PASSWORD))) { + if (lws_spa_get_string(pss->spa, FGS_ADMIN)) + cp = lws_spa_get_string(pss->spa, FGS_ADMIN); + else + if (lws_spa_get_string(pss->spa, FGS_GOOD)) + cp = lws_spa_get_string(pss->spa, FGS_GOOD); + else { + lwsl_info("No admin or good target url in form\n"); + return -1; + } + lwsl_debug("admin\n"); + goto pass; + } + + /* check users in database */ + + if (!lwsgs_check_credentials(vhd, lws_spa_get_string(pss->spa, FGS_USERNAME), + lws_spa_get_string(pss->spa, FGS_PASSWORD))) { + lwsl_info("pw hash check met\n"); + cp = lws_spa_get_string(pss->spa, FGS_GOOD); + goto pass; + } else + lwsl_notice("user/password no good %s\n", + lws_spa_get_string(pss->spa, FGS_USERNAME)); + + if (!lws_spa_get_string(pss->spa, FGS_BAD)) { + lwsl_info("No admin or good target url in form\n"); + return -1; + } + + strncpy(pss->onward, lws_spa_get_string(pss->spa, FGS_BAD), + sizeof(pss->onward) - 1); + pss->onward[sizeof(pss->onward) - 1] = '\0'; + lwsl_debug("failed\n"); + + goto completion_flow; + } + + if (!strcmp((char *)pss->onward, "/lwsgs-logout")) { + + lwsl_notice("/logout\n"); + + if (lwsgs_get_sid_from_wsi(wsi, &pss->login_session)) { + lwsl_notice("not logged in...\n"); + return 1; + } + + lwsgw_update_session(vhd, &pss->login_session, ""); + + if (!lws_spa_get_string(pss->spa, FGS_GOOD)) { + lwsl_info("No admin or good target url in form\n"); + return -1; + } + + strncpy(pss->onward, lws_spa_get_string(pss->spa, FGS_GOOD), sizeof(pss->onward) - 1); + pss->onward[sizeof(pss->onward) - 1] = '\0'; + + pss->login_expires = 0; + pss->logging_out = 1; + + goto completion_flow; + } + + break; + +pass: + strncpy(pss->onward, cp, sizeof(pss->onward) - 1); + pss->onward[sizeof(pss->onward) - 1] = '\0'; + + if (lwsgs_get_sid_from_wsi(wsi, &sid)) + sid.id[0] = '\0'; + + pss->login_expires = lws_now_secs() + + vhd->timeout_absolute_secs; + + if (!sid.id[0]) { + /* we need to create a new, authorized session */ + + if (lwsgs_new_session_id(vhd, &pss->login_session, + lws_spa_get_string(pss->spa, FGS_USERNAME), + pss->login_expires)) + goto try_to_reuse; + + lwsl_info("Creating new session: %s\n", + pss->login_session.id); + } else { + /* + * we can just update the existing session to be + * authorized + */ + lwsl_info("Authorizing existing session %s", sid.id); + lwsgw_update_session(vhd, &sid, + lws_spa_get_string(pss->spa, FGS_USERNAME)); + pss->login_session = sid; + } + +completion_flow: + lwsgw_expire_old_sessions(vhd); + goto redirect_with_cookie; + + case LWS_CALLBACK_HTTP_DROP_PROTOCOL: + if (pss->spa) { + lws_spa_destroy(pss->spa); + pss->spa = NULL; + } + break; + + case LWS_CALLBACK_ADD_HEADERS: + lwsgw_expire_old_sessions(vhd); + + args = (struct lws_process_html_args *)in; + + if (pss->delete_session.id[0]) { + pc = cookie; + lwsgw_cookie_from_session(&pss->delete_session, 0, &pc, + cookie + sizeof(cookie) - 1); + + lwsl_info("deleting cookie '%s'\n", cookie); + + if (lws_add_http_header_by_name(wsi, + (unsigned char *)"set-cookie:", + (unsigned char *)cookie, pc - cookie, + (unsigned char **)&args->p, + (unsigned char *)args->p + args->max_len)) + return 1; + } + + if (!pss->login_session.id[0]) + lwsgs_get_sid_from_wsi(wsi, &pss->login_session); + + if (!pss->login_session.id[0] && !pss->logging_out) { + + pss->login_expires = lws_now_secs() + + vhd->timeout_anon_absolute_secs; + if (lwsgs_new_session_id(vhd, &pss->login_session, "", + pss->login_expires)) + goto try_to_reuse; + pc = cookie; + lwsgw_cookie_from_session(&pss->login_session, + pss->login_expires, &pc, + cookie + sizeof(cookie) - 1); + + lwsl_info("LWS_CALLBACK_ADD_HEADERS: setting cookie '%s'\n", cookie); + if (lws_add_http_header_by_name(wsi, + (unsigned char *)"set-cookie:", + (unsigned char *)cookie, pc - cookie, + (unsigned char **)&args->p, + (unsigned char *)args->p + args->max_len)) + return 1; + } + break; + + default: + break; + } + + return 0; + +redirect_with_cookie: + p = buffer + LWS_PRE; + start = p; + end = p + sizeof(buffer) - LWS_PRE; + + if (lws_add_http_header_status(wsi, HTTP_STATUS_SEE_OTHER, &p, end)) + return 1; + + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, + (unsigned char *)pss->onward, + strlen(pss->onward), &p, end)) + return 1; + + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)"text/html", 9, &p, end)) + return 1; + if (lws_add_http_header_content_length(wsi, 0, &p, end)) + return 1; + + if (pss->delete_session.id[0]) { + lwsgw_cookie_from_session(&pss->delete_session, 0, &pc, + cookie + sizeof(cookie) - 1); + + lwsl_notice("deleting cookie '%s'\n", cookie); + + if (lws_add_http_header_by_name(wsi, + (unsigned char *)"set-cookie:", + (unsigned char *)cookie, pc - cookie, + &p, end)) + return 1; + } + + if (!pss->login_session.id[0]) { + pss->login_expires = lws_now_secs() + + vhd->timeout_anon_absolute_secs; + if (lwsgs_new_session_id(vhd, &pss->login_session, "", + pss->login_expires)) + return 1; + } else + pss->login_expires = lws_now_secs() + + vhd->timeout_absolute_secs; + + if (pss->login_session.id[0] || pss->logging_out) { + /* + * we succeeded to login, we must issue a login + * cookie with the prepared data + */ + pc = cookie; + + lwsgw_cookie_from_session(&pss->login_session, + pss->login_expires, &pc, + cookie + sizeof(cookie) - 1); + + lwsl_info("setting cookie '%s'\n", cookie); + + pss->logging_out = 0; + + if (lws_add_http_header_by_name(wsi, + (unsigned char *)"set-cookie:", + (unsigned char *)cookie, pc - cookie, + &p, end)) + return 1; + } + + if (lws_finalize_http_header(wsi, &p, end)) + return 1; + + n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); + if (n < 0) + return 1; + + /* fallthru */ + +try_to_reuse: + if (lws_http_transaction_completed(wsi)) + return -1; + + return 0; +} + +static const struct lws_protocols protocols[] = { + { + "protocol-generic-sessions", + callback_generic_sessions, + sizeof(struct per_session_data__gs), + 1024, + }, +}; + +LWS_EXTERN LWS_VISIBLE int +init_protocol_generic_sessions(struct lws_context *context, + struct lws_plugin_capability *c) +{ + if (c->api_magic != LWS_PLUGIN_API_MAGIC) { + lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC, + c->api_magic); + return 1; + } + + c->protocols = protocols; + c->count_protocols = ARRAY_SIZE(protocols); + c->extensions = NULL; + c->count_extensions = 0; + + return 0; +} + +LWS_EXTERN LWS_VISIBLE int +destroy_protocol_generic_sessions(struct lws_context *context) +{ + return 0; +} diff --git a/plugins/generic-sessions/utils.c b/plugins/generic-sessions/utils.c new file mode 100644 index 00000000..0d458d7b --- /dev/null +++ b/plugins/generic-sessions/utils.c @@ -0,0 +1,450 @@ +/* + * ws protocol handler plugin for "generic sessions" + * + * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-lwsgs.h" + +void +sha1_to_lwsgw_hash(unsigned char *hash, lwsgw_hash *shash) +{ + static const char *hex = "0123456789abcdef"; + char *p = shash->id; + int n; + + for (n = 0; n < 20; n++) { + *p++ = hex[hash[n] >> 4]; + *p++ = hex[hash[n] & 15]; + } + + *p = '\0'; +} + +int +lwsgw_check_admin(struct per_vhost_data__gs *vhd, + const char *username, const char *password) +{ + lwsgw_hash_bin hash_bin; + lwsgw_hash pw_hash; + + if (strcmp(vhd->admin_user, username)) + return 0; + + lws_SHA1((unsigned char *)password, strlen(password), hash_bin.bin); + sha1_to_lwsgw_hash(hash_bin.bin, &pw_hash); + + return !strcmp(vhd->admin_password_sha1.id, pw_hash.id); +} + +/* + * secure cookie: it can only be passed over https where it cannot be + * snooped in transit + * HttpOnly: it can only be accessed via http[s] transport, it cannot be + * gotten at by JS + */ +void +lwsgw_cookie_from_session(lwsgw_hash *sid, time_t expires, char **p, char *end) +{ + struct tm *tm = gmtime(&expires); + time_t n = lws_now_secs(); + + *p += snprintf(*p, end - *p, "id=%s;Expires=", sid->id); +#ifdef WIN32 + *p += strftime(*p, end - *p, "%Y %H:%M %Z", tm); +#else + *p += strftime(*p, end - *p, "%F %H:%M %Z", tm); +#endif + *p += snprintf(*p, end - *p, ";path=/"); + *p += snprintf(*p, end - *p, ";Max-Age=%lu", (unsigned long)(expires - n)); +// *p += snprintf(*p, end - *p, ";secure"); + *p += snprintf(*p, end - *p, ";HttpOnly"); +} + +int +lwsgw_expire_old_sessions(struct per_vhost_data__gs *vhd) +{ + time_t n = lws_now_secs(); + char s[200]; + + if (n - vhd->last_session_expire < 5) + return 0; + + vhd->last_session_expire = n; + + snprintf(s, sizeof(s) - 1, + "delete from sessions where " + "expire <= %lu;", (unsigned long)n); + + if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("Unable to expire sessions: %s\n", + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + return 0; +} + +int +lwsgw_update_session(struct per_vhost_data__gs *vhd, + lwsgw_hash *hash, const char *user) +{ + time_t n = lws_now_secs(); + char s[200], esc[50], esc1[50]; + + if (user[0]) + n += vhd->timeout_absolute_secs; + else + n += vhd->timeout_anon_absolute_secs; + + snprintf(s, sizeof(s) - 1, + "update sessions set expire=%lu,username='%s' where name='%s';", + (unsigned long)n, + lws_sql_purify(esc, user, sizeof(esc)), + lws_sql_purify(esc1, hash->id, sizeof(esc1))); + + if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("Unable to update session: %s\n", + sqlite3_errmsg(vhd->pdb)); + return 1; + } + + return 0; +} + +static int +lwsgw_session_from_cookie(const char *cookie, lwsgw_hash *sid) +{ + const char *p = cookie; + int n; + + while (*p) { + if (p[0] == 'i' && p[1] == 'd' && p[2] == '=') { + p += 3; + break; + } + p++; + } + if (!*p) { + lwsl_info("no id= in cookie\n"); + return 1; + } + + for (n = 0; n < sizeof(sid->id) - 1 && *p; n++) { + /* our SID we issue only has these chars */ + if ((*p >= '0' && *p <= '9') || + (*p >= 'a' && *p <= 'f')) + sid->id[n] = *p++; + else { + lwsl_info("bad chars in cookie id %c\n", *p); + return 1; + } + } + + if (n < sizeof(sid->id) - 1) { + lwsl_info("cookie id too short\n"); + return 1; + } + + sid->id[sizeof(sid->id) - 1] = '\0'; + + return 0; +} + +int +lwsgs_get_sid_from_wsi(struct lws *wsi, lwsgw_hash *sid) +{ + char cookie[1024]; + + /* fail it on no cookie */ + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { + lwsl_info("%s: no cookie\n", __func__); + return 1; + } + if (lws_hdr_copy(wsi, cookie, sizeof cookie, WSI_TOKEN_HTTP_COOKIE) < 0) { + lwsl_info("cookie copy failed\n"); + return 1; + } + /* extract the sid from the cookie */ + if (lwsgw_session_from_cookie(cookie, sid)) { + lwsl_info("session from cookie failed\n"); + return 1; + } + + return 0; +} + +struct lla { + char *username; + int len; + int results; +}; + +static int +lwsgs_lookup_callback(void *priv, int cols, char **col_val, char **col_name) +{ + struct lla *lla = (struct lla *)priv; + + //lwsl_err("%s: %d\n", __func__, cols); + + if (cols) + lla->results = 0; + if (col_val && col_val[0]) { + strncpy(lla->username, col_val[0], lla->len); + lla->username[lla->len - 1] = '\0'; + lwsl_info("%s: %s\n", __func__, lla->username); + } + + return 0; +} + +int +lwsgs_lookup_session(struct per_vhost_data__gs *vhd, + const lwsgw_hash *sid, char *username, int len) +{ + struct lla lla = { username, len, 1 }; + char s[150], esc[50]; + + lwsgw_expire_old_sessions(vhd); + + snprintf(s, sizeof(s) - 1, + "select username from sessions where name = '%s';", + lws_sql_purify(esc, sid->id, sizeof(esc) - 1)); + + if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback, &lla, NULL) != SQLITE_OK) { + lwsl_err("Unable to create user table: %s\n", + sqlite3_errmsg(vhd->pdb)); + + return 1; + } + + /* 0 if found */ + return lla.results; +} + +int +lwsgs_lookup_callback_user(void *priv, int cols, char **col_val, char **col_name) +{ + struct lwsgs_user *u = (struct lwsgs_user *)priv; + int n; + + for (n = 0; n < cols; n++) { + if (!strcmp(col_name[n], "username")) { + strncpy(u->username, col_val[n], sizeof(u->username) - 1); + u->username[sizeof(u->username) - 1] = '\0'; + continue; + } + if (!strcmp(col_name[n], "ip")) { + strncpy(u->ip, col_val[n], sizeof(u->ip) - 1); + u->ip[sizeof(u->ip) - 1] = '\0'; + continue; + } + if (!strcmp(col_name[n], "creation_time")) { + u->created = atol(col_val[n]); + continue; + } + if (!strcmp(col_name[n], "last_forgot_validated")) { + if (col_val[n]) + u->last_forgot_validated = atol(col_val[n]); + else + u->last_forgot_validated = 0; + continue; + } + if (!strcmp(col_name[n], "email")) { + strncpy(u->email, col_val[n], sizeof(u->email) - 1); + u->email[sizeof(u->email) - 1] = '\0'; + continue; + } + if (!strcmp(col_name[n], "verified")) { + u->verified = atoi(col_val[n]); + continue; + } + if (!strcmp(col_name[n], "pwhash")) { + strncpy(u->pwhash.id, col_val[n], sizeof(u->pwhash.id) - 1); + u->pwhash.id[sizeof(u->pwhash.id) - 1] = '\0'; + continue; + } + if (!strcmp(col_name[n], "pwsalt")) { + strncpy(u->pwsalt.id, col_val[n], sizeof(u->pwsalt.id) - 1); + u->pwsalt.id[sizeof(u->pwsalt.id) - 1] = '\0'; + continue; + } + if (!strcmp(col_name[n], "token")) { + strncpy(u->token.id, col_val[n], sizeof(u->token.id) - 1); + u->token.id[sizeof(u->token.id) - 1] = '\0'; + continue; + } + } + return 0; +} + +int +lwsgs_lookup_user(struct per_vhost_data__gs *vhd, + const char *username, struct lwsgs_user *u) +{ + char s[150], esc[50]; + + u->username[0] = '\0'; + snprintf(s, sizeof(s) - 1, + "select username,creation_time,ip,email,verified,pwhash,pwsalt,last_forgot_validated " + "from users where username = '%s';", + lws_sql_purify(esc, username, sizeof(esc) - 1)); + + if (sqlite3_exec(vhd->pdb, s, lwsgs_lookup_callback_user, u, NULL) != + SQLITE_OK) { + lwsl_err("Unable to lookup user: %s\n", + sqlite3_errmsg(vhd->pdb)); + + return -1; + } + + return !u->username[0]; +} + +int +lwsgs_new_session_id(struct per_vhost_data__gs *vhd, + lwsgw_hash *sid, const char *username, int exp) +{ + unsigned char sid_rand[20]; + const char *u; + char s[300], esc[50], esc1[50]; + + if (username) + u = username; + else + u = ""; + + if (!sid) + return 1; + + memset(sid, 0, sizeof(*sid)); + + if (lws_get_random(vhd->context, sid_rand, sizeof(sid_rand)) != + sizeof(sid_rand)) + return 1; + + sha1_to_lwsgw_hash(sid_rand, sid); + + snprintf(s, sizeof(s) - 1, + "insert into sessions(name, username, expire) " + "values ('%s', '%s', %u);", + lws_sql_purify(esc, sid->id, sizeof(esc) - 1), + lws_sql_purify(esc1, u, sizeof(esc1) - 1), exp); + + if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) { + lwsl_err("Unable to insert session: %s\n", + sqlite3_errmsg(vhd->pdb)); + + return 1; + } + + return 0; +} + +int +lwsgs_get_auth_level(struct per_vhost_data__gs *vhd, + const char *username) +{ + struct lwsgs_user u; + int n = 0; + + /* we are logged in as some kind of user */ + if (username[0]) { + n |= LWSGS_AUTH_LOGGED_IN; + /* we are logged in as admin */ + if (!strcmp(username, vhd->admin_user)) + n |= LWSGS_AUTH_VERIFIED | LWSGS_AUTH_ADMIN; /* automatically verified */ + } + + if (!lwsgs_lookup_user(vhd, username, &u)) { + if ((u.verified & 0xff) == LWSGS_VERIFIED_ACCEPTED) + n |= LWSGS_AUTH_VERIFIED; + + if (u.last_forgot_validated > lws_now_secs() - 300) + n |= LWSGS_AUTH_FORGOT_FLOW; + } + + return n; +} + +int +lwsgs_check_credentials(struct per_vhost_data__gs *vhd, + const char *username, const char *password) +{ + unsigned char buffer[300]; + lwsgw_hash_bin hash_bin; + struct lwsgs_user u; + lwsgw_hash hash; + int n; + + if (lwsgs_lookup_user(vhd, username, &u)) + return -1; + + lwsl_info("user %s found, salt '%s'\n", username, u.pwsalt.id); + + /* [password in ascii][salt] */ + n = snprintf((char *)buffer, sizeof(buffer) - 1, + "%s-%s-%s", password, vhd->confounder, u.pwsalt.id); + + /* sha1sum of password + salt */ + lws_SHA1(buffer, n, hash_bin.bin); + sha1_to_lwsgw_hash(&hash_bin.bin[0], &hash); + + return !!strcmp(hash.id, u.pwhash.id); +} + +/* sets u->pwsalt and u->pwhash */ + +int +lwsgs_hash_password(struct per_vhost_data__gs *vhd, + const char *password, struct lwsgs_user *u) +{ + lwsgw_hash_bin hash_bin; + lwsgw_hash hash; + unsigned char sid_rand[20]; + unsigned char buffer[150]; + int n; + + /* create a random salt as big as the hash */ + + if (lws_get_random(vhd->context, sid_rand, + sizeof(sid_rand)) != + sizeof(sid_rand)) { + lwsl_err("Problem getting random for salt\n"); + return 1; + } + sha1_to_lwsgw_hash(sid_rand, &u->pwsalt); + + if (lws_get_random(vhd->context, sid_rand, + sizeof(sid_rand)) != + sizeof(sid_rand)) { + lwsl_err("Problem getting random for token\n"); + return 1; + } + sha1_to_lwsgw_hash(sid_rand, &hash); + + /* [password in ascii][salt] */ + n = snprintf((char *)buffer, sizeof(buffer) - 1, + "%s-%s-%s", password, vhd->confounder, u->pwsalt.id); + + /* sha1sum of password + salt */ + lws_SHA1(buffer, n, hash_bin.bin); + sha1_to_lwsgw_hash(&hash_bin.bin[0], &u->pwhash); + + return 0; +} diff --git a/plugins/protocol_lws_status.c b/plugins/protocol_lws_status.c index 817fe76d..912eec85 100644 --- a/plugins/protocol_lws_status.c +++ b/plugins/protocol_lws_status.c @@ -25,6 +25,7 @@ #include <time.h> #include <string.h> #ifdef WIN32 +#include <io.h> #include <gettimeofday.h> #endif diff --git a/plugins/protocol_post_demo.c b/plugins/protocol_post_demo.c index 9f0cab89..8520a315 100644 --- a/plugins/protocol_post_demo.c +++ b/plugins/protocol_post_demo.c @@ -23,6 +23,13 @@ #include "../lib/libwebsockets.h" #include <string.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#ifdef WIN32 +#include <io.h> +#endif +#include <stdio.h> struct per_session_data__post_demo { struct lws_spa *spa; @@ -31,7 +38,7 @@ struct per_session_data__post_demo { char filename[256]; long file_length; - int fd; + lws_filefd_type fd; }; static const char * const param_names[] = { diff --git a/test-server/test-server-v2.0.c b/test-server/test-server-v2.0.c index b6feafe7..d12c0b6b 100644 --- a/test-server/test-server-v2.0.c +++ b/test-server/test-server-v2.0.c @@ -82,6 +82,9 @@ static const struct lws_http_mount mount_post = { NULL, /* default filename if none given */ NULL, NULL, + NULL, + NULL, + 0, 0, 0, 0, @@ -104,6 +107,9 @@ static const struct lws_http_mount mount = { "test.html", /* default filename if none given */ NULL, NULL, + NULL, + NULL, + 0, 0, 0, 0, diff --git a/test-server/test-server.h b/test-server/test-server.h index 95474029..7158abc6 100644 --- a/test-server/test-server.h +++ b/test-server/test-server.h @@ -85,7 +85,7 @@ struct per_session_data__http { char filename[256]; long file_length; - int post_fd; + lws_filefd_type post_fd; }; /* -- GitLab