blob: 8488cd8b93993cdeccc7349115df492781ea3f4c [file] [log] [blame]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen 1.8.12"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>libwebsockets: Notes about generic-sessions Plugin</title>
<link href="tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="dynsections.js"></script>
<link href="navtree.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="resize.js"></script>
<script type="text/javascript" src="navtreedata.js"></script>
<script type="text/javascript" src="navtree.js"></script>
<script type="text/javascript">
$(document).ready(initResizable);
</script>
<link href="doxygen.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px;">
<td id="projectlogo"><img alt="Logo" src="libwebsockets.org-logo.png"/></td>
<td id="projectalign" style="padding-left: 0.5em;">
<div id="projectname">libwebsockets
</div>
<div id="projectbrief">Lightweight C library for HTML5 websockets</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- end header part -->
<!-- Generated by Doxygen 1.8.12 -->
<script type="text/javascript" src="menudata.js"></script>
<script type="text/javascript" src="menu.js"></script>
<script type="text/javascript">
$(function() {
initMenu('',false,false,'search.php','Search');
});
</script>
<div id="main-nav"></div>
</div><!-- top -->
<div id="side-nav" class="ui-resizable side-nav-resizable">
<div id="nav-tree">
<div id="nav-tree-contents">
<div id="nav-sync" class="sync"></div>
</div>
</div>
<div id="splitbar" style="-moz-user-select:none;"
class="ui-resizable-handle">
</div>
</div>
<script type="text/javascript">
$(document).ready(function(){initNavTree('md_README_8generic-sessions.html','');});
</script>
<div id="doc-content">
<div class="header">
<div class="headertitle">
<div class="title">Notes about generic-sessions Plugin </div> </div>
</div><!--header-->
<div class="contents">
<div class="textblock"><h1><a class="anchor" id="gseb"></a>
Enabling lwsgs for build</h1>
<p>Enable at CMake with -DLWS_WITH_GENERIC_SESSIONS=1</p>
<p>This also needs sqlite3 (libsqlite3-dev or similar package)</p>
<h1><a class="anchor" id="gsi"></a>
lwsgs Introduction</h1>
<p>The generic-sessions protocol plugin provides cookie-based login authentication for lws web and ws connections.</p>
<p>The plugin handles everything about generic account registration, email verification, lost password, account deletion, and other generic account management.</p>
<p>Other code, in another eg, ws protocol handler, only needs very high-level state information from generic-sessions, ie, which user the client is authenticated as. Everything underneath is managed in generic-sessions.</p>
<ul>
<li>random 20-byte session id managed in a cookie</li>
<li>all information related to the session held at the server, nothing managed clientside</li>
<li>sqlite3 used at the server to manage active sessions and users</li>
<li>defaults to creating anonymous sessions with no user associated</li>
<li>admin account (with user-selectable username) is defined in config with a SHA-1 of the password; rest of the accounts are in sqlite3</li>
<li>user account passwords stored as salted SHA-1 with additional confounder only stored in the JSON config, not the database</li>
<li>login, logout, register account + email verification built-in with examples</li>
<li>in a mount, some file suffixes (ie, .js) can be associated with a protocol for the purposes of rewriting symbolnames. These are read-only copies of logged-in server state.</li>
<li>When your page fetches .js or other rewritten files from that mount, "$lwsgs_user" and so on are rewritten on the fly using chunked transfer encoding</li>
<li>Eliminates server-side scripting with a few rewritten symbols and javascript on client side</li>
<li>32-bit bitfield for authentication sectoring, mounts can provide a mask on the loggin-in session's associated server-side bitfield that must be set for access.</li>
<li>No code (just config) required for, eg, private URL namespace that requires login to access.</li>
</ul>
<h1><a class="anchor" id="gsin"></a>
Lwsgs Integration to HTML</h1>
<p>Only three steps are needed to integrate lwsgs in your HTML.</p>
<p>1) lwsgs HTML UI is bundled with the javascript it uses in <code>lwsgs.js</code>, so import that script file in your head section</p>
<p>2) define an empty div of id "lwsgs" somewhere</p>
<p>3) Call lwsgs_initial() in your page</p>
<p>That's it. An example is below</p>
<div class="fragment"><div class="line">&lt;html&gt;</div><div class="line"> &lt;head&gt;</div><div class="line"> &lt;script src=&quot;lwsgs.js&quot;&gt;&lt;/script&gt;</div><div class="line"> &lt;style&gt;</div><div class="line"> .body { font-size: 12 }</div><div class="line"> .gstitle { font-size: 18 }</div><div class="line"> &lt;/style&gt;</div><div class="line"> &lt;/head&gt;</div><div class="line"> &lt;body style=&quot;background-image:url(seats.jpg)&quot;&gt;</div><div class="line"> &lt;table style=&quot;width:100%;transition: max-height 2s;&quot;&gt;</div><div class="line"> &lt;tr&gt;</div><div class="line"> &lt;td style=&quot;vertical-align:top;text-align:left;width=200px&quot;&gt;</div><div class="line"> &lt;img src=&quot;lwsgs-logo.png&quot;&gt;</div><div class="line"> &lt;/td&gt;</div><div class="line"> &lt;td style=&quot;vertical-align:top;float:right&quot;&gt;</div><div class="line"> &lt;div id=lwsgs style=&quot;text-align:right;background-color: rgba(255, 255, 255, 0.8);&quot;&gt;&lt;/div&gt;</div><div class="line"> &lt;/td&gt;</div><div class="line"> &lt;/tr&gt;</div><div class="line"> &lt;/table&gt;</div><div class="line"> &lt;/form&gt;</div><div class="line"></div><div class="line"> &lt;script&gt;lwsgs_initial();&lt;/script&gt;</div><div class="line"></div><div class="line"> &lt;/body&gt;</div><div class="line">&lt;/html&gt;</div></div><!-- fragment --><h1><a class="anchor" id="gsof"></a>
Lwsgs Overall Flow@</h1>
<p>When the protocol is initialized, it gets per-vhost information from the config, such as where the sqlite3 databases are to be stored. The admin username and sha-1 of the admin password are also taken from here.</p>
<p>In the mounts using protocol-generic-sessions, a cookie is maintained against any requests; if no cookie was active on the initial request a new session is created with no attached user.</p>
<p>So there should always be an active session after any transactions with the server.</p>
<p>In the example html going to the mount /lwsgs loads a login / register page as the default.</p>
<p>The &lt;form&gt; in the login page contains 'next url' hidden inputs that let the html 'program' where the form handler will go after a successful admin login, a successful user login and a failed login.</p>
<p>After a successful login, the sqlite record at the server for the current session is updated to have the logged-in username associated with it.</p>
<h1><a class="anchor" id="gsconf"></a>
Lwsgs Configuration</h1>
<p>"auth-mask" defines the authorization sector bits that must be enabled on the session to gain access.</p>
<p>"auth-mask" 0 is the default.</p>
<ul>
<li>b0 is set if you are logged in as a user at all.</li>
<li>b1 is set if you are logged in with the user configured to be admin</li>
<li>b2 is set if the account has been verified (the account configured for admin is always verified)</li>
<li>b3 is set if your session just did the forgot password flow successfully</li>
</ul>
<div class="fragment"><div class="line">{</div><div class="line"> # things in here can always be served</div><div class="line"> &quot;mountpoint&quot;: &quot;/lwsgs&quot;,</div><div class="line"> &quot;origin&quot;: &quot;file:///usr/share/libwebsockets-test-server/generic-sessions&quot;,</div><div class="line"> &quot;origin&quot;: &quot;callback://protocol-lws-messageboard&quot;,</div><div class="line"> &quot;default&quot;: &quot;generic-sessions-login-example.html&quot;,</div><div class="line"> &quot;auth-mask&quot;: &quot;0&quot;,</div><div class="line"> &quot;interpret&quot;: {</div><div class="line"> &quot;.js&quot;: &quot;protocol-lws-messageboard&quot;</div><div class="line"> }</div><div class="line"> }, {</div><div class="line"> # things in here can only be served if logged in as a user</div><div class="line"> &quot;mountpoint&quot;: &quot;/lwsgs/needauth&quot;,</div><div class="line"> &quot;origin&quot;: &quot;file:///usr/share/libwebsockets-test-server/generic-sessions/needauth&quot;,</div><div class="line"> &quot;origin&quot;: &quot;callback://protocol-lws-messageboard&quot;,</div><div class="line"> &quot;default&quot;: &quot;generic-sessions-login-example.html&quot;,</div><div class="line"> &quot;auth-mask&quot;: &quot;5&quot;, # logged in as a verified user</div><div class="line"> &quot;interpret&quot;: {</div><div class="line"> &quot;.js&quot;: &quot;protocol-lws-messageboard&quot;</div><div class="line"> }</div><div class="line"> }, {</div><div class="line"> # things in here can only be served if logged in as admin</div><div class="line"> &quot;mountpoint&quot;: &quot;/lwsgs/needadmin&quot;,</div><div class="line"> &quot;origin&quot;: &quot;file:///usr/share/libwebsockets-test-server/generic-sessions/needadmin&quot;,</div><div class="line"> &quot;origin&quot;: &quot;callback://protocol-lws-messageboard&quot;,</div><div class="line"> &quot;default&quot;: &quot;generic-sessions-login-example.html&quot;,</div><div class="line"> &quot;auth-mask&quot;: &quot;7&quot;, # b2 = verified (by email / or admin), b1 = admin, b0 = logged in with any user name</div><div class="line"> &quot;interpret&quot;: {</div><div class="line"> &quot;.js&quot;: &quot;protocol-lws-messageboard&quot;</div><div class="line"> }</div><div class="line"> }</div></div><!-- fragment --><p> Note that the name of the real application protocol that uses generic-sessions is used, not generic-sessions itself.</p>
<p>The vhost configures the storage dir, admin credentials and session cookie lifetimes:</p>
<div class="fragment"><div class="line"> &quot;ws-protocols&quot;: [{</div><div class="line"> &quot;protocol-generic-sessions&quot;: {</div><div class="line"> &quot;status&quot;: &quot;ok&quot;,</div><div class="line"> &quot;admin-user&quot;: &quot;admin&quot;,</div><div class="line"></div><div class="line"># create the pw hash like this (for the example pw, &quot;jipdocesExunt&quot; )</div><div class="line"># $ echo -n &quot;jipdocesExunt&quot; | sha1sum</div><div class="line"># 046ce9a9cca769e85798133be06ef30c9c0122c9 -</div><div class="line">#</div><div class="line"># Obviously ** change this password hash to a secret one before deploying **</div><div class="line">#</div><div class="line"> &quot;admin-password-sha1&quot;: &quot;046ce9a9cca769e85798133be06ef30c9c0122c9&quot;,</div><div class="line"> &quot;session-db&quot;: &quot;/var/www/sessions/lws.sqlite3&quot;,</div><div class="line"> &quot;timeout-idle-secs&quot;: &quot;600&quot;,</div><div class="line"> &quot;timeout-anon-idle-secs&quot;: &quot;1200&quot;,</div><div class="line"> &quot;timeout-absolute-secs&quot;: &quot;6000&quot;,</div><div class="line"># the confounder is part of the salted password hashes. If this config</div><div class="line"># file is in a 0700 root:root dir, an attacker with apache credentials</div><div class="line"># will have to get the confounder out of the process image to even try</div><div class="line"># to guess the password hashes.</div><div class="line"> &quot;confounder&quot;: &quot;Change to &lt;=31 chars of junk&quot;,</div><div class="line"></div><div class="line"> &quot;email-from&quot;: &quot;noreply@example.com&quot;,</div><div class="line"> &quot;email-smtp-ip&quot;: &quot;127.0.0.1&quot;,</div><div class="line"> &quot;email-expire&quot;: &quot;3600&quot;,</div><div class="line"> &quot;email-helo&quot;: &quot;myhost.com&quot;,</div><div class="line"> &quot;email-contact-person&quot;: &quot;Set Me &lt;real-person@email.com&gt;&quot;,</div><div class="line"> &quot;email-confirm-url-base&quot;: &quot;http://localhost:7681/lwsgs&quot;</div><div class="line"> }</div></div><!-- fragment --><p>The email- related settings control generation of automatic emails for registration and forgotten password.</p>
<ul>
<li><code>email-from</code>: The email address automatic emails are sent from</li>
<li><code>email-smtp-ip</code>: Normally 127.0.0.1, if you have a suitable server on port 25 on your lan you can use this instead here.</li>
<li><code>email-expire</code>: Seconds that links sent in email will work before being deleted</li>
<li><code>email-helo</code>: HELO to use when communicating with your SMTP server</li>
<li><code>email-contact-person</code>: mentioned in the automatic emails as a human who can answer questions</li>
<li><code>email-confirm-url-base</code>: the URL to start links with in the emails, so the recipient can get back to the web server</li>
</ul>
<p>The real protocol that makes use of generic-sessions must also be listed and any configuration it needs given</p>
<div class="fragment"><div class="line">&quot;protocol-lws-messageboard&quot;: {</div><div class="line"> &quot;status&quot;: &quot;ok&quot;,</div><div class="line"> &quot;message-db&quot;: &quot;/var/www/sessions/messageboard.sqlite3&quot;</div><div class="line">},</div></div><!-- fragment --><p>Notice the real application uses his own sqlite db, no details about how generic-sessions works or how it stores data are available to it.</p>
<h1><a class="anchor" id="gspwc"></a>
Lwsgs Password Confounder</h1>
<p>You can also define a per-vhost confounder shown in the example above, used when aggregating the password with the salt when it is hashed. Any attacker will also need to get the confounder along with the database, which you can make harder by making the config dir only eneterable / readable by root.</p>
<h1><a class="anchor" id="gsprep"></a>
Lwsgs Preparing the db directory</h1>
<p>You will have to prepare the db directory so it's suitable for the lwsws user to use, that usually means apache, eg</p>
<div class="fragment"><div class="line"># mkdir -p /var/www/sessions</div><div class="line"># chown root:apache /var/www/sessions</div><div class="line"># chmod 770 /var/www/sessions</div></div><!-- fragment --><h1><a class="anchor" id="gsrmail"></a>
Lwsgs Email configuration</h1>
<p>lwsgs will can send emails by talking to an SMTP server on localhost:25. That will usually be sendmail or postfix, you should confirm that works first by itself using the <code>mail</code> application to send on it.</p>
<p>lwsgs has been tested on stock Fedora sendmail and postfix.</p>
<h1><a class="anchor" id="gsap"></a>
Lwsgs Integration with another protocol</h1>
<p>lwsgs is designed to provide sessions and accounts in a standalone and generic way.</p>
<p>But it's not useful by itself, there will always be the actual application who wants to make use of generic-sessions features.</p>
<p>We provide the "messageboard" plugin as an example of how to integrate with your actual application protocol.</p>
<p>The basic approach is the 'real' protocol handler (usually a plugin itself) subclasses the generic-sessions plugin and calls through to it by default.</p>
<p>The "real" protocol handler entirely deals with ws-related stuff itself, since generic-sessions does not use ws. But for</p>
<ul>
<li>LWS_CALLBACK_HTTP</li>
<li>LWS_CALLBACK_HTTP_BODY</li>
<li>LWS_CALLBACK_HTTP_BODY_COMPLETION</li>
<li>LWS_CALLBACK_HTTP_DROP_PROTOCOL</li>
</ul>
<p>the "real" protocol handler checks if it recognizes the activity (eg, his own POST form URL) and if not, passes stuff through to the generic-sessions protocol callback to handle it. To simplify matters the real protocol can just pass through any unhandled messages to generic-sessions.</p>
<p>The "real" protocol can get a pointer to generic-sessions protocol on the same vhost using</p>
<div class="fragment"><div class="line">vhd-&gt;gsp = lws_vhost_name_to_protocol(vhd-&gt;vh, &quot;protocol-generic-sessions&quot;);</div></div><!-- fragment --><p>The "real" protocol must also arrange generic-sessions per_session_data in his own per-session allocation. To allow keeping generic-sessions opaque, the real protocol must allocate that space at runtime, using the pss size the generic-sessions protocol struct exposes</p>
<div class="fragment"><div class="line">struct per_session_data__myapp {</div><div class="line"> void *pss_gs;</div><div class="line">...</div><div class="line"></div><div class="line"> pss-&gt;pss_gs = malloc(vhd-&gt;gsp-&gt;per_session_data_size);</div></div><!-- fragment --><p>The allocation reserved for generic-sessions is then used as user_space when the real protocol calls through to the generic-sessions callback</p>
<div class="fragment"><div class="line">vhd-&gt;gsp-&gt;callback(wsi, reason, &amp;pss-&gt;pss_gs, in, len);</div></div><!-- fragment --><p>In that way the "real" protocol can subclass generic-sessions functionality.</p>
<p>To ease management of these secondary allocations, there are callbacks that occur when a wsi binds to a protocol and when the binding is dropped. These should be used to malloc and free and kind of per-connection secondary allocations.</p>
<div class="fragment"><div class="line">case LWS_CALLBACK_HTTP_BIND_PROTOCOL:</div><div class="line"> if (!pss || pss-&gt;pss_gs)</div><div class="line"> break;</div><div class="line"></div><div class="line"> pss-&gt;pss_gs = malloc(vhd-&gt;gsp-&gt;per_session_data_size);</div><div class="line"> if (!pss-&gt;pss_gs)</div><div class="line"> return -1;</div><div class="line"></div><div class="line"> memset(pss-&gt;pss_gs, 0, vhd-&gt;gsp-&gt;per_session_data_size);</div><div class="line"> break;</div><div class="line"></div><div class="line">case LWS_CALLBACK_HTTP_DROP_PROTOCOL:</div><div class="line"> if (vhd-&gt;gsp-&gt;callback(wsi, reason, pss ? pss-&gt;pss_gs : NULL, in, len))</div><div class="line"> return -1;</div><div class="line"></div><div class="line"> if (pss-&gt;pss_gs) {</div><div class="line"> free(pss-&gt;pss_gs);</div><div class="line"> pss-&gt;pss_gs = NULL;</div><div class="line"> }</div><div class="line"> break;</div></div><!-- fragment --><p>#section gsapsib Getting session-specific information from another protocol</p>
<p>At least at the time when someone tries to upgrade an http(s) connection to ws(s) with your real protocol, it is necessary to confirm the cookie the http(s) connection has with generic-sessions and find out his username and other info.</p>
<p>Generic sessions lets another protocol check it again by calling his callback, and lws itself provides a generic session info struct to pass the related data</p>
<div class="fragment"><div class="line">struct lws_session_info {</div><div class="line"> char username[32];</div><div class="line"> char email[100];</div><div class="line"> char ip[72];</div><div class="line"> unsigned int mask;</div><div class="line"> char session[42];</div><div class="line">};</div><div class="line"></div><div class="line">struct lws_session_info sinfo;</div><div class="line">...</div><div class="line">vhd-&gt;gsp-&gt;callback(wsi, LWS_CALLBACK_SESSION_INFO,</div><div class="line"> &amp;pss-&gt;pss_gs, &amp;sinfo, 0);</div></div><!-- fragment --><p>After the call to generic-sessions, the results can be</p>
<ul>
<li>all the strings will be zero-length and .mask zero, there is no usable cookie<ul>
<li>only .ip and .session are set: the cookie is OK but no user logged in</li>
<li>all the strings contain information about the logged-in user</li>
</ul>
</li>
</ul>
<p>the real protocol can use this to reject attempts to open ws connections from http connections that are not authenticated; afterwards there's no need to check the ws connection auth status again. </p>
</div></div><!-- contents -->
</div><!-- doc-content -->
<!-- start footer part -->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
<ul>
<li class="footer">Generated by
<a href="http://www.doxygen.org/index.html">
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.12 </li>
</ul>
</div>
</body>
</html>