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")
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)
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)
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)
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.
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)
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} )
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
# 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)
#
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
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("---------------------------------------------------------------------")
--- /dev/null
+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.
+
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
- 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:
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];
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];
* 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;
}
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;
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;
}
"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",
"vhosts[].mounts[].cache-revalidate",
"vhosts[].mounts[].cache-intermediaries",
"vhosts[].mounts[].extra-mimetypes.*",
+ "vhosts[].mounts[].interpret.*",
"vhosts[].ws-protocols[].*.*",
"vhosts[].ws-protocols[].*",
"vhosts[].ws-protocols[]",
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,
LEJPVP_MOUNT_CACHE_REVALIDATE,
LEJPVP_MOUNT_CACHE_INTERMEDIARIES,
LEJPVP_MOUNT_EXTRA_MIMETYPES,
+ LEJPVP_MOUNT_INTERPRET,
LEJPVP_PROTOCOL_NAME_OPT,
LEJPVP_PROTOCOL_NAME,
LEJPVP_PROTOCOL,
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;
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;
}
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;
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;
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
*
while (*p && len-- > 2) {
if (*p == '\'') {
- *q++ = '\\';
+ *q++ = '\'';
*q++ = '\'';
len --;
p++;
#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)
{
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 ---^ ******/
* 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;
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,
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;
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;
{
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)) {
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);
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 */
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
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;
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)
{
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)
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;
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)))
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];
#endif
};
#endif
+ static const char * const oprot[] = {
+ "http://", "https://"
+ };
/* it's not websocket.... shall we accept it as http? */
) {
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;
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
(*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);
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);
* 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;
}
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);
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;
(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) {
{
int ret = 0;
- lwsl_notice("%s\n", __func__);
-
if (s->state != US_IDLE)
ret = -1;
--- /dev/null
+<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>
--- /dev/null
+<html>
+This is an example destination that will appear after a failed login
+</html>
--- /dev/null
+<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>
+
--- /dev/null
+<!-- lwsgs rewrites the below $vars $v $v into the correct values on the fly -->
+
+var lwsgs_user = "$lwsgs_user";
+var lwsgs_auth = "$lwsgs_auth";
+var lwsgs_email = "$lwsgs_email";
+
+var lwsgs_html = '\
+ <div id="dlogin" style="display:none"> \
+ <form action="lwsgs-login" method="post"> \
+ <input type="hidden" name="admin" value="needadmin/admin-login.html"> \
+ <input type="hidden" name="good" value="index.html"> \
+ <input type="hidden" name="bad" value="failed-login.html"> \
+ <input type="hidden" name="forgot-good" value="sent-forgot-ok.html"> \
+ <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\
+ <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\
+ <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\
+ <table style="vertical-align:top;text-align:right"\
+ <tr>\
+ <td>User Name\
+ <input type="text" size="10" id="username" name="username" oninput="lwsgs_update()" onchange="lwsgs_update()"></td>\
+ <td>Password\
+ <input type="password" id="password" size="10" name="password" oninput="lwsgs_update()" onchange="lwsgs_update()"><div id="pw1"></div></td>\
+ </tr><tr>\
+ <td colspan="2" style="text-align:center"><input type="submit" id="login" name="login" value="Login" style="margin: 4px; padding: 2px; font-weight=bold;">\
+ <input type="submit" id="forgot" name="forgot" value="Forgot password" style="margin: 2px; padding: 2px">\
+ <input type="button" onclick="lwsgs_open_registration()" value="Sign up" style="margin: 2px; padding: 2px"></td>\
+ </tr>\
+ </table>\
+ </form>\
+ </div>\
+\
+ <div id="dlogout" style="display:none;text-align:right">\
+ <form action="lwsgs-logout" method="post" style="text-align:right">\
+ <input type="hidden" name="good" value="index.html">\
+ <table style="vertical-align:top;text-align:right">\
+ <tr><td><span id=grav></span></td>\
+ <td style="text-align:center"><table><tr><td style="text-align:center">\
+ <a href="#" onclick="lwsgs_select_change(); event.preventDefault();">\
+ <span id="curuser"></span></a></td></tr><tr>\
+ <td style="text-align:center"><input type="submit" name="logout" value="Logout" style="margin: 2px; padding: 2px"></td>\
+ </tr></table></td></tr>\
+ </table>\
+ </form></div>\
+\
+ <div id="dregister" style="display:none">\
+ <form action="lwsgs-login" method="post">\
+ <input type="hidden" name="admin" value="needadmin/admin-login.html">\
+ <input type="hidden" name="good" value="successful-login.html">\
+ <input type="hidden" name="bad" value="failed-login.html">\
+ <input type="hidden" name="reg-good" value="post-register-ok.html">\
+ <input type="hidden" name="reg-bad" value="post-register-fail.html">\
+ <input type="hidden" name="forgot-good" value="sent-forgot-ok.html">\
+ <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\
+ <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\
+ <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\
+ <table style="vertical-align:top;text-align:left">\
+ <tr>\
+ <td colspan=2 align=center>\
+ <span id="curuser"></span>\
+ <script>\
+ if (lwsgs_user)\
+ document.getElementById("curuser").innerHTML = "currently logged in as " + lwsgs_san(lwsgs_user) + "</br>";\
+ </script>\
+ <b>Please enter your details to register</b>:\
+ </td>\
+ </tr>\
+ <tr><td align=right>\
+ User Name:</td>\
+ <td><input type="text" size="10" id="rusername" name="username" oninput="lwsgs_rupdate(); lwsgs_check_user();"> <span id=uchk></span></td>\
+ </tr>\
+ <tr>\
+ <td align=right>Password:</td>\
+ <td><input type="password" size="10" id="rpassword" name="password" oninput="lwsgs_rupdate()"> <span id="rpw1"></span></td>\
+ </tr>\
+ <tr>\
+ </tr>\
+ <tr>\
+ <td align=right><span id="pw2">Password (again):</span></td>\
+ <td><input type="password" size="10" id="password2" name="password2" oninput="lwsgs_rupdate()"> <span id="match"></span></td>\
+ </tr>\
+ <tr>\
+ <td align=right>Email:</td>\
+ <td><input type="email" size="10" id="email" name="email"\
+ placeholder="me@example.com" oninput="lwsgs_rupdate(); lwsgs_check_email(\'email\')"> <span id=echk></span></td>\
+ </tr>\
+ <tr>\
+ <td colspan=2 align=center>\
+<input type="submit" id="register" name="register" value="Register" style="margin: 2px; padding: 2px">\
+<input type="submit" id="rforgot" name="forgot" value="Forgot Password" style="margin: 2px; padding: 2px;display: none">\
+<input type="button" id="cancel" name="cancel" value="Cancel" style="margin: 2px; padding: 2px;" onclick="lwsgs_cancel_registration()">\
+ </td>\
+ </tr>\
+ </table>\
+ </form>\
+ </div>\
+ \
+ <div id="dchange" style="display:none">\
+ <form action="lwsgs-change" method="post">\
+ <input type="hidden" id="cusername" name="username">\
+ <input type="hidden" name="admin" value="needadmin/admin-login.html">\
+ <input type="hidden" name="good" value="index.html">\
+ <input type="hidden" name="bad" value="failed-login.html">\
+ <input type="hidden" name="reg-good" value="post-register-ok.html">\
+ <input type="hidden" name="reg-bad" value="post-register-fail.html">\
+ <input type="hidden" name="forgot-good" value="sent-forgot-ok.html">\
+ <input type="hidden" name="forgot-bad" value="sent-forgot-fail.html">\
+ <input type="hidden" name="forgot-post-good" value="post-forgot-ok.html">\
+ <input type="hidden" name="forgot-post-bad" value="post-forgot-fail.html">\
+ <table style="vertical-align:top;text-align:left">\
+ <tr>\
+ <td colspan=2 align=center>\
+ <span id="ccuruser"></span>\
+ <script>\
+ if (lwsgs_user)\
+ document.getElementById("ccuruser").innerHTML =\
+ "<span class=\"gstitle\">Login settings for " +\
+ lwsgs_san(lwsgs_user) + "</span></br>";\
+ </script>\
+ <b>Please enter your details to change</b>:\
+ </td>\
+ </tr>\
+ <tr><td align=right id="ccurpw_name">\
+ Current Password:</td>\
+ <td><input type="password" size="10" id="ccurpw" name="curpw"\
+ oninput="lwsgs_cupdate();"> <span id=cuchk></span></td>\
+ </tr>\
+ <tr>\
+ <td align=right>Password:</td>\
+ <td><input type="password" size="10" id="cpassword" name="password"\
+ oninput="lwsgs_cupdate()"> <span id="cpw1"></span></td>\
+ </tr>\
+ <tr>\
+ <td align=right><span id="pw2">Password (again)</span></td>\
+ <td><input type="password" size="10" id="cpassword2" name="password2"\
+ oninput="lwsgs_cupdate()"> <span id="cmatch"></span></td>\
+ </tr>\
+ <!-- not supported yet\
+ <tr>\
+ <td align=right id="cemail_name">Email:</td>\
+ <td><input type="email" size="10" id="cemail" name="email"\
+ placeholder="?" oninput="lwsgs_cupdate(); lwsgs_check_email(\'cemail\')">\
+ <span id=cechk></span></td>\
+ </tr> -->\
+ <tr>\
+ <td colspan=2 align=center>\
+ <input type="submit" id="change" name="change"\
+ value="Change" style="margin: 6px; padding: 6px">\
+ <input type="submit" id="cforgot" name="forgot"\
+ value="Forgot Password" style="margin: 6px; padding: 6px;display: none">\
+ <input type="button" id="cancel" name="cancel"\
+ value="Cancel" style="margin: 6px; padding: 6px;"\
+ onclick="lwsgs_cancel_registration()">\
+ </td>\
+ </tr>\
+ <tr>\
+ <td colspan=2>\
+ <input type="checkbox" id="showdel" name="showdel"\
+ onchange="lwsgs_cupdate();"> Show Delete \
+ <input type="submit" id="delete" name="delete" \
+ value="Delete Account" style="margin: 6px; padding: 6px;display: none">\
+ </td>\
+ </tr>\
+ </table>\
+ </form>\
+ </div>\
+ \
+ <div id="dadmin" style="display:none">\
+ Admin settings TBD\
+ </div>\
+';
+
+/*-- this came from
+ -- https://raw.githubusercontent.com/blueimp/JavaScript-MD5/master/js/md5.min.js
+ -- under MIT license */
+!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t),e=(n>>16)+(t>>16)+(r>>16);return e<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[(r+64>>>9<<4)+14]=r;var e,i,a,h,d,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,h=v,d=m,l=o(l,g,v,m,n[e],7,-680876936),m=o(m,l,g,v,n[e+1],12,-389564586),v=o(v,m,l,g,n[e+2],17,606105819),g=o(g,v,m,l,n[e+3],22,-1044525330),l=o(l,g,v,m,n[e+4],7,-176418897),m=o(m,l,g,v,n[e+5],12,1200080426),v=o(v,m,l,g,n[e+6],17,-1473231341),g=o(g,v,m,l,n[e+7],22,-45705983),l=o(l,g,v,m,n[e+8],7,1770035416),m=o(m,l,g,v,n[e+9],12,-1958414417),v=o(v,m,l,g,n[e+10],17,-42063),g=o(g,v,m,l,n[e+11],22,-1990404162),l=o(l,g,v,m,n[e+12],7,1804603682),m=o(m,l,g,v,n[e+13],12,-40341101),v=o(v,m,l,g,n[e+14],17,-1502002290),g=o(g,v,m,l,n[e+15],22,1236535329),l=u(l,g,v,m,n[e+1],5,-165796510),m=u(m,l,g,v,n[e+6],9,-1069501632),v=u(v,m,l,g,n[e+11],14,643717713),g=u(g,v,m,l,n[e],20,-373897302),l=u(l,g,v,m,n[e+5],5,-701558691),m=u(m,l,g,v,n[e+10],9,38016083),v=u(v,m,l,g,n[e+15],14,-660478335),g=u(g,v,m,l,n[e+4],20,-405537848),l=u(l,g,v,m,n[e+9],5,568446438),m=u(m,l,g,v,n[e+14],9,-1019803690),v=u(v,m,l,g,n[e+3],14,-187363961),g=u(g,v,m,l,n[e+8],20,1163531501),l=u(l,g,v,m,n[e+13],5,-1444681467),m=u(m,l,g,v,n[e+2],9,-51403784),v=u(v,m,l,g,n[e+7],14,1735328473),g=u(g,v,m,l,n[e+12],20,-1926607734),l=c(l,g,v,m,n[e+5],4,-378558),m=c(m,l,g,v,n[e+8],11,-2022574463),v=c(v,m,l,g,n[e+11],16,1839030562),g=c(g,v,m,l,n[e+14],23,-35309556),l=c(l,g,v,m,n[e+1],4,-1530992060),m=c(m,l,g,v,n[e+4],11,1272893353),v=c(v,m,l,g,n[e+7],16,-155497632),g=c(g,v,m,l,n[e+10],23,-1094730640),l=c(l,g,v,m,n[e+13],4,681279174),m=c(m,l,g,v,n[e],11,-358537222),v=c(v,m,l,g,n[e+3],16,-722521979),g=c(g,v,m,l,n[e+6],23,76029189),l=c(l,g,v,m,n[e+9],4,-640364487),m=c(m,l,g,v,n[e+12],11,-421815835),v=c(v,m,l,g,n[e+15],16,530742520),g=c(g,v,m,l,n[e+2],23,-995338651),l=f(l,g,v,m,n[e],6,-198630844),m=f(m,l,g,v,n[e+7],10,1126891415),v=f(v,m,l,g,n[e+14],15,-1416354905),g=f(g,v,m,l,n[e+5],21,-57434055),l=f(l,g,v,m,n[e+12],6,1700485571),m=f(m,l,g,v,n[e+3],10,-1894986606),v=f(v,m,l,g,n[e+10],15,-1051523),g=f(g,v,m,l,n[e+1],21,-2054922799),l=f(l,g,v,m,n[e+8],6,1873313359),m=f(m,l,g,v,n[e+15],10,-30611744),v=f(v,m,l,g,n[e+6],15,-1560198380),g=f(g,v,m,l,n[e+13],21,1309151649),l=f(l,g,v,m,n[e+4],6,-145523070),m=f(m,l,g,v,n[e+11],10,-1120210379),v=f(v,m,l,g,n[e+2],15,718787259),g=f(g,v,m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,h),m=t(m,d);return[l,g,v,m]}function a(n){var t,r="";for(t=0;t<32*n.length;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function h(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;for(t=0;t<8*n.length;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function d(n){return a(i(h(n),8*n.length))}function l(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;16>r;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(h(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="0123456789abcdef",o="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),o+=e.charAt(t>>>4&15)+e.charAt(15&t);return o}function v(n){return unescape(encodeURIComponent(n))}function m(n){return d(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);
+
+if (lwsgs_user.substring(0, 1) == "$") {
+ alert("lwsgs.js: lws generic sessions misconfigured and not providing vars");
+}
+function lwsgs_san(s)
+{
+ if (s.search("<") != -1)
+ return "invalid string";
+
+ return s;
+}
+
+function lwsgs_update()
+{
+ var en_login = 1, en_forgot = 1;
+
+ if (document.getElementById('password').value.length &&
+ document.getElementById('password').value.length < 8)
+ en_login = 0;
+
+ if (!document.getElementById('username').value ||
+ !document.getElementById('password').value)
+ en_login = 0;
+
+ if (!document.getElementById('username').value ||
+ document.getElementById('password').value)
+ en_forgot = 0;
+
+ document.getElementById('login').disabled = !en_login;
+ document.getElementById('forgot').disabled = !en_forgot;
+
+ if (lwsgs_user)
+ document.getElementById("curuser").innerHTML = lwsgs_san(lwsgs_user);
+
+ if (lwsgs_user === "")
+ document.getElementById("dlogin").style.display = "inline";
+ else
+ document.getElementById("dlogout").style.display = "inline";
+ }
+
+function lwsgs_open_registration()
+{
+ document.getElementById("dadmin").style.display = "none";
+ document.getElementById("dlogin").style.display = "none";
+ document.getElementById("dlogout").style.display = "none";
+ document.getElementById("dchange").style.display = "none";
+ document.getElementById("dregister").style.display = "inline";
+}
+
+function lwsgs_cancel_registration()
+{
+ document.getElementById("dadmin").style.display = "none";
+ document.getElementById("dregister").style.display = "none";
+ document.getElementById("dchange").style.display = "none";
+
+ if (lwsgs_user === "")
+ document.getElementById("dlogin").style.display = "inline";
+ else
+ document.getElementById("dlogout").style.display = "inline";
+}
+
+function lwsgs_select_change()
+{
+ document.getElementById("dlogin").style.display = "none";
+ document.getElementById("dlogout").style.display = "none";
+ document.getElementById("dregister").style.display = "none";
+ if (lwsgs_auth & 2) {
+ document.getElementById("dadmin").style.display = "inline";
+ document.getElementById("dchange").style.display = "none";
+ } else {
+ document.getElementById("dadmin").style.display = "none";
+ document.getElementById("dchange").style.display = "inline";
+ }
+}
+
+var lwsgs_user_check = '0';
+var lwsgs_email_check = '0';
+
+function lwsgs_rupdate()
+{
+ var en_register = 1, en_forgot = 0;
+
+ if (document.getElementById('rpassword').value ==
+ document.getElementById('password2').value) {
+ if (document.getElementById('rpassword').value.length)
+ document.getElementById('match').innerHTML =
+ "<b style=\"color:green\">\u2713</b>";
+ else
+ document.getElementById('match').innerHTML = "";
+ document.getElementById('pw2').style = "";
+ } else {
+ if (document.getElementById('password2').value ||
+ document.getElementById('email').value) { // ie, he is filling in "register" path and cares
+ document.getElementById('match').innerHTML =
+ "<span style=\"color: red\">\u2718 <b>Passwords do not match</b></span>";
+ } else
+ document.getElementById('match').innerHTML =
+ "<span style=\"color: gray\">\u2718 Passwords do not match</span>";
+
+ en_register = 0;
+ }
+
+ if (document.getElementById('rpassword').value.length &&
+ document.getElementById('rpassword').value.length < 8) {
+ en_register = 0;
+ document.getElementById('rpw1').innerHTML = "Need 8 chars";
+ } else
+ if (document.getElementById('rpassword').value.length)
+ document.getElementById('rpw1').innerHTML = "<b style=\"color:green\">\u2713</b>";
+ else
+ document.getElementById('rpw1').innerHTML = "";
+
+ if (!document.getElementById('rpassword').value ||
+ !document.getElementById('password2').value ||
+ !document.getElementById('rusername').value ||
+ !document.getElementById('email').value ||
+ lwsgs_email_check === '1'||
+ lwsgs_user_check === '1')
+ en_register = 0;
+
+ document.getElementById('register').disabled = !en_register;
+ document.getElementById('rpassword').disabled = lwsgs_user_check === '1';
+ document.getElementById('password2').disabled = lwsgs_user_check === '1';
+ document.getElementById('email').disabled = lwsgs_user_check === '1';
+
+ if (lwsgs_user_check === '0') {
+ if (document.getElementById('rusername').value)
+ document.getElementById('uchk').innerHTML = "<b style=\"color:green\">\u2713</b>";
+ else
+ document.getElementById('uchk').innerHTML = "";
+ } else {
+ document.getElementById('uchk').innerHTML = "<b style=\"color:red\">\u2718 Already registered</b>";
+ en_forgot = 1;
+ }
+
+ if (lwsgs_email_check === '0') {
+ if (document.getElementById('email').value)
+ document.getElementById('echk').innerHTML = "<b style=\"color:green\">\u2713</b>";
+ else
+ document.getElementById('echk').innerHTML = "";
+ } else {
+ document.getElementById('echk').innerHTML = "<b style=\"color:red\">\u2718 Already registered</b>";
+ en_forgot = 1;
+ }
+
+ if (en_forgot)
+ document.getElementById('rforgot').style.display = "inline";
+ else
+ document.getElementById('rforgot').style.display = "none";
+
+ if (lwsgs_user_check === '1')
+ op = '0.5';
+ else
+ op = '1.0';
+ document.getElementById('rpassword').style.opacity = op;
+ document.getElementById('password2').style.opacity = op;
+ document.getElementById('email').style.opacity = op;
+ }
+
+function lwsgs_cupdate()
+{
+ var en_change = 1, en_forgot = 1, pwok = 1;
+
+ if (lwsgs_auth & 8) {
+ document.getElementById('ccurpw').style.display = "none";
+ document.getElementById('ccurpw_name').style.display = "none";
+ } else {
+ if (!document.getElementById('ccurpw').value ||
+ document.getElementById('ccurpw').value.length < 8) {
+ en_change = 0;
+ pwok = 0;
+ document.getElementById('cuchk').innerHTML = "<b style=\"color:red\">\u2718</b>";
+ } else {
+ en_forgot = 0;
+ document.getElementById('cuchk').innerHTML = "";
+ }
+ document.getElementById('ccurpw').style.display = "inline";
+ document.getElementById('ccurpw_name').style.display = "inline";
+ }
+
+ if (document.getElementById('cpassword').value ==
+ document.getElementById('cpassword2').value) {
+ if (document.getElementById('cpassword').value.length)
+ document.getElementById('cmatch').innerHTML = "<b style=\"color:green\">\u2713</b>";
+ else
+ document.getElementById('cmatch').innerHTML = "";
+ document.getElementById('pw2').style = "";
+ } else {
+ if (document.getElementById('cpassword2').value //||
+ //document.getElementById('cemail').value
+ ) { // ie, he is filling in "register" path and cares
+ document.getElementById('cmatch').innerHTML =
+ "<span style=\"color: red\">\u2718 <b>Passwords do not match</b></span>";
+ } else
+ document.getElementById('cmatch').innerHTML = "<span style=\"color: gray\">\u2718 Passwords do not match</span>";
+
+ en_change = 0;
+ }
+
+ if (document.getElementById('cpassword').value.length &&
+ document.getElementById('cpassword').value.length < 8) {
+ en_change = 0;
+ document.getElementById('cpw1').innerHTML = "Need 8 chars";
+ } else
+ if (document.getElementById('cpassword').value.length)
+ document.getElementById('cpw1').innerHTML = "<b style=\"color:green\">\u2713</b>";
+ else
+ document.getElementById('cpw1').innerHTML = "";
+
+ if (!document.getElementById('cpassword').value ||
+ !document.getElementById('cpassword2').value ||
+ pwok == 0)
+ en_change = 0;
+
+ if (document.getElementById('showdel').checked)
+ document.getElementById('delete').style.display = "inline";
+ else
+ document.getElementById('delete').style.display = "none";
+
+ document.getElementById('change').disabled = !en_change;
+ document.getElementById('cpassword').disabled = pwok === 0;
+ document.getElementById('cpassword2').disabled = pwok === 0;
+ document.getElementById('showdel').disabled = pwok === 0;
+ document.getElementById('delete').disabled = pwok === 0;
+ //document.getElementById('cemail').disabled = pwok === 0;
+
+ /*
+ if (lwsgs_auth & 8) {
+ document.getElementById('cemail').style.display = "none";
+ document.getElementById('cemail_name').style.display = "none";
+ } else {
+ document.getElementById('cemail').style.display = "inline";
+ document.getElementById('cemail_name').style.display = "inline";
+ if (lwsgs_email_check === '0' &&
+ document.getElementById('cemail').value != lwsgs_email) {
+ if (document.getElementById('cemail').value)
+ document.getElementById('cechk').innerHTML = "<b style=\"color:green\">\u2713</b>";
+ else
+ document.getElementById('cechk').innerHTML = "";
+ } else {
+ document.getElementById('cechk').innerHTML = "<b style=\"color:red\">\u2718 Already registered</b>";
+ en_forgot = 1;
+ }
+ } */
+
+ if (lwsgs_auth & 8)
+ en_forgot = 0;
+
+ if (en_forgot)
+ document.getElementById('cforgot').style.display = "inline";
+ else
+ document.getElementById('cforgot').style.display = "none";
+
+ if (pwok == 0)
+ op = '0.5';
+ else
+ op = '1.0';
+ document.getElementById('cpassword').style.opacity = op;
+ document.getElementById('cpassword2').style.opacity = op;
+ // document.getElementById('cemail').style.opacity = op;
+ }
+
+function lwsgs_check_user()
+{
+ var xmlHttp = new XMLHttpRequest();
+ xmlHttp.onreadystatechange = function() {
+ if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
+ lwsgs_user_check = xmlHttp.responseText;
+ lwsgs_rupdate();
+ }
+ }
+ xmlHttp.open("GET", "lwsgs-check?username="+document.getElementById('rusername').value, true);
+ xmlHttp.send(null);
+}
+
+function lwsgs_check_email(id)
+{
+ var xmlHttp = new XMLHttpRequest();
+ xmlHttp.onreadystatechange = function() {
+ if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
+ lwsgs_email_check = xmlHttp.responseText;
+ lwsgs_rupdate();
+ }
+ }
+ xmlHttp.open("GET", "lwsgs-check?email="+document.getElementById(id).value, true);
+ xmlHttp.send(null);
+}
+
+function lwsgs_initial()
+{
+ document.getElementById('lwsgs').innerHTML = lwsgs_html;
+ if (lwsgs_email)
+ document.getElementById('grav').innerHTML =
+ "<img src=\"https://www.gravatar.com/avatar/" + md5(lwsgs_email) +
+ "?d=identicon\">";
+ //if (lwsgs_email)
+ //document.getElementById('cemail').placeholder = lwsgs_email;
+ document.getElementById('cusername').value = lwsgs_user;
+ lwsgs_update();
+ lwsgs_cupdate();
+}
--- /dev/null
+!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
--- /dev/null
+<html>
+Sorry, something went wrong.
+
+Click <a href="../">here</a> to continue.
+</html>
--- /dev/null
+<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>
+
--- /dev/null
+Registration failed, sorry
--- /dev/null
+<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>
+
--- /dev/null
+<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>
+
--- /dev/null
+<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>
+
--- /dev/null
+<html>
+Sorry, something went wrong.
+
+Click <a href="../">here</a> to continue.
+</html>
--- /dev/null
+An email has been sent to your registered address.
+
+Please follow the instructions to reset your password.
+
--- /dev/null
+<html>
+This is an example destination that will appear after successful non-Admin login
+</html>
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
#include <time.h>
#include <string.h>
#ifdef WIN32
+#include <io.h>
#include <gettimeofday.h>
#endif
#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;
char filename[256];
long file_length;
- int fd;
+ lws_filefd_type fd;
};
static const char * const param_names[] = {
NULL, /* default filename if none given */
NULL,
NULL,
+ NULL,
+ NULL,
+ 0,
0,
0,
0,
"test.html", /* default filename if none given */
NULL,
NULL,
+ NULL,
+ NULL,
+ 0,
0,
0,
0,
char filename[256];
long file_length;
- int post_fd;
+ lws_filefd_type post_fd;
};
/*