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 a496700b3a lws_snprintf
Thanks to Fabrice Gilot for reporting the problem that led to uncovering this.

Due to a misunderstanding of the return value of snprintf (it is not truncated according
to the max size passed in) in several places relying on snprintf to truncate the length
overflows are possible.

This patch wraps snprintf with a new lws_snprintf() which does truncate its length to allow
the buffer limiting scheme to work properly.

All users should update with these fixes.
2016-09-15 02:22:57 +08:00

450 lines
10 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 General Public
* License as published by the Free Software Foundation:
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include "private-lwsgs.h"
void
sha1_to_lwsgw_hash(unsigned char *hash, lwsgw_hash *shash)
{
static const char *hex = "0123456789abcdef";
char *p = shash->id;
int n;
for (n = 0; n < 20; n++) {
*p++ = hex[(hash[n] >> 4) & 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);
sha1_to_lwsgw_hash(hash_bin.bin, &pw_hash);
return !strcmp(vhd->admin_password_sha1.id, pw_hash.id);
}
/*
* secure cookie: it can only be passed over https where it cannot be
* snooped in transit
* HttpOnly: it can only be accessed via http[s] transport, it cannot be
* gotten at by JS
*/
void
lwsgw_cookie_from_session(lwsgw_hash *sid, time_t expires, char **p, char *end)
{
struct tm *tm = gmtime(&expires);
time_t n = lws_now_secs();
*p += 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[50], esc1[50];
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;
}
return 0;
}
static int
lwsgw_session_from_cookie(const char *cookie, lwsgw_hash *sid)
{
const char *p = cookie;
int n;
while (*p) {
if (p[0] == 'i' && p[1] == 'd' && p[2] == '=') {
p += 3;
break;
}
p++;
}
if (!*p) {
lwsl_info("no id= in cookie\n");
return 1;
}
for (n = 0; n < sizeof(sid->id) - 1 && *p; n++) {
/* our SID we issue only has these chars */
if ((*p >= '0' && *p <= '9') ||
(*p >= 'a' && *p <= 'f'))
sid->id[n] = *p++;
else {
lwsl_info("bad chars in cookie id %c\n", *p);
return 1;
}
}
if (n < sizeof(sid->id) - 1) {
lwsl_info("cookie id too short\n");
return 1;
}
sid->id[sizeof(sid->id) - 1] = '\0';
return 0;
}
int
lwsgs_get_sid_from_wsi(struct lws *wsi, lwsgw_hash *sid)
{
char cookie[1024];
/* fail it on no cookie */
if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
lwsl_info("%s: no cookie\n", __func__);
return 1;
}
if (lws_hdr_copy(wsi, cookie, sizeof cookie, WSI_TOKEN_HTTP_COOKIE) < 0) {
lwsl_info("cookie copy failed\n");
return 1;
}
/* extract the sid from the cookie */
if (lwsgw_session_from_cookie(cookie, sid)) {
lwsl_info("session from cookie failed\n");
return 1;
}
return 0;
}
struct lla {
char *username;
int len;
int results;
};
static int
lwsgs_lookup_callback(void *priv, int cols, char **col_val, char **col_name)
{
struct lla *lla = (struct lla *)priv;
//lwsl_err("%s: %d\n", __func__, cols);
if (cols)
lla->results = 0;
if (col_val && col_val[0]) {
strncpy(lla->username, col_val[0], lla->len);
lla->username[lla->len - 1] = '\0';
lwsl_info("%s: %s\n", __func__, lla->username);
}
return 0;
}
int
lwsgs_lookup_session(struct per_vhost_data__gs *vhd,
const lwsgw_hash *sid, char *username, int len)
{
struct lla lla = { username, len, 1 };
char s[150], esc[50];
lwsgw_expire_old_sessions(vhd);
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")) {
strncpy(u->username, col_val[n], sizeof(u->username) - 1);
u->username[sizeof(u->username) - 1] = '\0';
continue;
}
if (!strcmp(col_name[n], "ip")) {
strncpy(u->ip, col_val[n], sizeof(u->ip) - 1);
u->ip[sizeof(u->ip) - 1] = '\0';
continue;
}
if (!strcmp(col_name[n], "creation_time")) {
u->created = atol(col_val[n]);
continue;
}
if (!strcmp(col_name[n], "last_forgot_validated")) {
if (col_val[n])
u->last_forgot_validated = atol(col_val[n]);
else
u->last_forgot_validated = 0;
continue;
}
if (!strcmp(col_name[n], "email")) {
strncpy(u->email, col_val[n], sizeof(u->email) - 1);
u->email[sizeof(u->email) - 1] = '\0';
continue;
}
if (!strcmp(col_name[n], "verified")) {
u->verified = atoi(col_val[n]);
continue;
}
if (!strcmp(col_name[n], "pwhash")) {
strncpy(u->pwhash.id, col_val[n], sizeof(u->pwhash.id) - 1);
u->pwhash.id[sizeof(u->pwhash.id) - 1] = '\0';
continue;
}
if (!strcmp(col_name[n], "pwsalt")) {
strncpy(u->pwsalt.id, col_val[n], sizeof(u->pwsalt.id) - 1);
u->pwsalt.id[sizeof(u->pwsalt.id) - 1] = '\0';
continue;
}
if (!strcmp(col_name[n], "token")) {
strncpy(u->token.id, col_val[n], sizeof(u->token.id) - 1);
u->token.id[sizeof(u->token.id) - 1] = '\0';
continue;
}
}
return 0;
}
int
lwsgs_lookup_user(struct per_vhost_data__gs *vhd,
const char *username, struct lwsgs_user *u)
{
char s[150], esc[50];
u->username[0] = '\0';
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[20];
const char *u;
char s[300], esc[50], esc1[50];
if (username)
u = username;
else
u = "";
if (!sid)
return 1;
memset(sid, 0, sizeof(*sid));
if (lws_get_random(vhd->context, sid_rand, sizeof(sid_rand)) !=
sizeof(sid_rand))
return 1;
sha1_to_lwsgw_hash(sid_rand, sid);
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;
}
return 0;
}
int
lwsgs_get_auth_level(struct per_vhost_data__gs *vhd,
const char *username)
{
struct lwsgs_user u;
int n = 0;
/* we are logged in as some kind of user */
if (username[0]) {
n |= LWSGS_AUTH_LOGGED_IN;
/* we are logged in as admin */
if (!strcmp(username, vhd->admin_user))
n |= LWSGS_AUTH_VERIFIED | LWSGS_AUTH_ADMIN; /* automatically verified */
}
if (!lwsgs_lookup_user(vhd, username, &u)) {
if ((u.verified & 0xff) == LWSGS_VERIFIED_ACCEPTED)
n |= LWSGS_AUTH_VERIFIED;
if (u.last_forgot_validated > lws_now_secs() - 300)
n |= LWSGS_AUTH_FORGOT_FLOW;
}
return n;
}
int
lwsgs_check_credentials(struct per_vhost_data__gs *vhd,
const char *username, const char *password)
{
unsigned char buffer[300];
lwsgw_hash_bin hash_bin;
struct lwsgs_user u;
lwsgw_hash hash;
int n;
if (lwsgs_lookup_user(vhd, username, &u))
return -1;
lwsl_info("user %s found, salt '%s'\n", username, u.pwsalt.id);
/* [password in ascii][salt] */
n = lws_snprintf((char *)buffer, sizeof(buffer) - 1,
"%s-%s-%s", password, vhd->confounder, u.pwsalt.id);
/* sha1sum of password + salt */
lws_SHA1(buffer, n, hash_bin.bin);
sha1_to_lwsgw_hash(&hash_bin.bin[0], &hash);
return !!strcmp(hash.id, u.pwhash.id);
}
/* sets u->pwsalt and u->pwhash */
int
lwsgs_hash_password(struct per_vhost_data__gs *vhd,
const char *password, struct lwsgs_user *u)
{
lwsgw_hash_bin hash_bin;
lwsgw_hash hash;
unsigned char sid_rand[20];
unsigned char buffer[150];
int n;
/* create a random salt as big as the hash */
if (lws_get_random(vhd->context, sid_rand,
sizeof(sid_rand)) !=
sizeof(sid_rand)) {
lwsl_err("Problem getting random for salt\n");
return 1;
}
sha1_to_lwsgw_hash(sid_rand, &u->pwsalt);
if (lws_get_random(vhd->context, sid_rand,
sizeof(sid_rand)) !=
sizeof(sid_rand)) {
lwsl_err("Problem getting random for token\n");
return 1;
}
sha1_to_lwsgw_hash(sid_rand, &hash);
/* [password in ascii][salt] */
n = lws_snprintf((char *)buffer, sizeof(buffer) - 1,
"%s-%s-%s", password, vhd->confounder, u->pwsalt.id);
/* sha1sum of password + salt */
lws_SHA1(buffer, n, hash_bin.bin);
sha1_to_lwsgw_hash(&hash_bin.bin[0], &u->pwhash);
return 0;
}