From 52a8c878388f5651b9ed72c53661eafc28325dbe Mon Sep 17 00:00:00 2001 From: Andy Green Date: Mon, 13 Jul 2020 13:24:38 +0100 Subject: [PATCH] remove: generic sessions As far as I know there are no users of this, although it worked it's basically unmaintainable due to handling the sql and JSON manually. Gradually better capabilities have appeared in lws, like lws_struct abstracting out the sql and JSON, and now generic JWT... these have been used in Sai to great effect and displaced the only organic would-be user of this. There is a better path to do this stuff now and no point keeping this around. --- CMakeLists-implied-options.txt | 7 - CMakeLists.txt | 1 - plugins/CMakeLists.txt | 57 -- .../generic-sessions/assets/admin-login.html | 5 - .../generic-sessions/assets/failed-login.html | 3 - plugins/generic-sessions/assets/index.html | 56 -- .../generic-sessions/assets/lwsgs-logo.png | Bin 9729 -> 0 bytes plugins/generic-sessions/assets/lwsgs.css | 134 --- plugins/generic-sessions/assets/lwsgs.js | 627 ------------ plugins/generic-sessions/assets/md5.min.js | 2 - .../assets/post-forgot-fail.html | 5 - .../assets/post-forgot-ok.html | 6 - .../assets/post-register-fail.html | 1 - .../assets/post-register-ok.html | 27 - .../assets/post-verify-fail.html | 20 - .../assets/post-verify-ok.html | 25 - plugins/generic-sessions/assets/seats.jpg | Bin 122754 -> 0 bytes .../assets/sent-forgot-fail.html | 5 - .../assets/sent-forgot-ok.html | 4 - .../assets/successful-login.html | 4 - plugins/generic-sessions/handlers.c | 663 ------------- plugins/generic-sessions/private-lwsgs.h | 171 ---- .../protocol_generic_sessions.c | 920 ------------------ .../protocol_lws_messageboard.c | 438 --------- plugins/generic-sessions/utils.c | 465 --------- plugins/generic-table/assets/index.html | 75 -- plugins/generic-table/assets/lwsgt.js | 139 --- .../generic-table/protocol_table_dirlisting.c | 397 -------- 28 files changed, 4257 deletions(-) delete mode 100644 plugins/generic-sessions/assets/admin-login.html delete mode 100644 plugins/generic-sessions/assets/failed-login.html delete mode 100644 plugins/generic-sessions/assets/index.html delete mode 100644 plugins/generic-sessions/assets/lwsgs-logo.png delete mode 100644 plugins/generic-sessions/assets/lwsgs.css delete mode 100644 plugins/generic-sessions/assets/lwsgs.js delete mode 100644 plugins/generic-sessions/assets/md5.min.js delete mode 100644 plugins/generic-sessions/assets/post-forgot-fail.html delete mode 100644 plugins/generic-sessions/assets/post-forgot-ok.html delete mode 100644 plugins/generic-sessions/assets/post-register-fail.html delete mode 100644 plugins/generic-sessions/assets/post-register-ok.html delete mode 100644 plugins/generic-sessions/assets/post-verify-fail.html delete mode 100644 plugins/generic-sessions/assets/post-verify-ok.html delete mode 100644 plugins/generic-sessions/assets/seats.jpg delete mode 100644 plugins/generic-sessions/assets/sent-forgot-fail.html delete mode 100644 plugins/generic-sessions/assets/sent-forgot-ok.html delete mode 100644 plugins/generic-sessions/assets/successful-login.html delete mode 100644 plugins/generic-sessions/handlers.c delete mode 100644 plugins/generic-sessions/private-lwsgs.h delete mode 100644 plugins/generic-sessions/protocol_generic_sessions.c delete mode 100644 plugins/generic-sessions/protocol_lws_messageboard.c delete mode 100644 plugins/generic-sessions/utils.c delete mode 100644 plugins/generic-table/assets/index.html delete mode 100644 plugins/generic-table/assets/lwsgt.js delete mode 100644 plugins/generic-table/protocol_table_dirlisting.c diff --git a/CMakeLists-implied-options.txt b/CMakeLists-implied-options.txt index 8ec12f157..0da12124d 100644 --- a/CMakeLists-implied-options.txt +++ b/CMakeLists-implied-options.txt @@ -134,7 +134,6 @@ if (NOT LWS_WITH_NETWORK) set(LWS_WITH_CGI 0) set(LWS_ROLE_RAW_PROXY 0) set(LWS_WITH_PEER_LIMITS 0) - set(LWS_WITH_GENERIC_SESSIONS 0) set(LWS_WITH_HTTP_STREAM_COMPRESSION 0) set(LWS_WITH_HTTP_BROTLI 0) set(LWS_WITH_POLL 0) @@ -228,12 +227,6 @@ if (LWS_WITH_PLUGINS OR LWS_WITH_CGI) set(LWS_WITH_GENCRYPTO 1) endif() -if (LWS_WITH_GENERIC_SESSIONS) - set(LWS_WITH_SQLITE3 1) - # set(LWS_WITH_SMTP 1) - set(LWS_WITH_STRUCT_SQLITE3 1) -endif() - if (LWS_PLAT_FREERTOS) set(LWS_WITH_SHARED OFF) set(LWS_WITH_MBEDTLS ON) diff --git a/CMakeLists.txt b/CMakeLists.txt index bd74640a3..f5b55007f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,6 @@ option(LWS_WITH_PLUGINS "Support plugins for protocols and extensions" OFF) option(LWS_WITH_HTTP_PROXY "Support for active HTTP proxying" OFF) option(LWS_WITH_ZIP_FOPS "Support serving pre-zipped files" OFF) option(LWS_WITH_SOCKS5 "Allow use of SOCKS5 proxy on client connections" OFF) -option(LWS_WITH_GENERIC_SESSIONS "With the Generic Sessions plugin" OFF) option(LWS_WITH_PEER_LIMITS "Track peers and restrict resources a single peer can allocate" OFF) option(LWS_WITH_ACCESS_LOG "Support generating Apache-compatible access logs" OFF) option(LWS_WITH_RANGES "Support http ranges (RFC7233)" OFF) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 0e1560eb1..f99a65efb 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -90,8 +90,6 @@ if (LWS_ROLE_WS) "protocol_lws_mirror.c" "" "") create_plugin(protocol_lws_status "" "protocol_lws_status.c" "" "") - create_plugin(protocol_lws_table_dirlisting "" - "generic-table/protocol_table_dirlisting.c" "" "") if (NOT WIN32) create_plugin(protocol_lws_raw_test "" "protocol_lws_raw_test.c" "" "") @@ -143,29 +141,6 @@ if (LWS_WITH_ACME) "acme-client/protocol_lws_acme_client.c" "" "") endif() -if (LWS_WITH_GENERIC_SESSIONS AND LWS_ROLE_WS AND LWS_WITH_TLS) - create_plugin(protocol_generic_sessions "" - "generic-sessions/protocol_generic_sessions.c" - "generic-sessions/utils.c" - "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) - - create_plugin(protocol_lws_messageboard "" - "generic-sessions/protocol_lws_messageboard.c" "" "") - if (WIN32) - target_link_libraries(protocol_lws_messageboard ${LWS_SQLITE3_LIBRARIES}) - else() - target_link_libraries(protocol_lws_messageboard sqlite3 ) - endif(WIN32) - -endif(LWS_WITH_GENERIC_SESSIONS AND LWS_ROLE_WS AND LWS_WITH_TLS) - - endif(LWS_WITH_PLUGINS AND LWS_WITH_SHARED) @@ -189,36 +164,4 @@ if (LWS_WITH_SERVER_STATUS) DESTINATION share/libwebsockets-test-server/server-status COMPONENT examples) endif() -if (LWS_WITH_GENERIC_SESSIONS) - install(FILES - generic-sessions/assets/lwsgs-logo.png - generic-sessions/assets/seats.jpg - generic-sessions/assets/failed-login.html - generic-sessions/assets/lwsgs.js - generic-sessions/assets/lwsgs.css - generic-sessions/assets/post-register-fail.html - generic-sessions/assets/post-register-ok.html - generic-sessions/assets/post-verify-ok.html - generic-sessions/assets/post-verify-fail.html - generic-sessions/assets/sent-forgot-ok.html - generic-sessions/assets/sent-forgot-fail.html - generic-sessions/assets/post-forgot-ok.html - generic-sessions/assets/post-forgot-fail.html - generic-sessions/assets/index.html - DESTINATION share/libwebsockets-test-server/generic-sessions - COMPONENT examples) - install(FILES generic-sessions/assets/successful-login.html - DESTINATION share/libwebsockets-test-server/generic-sessions/needauth - COMPONENT examples) - install(FILES generic-sessions/assets/admin-login.html - DESTINATION share/libwebsockets-test-server/generic-sessions/needadmin - COMPONENT examples) -endif() - - install(FILES - generic-table/assets/lwsgt.js - generic-table/assets/index.html - DESTINATION share/libwebsockets-test-server/generic-table - COMPONENT examples) - endif() diff --git a/plugins/generic-sessions/assets/admin-login.html b/plugins/generic-sessions/assets/admin-login.html deleted file mode 100644 index 113df9cd3..000000000 --- a/plugins/generic-sessions/assets/admin-login.html +++ /dev/null @@ -1,5 +0,0 @@ - -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. - diff --git a/plugins/generic-sessions/assets/failed-login.html b/plugins/generic-sessions/assets/failed-login.html deleted file mode 100644 index 9ab065b53..000000000 --- a/plugins/generic-sessions/assets/failed-login.html +++ /dev/null @@ -1,3 +0,0 @@ - -This is an example destination that will appear after a failed login - diff --git a/plugins/generic-sessions/assets/index.html b/plugins/generic-sessions/assets/index.html deleted file mode 100644 index d40904af5..000000000 --- a/plugins/generic-sessions/assets/index.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - -
-
-
- -
- - This is a demo application for lws generic-sessions.

- It's a simple messageboard.

- What's interesting about it is there is no serverside scripting,
- instead client js makes a wss:// connection back to the server
- and then reacts to JSON from the ws protocol. Sessions stuff is
- handled by lws generic sessions, making the actual
- test application
very small.

- And because it's natively websocket, it's naturally connected
- for dynamic events and easy to maintain. -

- Register / Login at the top right to see and create new messages. -
- -
-
- New message
-
- -
-
-
-
- -
- -
-
- - - - diff --git a/plugins/generic-sessions/assets/lwsgs-logo.png b/plugins/generic-sessions/assets/lwsgs-logo.png deleted file mode 100644 index 723a124431189c21c340de517bd9b82cb35374d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9729 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 ARR910 diff --git a/plugins/generic-sessions/assets/lwsgs.css b/plugins/generic-sessions/assets/lwsgs.css deleted file mode 100644 index 9dfde7577..000000000 --- a/plugins/generic-sessions/assets/lwsgs.css +++ /dev/null @@ -1,134 +0,0 @@ -.body { font-size: 12px } -.gstitle { font-size: 18px } - -.group1 { - vertical-align:middle; - text-align:center; - background:#f0f0e0; - padding:12px; - border-radius:10px; -} -.group2 { - display:none; - vertical-align:middle; - font-size: 22px; - text-align:center; - margin:auto; - align:center; - background-color: rgba(255, 255, 255, 0.8); - padding:12px; - border-radius:10px; -} - -body.seats { - background-image:url(seats.jpg) -} - -div.lwsgs { - z-index: 3; - text-align:right; - background-color: rgba(255, 255, 255, 0.8); -} - -table.lwsgs { - width:100%; - height:100%; - transition: max-height 2s; -} -table.c100 { - text-align:center; - width:100%; -} - -table.r { - vertical-align:top; - text-align:right; -} - -table.l { - vertical-align:top; - text-align:left; -} - -table.fixed { - table-layout: fixed; -} - -td.logo { - vertical-align:top; - text-align:left; - width:200px -} - -td.lwsgs { - vertical-align:top; - float:right; -} - -td.h99 { - height:99%; - vertical-align:middle; -} - -td.c { - margin:auto; - align:center -} - -td.tac { - text-align:center -} - -td.ava { - display:inline-block; - vertical-align:top; - word-wrap:break-word; -} - -iframe.hidden { - display:none; -} - -div.hidden { - display:none; -} - -div.hiddenr { - display:none; - text-align:right; -} - -input { - margin: 2px; - padding: 2px; -} - -input.em { - margin: 4px; - font-weight:bold; -} - -input.wide { - margin: 6px; - padding: 6px; -} - -input.hidden { - display: none; -} - -form.r { - text-align:right; -} - -span.bad { - color: red; -} - -span.small { - font-size:8pt; -} - -.green { - color: green; -} diff --git a/plugins/generic-sessions/assets/lwsgs.js b/plugins/generic-sessions/assets/lwsgs.js deleted file mode 100644 index bcfd3d14b..000000000 --- a/plugins/generic-sessions/assets/lwsgs.js +++ /dev/null @@ -1,627 +0,0 @@ - - -var lwsgs_user = "$lwsgs_user"; -var lwsgs_auth = "$lwsgs_auth"; -var lwsgs_email = "$lwsgs_email"; - -var lwsgs_html = '\ - \ -\ -
\ -
\ - \ - \ - \ - \ -
\ - \ -
\ - \ -
\ -
\ -\ - \ - \ - \ - \ - \ -'; - -/*-- 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<>>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<>>9<<4)+14]=r;var e,i,a,h,d,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e>5]>>>t%32&255);return r}function h(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t>5]|=(255&n.charCodeAt(t/8))<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>>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"; - } - - event.preventDefault() -} - -var lwsgs_user_check = '0'; -var lwsgs_email_check = '0'; - -function lwsgs_rupdate() -{ - var en_register = 1, en_forgot = 0, op; - - if (document.getElementById('rpassword').value == - document.getElementById('password2').value) { - if (document.getElementById('rpassword').value.length) - document.getElementById('match').innerHTML = - "\u2713"; - 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 = - "\u2718 Passwords do not match"; - } else - document.getElementById('match').innerHTML = - "\u2718 Passwords do not match"; - - 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 = "\u2713"; - 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') { - var uc = document.getElementById('uchk'); - - if (uc) { - if (document.getElementById('rusername').value) - uc.innerHTML = "\u2713"; - else - uc.innerHTML = ""; - } - } else { - if (document.getElementById('uchk')) - ocument.getElementById('uchk').innerHTML = "\u2718 Already registered"; - en_forgot = 1; - } - - if (lwsgs_email_check === '0') { - var ec = document.getElementById('echk'); - - if (ec) { - if (document.getElementById('email').value) - ec.innerHTML = "\u2713"; - else - ec.innerHTML = ""; - } - } else { - if (document.getElementById('echk')) - document.getElementById('echk').innerHTML = "\u2718 Already registered"; - 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, op; - - 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 = "\u2718"; - } 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 = "\u2713"; - 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 = - "\u2718 Passwords do not match"; - } else - document.getElementById('cmatch').innerHTML = "\u2718 Passwords do not match"; - - 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 { - var cpw = document.getElementById('cpw1'); - - if (cpw) { - if (document.getElementById('cpassword').value.length) - cpw.innerHTML = "\u2713"; - else - cpw.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 = "\u2713"; - else - document.getElementById('cechk').innerHTML = ""; - } else { - document.getElementById('cechk').innerHTML = "\u2718 Already registered"; - 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 rupdate_user() -{ - lwsgs_rupdate(); - lwsgs_check_user(); -} - -function rupdate_email() -{ - lwsgs_rupdate(); - lwsgs_check_email('email'); -} - -function cupdate_email() -{ - lwsgs_cupdate(); - lwsgs_check_email('cemail'); -} - - -function lwsgs_initial() -{ - document.getElementById('lwsgs').innerHTML = lwsgs_html; - - if (lwsgs_user) { - document.getElementById("curuser").innerHTML = - "currently logged in as " + lwsgs_san(lwsgs_user) + "
"; - - document.getElementById("ccuruser").innerHTML = - "Login settings for " + - lwsgs_san(lwsgs_user) + "
"; - } - - document.getElementById('username').oninput = lwsgs_update; - document.getElementById('username').onchange = lwsgs_update; - document.getElementById('password').oninput = lwsgs_update; - document.getElementById('password').onchange = lwsgs_update; - document.getElementById('doreg').onclick = lwsgs_open_registration; - document.getElementById('clink').onclick = lwsgs_select_change; - document.getElementById('cancel').onclick =lwsgs_cancel_registration; - document.getElementById('cancel2').onclick =lwsgs_cancel_registration; - document.getElementById('rpassword').oninput = lwsgs_rupdate; - document.getElementById('password2').oninput = lwsgs_rupdate; - document.getElementById('rusername').oninput = rupdate_user; - document.getElementById('email').oninput = rupdate_email; - document.getElementById('ccurpw').oninput = lwsgs_cupdate; - document.getElementById('cpassword').oninput = lwsgs_cupdate; - document.getElementById('cpassword2').oninput = lwsgs_cupdate; - - document.getElementById('showdel').onchange = lwsgs_cupdate; - - if (lwsgs_email) - document.getElementById('grav').innerHTML = - ""; - //if (lwsgs_email) - //document.getElementById('cemail').placeholder = lwsgs_email; - document.getElementById('cusername').value = lwsgs_user; - lwsgs_update(); - lwsgs_cupdate(); -} - -window.addEventListener("load", function() { - lwsgs_initial(); - document.getElementById("nolog").style.display = !!lwsgs_user ? "none" : "inline-block"; - document.getElementById("logged").style.display = !lwsgs_user ? "none" : "inline-block"; - - document.getElementById("msg").onkeyup = mupd; - document.getElementById("msg").onchange = mupd; - - var ws; - - function mb_format(s) - { - var r = "", n, wos = 0; - - for (n = 0; n < s.length; n++) { - if (s[n] == ' ') - wos = 0; - else { - wos++; - if (wos === 40) { - wos = 0; - r = r + ' '; - } - } - if (s[n] == '<') { - r = r + "<"; - continue; - } - if (s[n] == '\n') { - r = r + "
"; - continue; - } - - r = r + s[n]; - } - - return r; - } - - function add_div(n, m) - { - var q = document.getElementById(n); - var d = new Date(m.time * 1000); - - q.innerHTML = "
" + - "
" + - "" + lwsgs_san(m.username) + "
" + - "" + d.toDateString() + - "
" + d.toTimeString() + "

" + - "IP: " + lwsgs_san(m.ip) + - "
" + - mb_format(m.content) + - "

" + q.innerHTML; - } - - function get_appropriate_ws_url() - { - var pcol; - var u = document.URL; - - if (u.substring(0, 5) == "https") { - pcol = "wss://"; - u = u.substr(8); - } else { - pcol = "ws://"; - if (u.substring(0, 4) == "http") - u = u.substr(7); - } - u = u.split('/'); - - return pcol + u[0] + "/xxx"; - } - - if (lwsgs_user) { - ws = new WebSocket(get_appropriate_ws_url(), - "protocol-lws-messageboard"); - - try { - ws.onopen = function() { - document.getElementById("debug").textContent = "ws opened"; - } - ws.onmessage =function got_packet(msg) { - add_div("messages", JSON.parse(msg.data)); - } - ws.onclose = function(){ - } - } catch(exception) { - alert('

Error' + exception); - } - } - - function mupd() - { - document.getElementById("send").disabled = !document.getElementById("msg").value; - } -}, false); diff --git a/plugins/generic-sessions/assets/md5.min.js b/plugins/generic-sessions/assets/md5.min.js deleted file mode 100644 index 4bd9de1e9..000000000 --- a/plugins/generic-sessions/assets/md5.min.js +++ /dev/null @@ -1,2 +0,0 @@ -!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<>>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<>>9<<4)+14]=r;var e,i,a,h,d,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e>5]>>>t%32&255);return r}function h(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t>5]|=(255&n.charCodeAt(t/8))<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>>4&15)+e.charAt(15&t);return o}function v(n){return unescape(encodeURIComponent(n))}function m(n){return d(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this); -//# sourceMappingURL=md5.min.js.map \ No newline at end of file diff --git a/plugins/generic-sessions/assets/post-forgot-fail.html b/plugins/generic-sessions/assets/post-forgot-fail.html deleted file mode 100644 index ead3d13ec..000000000 --- a/plugins/generic-sessions/assets/post-forgot-fail.html +++ /dev/null @@ -1,5 +0,0 @@ - -Sorry, something went wrong. - -Click here to continue. - diff --git a/plugins/generic-sessions/assets/post-forgot-ok.html b/plugins/generic-sessions/assets/post-forgot-ok.html deleted file mode 100644 index 3e8e9cf59..000000000 --- a/plugins/generic-sessions/assets/post-forgot-ok.html +++ /dev/null @@ -1,6 +0,0 @@ - -This is a one-time password recovery login. - -Please click here and click your username at the top to reset your password. - - diff --git a/plugins/generic-sessions/assets/post-register-fail.html b/plugins/generic-sessions/assets/post-register-fail.html deleted file mode 100644 index 063c3c50f..000000000 --- a/plugins/generic-sessions/assets/post-register-fail.html +++ /dev/null @@ -1 +0,0 @@ -Registration failed, sorry diff --git a/plugins/generic-sessions/assets/post-register-ok.html b/plugins/generic-sessions/assets/post-register-ok.html deleted file mode 100644 index c00c3f3d2..000000000 --- a/plugins/generic-sessions/assets/post-register-ok.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - -
- -
- Your registration as is accepted,
- you will receive an email shortly with instructions
- to verify and enable the account for normal use.

- The link is only valid for an hour, after that if it has
- not been verified your account will be deleted. -
- - - - diff --git a/plugins/generic-sessions/assets/post-verify-fail.html b/plugins/generic-sessions/assets/post-verify-fail.html deleted file mode 100644 index d1d89ca56..000000000 --- a/plugins/generic-sessions/assets/post-verify-fail.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - -
- -
- Sorry, the link was invalid. -
- - - diff --git a/plugins/generic-sessions/assets/post-verify-ok.html b/plugins/generic-sessions/assets/post-verify-ok.html deleted file mode 100644 index ae647fc5c..000000000 --- a/plugins/generic-sessions/assets/post-verify-ok.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - -
- -
- Thanks for signing up, your registration as is verified.
-
- Click here to continue. -
- - - - diff --git a/plugins/generic-sessions/assets/seats.jpg b/plugins/generic-sessions/assets/seats.jpg deleted file mode 100644 index 5bed40d919872359f2fcc00a8430e7dcfabaab4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122754 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 diff --git a/plugins/generic-sessions/assets/sent-forgot-fail.html b/plugins/generic-sessions/assets/sent-forgot-fail.html deleted file mode 100644 index ead3d13ec..000000000 --- a/plugins/generic-sessions/assets/sent-forgot-fail.html +++ /dev/null @@ -1,5 +0,0 @@ - -Sorry, something went wrong. - -Click here to continue. - diff --git a/plugins/generic-sessions/assets/sent-forgot-ok.html b/plugins/generic-sessions/assets/sent-forgot-ok.html deleted file mode 100644 index 83df7510a..000000000 --- a/plugins/generic-sessions/assets/sent-forgot-ok.html +++ /dev/null @@ -1,4 +0,0 @@ -An email has been sent to your registered address. - -Please follow the instructions to reset your password. - diff --git a/plugins/generic-sessions/assets/successful-login.html b/plugins/generic-sessions/assets/successful-login.html deleted file mode 100644 index dfc25cf74..000000000 --- a/plugins/generic-sessions/assets/successful-login.html +++ /dev/null @@ -1,4 +0,0 @@ - -This is an example destination that will appear after successful non-Admin login - - diff --git a/plugins/generic-sessions/handlers.c b/plugins/generic-sessions/handlers.c deleted file mode 100644 index 604841248..000000000 --- a/plugins/generic-sessions/handlers.c +++ /dev/null @@ -1,663 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010 - 2019 Andy Green - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include "private-lwsgs.h" - -#if defined(LWS_WITH_SMTP) - -static int -lwsgs_smtp_client_done(struct lws_smtp_email *e, void *buf, size_t len) -{ - free(e); - - return 0; -} - -static int -lwsgs_smtp_client_done_sentvfy(struct lws_smtp_email *e, void *buf, size_t len) -{ - struct per_vhost_data__gs *vhd = (struct per_vhost_data__gs *)e->data; - const char *username = (const char *)e->extra; - char s[200], esc[96]; - - lwsl_notice("%s: registration email sent: %s\n", __func__, username); - - /* mark the user as having sent the verification email */ - lws_snprintf(s, sizeof(s) - 1, - "update users set verified=1 where username='%s' and verified==0;", - lws_sql_purify(esc, 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; - } - - free(e); - - return 0; -} -#endif - -/* 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[90]; - 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) { - lwsl_err("%s: missing URI_ARGS\n", __func__); - goto verf_fail; - } - - if (strncmp(cookie, "token=", 6)) { - lwsl_err("%s: missing URI_ARGS token=\n", __func__); - goto verf_fail; - } - - u.username[0] = '\0'; - u.verified = -1; - lws_snprintf(s, sizeof(s) - 1, - "select username,email,verified from users where token = '%s';", - lws_sql_purify(esc, &cookie[6], sizeof(esc) - 1)); - puts(s); - 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 %s doesn't map to unverified user (user='%s', verified=%d)\n", - &cookie[6], u.username, u.verified); - goto verf_fail; - } - - lwsl_notice("Verifying %s\n", u.username); - lws_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_vhost(lws_get_vhost(wsi), - LWS_CALLBACK_GS_EVENT, &a, 0); - - lws_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; - - lws_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[96]; - 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'; - lws_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 */ - - lws_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"; - - lws_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"; - - lws_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, - const char *in) -{ - static const char * const colname[] = { "username", "email" }; - char s[256], esc[96], *pc; - unsigned char *p, *start, *end, buffer[LWS_PRE + 1024]; - 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'; - - n = !strncmp(in, "email=", 6); - pc = strchr(in, '='); - 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; - } - - lws_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, HTTP_STATUS_OK, &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 %ld\n", n, (long)(p - start)); - return -1; - } - - pss->check_response_value = s[0]; - pss->check_response = 1; - - lws_callback_on_writable(wsi); - - 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[96], username[96]; - 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 > (time_t)lws_now_secs() - 300) { - n |= LWSGS_AUTH_FORGOT_FLOW; - lwsl_debug("within forgot password flow\n"); - } - } - } - - lwsl_debug("auth value %d\n", n); - - /* if he just did forgot pw flow, don't need old pw */ - if ((n & (LWSGS_AUTH_FORGOT_FLOW | 1)) != (LWSGS_AUTH_FORGOT_FLOW | 1)) { - /* otherwise user:pass must be right */ - lwsl_debug("checking pw\n"); - 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; - } - - lwsl_debug("current pw checks out\n"); - - lws_strncpy(u.username, lws_spa_get_string(pss->spa, FGS_USERNAME), - sizeof(u.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_vhost(lws_get_vhost(wsi), - LWS_CALLBACK_GS_EVENT, &a, 0); - - lws_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"); - - lws_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 esc[96], esc1[96], esc2[96], esc3[96], esc4[96]; - char s[LWSGS_EMAIL_CONTENT_SIZE]; - unsigned char sid_rand[32]; -#if defined(LWS_WITH_SMTP) - lws_smtp_email_t *em; -#endif - struct lwsgs_user u; - lwsgw_hash hash; - 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)) - lws_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 - lws_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; - } - sha256_to_lwsgw_hash(sid_rand, &hash); - - lws_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; - } - - n = lws_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_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)); - lws_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); - - puts(s); -#if defined(LWS_WITH_SMTP) - - em = lws_smtpc_alloc_email_helper(s, n, vhd->email_from, u.email, - u.username, strlen(u.username), - vhd, lwsgs_smtp_client_done); - if (!em) - return 1; - if (lws_smtpc_add_email(vhd->smtp_client, em)) - return 1; -#endif - 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[96], esc1[96], esc2[96], esc3[96], esc4[96]; - char s[LWSGS_EMAIL_CONTENT_SIZE]; - unsigned char sid_rand[32]; -#if defined(LWS_WITH_SMTP) - lws_smtp_email_t *em; -#endif - struct lwsgs_user u; - lwsgw_hash hash; - size_t n; - - 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'; - lws_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; - } - sha256_to_lwsgw_hash(sid_rand, &hash); - - lws_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; - } - - n = lws_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_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); - -#if defined(LWS_WITH_SMTP) - em = lws_smtpc_alloc_email_helper(s, n, vhd->email_from, - lws_spa_get_string(pss->spa, FGS_EMAIL), - lws_spa_get_string(pss->spa, FGS_USERNAME), - strlen(lws_spa_get_string(pss->spa, FGS_USERNAME)), - vhd, lwsgs_smtp_client_done_sentvfy); - if (!em) - return 1; - - if (lws_smtpc_add_email(vhd->smtp_client, em)) - return 1; -#else - (void)n; -#endif - - return 0; -} diff --git a/plugins/generic-sessions/private-lwsgs.h b/plugins/generic-sessions/private-lwsgs.h deleted file mode 100644 index 157679e71..000000000 --- a/plugins/generic-sessions/private-lwsgs.h +++ /dev/null @@ -1,171 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010 - 2019 Andy Green - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#define LWS_DLL -#define LWS_INTERNAL -#include - -#include -#include - -#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 { - lws_abs_t *smtp_client; - struct lwsgs_user u; - lws_token_map_t transport_tokens[3]; - lws_token_map_t protocol_tokens[2]; - char helo[64], ip[64]; - struct lws_context *context; - char session_db[256]; - char admin_user[32]; - char urlroot[48]; - char confounder[32]; - char email_contact_person[128]; - char email_title[128]; - char email_template[128]; - char email_confirm_url[128]; - char email_from[128]; - lwsgw_hash admin_password_sha256; - 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 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; - char check_response_value; - - unsigned int logging_out:1; - unsigned int check_response: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 -sha256_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, const char *in); -int -lwsgs_handler_change_password(struct per_vhost_data__gs *vhd, struct lws *wsi, - struct per_session_data__gs *pss); -int -lwsgs_handler_forgot_pw_form(struct per_vhost_data__gs *vhd, struct lws *wsi, - struct per_session_data__gs *pss); -int -lwsgs_handler_register_form(struct per_vhost_data__gs *vhd, struct lws *wsi, - struct per_session_data__gs *pss); - diff --git a/plugins/generic-sessions/protocol_generic_sessions.c b/plugins/generic-sessions/protocol_generic_sessions.c deleted file mode 100644 index 7622187d6..000000000 --- a/plugins/generic-sessions/protocol_generic_sessions.c +++ /dev/null @@ -1,920 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010 - 2019 Andy Green - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include "private-lwsgs.h" -#include - -/* 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[]; - -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[96], 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; - } - lws_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"); - - lws_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 -lws_get_effective_host(struct lws *wsi, char *buf, size_t buflen) -{ -#if defined(LWS_ROLE_H2) - /* h2 */ - if (lws_hdr_copy(wsi, buf, buflen - 1, - WSI_TOKEN_HTTP_COLON_AUTHORITY) > 0) - return 0; -#endif - /* h1 */ - if (lws_hdr_copy(wsi, buf, buflen - 1, WSI_TOKEN_HOST) > 0) - return 0; - - return 1; -} - -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), - lws_vhost_name_to_protocol(lws_get_vhost(wsi), - "protocol-generic-sessions")); - char cookie[1024], username[32], *pc = cookie; - unsigned char buffer[LWS_PRE + LWSGS_EMAIL_CONTENT_SIZE]; - struct lws_process_html_args *args = in; - struct lws_session_info *sinfo; - char s[LWSGS_EMAIL_CONTENT_SIZE]; - unsigned char *p, *start, *end; - const char *cp, *cp1; - sqlite3_stmt *sm; - lwsgw_hash sid; -#if defined(LWS_WITH_SMTP) - lws_abs_t abs; -#endif - int n; - - switch (reason) { - case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */ - - vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), - lws_get_protocol(wsi), 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->helo, "unconfigured.com"); - strcpy(vhd->ip, "127.0.0.1"); - strcpy(vhd->email_from, "noreply@unconfigured.com"); - strcpy(vhd->email_title, "Registration Email from unconfigured"); - vhd->urlroot[0] = '\0'; - - pvo = (const struct lws_protocol_vhost_options *)in; - while (pvo) { - if (!strcmp(pvo->name, "admin-user")) - lws_strncpy(vhd->admin_user, pvo->value, - sizeof(vhd->admin_user)); - if (!strcmp(pvo->name, "urlroot")) - lws_strncpy(vhd->urlroot, pvo->value, - sizeof(vhd->urlroot)); - if (!strcmp(pvo->name, "admin-password-sha256")) - lws_strncpy(vhd->admin_password_sha256.id, pvo->value, - sizeof(vhd->admin_password_sha256.id)); - if (!strcmp(pvo->name, "session-db")) - lws_strncpy(vhd->session_db, pvo->value, - sizeof(vhd->session_db)); - if (!strcmp(pvo->name, "confounder")) - lws_strncpy(vhd->confounder, pvo->value, - sizeof(vhd->confounder)); - if (!strcmp(pvo->name, "email-from")) - lws_strncpy(vhd->email_from, pvo->value, - sizeof(vhd->email_from)); - if (!strcmp(pvo->name, "email-helo")) - lws_strncpy(vhd->helo, pvo->value, sizeof(vhd->helo)); - if (!strcmp(pvo->name, "email-template")) - lws_strncpy(vhd->email_template, pvo->value, - sizeof(vhd->email_template)); - if (!strcmp(pvo->name, "email-title")) - lws_strncpy(vhd->email_title, pvo->value, - sizeof(vhd->email_title)); - if (!strcmp(pvo->name, "email-contact-person")) - lws_strncpy(vhd->email_contact_person, pvo->value, - sizeof(vhd->email_contact_person)); - if (!strcmp(pvo->name, "email-confirm-url-base")) - lws_strncpy(vhd->email_confirm_url, pvo->value, - sizeof(vhd->email_confirm_url)); - if (!strcmp(pvo->name, "email-server-ip")) - lws_strncpy(vhd->ip, pvo->value, sizeof(vhd->ip)); - - 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_sha256.id[0] || - !vhd->session_db[0]) { - lwsl_err("generic-sessions: " - "You must give \"admin-user\", " - "\"admin-password-sha256\", " - "and \"session_db\" per-vhost options\n"); - return 1; - } - - if (lws_struct_sq3_open(lws_get_context(wsi), - vhd->session_db, 1, &vhd->pdb)) { - 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(65)," - " 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(65)," - " pwsalt varchar(65)," - " pwchange_time integer," - " token varchar(65)," - " 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; - } - -#if defined(LWS_WITH_SMTP) - - memset(&abs, 0, sizeof(abs)); - abs.vh = lws_get_vhost(wsi); - - /* select the protocol and bind its tokens */ - - abs.ap = lws_abs_protocol_get_by_name("smtp"); - if (!abs.ap) - return 1; - - vhd->protocol_tokens[0].name_index = LTMI_PSMTP_V_HELO; - vhd->protocol_tokens[0].u.value = vhd->helo; - - abs.ap_tokens = vhd->protocol_tokens; - - /* select the transport and bind its tokens */ - - abs.at = lws_abs_transport_get_by_name("raw_skt"); - if (!abs.at) - return 1; - - vhd->transport_tokens[0].name_index = LTMI_PEER_V_DNS_ADDRESS; - vhd->transport_tokens[0].u.value = vhd->ip; - vhd->transport_tokens[1].name_index = LTMI_PEER_LV_PORT; - vhd->transport_tokens[1].u.lvalue = 25; - - abs.at_tokens = vhd->transport_tokens; - - vhd->smtp_client = lws_abs_bind_and_create_instance(&abs); - if (!vhd->smtp_client) - return 1; - - lwsl_notice("%s: created SMTP client\n", __func__); -#endif - break; - - case LWS_CALLBACK_PROTOCOL_DESTROY: - // lwsl_notice("gs: LWS_CALLBACK_PROTOCOL_DESTROY: v=%p, ctx=%p\n", vhd, vhd->context); - if (vhd->pdb) { - sqlite3_close(vhd->pdb); - vhd->pdb = NULL; - } -#if defined(LWS_WITH_SMTP) - if (vhd->smtp_client) - lws_abs_destroy_instance(&vhd->smtp_client); -#endif - break; - - case LWS_CALLBACK_HTTP_WRITEABLE: - if (!pss->check_response) - break; - pss->check_response = 0; - n = lws_write(wsi, (unsigned char *)&pss->check_response_value, - 1, LWS_WRITE_HTTP | LWS_WRITE_H2_STREAM_END); - if (n != 1) - return -1; - goto try_to_reuse; - - case LWS_CALLBACK_HTTP: - if (!pss) { - lwsl_err("%s: no valid pss\n", __func__); - return 1; - } - - pss->login_session.id[0] = '\0'; - pss->phs.pos = 0; - - cp = in; - if ((*(const char *)in == '/')) - cp++; - - if (lws_get_effective_host(wsi, cookie, sizeof(cookie))) { - lwsl_err("%s: HTTP: no effective host\n", __func__); - return 1; - } - - lwsl_notice("LWS_CALLBACK_HTTP: %s, HOST '%s'\n", - (const char *)in, cookie); - - n = strlen(cp); - - lws_snprintf(pss->onward, sizeof(pss->onward), - "%s%s", vhd->urlroot, (const char *)in); - - if (n >= 12 && - !strcmp(cp + n - 12, "lwsgs-forgot")) { - lwsgs_handler_forgot(vhd, wsi, pss); - goto redirect_with_cookie; - } - - if (n >= 13 && - !strcmp(cp + n - 13, "lwsgs-confirm")) { - lwsgs_handler_confirm(vhd, wsi, pss); - goto redirect_with_cookie; - } - cp1 = strstr(cp, "lwsgs-check/"); - if (cp1) { - lwsgs_handler_check(vhd, wsi, pss, cp1 + 12); - /* second, async part will complete transaction */ - break; - } - - if (n >= 11 && cp && !strcmp(cp + n - 11, "lwsgs-login")) - break; - if (n >= 12 && cp && !strcmp(cp + n - 12, "lwsgs-logout")) - break; - if (n >= 12 && cp && !strcmp(cp + n - 12, "lwsgs-forgot")) - break; - if (n >= 12 && cp && !strcmp(cp + n - 12, "lwsgs-change")) - break; - - /* if no legitimate url for GET, return 404 */ - - lwsl_err("%s: http doing 404 on %s\n", __func__, cp ? cp : "null"); - lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); - - return -1; - //goto try_to_reuse; - - case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: - args = (struct lws_process_html_args *)in; - if (!args->chunked) - break; - case LWS_CALLBACK_CHECK_ACCESS_RIGHTS: - n = 0; - username[0] = '\0'; - sid.id[0] = '\0'; - args = (struct lws_process_html_args *)in; - lwsl_notice("%s: LWS_CALLBACK_CHECK_ACCESS_RIGHTS: need 0x%x\n", - __func__, args->max_len); - if (!lwsgs_get_sid_from_wsi(wsi, &sid)) { - if (lwsgs_lookup_session(vhd, &sid, username, - sizeof(username))) { - - /* - * if we're authenticating for ws, we don't - * want to redirect it or gain a cookie on that, - * he'll need to get the cookie from http - * interactions outside of this. - */ - if (args->chunked) { - lwsl_notice("%s: ws auth failed\n", - __func__); - - return 1; - } - - lwsl_notice("session lookup for %s failed, " - "probably expired\n", sid.id); - pss->delete_session = sid; - args->final = 1; /* signal we dealt with it */ - lws_snprintf(pss->onward, sizeof(pss->onward) - 1, - "%s%s", vhd->urlroot, 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("%s: failed to get sid from wsi\n", __func__); - - n = lwsgs_get_auth_level(vhd, username); - lwsl_notice("%s: lwsgs_get_auth_level '%s' says %d\n", __func__, username, n); - - 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; - - lws_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; - } - lws_strncpy(sinfo->username, u.username, sizeof(sinfo->username)); - lws_strncpy(sinfo->email, u.email, sizeof(sinfo->email)); - lws_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 = LWS_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) { - lwsl_err("%s: HTTP_BODY: len %d < 2\n", __func__, (int)len); - break; - } - - if (!pss->spa) { - pss->spa = lws_spa_create(wsi, param_names, - LWS_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: - - lwsl_debug("%s: LWS_CALLBACK_HTTP_BODY_COMPLETION\n", __func__); - - if (!pss->spa) - break; - - cp1 = (const char *)pss->onward; - if (*cp1 == '/') - cp1++; - - - lws_spa_finalize(pss->spa); - n = strlen(cp1); - - if (lws_get_effective_host(wsi, cookie, sizeof(cookie))) - return 1; - - if (!strcmp(cp1 + n - 12, "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)); - lws_snprintf(pss->onward, sizeof(pss->onward), - "%s%s", vhd->urlroot, cp); - - pss->onward[sizeof(pss->onward) - 1] = '\0'; - goto completion_flow; - } - - if (!strcmp(cp1 + n - 11, "lwsgs-login")) { - lwsl_err("%s: lwsgs-login\n", __func__); - 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; - } -#if defined(LWS_WITH_SMTP) - /* get the email monitor to take a look */ - lws_smtpc_kick(vhd->smtp_client); -#endif - 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; -#if defined(LWS_WITH_SMTP) - /* get the email monitor to take a look */ - lws_smtpc_kick(vhd->smtp_client); -#endif - } -reg_done: - lws_snprintf(pss->onward, sizeof(pss->onward), - "%s%s", vhd->urlroot, - lws_spa_get_string(pss->spa, n)); - - 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_notice("pw hash check met\n"); - cp = lws_spa_get_string(pss->spa, FGS_GOOD); - goto pass; - } else - lwsl_notice("user/password no good %s %s\n", - lws_spa_get_string(pss->spa, FGS_USERNAME), - lws_spa_get_string(pss->spa, FGS_PASSWORD)); - - if (!lws_spa_get_string(pss->spa, FGS_BAD)) { - lwsl_info("No admin or good target url in form\n"); - return -1; - } - - lws_snprintf(pss->onward, sizeof(pss->onward), - "%s%s", vhd->urlroot, - lws_spa_get_string(pss->spa, FGS_BAD)); - - lwsl_notice("failed: %s\n", pss->onward); - - goto completion_flow; - } - - if (!strcmp(cp1 + n - 12, "lwsgs-logout")) { - - lwsl_notice("/logout\n"); - - if (lwsgs_get_sid_from_wsi(wsi, &pss->login_session)) { - lwsl_notice("not logged in...\n"); - return 1; - } - - /* - * We keep the same session, but mark it as not - * being associated to any authenticated user - */ - - 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; - } - - lws_snprintf(pss->onward, sizeof(pss->onward), - "%s%s", vhd->urlroot, - lws_spa_get_string(pss->spa, FGS_GOOD)); - - pss->login_expires = 0; - pss->logging_out = 1; - - goto completion_flow; - } - - break; - -pass: - lws_snprintf(pss->onward, sizeof(pss->onward), - "%s%s", vhd->urlroot, cp); - - 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_notice("%s: Creating new session: %s\n", __func__, - pss->login_session.id); - } else { - /* - * we can just update the existing session to be - * authorized - */ - lwsl_notice("%s: Authorizing existing session %s, name %s\n", - __func__, sid.id, - lws_spa_get_string(pss->spa, FGS_USERNAME)); - 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 && pss->spa) { - lws_spa_destroy(pss->spa); - pss->spa = NULL; - } - break; - - case LWS_CALLBACK_ADD_HEADERS: - lwsgw_expire_old_sessions(vhd); - - lwsl_warn("ADD_HEADERS\n"); - - args = (struct lws_process_html_args *)in; - if (!pss) - return 1; - if (pss->delete_session.id[0]) { - pc = cookie; - 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, - (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; - - lwsl_warn("%s: redirect_with_cookie\n", __func__); - - if (lws_add_http_header_status(wsi, HTTP_STATUS_SEE_OTHER, &p, end)) - return 1; - - { - char loc[1024], uria[128]; - - uria[0] = '\0'; - lws_hdr_copy_fragment(wsi, uria, sizeof(uria), - WSI_TOKEN_HTTP_URI_ARGS, 0); - n = lws_snprintf(loc, sizeof(loc), "%s?%s", - pss->onward, uria); - lwsl_notice("%s: redirect to '%s'\n", __func__, loc); - if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, - (unsigned char *)loc, n, &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)) { - lwsl_err("fail0\n"); - 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)) { - lwsl_err("fail1\n"); - 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_err("%s: setting cookie '%s'\n", __func__, cookie); - - pss->logging_out = 0; - - if (lws_add_http_header_by_name(wsi, - (unsigned char *)"set-cookie:", - (unsigned char *)cookie, pc - cookie, - &p, end)) { - lwsl_err("fail2\n"); - return 1; - } - } - - if (lws_finalize_http_header(wsi, &p, end)) - return 1; - - // lwsl_hexdump_notice(start, p - start); - - n = lws_write(wsi, start, p - start, LWS_WRITE_H2_STREAM_END | - 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_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 = LWS_ARRAY_SIZE(protocols); - c->extensions = NULL; - c->count_extensions = 0; - - return 0; -} - -LWS_VISIBLE int -destroy_protocol_generic_sessions(struct lws_context *context) -{ - return 0; -} diff --git a/plugins/generic-sessions/protocol_lws_messageboard.c b/plugins/generic-sessions/protocol_lws_messageboard.c deleted file mode 100644 index 94c01ab8f..000000000 --- a/plugins/generic-sessions/protocol_lws_messageboard.c +++ /dev/null @@ -1,438 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010 - 2019 Andy Green - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - - -#define LWS_DLL -#define LWS_INTERNAL -#include - -#include -#include -#include - -struct per_vhost_data__gs_mb { - struct lws_vhost *vh; - const struct lws_protocols *gsp; - sqlite3 *pdb; - char message_db[256]; - unsigned long last_idx; -}; - -struct per_session_data__gs_mb { - void *pss_gs; /* for use by generic-sessions */ - struct lws_session_info sinfo; - struct lws_spa *spa; - unsigned long last_idx; - unsigned int our_form:1; - char second_http_part; -}; - -static const char * const param_names[] = { - "send", - "msg", -}; -enum { - MBSPA_SUBMIT, - MBSPA_MSG, -}; - -#define MAX_MSG_LEN 512 - -struct message { - unsigned long idx; - unsigned long time; - char username[32]; - char email[100]; - char ip[72]; - char content[MAX_MSG_LEN]; -}; - -static int -lookup_cb(void *priv, int cols, char **col_val, char **col_name) -{ - struct message *m = (struct message *)priv; - int n; - - for (n = 0; n < cols; n++) { - - if (!strcmp(col_name[n], "idx") || - !strcmp(col_name[n], "MAX(idx)")) { - if (!col_val[n]) - m->idx = 0; - else - m->idx = atol(col_val[n]); - continue; - } - if (!strcmp(col_name[n], "time")) { - m->time = atol(col_val[n]); - continue; - } - if (!strcmp(col_name[n], "username")) { - lws_strncpy(m->username, col_val[n], sizeof(m->username)); - continue; - } - if (!strcmp(col_name[n], "email")) { - lws_strncpy(m->email, col_val[n], sizeof(m->email)); - continue; - } - if (!strcmp(col_name[n], "ip")) { - lws_strncpy(m->ip, col_val[n], sizeof(m->ip)); - continue; - } - if (!strcmp(col_name[n], "content")) { - lws_strncpy(m->content, col_val[n], sizeof(m->content)); - continue; - } - } - return 0; -} - -static unsigned long -get_last_idx(struct per_vhost_data__gs_mb *vhd) -{ - struct message m; - - if (sqlite3_exec(vhd->pdb, "SELECT MAX(idx) FROM msg;", - lookup_cb, &m, NULL) != SQLITE_OK) { - lwsl_err("Unable to lookup token: %s\n", - sqlite3_errmsg(vhd->pdb)); - return 0; - } - - return m.idx; -} - -static int -post_message(struct lws *wsi, struct per_vhost_data__gs_mb *vhd, - struct per_session_data__gs_mb *pss) -{ - struct lws_session_info sinfo; - char s[MAX_MSG_LEN + 512]; - char esc[MAX_MSG_LEN + 256]; - - vhd->gsp->callback(wsi, LWS_CALLBACK_SESSION_INFO, - pss->pss_gs, &sinfo, 0); - - lws_snprintf((char *)s, sizeof(s) - 1, - "insert into msg(time, username, email, ip, content)" - " values (%lu, '%s', '%s', '%s', '%s');", - (unsigned long)lws_now_secs(), sinfo.username, sinfo.email, sinfo.ip, - lws_sql_purify(esc, lws_spa_get_string(pss->spa, MBSPA_MSG), - sizeof(esc) - 1)); - if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) { - lwsl_err("Unable to insert msg: %s\n", sqlite3_errmsg(vhd->pdb)); - return 1; - } - vhd->last_idx = get_last_idx(vhd); - - /* let everybody connected by this protocol on this vhost know */ - lws_callback_on_writable_all_protocol_vhost(lws_get_vhost(wsi), - lws_get_protocol(wsi)); - - return 0; -} - -static int -callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason, - void *user, void *in, size_t len) -{ - struct per_session_data__gs_mb *pss = (struct per_session_data__gs_mb *)user; - const struct lws_protocol_vhost_options *pvo; - struct per_vhost_data__gs_mb *vhd = (struct per_vhost_data__gs_mb *) - lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi)); - unsigned char *p, *start, *end, buffer[LWS_PRE + 4096]; - char s[512]; - int n; - - switch (reason) { - case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */ - - vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), - lws_get_protocol(wsi), sizeof(struct per_vhost_data__gs_mb)); - if (!vhd) - return 1; - vhd->vh = lws_get_vhost(wsi); - vhd->gsp = lws_vhost_name_to_protocol(vhd->vh, - "protocol-generic-sessions"); - if (!vhd->gsp) { - lwsl_err("messageboard: requires generic-sessions\n"); - return 1; - } - - pvo = (const struct lws_protocol_vhost_options *)in; - while (pvo) { - if (!strcmp(pvo->name, "message-db")) - strncpy(vhd->message_db, pvo->value, - sizeof(vhd->message_db) - 1); - pvo = pvo->next; - } - if (!vhd->message_db[0]) { - lwsl_err("messageboard: \"message-db\" pvo missing\n"); - return 1; - } - - if (lws_struct_sq3_open(lws_get_context(wsi), - vhd->message_db, 1, &vhd->pdb)) { - lwsl_err("Unable to open message db %s: %s\n", - vhd->message_db, sqlite3_errmsg(vhd->pdb)); - - return 1; - } - if (sqlite3_exec(vhd->pdb, "create table if not exists msg (" - " idx integer primary key, time integer," - " username varchar(32), email varchar(100)," - " ip varchar(80), content blob);", - NULL, NULL, NULL) != SQLITE_OK) { - lwsl_err("Unable to create msg table: %s\n", - sqlite3_errmsg(vhd->pdb)); - - return 1; - } - - vhd->last_idx = get_last_idx(vhd); - break; - - case LWS_CALLBACK_PROTOCOL_DESTROY: - if (vhd && vhd->pdb) - sqlite3_close(vhd->pdb); - goto passthru; - - case LWS_CALLBACK_ESTABLISHED: - vhd->gsp->callback(wsi, LWS_CALLBACK_SESSION_INFO, - pss->pss_gs, &pss->sinfo, 0); - if (!pss->sinfo.username[0]) { - lwsl_notice("messageboard ws attempt with no session\n"); - - return -1; - } - - lws_callback_on_writable(wsi); - break; - - case LWS_CALLBACK_CLOSED: - lwsl_debug("%s: LWS_CALLBACK_CLOSED\n", __func__); - if (pss && pss->pss_gs) { - free(pss->pss_gs); - pss->pss_gs = NULL; - } - break; - - case LWS_CALLBACK_SERVER_WRITEABLE: - { - struct message m; - char j[MAX_MSG_LEN + 512], e[MAX_MSG_LEN + 512], - *p = j + LWS_PRE, *start = p, - *end = j + sizeof(j) - LWS_PRE; - - if (pss->last_idx == vhd->last_idx) - break; - - /* restrict to last 10 */ - if (!pss->last_idx) - if (vhd->last_idx >= 10) - pss->last_idx = vhd->last_idx - 10; - - sprintf(s, "select idx, time, username, email, ip, content " - "from msg where idx > %lu order by idx limit 1;", - pss->last_idx); - if (sqlite3_exec(vhd->pdb, s, lookup_cb, &m, NULL) != SQLITE_OK) { - lwsl_err("Unable to lookup msg: %s\n", - sqlite3_errmsg(vhd->pdb)); - return 0; - } - - /* format in JSON */ - p += lws_snprintf(p, end - p, - "{\"idx\":\"%lu\",\"time\":\"%lu\",", - m.idx, m.time); - p += lws_snprintf(p, end - p, " \"username\":\"%s\",", - lws_json_purify(e, m.username, sizeof(e), NULL)); - p += lws_snprintf(p, end - p, " \"email\":\"%s\",", - lws_json_purify(e, m.email, sizeof(e), NULL)); - p += lws_snprintf(p, end - p, " \"ip\":\"%s\",", - lws_json_purify(e, m.ip, sizeof(e), NULL)); - p += lws_snprintf(p, end - p, " \"content\":\"%s\"}", - lws_json_purify(e, m.content, sizeof(e), NULL)); - - if (lws_write(wsi, (unsigned char *)start, p - start, - LWS_WRITE_TEXT) < 0) - return -1; - - pss->last_idx = m.idx; - if (pss->last_idx == vhd->last_idx) - break; - - lws_callback_on_writable(wsi); /* more to do */ - } - break; - - case LWS_CALLBACK_HTTP: - pss->our_form = 0; - - /* ie, it's our messageboard new message form */ - if (!strcmp((const char *)in, "/msg") || - !strcmp((const char *)in, "msg")) { - pss->our_form = 1; - break; - } - - goto passthru; - - case LWS_CALLBACK_HTTP_BODY: - if (!pss->our_form) - goto passthru; - - if (len < 2) - break; - if (!pss->spa) { - pss->spa = lws_spa_create(wsi, param_names, - LWS_ARRAY_SIZE(param_names), - MAX_MSG_LEN + 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_WRITEABLE: - if (!pss->second_http_part) - goto passthru; - - s[0] = '0'; - n = lws_write(wsi, (unsigned char *)s, 1, LWS_WRITE_HTTP| - LWS_WRITE_H2_STREAM_END); - if (n != 1) - return -1; - - goto try_to_reuse; - - case LWS_CALLBACK_HTTP_BODY_COMPLETION: - if (!pss->our_form) - goto passthru; - - if (post_message(wsi, vhd, pss)) - return -1; - - p = buffer + LWS_PRE; - start = p; - end = p + sizeof(buffer) - LWS_PRE; - - if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &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 %ld\n", n, (long)(p - start)); - return -1; - } - pss->second_http_part = 1; - lws_callback_on_writable(wsi); - break; - - case LWS_CALLBACK_HTTP_BIND_PROTOCOL: - if (!pss || !vhd || 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->spa) { - lws_spa_destroy(pss->spa); - pss->spa = NULL; - } - if (pss && pss->pss_gs) { - free(pss->pss_gs); - pss->pss_gs = NULL; - } - break; - - default: -passthru: - if (!pss || !vhd) - break; - - return vhd->gsp->callback(wsi, reason, pss->pss_gs, in, len); - } - - return 0; - - -try_to_reuse: - if (lws_http_transaction_completed(wsi)) - return -1; - - return 0; -} - -static const struct lws_protocols protocols[] = { - { - "protocol-lws-messageboard", - callback_messageboard, - sizeof(struct per_session_data__gs_mb), - 4096, - }, -}; - -LWS_VISIBLE int -init_protocol_lws_messageboard(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 = LWS_ARRAY_SIZE(protocols); - c->extensions = NULL; - c->count_extensions = 0; - - return 0; -} - -LWS_VISIBLE int -destroy_protocol_lws_messageboard(struct lws_context *context) -{ - return 0; -} diff --git a/plugins/generic-sessions/utils.c b/plugins/generic-sessions/utils.c deleted file mode 100644 index 9e603ea71..000000000 --- a/plugins/generic-sessions/utils.c +++ /dev/null @@ -1,465 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010 - 2019 Andy Green - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include "private-lwsgs.h" -#include - -void -sha256_to_lwsgw_hash(unsigned char *hash, lwsgw_hash *shash) -{ - static const char *hex = "0123456789abcdef"; - char *p = shash->id; - int n; - - for (n = 0; n < (int)lws_genhash_size(LWS_GENHASH_TYPE_SHA256); n++) { - *p++ = hex[(hash[n] >> 4) & 0xf]; - *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); - sha256_to_lwsgw_hash(hash_bin.bin, &pw_hash); - - return !strcmp(vhd->admin_password_sha256.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 += lws_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 += lws_snprintf(*p, end - *p, ";path=/"); - *p += lws_snprintf(*p, end - *p, ";Max-Age=%lu", (unsigned long)(expires - n)); -// *p += lws_snprintf(*p, end - *p, ";secure"); - *p += lws_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; - - lws_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[96], esc1[96]; - - if (user[0]) - n += vhd->timeout_absolute_secs; - else - n += vhd->timeout_anon_absolute_secs; - - lws_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; - } - - puts(s); - - 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 < (int)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 < (int)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("%s: session from cookie failed\n", __func__); - 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]) { - lws_strncpy(lla->username, col_val[0], lla->len + 1); - 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[96]; - - lwsgw_expire_old_sessions(vhd); - - lws_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")) { - lws_strncpy(u->username, col_val[n], sizeof(u->username)); - continue; - } - if (!strcmp(col_name[n], "ip")) { - lws_strncpy(u->ip, col_val[n], sizeof(u->ip)); - 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")) { - lws_strncpy(u->email, col_val[n], sizeof(u->email)); - continue; - } - if (!strcmp(col_name[n], "verified")) { - u->verified = atoi(col_val[n]); - continue; - } - if (!strcmp(col_name[n], "pwhash")) { - lws_strncpy(u->pwhash.id, col_val[n], sizeof(u->pwhash.id)); - continue; - } - if (!strcmp(col_name[n], "pwsalt")) { - lws_strncpy(u->pwsalt.id, col_val[n], sizeof(u->pwsalt.id)); - continue; - } - if (!strcmp(col_name[n], "token")) { - lws_strncpy(u->token.id, col_val[n], sizeof(u->token.id)); - continue; - } - } - return 0; -} - -int -lwsgs_lookup_user(struct per_vhost_data__gs *vhd, - const char *username, struct lwsgs_user *u) -{ - char s[150], esc[96]; - - u->username[0] = '\0'; - lws_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[32]; - const char *u; - char s[300], esc[96], esc1[96]; - - if (username) - u = username; - else - u = ""; - - if (!sid) { - lwsl_err("%s: NULL sid\n", __func__); - return 1; - } - - memset(sid, 0, sizeof(*sid)); - - if (lws_get_random(vhd->context, sid_rand, sizeof(sid_rand)) != - sizeof(sid_rand)) - return 1; - - sha256_to_lwsgw_hash(sid_rand, sid); - - lws_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; - } - - lwsl_notice("%s: created session %s\n", __func__, sid->id); - - 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]) { - /* we are logged in as admin */ - if (!strcmp(username, vhd->admin_user)) - /* automatically verified */ - n |= LWSGS_AUTH_VERIFIED | LWSGS_AUTH_ADMIN; - } - - if (!lwsgs_lookup_user(vhd, username, &u)) { - if ((u.verified & 0xff) == LWSGS_VERIFIED_ACCEPTED) - n |= LWSGS_AUTH_LOGGED_IN | LWSGS_AUTH_VERIFIED; - - if (u.last_forgot_validated > (time_t)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) -{ - struct lws_genhash_ctx hash_ctx; - lwsgw_hash_bin hash_bin; - struct lwsgs_user u; - lwsgw_hash hash; - - if (lwsgs_lookup_user(vhd, username, &u)) - return -1; - - lwsl_info("user %s found, salt '%s'\n", username, u.pwsalt.id); - - /* sha256sum of password + salt */ - - if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256) || - lws_genhash_update(&hash_ctx, password, strlen(password)) || - lws_genhash_update(&hash_ctx, "-", 1) || - lws_genhash_update(&hash_ctx, vhd->confounder, strlen(vhd->confounder)) || - lws_genhash_update(&hash_ctx, "-", 1) || - lws_genhash_update(&hash_ctx, u.pwsalt.id, strlen(u.pwsalt.id)) || - lws_genhash_destroy(&hash_ctx, hash_bin.bin)) { - lws_genhash_destroy(&hash_ctx, NULL); - - return 1; - } - - sha256_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) -{ - unsigned char sid_rand[32]; - struct lws_genhash_ctx hash_ctx; - lwsgw_hash_bin hash_bin; - - /* 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; - } - sha256_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; - } - sha256_to_lwsgw_hash(sid_rand, &hash); -*/ - /* sha256sum of password + salt */ - - if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256) || - lws_genhash_update(&hash_ctx, password, strlen(password)) || - lws_genhash_update(&hash_ctx, "-", 1) || - lws_genhash_update(&hash_ctx, vhd->confounder, strlen(vhd->confounder)) || - lws_genhash_update(&hash_ctx, "-", 1) || - lws_genhash_update(&hash_ctx, u->pwsalt.id, strlen(u->pwsalt.id)) || - lws_genhash_destroy(&hash_ctx, hash_bin.bin)) { - lws_genhash_destroy(&hash_ctx, NULL); - - return 1; - } - - sha256_to_lwsgw_hash(&hash_bin.bin[0], &u->pwhash); - - return 0; -} diff --git a/plugins/generic-table/assets/index.html b/plugins/generic-table/assets/index.html deleted file mode 100644 index 635d02aa4..000000000 --- a/plugins/generic-table/assets/index.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - -
- LWS Generic Table demo -
- This is a demo of lws generic table, using a protocol plugin - "protocol-lws-table-dirlisting". It shows a directory listing, - but unlike an oldstyle directory listing done on the - server side with a script, this is static html that connects - back to the server with a websocket, and gets live JSON from - that. -

- Actually the static html is extremely simple, since it uses - lwsgt, LWS Generic Table, JS include on the client-side that - handles all the table generation from a template sent in JSON - over the ws link. It means there is no custom JS required - clientside either. It's just CSS, this text and a call to - initialize lwsgt with the appropriate ws protocol. -

- There's no problem having multiple independent instances per - page... -
-

- - - - diff --git a/plugins/generic-table/assets/lwsgt.js b/plugins/generic-table/assets/lwsgt.js deleted file mode 100644 index 5e4365a23..000000000 --- a/plugins/generic-table/assets/lwsgt.js +++ /dev/null @@ -1,139 +0,0 @@ -function lwsgt_get_appropriate_ws_url() -{ - var pcol; - var u = document.URL; - - if (u.substring(0, 5) === "https") { - pcol = "wss://"; - u = u.substr(8); - } else { - pcol = "ws://"; - if (u.substring(0, 4) === "http") - u = u.substr(7); - } - - return pcol + u; -} - -function lwsgt_app_hdr(j, bc, ws) -{ - var s = "", n, m = 0; - - ws.bcq = 0; - - for (n = 0; n < j.cols.length; n++) - if (!j.cols[n].hide) - m++; - - s = "" + - ws.lwsgt_title + ""; - - if (!!bc) { - s += ""; - for (n = 0; n < bc.length; n++) { - s += " / "; - if (!bc[n].url && bc[n].url !== "") - s += " " + lws_san(bc[n].name) + " "; - else { - s += "" + - lws_san(bc[n].name) + " "; - ws.bcq++; - } - } - s += ""; - } - s += ""; - for (n = 0; n < j.cols.length; n++) - if (!j.cols[n].hide) - s = s + "" + lws_san(j.cols[n].name) + - ""; - - s += ""; - - return s; -} - -function lwsgt_click_callthru() -{ - window[this.getAttribute("h")](this.getAttribute("p"), this.getAttribute("aa"), this.getAttribute("m"), this.getAttribute("n")); - event.preventDefault(); -} - -function lwsgt_initial(title, pcol, divname, cb, gname) -{ - this.divname = divname; - - lws_gray_out(true,{"zindex":"499"}); - - this.lwsgt_ws = new WebSocket(lwsgt_get_appropriate_ws_url(), pcol); - this.lwsgt_ws.divname = divname; - this.lwsgt_ws.lwsgt_cb = cb; - this.lwsgt_ws.lwsgt_parent = gname; - this.lwsgt_ws.lwsgt_title = title; - try { - this.lwsgt_ws.onopen = function() { - lws_gray_out(false); - // document.getElementById("debug").textContent = - // "ws opened " + lwsgt_get_appropriate_ws_url(); - }; - this.lwsgt_ws.onmessage = function got_packet(msg) { - var s, m, n, j = JSON.parse(msg.data); - document.getElementById("debug").textContent = msg.data; - if (j.cols) { - this.hdr = j; - } - if (j.breadcrumbs) - this.breadcrumbs = j.breadcrumbs; - - if (j.data) { - var q = 0; - s = "" + - lwsgt_app_hdr(this.hdr, this.breadcrumbs, this); - for (m = 0; m < j.data.length; m++) { - s = s + ""; - for (n = 0; n < this.hdr.cols.length; n++) { - if (!this.hdr.cols[n].hide) { - if (!this.hdr.cols[n].align) - s = s + ""; - } - } - - s = s + ""; - } - s = s + "
"; - else - s = s + ""; - - if (this.hdr.cols[n].href && - !!j.data[m][this.hdr.cols[n].href]) { - s = s + "" + - lws_san(j.data[m][this.hdr.cols[n].name]) + - ""; - q++; - } - else - s = s + lws_san(j.data[m][this.hdr.cols[n].name]); - - s = s + "
"; - document.getElementById(this.divname).innerHTML = s; - for (n = 0; n < q; n++) - document.getElementById(this.divname + n).onclick = - lwsgt_click_callthru; - - for (n = 0; n < this.bcq; n++) - document.getElementById("bc_" + this.divname + n).onclick = - lwsgt_click_callthru; - - } - }; - this.lwsgt_ws.onclose = function(){ - lws_gray_out(true,{"zindex":"499"}); - }; - } catch(exception) { - alert("

Error" + exception); - } -} - diff --git a/plugins/generic-table/protocol_table_dirlisting.c b/plugins/generic-table/protocol_table_dirlisting.c deleted file mode 100644 index 8537b6156..000000000 --- a/plugins/generic-table/protocol_table_dirlisting.c +++ /dev/null @@ -1,397 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010 - 2019 Andy Green - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#define LWS_DLL -#define LWS_INTERNAL -#include - -#include -#include -#include - -struct fobj { - struct fobj *next; - const char *name, *uri, *icon, *date; - time_t m; - unsigned long size; -}; - -struct per_session_data__tbl_dir { - struct fobj base; - char strings[64 * 1024]; - char reldir[256]; - char *p; - const char *dir; - -#if UV_VERSION_MAJOR > 0 - uv_fs_event_t *event_req; -#endif - struct lws *wsi; -}; - -#if UV_VERSION_MAJOR > 0 -static void -mon_cb(uv_fs_event_t *handle, const char *filename, int events, int status) -{ - struct per_session_data__tbl_dir *pss = handle->data; - - //lwsl_notice("%s\n", __func__); - - if (pss && pss->wsi) - lws_callback_on_writable(pss->wsi); -} - -static void lws_uv_close_cb(uv_handle_t *handle) -{ - free(handle); -} - -static void -lws_protocol_dir_kill_monitor(struct per_session_data__tbl_dir *pss) -{ - if (!pss->event_req) - return; - pss->wsi = NULL; - pss->event_req->data = NULL; - uv_fs_event_stop(pss->event_req); - uv_close((uv_handle_t *)pss->event_req, lws_uv_close_cb); - pss->event_req = NULL; -} -#endif - -static int -scan_dir(struct lws *wsi, struct per_session_data__tbl_dir *pss) -{ -/* uuh travis... */ -#if UV_VERSION_MAJOR > 0 - uv_loop_t *loop = lws_uv_getloop(lws_get_context(wsi), 0); - char *end = &(pss->strings[sizeof(pss->strings) - 1]); - struct fobj *prev = &pss->base; - char path[512], da[200]; - const char *icon; - uv_dirent_t dent; - struct fobj *f; - struct stat st; - struct tm *tm; - int ret = 0, n; - uv_fs_t req; - - lws_protocol_dir_kill_monitor(pss); - - lws_snprintf(path, sizeof(path) - 1, "%s/%s", pss->dir, pss->reldir); - //lwsl_notice("path = %s\n", path); - - pss->event_req = malloc(sizeof(*pss->event_req)); - if (!pss->event_req) - return 2; - - pss->wsi = wsi; - pss->event_req->data = pss; - - uv_fs_event_init(lws_uv_getloop(lws_get_context(wsi), 0), - pss->event_req); - // The recursive flag watches subdirectories too. - n = uv_fs_event_start(pss->event_req, mon_cb, path, UV_FS_EVENT_RECURSIVE); - //lwsl_notice("monitoring %s (%d)\n", path, n); - - if (!uv_fs_scandir(loop, &req, path, 0, NULL)) { - lwsl_err("Scandir on %s failed\n", path); - return 2; - } - - pss->p = pss->strings; - - while (uv_fs_scandir_next(&req, &dent) != UV_EOF) { - lws_snprintf(path, sizeof(path) - 1, "%s/%s/%s", pss->dir, pss->reldir, dent.name); - - if (stat(path, &st)) { - lwsl_info("unable to stat %s\n", path); - continue; - } - f = malloc(sizeof(*f)); - f->next = NULL; - f->name = pss->p; - n = lws_snprintf(pss->p, end - pss->p, "%s", dent.name); - pss->p += n + 1; - f->uri = NULL; - if ((S_IFMT & st.st_mode) == S_IFDIR) { - n = lws_snprintf(pss->p, end - pss->p, "=%s/%s", pss->reldir, dent.name); - f->uri = pss->p; - } - if (lws_get_mimetype(dent.name, NULL)) { - n = lws_snprintf(pss->p, end - pss->p, "./serve/%s/%s", pss->reldir, dent.name); - f->uri = pss->p; - } - if (f->uri) - pss->p += n + 1; - - if (end - pss->p < 100) { - free(f); - break; - } - - icon = " "; - if ((S_IFMT & st.st_mode) == S_IFDIR) - icon = "📂"; - - f->icon = pss->p; - n = lws_snprintf(pss->p, end - pss->p, "%s", icon); - pss->p += n + 1; - - f->date = pss->p; - tm = gmtime(&st.st_mtime); - strftime(da, sizeof(da), "%Y-%b-%d %H:%M:%S %z", tm); - n = lws_snprintf(pss->p, end - pss->p, "%s", da); - pss->p += n + 1; - - f->size = st.st_size; - f->m = st.st_mtime; - prev->next = f; - prev = f; - } - - uv_fs_req_cleanup(&req); - - return ret; -#else - return 0; -#endif -} - -static void -free_scan_dir(struct per_session_data__tbl_dir *pss) -{ - struct fobj *f = pss->base.next, *f1; - - while (f) { - f1 = f->next; - free(f); - f = f1; - } - - pss->base.next = NULL; -} - -static int -callback_lws_table_dirlisting(struct lws *wsi, enum lws_callback_reasons reason, - void *user, void *in, size_t len) -{ - struct per_session_data__tbl_dir *pss = (struct per_session_data__tbl_dir *)user; - char j[LWS_PRE + 16384], *p = j + LWS_PRE, *start = p, *q, *q1, *w, - *end = j + sizeof(j) - LWS_PRE, e[384], s[384], s1[384]; - const struct lws_protocol_vhost_options *pmo; - struct fobj *f; - int n, first = 1; - - switch (reason) { - case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */ - break; - - case LWS_CALLBACK_ESTABLISHED: - lwsl_debug("LWS_CALLBACK_ESTABLISHED\n"); - /* - * send client the lwsgt table layout - */ - start = "{\"cols\":[" - " {\"name\": \"Date\"}," - " {\"name\": \"Size\", \"align\": \"right\"}," - " {\"name\": \"Icon\"}," - " {\"name\": \"Name\", \"href\": \"uri\"}," - " {\"name\": \"uri\", \"hide\": \"1\" }" - " ]" - "}"; - if (lws_write(wsi, (unsigned char *)start, strlen(start), - LWS_WRITE_TEXT) < 0) - return -1; - - /* send a view update next */ - lws_callback_on_writable(wsi); - break; - - case LWS_CALLBACK_RECEIVE: - if (len > sizeof(pss->reldir) - 1) - len = sizeof(pss->reldir) - 1; - if (!strstr(in, "..") && !strchr(in, '~')) - lws_strncpy(pss->reldir, in, len + 1); - else - len = 0; - pss->reldir[len] = '\0'; - if (pss->reldir[0] == '/' && !pss->reldir[1]) - pss->reldir[0] = '\0'; - lwsl_info("%s\n", pss->reldir); - lws_callback_on_writable(wsi); - break; - - case LWS_CALLBACK_SERVER_WRITEABLE: - - if (scan_dir(wsi, pss)) - return 1; - - p += lws_snprintf(p, end - p, "{\"breadcrumbs\":["); - q = pss->reldir; - - if (!q[0]) - p += lws_snprintf(p, end - p, "{\"name\":\"top\"}"); - - while (*q) { - - q1 = strchr(q, '/'); - if (!q1) { - if (first) - strcpy(s, "top1"); - else - strcpy(s, q); - s1[0] = '\0'; - q += strlen(q); - } else { - n = lws_ptr_diff(q1, q); - if (n > (int)sizeof(s) - 1) - n = sizeof(s) - 1; - if (first) { - strcpy(s1, "/"); - strcpy(s, "top"); - } else { - lws_strncpy(s, q, n + 1); - - n = lws_ptr_diff(q1, pss->reldir); - if (n > (int)sizeof(s1) - 1) - n = sizeof(s1) - 1; - lws_strncpy(s1, pss->reldir, n + 1); - } - q = q1 + 1; - } - if (!first) - p += lws_snprintf(p, end - p, ","); - else - first = 0; - - p += lws_snprintf(p, end - p, "{\"name\":\"%s\"", - lws_json_purify(e, s, sizeof(e), NULL)); - if (*q) { - w = s1; - while (w[0] == '/' && w[1] == '/') - w++; - p += lws_snprintf(p, end - p, ",\"url\":\"%s\"", - lws_json_purify(e, w, sizeof(e), NULL)); - } - p += lws_snprintf(p, end - p, "}"); - if (!q1) - break; - } - - p += lws_snprintf(p, end - p, "],\"data\":["); - - f = pss->base.next; - while (f) { - /* format in JSON */ - p += lws_snprintf(p, end - p, "{\"Icon\":\"%s\",", - lws_json_purify(e, f->icon, sizeof(e), NULL)); - p += lws_snprintf(p, end - p, " \"Date\":\"%s\",", - lws_json_purify(e, f->date, sizeof(e), NULL)); - p += lws_snprintf(p, end - p, " \"Size\":\"%ld\",", - f->size); - if (f->uri) - p += lws_snprintf(p, end - p, " \"uri\":\"%s\",", - lws_json_purify(e, f->uri, sizeof(e), NULL)); - p += lws_snprintf(p, end - p, " \"Name\":\"%s\"}", - lws_json_purify(e, f->name, sizeof(e), NULL)); - - f = f->next; - - if (f) - p += lws_snprintf(p, end - p, ","); - } - - p += lws_snprintf(p, end - p, "]}"); - - free_scan_dir(pss); - - if (lws_write(wsi, (unsigned char *)start, p - start, - LWS_WRITE_TEXT) < 0) - return -1; - - break; - - case LWS_CALLBACK_HTTP_PMO: - /* find the per-mount options we're interested in */ - lwsl_debug("LWS_CALLBACK_HTTP_PMO\n"); - pmo = (struct lws_protocol_vhost_options *)in; - while (pmo) { - if (!strcmp(pmo->name, "dir")) /* path to list files */ - pss->dir = pmo->value; - pmo = pmo->next; - } - if (!pss->dir[0]) { - lwsl_err("dirlisting: \"dir\" pmo missing\n"); - return 1; - } - break; - - case LWS_CALLBACK_HTTP_DROP_PROTOCOL: - //lwsl_notice("LWS_CALLBACK_HTTP_DROP_PROTOCOL\n"); -#if UV_VERSION_MAJOR > 0 - lws_protocol_dir_kill_monitor(pss); -#endif - break; - - default: - return 0; - } - - return 0; - -} - -static const struct lws_protocols protocols[] = { - { - "protocol-lws-table-dirlisting", - callback_lws_table_dirlisting, - sizeof(struct per_session_data__tbl_dir), - 0, - }, -}; - -LWS_VISIBLE int -init_protocol_lws_table_dirlisting(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 = LWS_ARRAY_SIZE(protocols); - c->extensions = NULL; - c->count_extensions = 0; - - return 0; -} - -LWS_VISIBLE int -destroy_protocol_lws_table_dirlisting(struct lws_context *context) -{ - return 0; -}