protocol generic sessions
authorAndy Green <andy@warmcat.com>
Thu, 19 May 2016 07:28:31 +0000 (15:28 +0800)
committerAndy Green <andy@warmcat.com>
Fri, 17 Jun 2016 22:44:31 +0000 (06:44 +0800)
Signed-off-by: Andy Green <andy@warmcat.com>
34 files changed:
CMakeLists.txt
README.generic-sessions.md [new file with mode: 0644]
appveyor.yml
lib/context.c
lib/lejp-conf.c
lib/libwebsockets.c
lib/libwebsockets.h
lib/output.c
lib/private-libwebsockets.h
lib/server.c
plugins/generic-sessions/assets/admin-login.html [new file with mode: 0644]
plugins/generic-sessions/assets/failed-login.html [new file with mode: 0644]
plugins/generic-sessions/assets/index.html [new file with mode: 0644]
plugins/generic-sessions/assets/lwsgs-logo.png [new file with mode: 0644]
plugins/generic-sessions/assets/lwsgs.js [new file with mode: 0644]
plugins/generic-sessions/assets/md5.min.js [new file with mode: 0644]
plugins/generic-sessions/assets/post-forgot-fail.html [new file with mode: 0644]
plugins/generic-sessions/assets/post-forgot-ok.html [new file with mode: 0644]
plugins/generic-sessions/assets/post-register-fail.html [new file with mode: 0644]
plugins/generic-sessions/assets/post-register-ok.html [new file with mode: 0644]
plugins/generic-sessions/assets/post-verify-fail.html [new file with mode: 0644]
plugins/generic-sessions/assets/post-verify-ok.html [new file with mode: 0644]
plugins/generic-sessions/assets/seats.jpg [new file with mode: 0644]
plugins/generic-sessions/assets/sent-forgot-fail.html [new file with mode: 0644]
plugins/generic-sessions/assets/sent-forgot-ok.html [new file with mode: 0644]
plugins/generic-sessions/assets/successful-login.html [new file with mode: 0644]
plugins/generic-sessions/handlers.c [new file with mode: 0644]
plugins/generic-sessions/private-lwsgs.h [new file with mode: 0644]
plugins/generic-sessions/protocol_generic_sessions.c [new file with mode: 0644]
plugins/generic-sessions/utils.c [new file with mode: 0644]
plugins/protocol_lws_status.c
plugins/protocol_post_demo.c
test-server/test-server-v2.0.c
test-server/test-server.h

index eceddac..33642ab 100644 (file)
@@ -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 (file)
index 0000000..dab2873
--- /dev/null
@@ -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.
+
index b5cb9af..4bbc0f1 100644 (file)
@@ -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:
 
index 0d3a41c..d3c22d6 100644 (file)
@@ -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;
        }
 
index 1d529a1..91429a5 100644 (file)
@@ -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;
index cac78db..f8d08fc 100755 (executable)
@@ -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)
 {
index b6ec9bb..11f6fff 100644 (file)
@@ -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;
index f14a4a3..be0c73c 100644 (file)
@@ -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 */
index 5ce6413..12719fc 100644 (file)
@@ -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;
index 8aafdbf..d2398e3 100644 (file)
@@ -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 (file)
index 0000000..113df9c
--- /dev/null
@@ -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 (file)
index 0000000..9ab065b
--- /dev/null
@@ -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 (file)
index 0000000..ea970ee
--- /dev/null
@@ -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 (file)
index 0000000..723a124
Binary files /dev/null and b/plugins/generic-sessions/assets/lwsgs-logo.png differ
diff --git a/plugins/generic-sessions/assets/lwsgs.js b/plugins/generic-sessions/assets/lwsgs.js
new file mode 100644 (file)
index 0000000..5362c9a
--- /dev/null
@@ -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;">\
+      &nbsp;<input type="submit" id="forgot" name="forgot" value="Forgot password" style="margin: 2px; padding: 2px">\
+           &nbsp;<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();">&nbsp;<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()">&nbsp;<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()">&nbsp;<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\')">&nbsp;<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();">&nbsp;<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()">&nbsp;<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()">&nbsp;<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\')">\
+                 &nbsp;<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&nbsp;\
+             <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 (file)
index 0000000..4bd9de1
--- /dev/null
@@ -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 (file)
index 0000000..ead3d13
--- /dev/null
@@ -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 (file)
index 0000000..3e8e9cf
--- /dev/null
@@ -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 (file)
index 0000000..063c3c5
--- /dev/null
@@ -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 (file)
index 0000000..2d15035
--- /dev/null
@@ -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 (file)
index 0000000..d1d89ca
--- /dev/null
@@ -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 (file)
index 0000000..e968f6a
--- /dev/null
@@ -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 (file)
index 0000000..5bed40d
Binary files /dev/null and b/plugins/generic-sessions/assets/seats.jpg differ
diff --git a/plugins/generic-sessions/assets/sent-forgot-fail.html b/plugins/generic-sessions/assets/sent-forgot-fail.html
new file mode 100644 (file)
index 0000000..ead3d13
--- /dev/null
@@ -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 (file)
index 0000000..83df751
--- /dev/null
@@ -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 (file)
index 0000000..dfc25cf
--- /dev/null
@@ -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 (file)
index 0000000..bd00c2b
--- /dev/null
@@ -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 (file)
index 0000000..cb408ae
--- /dev/null
@@ -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 (file)
index 0000000..6bf5d2b
--- /dev/null
@@ -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 (file)
index 0000000..0d458d7
--- /dev/null
@@ -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;
+}
index 817fe76..912eec8 100644 (file)
@@ -25,6 +25,7 @@
 #include <time.h>
 #include <string.h>
 #ifdef WIN32
+#include <io.h>
 #include <gettimeofday.h>
 #endif
 
index 9f0cab8..8520a31 100644 (file)
 #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[] = {
index b6feafe..d12c0b6 100644 (file)
@@ -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,
index 9547402..7158abc 100644 (file)
@@ -85,7 +85,7 @@ struct per_session_data__http {
 
        char filename[256];
        long file_length;
-       int post_fd;
+       lws_filefd_type post_fd;
 };
 
 /*