Statistics
| Branch: | Tag: | Revision:

mongoose / examples / cookie_auth / cookie_auth.c @ eaef5bd1

History | View | Annotate | Download (7.63 KB)

1
/*
2
 * Copyright (c) 2016 Cesanta Software Limited
3
 * All rights reserved
4
 *
5
 * This example demonstrates how to implement cookie authentication
6
 * and session management using Mongoose.
7
 */
8

    
9
#include <inttypes.h>
10
#include <stdlib.h>
11

    
12
#include "mongoose.h"
13

    
14
static const char *s_http_port = "8000";
15
static struct mg_serve_http_opts s_http_server_opts;
16

    
17
/* This is the name of the cookie carrying the session ID. */
18
#define SESSION_COOKIE_NAME "mgs"
19
/* In our example sessions are destroyed after 30 seconds of inactivity. */
20
#define SESSION_TTL 30.0
21
#define SESSION_CHECK_INTERVAL 5.0
22

    
23
/* Session information structure. */
24
struct session {
25
  /* Session ID. Must be unique and hard to guess. */
26
  uint64_t id;
27
  /*
28
   * Time when the session was created and time of last activity.
29
   * Used to clean up stale sessions.
30
   */
31
  double created;
32
  double last_used; /* Time when the session was last active. */
33

    
34
  /* User name this session is associated with. */
35
  char *user;
36
  /* Some state associated with user's session. */
37
  int lucky_number;
38
};
39

    
40
/*
41
 * This example uses a simple in-memory storage for just 10 sessions.
42
 * A real-world implementation would use persistent storage of some sort.
43
 */
44
#define NUM_SESSIONS 10
45
struct session s_sessions[NUM_SESSIONS];
46

    
47
/*
48
 * Password check function.
49
 * In our example all users have password "password".
50
 */
51
static int check_pass(const char *user, const char *pass) {
52
  (void) user;
53
  return (strcmp(pass, "password") == 0);
54
}
55

    
56
/*
57
 * Parses the session cookie and returns a pointer to the session struct
58
 * or NULL if not found.
59
 */
60
static struct session *get_session(struct http_message *hm) {
61
  struct mg_str *cookie_header = mg_get_http_header(hm, "cookie");
62
  if (cookie_header == NULL) return NULL;
63
  char ssid[21];
64
  if (!mg_http_parse_header(cookie_header, SESSION_COOKIE_NAME, ssid,
65
                            sizeof(ssid))) {
66
    return NULL;
67
  }
68
  uint64_t sid = strtoull(ssid, NULL, 16);
69
  for (int i = 0; i < NUM_SESSIONS; i++) {
70
    if (s_sessions[i].id == sid) {
71
      s_sessions[i].last_used = mg_time();
72
      return &s_sessions[i];
73
    }
74
  }
75
  return NULL;
76
}
77

    
78
/*
79
 * Destroys the session state.
80
 */
81
static void destroy_session(struct session *s) {
82
  free(s->user);
83
  memset(s, 0, sizeof(*s));
84
}
85

    
86
/*
87
 * Creates a new session for the user.
88
 */
89
static struct session *create_session(const char *user,
90
                                      const struct http_message *hm) {
91
  /* Find first available slot or use the oldest one. */
92
  struct session *s = NULL;
93
  struct session *oldest_s = s_sessions;
94
  for (int i = 0; i < NUM_SESSIONS; i++) {
95
    if (s_sessions[i].id == 0) {
96
      s = &s_sessions[i];
97
      break;
98
    }
99
    if (s_sessions[i].last_used < oldest_s->last_used) {
100
      oldest_s = &s_sessions[i];
101
    }
102
  }
103
  if (s == NULL) {
104
    destroy_session(oldest_s);
105
    printf("Evicted %" INT64_X_FMT "/%s\n", oldest_s->id, oldest_s->user);
106
    s = oldest_s;
107
  }
108
  /* Initialize new session. */
109
  s->created = s->last_used = mg_time();
110
  s->user = strdup(user);
111
  s->lucky_number = rand();
112
  /* Create an ID by putting various volatiles into a pot and stirring. */
113
  cs_sha1_ctx ctx;
114
  cs_sha1_init(&ctx);
115
  cs_sha1_update(&ctx, (const unsigned char *) hm->message.p, hm->message.len);
116
  cs_sha1_update(&ctx, (const unsigned char *) s, sizeof(*s));
117
  unsigned char digest[20];
118
  cs_sha1_final(digest, &ctx);
119
  s->id = *((uint64_t *) digest);
120
  return s;
121
}
122

    
123
/*
124
 * If requested via GET, serves the login page.
125
 * If requested via POST (form submission), checks password and logs user in.
126
 */
127
static void login_handler(struct mg_connection *nc, int ev, void *p) {
128
  struct http_message *hm = (struct http_message *) p;
129
  if (mg_vcmp(&hm->method, "POST") != 0) {
130
    /* Serve login.html */
131
    mg_serve_http(nc, (struct http_message *) p, s_http_server_opts);
132
  } else {
133
    /* Perform password check. */
134
    char user[50], pass[50];
135
    int ul = mg_get_http_var(&hm->body, "user", user, sizeof(user));
136
    int pl = mg_get_http_var(&hm->body, "pass", pass, sizeof(pass));
137
    if (ul > 0 && pl > 0) {
138
      if (check_pass(user, pass)) {
139
        struct session *s = create_session(user, hm);
140
        char shead[100];
141
        snprintf(shead, sizeof(shead),
142
                 "Set-Cookie: %s=%" INT64_X_FMT "; path=/", SESSION_COOKIE_NAME,
143
                 s->id);
144
        mg_http_send_redirect(nc, 302, mg_mk_str("/"), mg_mk_str(shead));
145
        fprintf(stderr, "%s logged in, sid %" INT64_X_FMT "\n", s->user, s->id);
146
      } else {
147
        mg_printf(nc, "HTTP/1.0 403 Unauthorized\r\n\r\nWrong password.\r\n");
148
      }
149
    } else {
150
      mg_printf(nc, "HTTP/1.0 400 Bad Request\r\n\r\nuser, pass required.\r\n");
151
    }
152
    nc->flags |= MG_F_SEND_AND_CLOSE;
153
  }
154
  (void) ev;
155
}
156

    
157
/*
158
 * Logs the user out.
159
 * Removes cookie and any associated session state.
160
 */
161
static void logout_handler(struct mg_connection *nc, int ev, void *p) {
162
  struct http_message *hm = (struct http_message *) p;
163
  char shead[100];
164
  snprintf(shead, sizeof(shead), "Set-Cookie: %s=", SESSION_COOKIE_NAME);
165
  mg_http_send_redirect(nc, 302, mg_mk_str("/"), mg_mk_str(shead));
166
  struct session *s = get_session(hm);
167
  if (s != NULL) {
168
    fprintf(stderr, "%s logged out, session %" INT64_X_FMT " destroyed\n",
169
            s->user, s->id);
170
    destroy_session(s);
171
  }
172
  nc->flags |= MG_F_SEND_AND_CLOSE;
173
  (void) ev;
174
}
175

    
176
/* Cleans up sessions that have been idle for too long. */
177
void check_sessions(void) {
178
  double threshold = mg_time() - SESSION_TTL;
179
  for (int i = 0; i < NUM_SESSIONS; i++) {
180
    struct session *s = &s_sessions[i];
181
    if (s->id != 0 && s->last_used < threshold) {
182
      fprintf(stderr, "Session %" INT64_X_FMT " (%s) closed due to idleness.\n",
183
              s->id, s->user);
184
      destroy_session(s);
185
    }
186
  }
187
}
188

    
189
/* Main event handler. */
190
static void ev_handler(struct mg_connection *nc, int ev, void *p) {
191
  switch (ev) {
192
    case MG_EV_HTTP_REQUEST: {
193
      struct http_message *hm = (struct http_message *) p;
194
      struct session *s = get_session(hm);
195
      /* Ask the user to log in if they did not present a valid cookie. */
196
      if (s == NULL) {
197
        mg_http_send_redirect(nc, 302, mg_mk_str("/login.html"),
198
                              mg_mk_str(NULL));
199
        nc->flags |= MG_F_SEND_AND_CLOSE;
200
        break;
201
      }
202
      /*
203
       * Serve the page that was requested.
204
       * Save session in user_data for use by SSI calls.
205
       */
206
      fprintf(stderr, "%s (sid %" INT64_X_FMT ") requested %.*s\n", s->user,
207
              s->id, (int) hm->uri.len, hm->uri.p);
208
      nc->user_data = s;
209
      mg_serve_http(nc, (struct http_message *) p, s_http_server_opts);
210
      break;
211
    }
212
    case MG_EV_SSI_CALL: {
213
      /* Expand variables in a page by using session data. */
214
      const char *var = (const char *) p;
215
      const struct session *s = (const struct session *) nc->user_data;
216
      if (strcmp(var, "user") == 0) {
217
        mg_printf_html_escape(nc, "%s", s->user);
218
      } else if (strcmp(var, "lucky_number") == 0) {
219
        mg_printf_html_escape(nc, "%d", s->lucky_number);
220
      }
221
      break;
222
    }
223
    case MG_EV_TIMER: {
224
      /* Perform session maintenance. */
225
      check_sessions();
226
      mg_set_timer(nc, mg_time() + SESSION_CHECK_INTERVAL);
227
      break;
228
    }
229
  }
230
}
231

    
232
int main(void) {
233
  struct mg_mgr mgr;
234
  struct mg_connection *nc;
235
  srand(mg_time());
236

    
237
  mg_mgr_init(&mgr, NULL);
238
  nc = mg_bind(&mgr, s_http_port, ev_handler);
239

    
240
  mg_set_protocol_http_websocket(nc);
241
  s_http_server_opts.document_root = ".";
242
  mg_register_http_endpoint(nc, "/login.html", login_handler);
243
  mg_register_http_endpoint(nc, "/logout", logout_handler);
244
  mg_set_timer(nc, mg_time() + SESSION_CHECK_INTERVAL);
245

    
246
  printf("Starting web server on port %s\n", s_http_port);
247
  for (;;) {
248
    mg_mgr_poll(&mgr, 1000);
249
  }
250
  mg_mgr_free(&mgr);
251

    
252
  return 0;
253
}