Commit b33755c9 authored by Sergey Lyubka's avatar Sergey Lyubka

sessions implemented (simplistically). security expert review would be nice to have.

parent 4b0424d0
/*
* This file is part of the Mongoose project, http://code.google.com/p/mongoose
* It implements an online chat server. For more details,
* see the documentation on project page.
* To start the server,
* a) type "make" in the directory where this file lives
* b) point your browser to http://127.0.0.1:8081
* see the documentation on the project web site.
* To test the application,
* 1. type "make" in the directory where this file lives
* 2. point your browser to http://127.0.0.1:8081
*
* NOTE(lsm): this file follows Google style, not BSD style as the rest of
* Mongoose code.
*
* $Id: chat.c 513 2010-05-03 11:06:08Z valenok $
*/
#include <stdio.h>
......@@ -25,13 +23,13 @@
#define MAX_MESSAGE_LEN 100
#define MAX_MESSAGES 5
#define MAX_SESSIONS 2
#define SESSION_TTL 120
static const char *login_url = "/login.html";
static const char *authorize_url = "/authorize";
static const char *web_root = "./html";
static const char *http_ports = "8081,8082s";
static const char *ssl_certificate = "ssl_cert.pem";
static const char *ajax_reply_start =
"HTTP/1.1 200 OK\r\n"
"Cache: no-cache\r\n"
......@@ -41,17 +39,18 @@ static const char *ajax_reply_start =
// Describes single message sent to a chat. If user is empty (0 length),
// the message is then originated from the server itself.
struct message {
long id;
char user[MAX_USER_LEN];
char text[MAX_MESSAGE_LEN];
time_t utc_timestamp;
long id; // Message ID
char user[MAX_USER_LEN]; // User that have sent the message
char text[MAX_MESSAGE_LEN]; // Message text
time_t timestamp; // Message timestamp, UTC
};
// Describes web session.
struct session {
char session_id[33];
char authenticated_user[MAX_USER_LEN];
time_t expiration_timestamp_utc;
char session_id[33]; // Session ID, must be unique
char random[20]; // Random data used for extra user validation
char user[MAX_USER_LEN]; // Authenticated user
time_t expire; // Expiration timestamp, UTC
};
static struct message messages[MAX_MESSAGES]; // Ringbuffer for messages
......@@ -80,7 +79,7 @@ static char *messages_to_json(long last_id) {
}
for (; last_id < last_message_id; last_id++) {
message = &messages[last_id % max_msgs];
if (message->utc_timestamp == 0) {
if (message->timestamp == 0) {
break;
}
// buf is allocated on stack and hopefully is large enough to hold all
......@@ -88,7 +87,7 @@ static char *messages_to_json(long last_id) {
// messages are large. in this case asserts will trigger).
len += snprintf(buf + len, sizeof(buf) - len,
"{user: '%s', text: '%s', timestamp: %lu, id: %lu},",
message->user, message->text, message->utc_timestamp, message->id);
message->user, message->text, message->timestamp, message->id);
assert(len > 0);
assert((size_t) len < sizeof(buf));
}
......@@ -153,7 +152,7 @@ static void ajax_send_message(struct mg_connection *conn,
// TODO(lsm): JSON-encode all text strings
strncpy(message->text, text, sizeof(text));
strncpy(message->user, "joe", sizeof(message->user));
message->utc_timestamp = time(0);
message->timestamp = time(0);
message->id = last_message_id++;
pthread_rwlock_unlock(&rwlock);
}
......@@ -169,43 +168,90 @@ static void ajax_send_message(struct mg_connection *conn,
// we came from, so that after the authorization we could redirect back.
static void redirect_to_login(struct mg_connection *conn,
const struct mg_request_info *request_info) {
const char *host;
host = mg_get_header(conn, "Host");
mg_printf(conn, "HTTP/1.1 302 Found\r\n"
"Set-Cookie: original_url=%s\r\n"
"Location: %s\r\n\r\n", request_info->uri, login_url);
"Set-Cookie: original_url=%s://%s%s\r\n"
"Location: %s\r\n\r\n",
request_info->is_ssl ? "https" : "http",
host ? host : "127.0.0.1",
request_info->uri,
login_url);
}
// Return 1 if username/password is allowed, 0 otherwise.
static int check_password(const char *user, const char *password) {
// In production environment we should ask an authentication system
// to authenticate the user.
// Here however we do trivial check: if username == password, allow.
return (strcmp(user, password) == 0 ? 1 : 0);
// Here however we do trivial check that user and password are not empty
return (user[0] && password[0]);
}
// Allocate new session object
static struct session *new_session(void) {
int i;
time_t now = time(NULL);
pthread_rwlock_wrlock(&rwlock);
for (i = 0; i < MAX_SESSIONS; i++) {
if (sessions[i].expire == 0 || sessions[i].expire < now) {
sessions[i].expire = time(0) + SESSION_TTL;
break;
}
}
pthread_rwlock_unlock(&rwlock);
return i == MAX_SESSIONS ? NULL : &sessions[i];
}
// Generate session ID. buf must be 33 bytes in size.
static void generate_session_id(char *buf, const char *random,
const char *user, const struct mg_request_info *request_info) {
char remote_ip[20], remote_port[20];
snprintf(remote_ip, sizeof(remote_ip), "%ld", request_info->remote_ip);
snprintf(remote_port, sizeof(remote_port), "%d", request_info->remote_port);
mg_md5(buf, random, user, remote_port, remote_ip, NULL);
}
// A handler for the /authorize endpoint.
// Login page form sends user name and password to this endpoint.
static void authorize(struct mg_connection *conn,
const struct mg_request_info *request_info) {
char user[20], password[20], original_url[200];
char user[MAX_USER_LEN], password[MAX_USER_LEN], original_url[200];
struct session *session;
// Fetch user name and password.
mg_get_qsvar(request_info, "user", user, sizeof(user));
mg_get_qsvar(request_info, "password", password, sizeof(password));
mg_get_cookie(conn, "original_url", original_url, sizeof(original_url));
if (user[0] && password[0] && check_password(user, password)) {
if (check_password(user, password) && (session = new_session()) != NULL) {
// Authentication success:
// 1. create new session
// 2. set session ID token in the cookie
// 3. remove original_url from the cookie - not needed anymore
// 4. redirect client back to the original URL
// TODO(lsm): implement sessions.
//
// The most secure way is to stay HTTPS all the time. However, just to
// show the technique, we redirect to HTTP after the successful
// authentication. The danger of doing this is that session cookie can
// be stolen and an attacker may impersonate the user.
// Secure application must use HTTPS all the time.
strlcpy(session->user, user, sizeof(session->user));
snprintf(session->random, sizeof(session->random), "%d", rand());
generate_session_id(session->session_id, session->random,
session->user, request_info);
printf("New session, user: %s, id: %s, redirecting to %s\n",
session->user, session->session_id, original_url);
mg_printf(conn, "HTTP/1.1 302 Found\r\n"
"Set-Cookie: sid=1234; max-age=2h; http-only\r\n" // Set session ID
"Set-Cookie: original_url=/; max_age=0\r\n" // Delete original_url
"Location: %s\r\n\r\n", original_url[0] == '\0' ? "/" : original_url);
"Set-Cookie: session=%s; max-age=3600; http-only\r\n" // Session ID
"Set-Cookie: user=%s\r\n" // Set user, needed by Javascript code
"Set-Cookie: original_url=/; max-age=0\r\n" // Delete original_url
"Location: %s\r\n\r\n",
session->session_id,
session->user,
original_url[0] == '\0' ? "/" : original_url);
} else {
// Authentication failure, redirect to login again.
// Authentication failure, redirect to login.
redirect_to_login(conn, request_info);
}
}
......@@ -213,8 +259,33 @@ static void authorize(struct mg_connection *conn,
// Return 1 if request is authorized, 0 otherwise.
static int is_authorized(const struct mg_connection *conn,
const struct mg_request_info *request_info) {
// TODO(lsm): implement this part: fetch session ID from the cookie.
return 0;
char valid_id[33], received_id[33];
int i, authorized = 0;
mg_get_cookie(conn, "session", received_id, sizeof(received_id));
if (received_id[0] != '\0') {
pthread_rwlock_rdlock(&rwlock);
for (i = 0; i < MAX_SESSIONS; i++) {
if (sessions[i].expire != 0 &&
sessions[i].expire > time(NULL) &&
strcmp(sessions[i].session_id, received_id) == 0) {
break;
}
}
if (i < MAX_SESSIONS) {
generate_session_id(valid_id, sessions[i].random,
sessions[i].user, request_info);
if (strcmp(valid_id, received_id) == 0) {
sessions[i].expire = time(0) + SESSION_TTL;
authorized = 1;
}
}
pthread_rwlock_unlock(&rwlock);
}
printf("session: %s, uri: %s, authorized: %s, cookie: %s\n",
received_id, request_info->uri, authorized ? "yes" : "no", mg_get_header(conn, "Cookie"));
return authorized;
}
// Return 1 if authorization is required for requested URL, 0 otherwise.
......@@ -246,18 +317,22 @@ static enum mg_error_t process_request(struct mg_connection *conn,
return processed;
}
int main(int argc, char *argv[]) {
int main(void) {
struct mg_context *ctx;
ctx = mg_start();
// Initialize random number generator. It will be used later on for
// the session identifier creation.
srand((unsigned) time(0));
// Start and setup Mongoose
ctx = mg_start();
mg_set_option(ctx, "root", web_root);
mg_set_option(ctx, "ssl_cert", ssl_certificate); // Must be set before ports
mg_set_option(ctx, "ports", http_ports);
mg_set_option(ctx, "dir_list", "no"); // Disable directory listing
mg_set_callback(ctx, MG_EVENT_NEW_REQUEST, &process_request);
// Wait until enter is pressed, then exit
printf("Chat server started on ports %s, press enter to quit.\n", http_ports);
getchar();
mg_stop(ctx);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment