1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-16 00:00:07 +01:00
libwebsockets/plugins/generic-sessions/utils.c
Andy Green f89aa401cc generic-sessions update
Generic sessions has been overdue some love to align it with
the progress in the rest of lws.

1) Strict Content Security Policy
2) http2 compatibility
3) fixes and additions for use in a separate process via unix domain socket
4) work on ws and http proxying in lws
5) add minimal example
2019-05-06 10:24:51 +01:00

462 lines
11 KiB
C

/*
* ws protocol handler plugin for "generic sessions"
*
* Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation:
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include "private-lwsgs.h"
#include <stdlib.h>
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;
}