Add to git.
[monolith.git] / src / monolith.c
1 /* Monolith core code.
2  * - by Richard W.M. Jones <rich@annexia.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  * $Id: monolith.c,v 1.27 2003/04/30 13:15:35 rich Exp $
19  */
20
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <assert.h>
26
27 #ifdef HAVE_SYS_TYPES_H
28 #include <sys/types.h>
29 #endif
30
31 #ifdef HAVE_STRING_H
32 #include <string.h>
33 #endif
34
35 #ifdef HAVE_UNISTD_H
36 #include <unistd.h>
37 #endif
38
39 #ifdef HAVE_FCNTL_H
40 #include <fcntl.h>
41 #endif
42
43 #ifdef HAVE_TIME_H
44 #include <time.h>
45 #endif
46
47 #ifdef HAVE_NETINET_IN_H
48 #include <netinet/in.h>
49 #endif
50
51 #ifdef HAVE_ARPA_INET_H
52 #include <arpa/inet.h>
53 #endif
54
55 #ifdef HAVE_SYS_SOCKET_H
56 #include <sys/socket.h>
57 #endif
58
59 #include <pool.h>
60 #include <hash.h>
61 #include <vector.h>
62 #include <pstring.h>
63 #include <pthr_reactor.h>
64 #include <pthr_pseudothread.h>
65 #include <pthr_iolib.h>
66 #include <pthr_mutex.h>
67 #include <pthr_http.h>
68 #include <pthr_wait_queue.h>
69 #include <rws_request.h>
70
71 #include "ml_window.h"
72 #include "monolith.h"
73
74 #ifndef STRINGIFY
75 #ifdef HAVE_STRINGIZE
76 #define STRINGIFY(STRING) #STRING
77 #else
78 #define STRINGIFY(STRING) "STRING"
79 #endif
80 #endif /* ! STRINGIFY */
81
82 /* Define some RFC-compliant dates to represent past and future. */
83 #define DISTANT_PAST   "Thu, 01 Dec 1994 16:00:00 GMT"
84 #define DISTANT_FUTURE "Sun, 01 Dec 2030 16:00:00 GMT"
85
86 /* Headers which are sent to defeat caches. */
87 #define NO_CACHE_HEADERS "Cache-Control", "must-revalidate", \
88                          "Expires", DISTANT_PAST, \
89                          "Pragma", "no-cache"
90
91 /* These are the default session reaping parameters, all in seconds. */
92 #define SESSION_REAP_MIN 600
93 #define SESSION_REAP_MAX 3600
94 #define SESSION_REAP_INC 600
95
96 struct ml_session
97 {
98   const char *sessionid;        /* Session ID (string of 32 hex digits). */
99   pool session_pool;            /* Pool for this session. */
100   mutex lock;                   /* Lock for this session. */
101   int hits;                     /* Number of requests in this session. */
102   reactor_time_t created, last_access; /* Session created, last accessed. */
103   int reap_min;                 /* Time to reap, if hits == 1. */
104   int reap_max;                 /* Maximum reap time. */
105   int reap_inc;                 /* Increment in reap time, per hit. */
106   struct sockaddr_in original_ip; /* IP address of initial request. */
107   cgi args;                     /* Initial arguments. */
108   cgi submitted_args;           /* Current arguments (short-lived). */
109   rws_request rws_rq;           /* Current request (short-lived). */
110   io_handle io;                 /* Current IO handle (short-lived). */
111   void (*app_main) (ml_session); /* Main entry point into the application. */
112   ml_window current_window;     /* "Current" window for the application. */
113   ml_window main_window;        /* Nominated main window for the application.*/
114   shash windows;                /* Maps window IDs -> ml_window. */
115   shash actions;                /* Maps action IDs -> callback functions. */
116   const char *host_header;      /* Host header. */
117   const char *canonical_path;   /* Full path to the script. */
118   const char *script_name;      /* Just the name of the script. */
119   const char *user_agent;       /* User agent. */
120   hash dbhs;                    /* Hash db_handle -> pools of
121                                  * handles given out in current session. */
122   int userid;                   /* Currently logged in user (0 = none). */
123   const char *auth_cookie;      /* If set, send an auth cookie at the end
124                                  * of the current HTTP request. */
125   const char *auth_cookie_path, *auth_cookie_expires;
126 };
127
128 struct action
129 {
130   void (*callback_fn) (ml_session, void *data);
131   void *data;
132 };
133
134 static pool ml_pool;            /* Monolith library's own pool. */
135 static shash sessions;          /* Maps session IDs -> ml_session. */
136 static const char *auth_dbf;    /* Connection used for authentication. */
137
138 static void run_action (ml_session, const char *);
139 static int bad_request_error (rws_request rq, const char *text);
140 static int auth_to_userid (ml_session, const char *auth);
141 static void monolith_init (void) __attribute__ ((constructor));
142 static void monolith_stop (void) __attribute__ ((destructor));
143 static void kill_session (const char *sessionid);
144
145 static void
146 monolith_init ()
147 {
148   ml_pool = new_subpool (global_pool);
149   sessions = new_shash (ml_pool, ml_session);
150 }
151
152 static void
153 monolith_stop ()
154 {
155   /* Note that this will also free up memory used by sessions, since
156    * each session pool is a subpool of ml_pool.
157    */
158   delete_pool (ml_pool);
159 }
160
161 static inline void
162 kill_old_sessions ()
163 {
164   static reactor_time_t last_reap = 0;
165
166   /* Only reap sessions every 10s. This also ensures that only one thread
167    * will try to kill sessions.
168    */
169   if (reactor_time - last_reap > 10000LL)
170     {
171       vector session_list;
172       int i;
173
174       last_reap = reactor_time;
175
176       session_list
177         = shash_values_in_pool (sessions, pth_get_pool (current_pth));
178
179       for (i = 0; i < vector_size (session_list); ++i)
180         {
181           ml_session session;
182           int reap_age;
183
184           vector_get (session_list, i, session);
185
186           /* Calculate the age before reaping. */
187           reap_age = session->reap_min +
188             (session->hits - 1) * session->reap_inc;
189           if (reap_age > session->reap_max)
190             reap_age = session->reap_max;
191
192           /* Session is older than this? */
193           if (reactor_time - session->last_access > reap_age * 1000LL)
194             {
195 #if 0
196               fprintf (stderr,
197                        "reaping session ID %s\n"
198                        "current time = %llu, last access = %llu, diff = %llu",
199                        session->sessionid,
200                        reactor_time,
201                        session->last_access,
202                        reactor_time - session->last_access
203                        );
204 #endif
205               kill_session (session->sessionid);
206             }
207         }
208     }
209 }
210
211 static inline int
212 my_isxdigit (char c)
213 {
214   return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
215 }
216
217 static inline const char *
218 get_sessionid_from_cookie (http_request http_request)
219 {
220   const char *sessionid;
221   int i;
222
223   sessionid = http_request_get_cookie (http_request, "ml_sessionid");
224   if (sessionid)
225     {
226       /* Check that the cookie has a valid form (32 hex digits). If
227        * not, just ignore it.
228        */
229       if (strlen (sessionid) == 32)
230         {
231           for (i = 0; i < 32; ++i)
232             if (!my_isxdigit (sessionid[i]))
233               break;
234           return sessionid;
235         }
236     }
237
238   return 0;
239 }
240
241 /* This function is so useful, it should be part of c2lib (XXX)
242  * See also the other occurrence in ml_login_nopw.c
243  */
244 static inline const char *
245 generate_sessionid (pool pool)
246 {
247   int fd, i;
248   unsigned char buffer[16];
249   char *sessionid = pmalloc (pool, 33 * sizeof (char));
250
251   fd = open ("/dev/urandom", O_RDONLY);
252   if (fd == -1) abort ();
253   if (read (fd, buffer, 16) != 16) abort ();
254   close (fd);
255
256   for (i = 0; i < 16; ++i)
257     sprintf (sessionid + i*2, "%02x", buffer[i]);
258
259   return sessionid;
260 }
261
262 static const char *
263 get_script_name (pool pool, const char *canonical_path)
264 {
265   char *t = strrchr (canonical_path, '/');
266   return t ? pstrdup (pool, t+1) : canonical_path;
267 }
268
269 int
270 ml_entry_point (rws_request rq, void (*app_main) (ml_session))
271 {
272   pool thread_pool = pth_get_pool (current_pth);
273   io_handle io = rws_request_io (rq);
274   http_request http_request = rws_request_http_request (rq);
275   const char *host_header = rws_request_host_header (rq);
276   const char *canonical_path = rws_request_canonical_path (rq);
277   const char *sessionid;
278   cgi cgi;
279   ml_session session;
280   int send_sessionid = 0;
281   http_response http_response;
282   int close;
283   const char *actionid, *windowid, *auth;
284
285   /* Look for old sessions and kill them. */
286   kill_old_sessions ();
287
288   /* Get the sessionid, if there is one. */
289   sessionid = get_sessionid_from_cookie (http_request);
290
291   /* Parse the CGI parameters, and extract the monolith-specific
292    * parameters. Note that these are parsed into the thread pool,
293    * not into the session pool (we don't even know yet if we have
294    * a session pool, so that's not an option).
295    */
296   cgi = new_cgi (thread_pool, http_request, io);
297
298   /* If ml_reset passed, begin a new session regardless. */
299   if (cgi_param (cgi, "ml_reset"))
300     {
301       /* But if there was an existing session, delete it now. */
302       if (sessionid)
303         kill_session (sessionid);
304
305       sessionid = 0;
306     }
307
308   if (sessionid && shash_get (sessions, sessionid, session))
309     {
310       /* It's an existing, valid session. */
311
312       /* Acquire the lock before accessing any parts of the session
313        * structure.
314        */
315       mutex_enter (session->lock);
316
317       /* Update the access time. */
318       session->last_access = reactor_time;
319
320       /* Hit. */
321       session->hits++;
322
323       /* Get the current window, from the ml_window parameter. If there
324        * is no ml_window parameter (can happen when opening new windows),
325        * then set current window to NULL and expect that the action will
326        * set the current window.
327        */
328       session->current_window = session->main_window;
329       windowid = cgi_param (cgi, "ml_window");
330       if (windowid &&
331           ! shash_get (session->windows, windowid, session->current_window))
332         {
333           return bad_request_error (rq,
334                                     psprintf (thread_pool,
335                                               "invalid window ID: %s",
336                                               windowid));
337         }
338
339       /* Set the rws_rq field to the current request. */
340       session->rws_rq = rq;
341
342       /* Set the current IO handle. */
343       session->io = io;
344
345       /* Get ready for sending back auth cookie. */
346       session->auth_cookie = 0;
347
348       /* If the userid is set, check to see if there is a "poison" cookie.
349        * If so, then we log out the user.
350        */
351       if (session->userid != 0 &&
352           (auth = http_request_get_cookie (http_request, "ml_auth")) != 0
353           && strcmp (auth, "poison") == 0)
354         {
355           session->userid = 0;
356         }
357       else if (session->userid == 0 &&
358                (auth = http_request_get_cookie (http_request, "ml_auth")) != 0)
359         {
360           session->userid = auth_to_userid (session, auth);
361         }
362
363       /* If the ml_action parameter is given, invoke the appropriate
364        * function.
365        */
366       actionid = cgi_param (cgi, "ml_action");
367
368       /* Save the submitted args, for forms. */
369       session->submitted_args = cgi;
370
371       if (actionid)
372         run_action (session, actionid);
373     }
374   else
375     {
376       /* Start a new session. */
377       pool session_pool;
378       socklen_t namelen;
379       const char *ua;
380
381       /* Create a pool for this session. */
382       session_pool = new_subpool (ml_pool);
383
384       /* Create some state for this session. */
385       session = pmalloc (session_pool, sizeof *session);
386       session->lock = new_mutex (session_pool);
387       session->hits = 1;
388       session->last_access = session->created = reactor_time;
389       session->reap_min = SESSION_REAP_MIN;
390       session->reap_max = SESSION_REAP_MAX;
391       session->reap_inc = SESSION_REAP_INC;
392       session->session_pool = session_pool;
393       session->app_main = app_main;
394       session->current_window = 0;
395       session->main_window = 0;
396       session->windows = new_shash (session_pool, ml_window);
397       session->sessionid = sessionid = generate_sessionid (session_pool);
398       session->actions = new_shash (session_pool, struct action);
399       session->host_header = pstrdup (session_pool, host_header);
400       session->canonical_path = pstrdup (session_pool, canonical_path);
401       session->script_name =
402         get_script_name (session_pool, session->canonical_path);
403
404       /* Get the User-Agent header (if any). */
405       ua = http_request_get_header (http_request, "User-Agent");
406       session->user_agent = ua ? pstrdup (session_pool, ua) : 0;
407
408       /* Get the IP address of the first request. */
409       namelen = sizeof (session->original_ip);
410       getpeername (io_fileno (io),
411                    (struct sockaddr *) &session->original_ip, &namelen);
412
413       /* Take the initial arguments and copy them into the
414        * session pool, dropping the private ml_* parameters.
415        */
416       session->args = copy_cgi (session_pool, cgi);
417       cgi_erase (session->args, "ml_reset");
418       cgi_erase (session->args, "ml_window");
419       cgi_erase (session->args, "ml_action");
420
421       /* Set the rws_rq field to the current request. */
422       session->rws_rq = rq;
423
424       /* Set the current IO handle. */
425       session->io = io;
426
427       /* Initialize the list of database handles. */
428       session->dbhs = new_hash (session_pool, db_handle, pool);
429
430       /* See if there's an ml_auth cookie. If so, and it's valid, then
431        * we initialize the session->userid from this. Otherwise we
432        * set session->userid to 0.
433        */
434       if ((auth = http_request_get_cookie (http_request, "ml_auth")) != 0)
435         session->userid = auth_to_userid (session, auth);
436       else
437         session->userid = 0;
438
439       /* Get ready for sending back auth cookie. */
440       session->auth_cookie = 0;
441
442       /* Remember to send back the ml_sessionid cookie. */
443       send_sessionid = 1;
444
445       /* Save the session. */
446       shash_insert (sessions, sessionid, session);
447
448       /* Acquire the lock. (Actually we don't strictly need to do this
449        * until after we have sent the cookie, but it makes the code
450        * simpler).
451        */
452       mutex_enter (session->lock);
453
454       /* Run the "main" program. */
455       app_main (session);
456     }
457
458   if (! session->current_window)
459     {
460       return bad_request_error (rq, "no current window");
461     }
462
463   /* Begin the response. */
464   _ml_window_notify_begin_response (session->current_window);
465
466   http_response = new_http_response
467     (thread_pool, http_request, io,
468      _ml_window_get_response_code (session->current_window),
469      _ml_window_get_response_name (session->current_window));
470
471   http_response_send_headers (http_response,
472                               /* Send headers to defeat caching. */
473                               NO_CACHE_HEADERS,
474                               /* End of headers. */
475                               NULL);
476
477   /* Send the session cookie if necessary. */
478   if (send_sessionid)
479     {
480       const char *cookie = psprintf (thread_pool,
481                                      "ml_sessionid=%s; path=%s",
482                                      sessionid,
483                                      canonical_path);
484
485       http_response_send_header (http_response, "Set-Cookie", cookie);
486       _ml_window_set_cookie_with_javascript (session->current_window, cookie);
487     }
488
489   /* Send the auth cookie if necessary. */
490   if (session->auth_cookie)
491     {
492       const char *cookie = psprintf (thread_pool,
493                                      "ml_auth=%s; path=%s; expires=%s",
494                                      session->auth_cookie,
495                                      session->auth_cookie_path
496                                      ? : canonical_path,
497                                      session->auth_cookie_expires
498                                      ? : "");
499
500       http_response_send_header (http_response, "Set-Cookie", cookie);
501       _ml_window_set_cookie_with_javascript (session->current_window, cookie);
502
503       session->auth_cookie = 0;
504     }
505
506   /* Send any additional headers required by the current window. */
507   _ml_window_send_headers (session->current_window, thread_pool,
508                            http_response);
509
510   close = http_response_end_headers (http_response);
511
512   if (!http_request_is_HEAD (http_request))
513     {
514       /* Display the main window. */
515       _ml_window_repaint (session->current_window, session, io);
516     }
517
518   /* XXX We might need to recover database handles here, particularly
519    * if we implement chunked encoding, and we start to process many
520    * requests over the same connection.
521    */
522
523   /* Free the session lock. */
524   mutex_leave (session->lock);
525
526   return close;
527 }
528
529 /* Delete a session.
530  *
531  * Killing a session is a non-trivial task, because we must make sure
532  * at all costs that no other thread is using the session structure, or
533  * might be waiting on the mutex to enter the session.
534  *
535  * The procedure is as follows. If any step fails, then we need to go
536  * back round to the top and try again. Eventually this function will
537  * delete the session.
538  *
539  * - Acquire the mutex.
540  * - Check if any other threads are waiting to enter the mutex.
541  * - If none, then remove the session from the sessions hash (this ensures
542  *   that no other thread will try to use the session - particularly
543  *   important if the session deletion is protracted and involves I/O).
544  * - Release the mutex (no other thread will try to acquire it).
545  * - Delete the session pool, which invokes any session finalisers.
546  */
547 static void
548 kill_session (const char *sessionid)
549 {
550   ml_session session;
551
552   /* Get the session structure. */
553   if (!shash_get (sessions, sessionid, session))
554     return;                     /* No such session - ignore it. */
555
556  again:
557   /* Acquire the session lock. */
558   mutex_enter (session->lock);
559
560   /* Any other threads waiting to enter the mutex? */
561   if (mutex_nr_sleepers (session->lock) > 0)
562     {
563       /* Release the lock and try again later. */
564       mutex_leave (session->lock);
565       pth_millisleep (100);     /* To be on the safe side ... */
566       goto again;
567     }
568
569   /* Remove the session from the list of sessions. After this, no
570    * other threads can find or enter this session.
571    */
572   assert (shash_erase (sessions, sessionid));
573
574   /* Release the lock. */
575   mutex_leave (session->lock);
576
577   /* Finally, we can delete the thread. */
578   delete_pool (session->session_pool);
579 }
580
581 pool
582 ml_session_pool (ml_session session)
583 {
584   return session->session_pool;
585 }
586
587 cgi
588 ml_session_args (ml_session session)
589 {
590   return session->args;
591 }
592
593 const char *
594 ml_session_sessionid (ml_session session)
595 {
596   return session->sessionid;
597 }
598
599 const char *
600 ml_session_host_header (ml_session session)
601 {
602   return session->host_header;
603 }
604
605 const char *
606 ml_session_canonical_path (ml_session session)
607 {
608   return session->canonical_path;
609 }
610
611 const char *
612 ml_session_script_name (ml_session session)
613 {
614   return session->script_name;
615 }
616
617 const char *
618 ml_session_user_agent (ml_session session)
619 {
620   return session->user_agent;
621 }
622
623 void
624 ml_session_set_main_window (ml_session session, ml_window win)
625 {
626   session->main_window = win;
627 }
628
629 ml_window
630 ml_session_get_main_window (ml_session session)
631 {
632   return session->main_window;
633 }
634
635 int
636 ml_session_get_peername (ml_session session,
637                          struct sockaddr *name, socklen_t *namelen)
638 {
639   int s;
640
641   s = io_fileno (session->io);
642   return getpeername (s, name, namelen);
643 }
644
645 const char *
646 ml_session_get_peernamestr (ml_session session)
647 {
648   struct sockaddr_in addr;
649   socklen_t len = sizeof (addr);
650
651   if (ml_session_get_peername (session, (struct sockaddr *) &addr, &len) == -1)
652     {
653       perror ("getpeername");
654       return 0;
655     }
656
657   assert (addr.sin_family == AF_INET);
658   return pstrdup (session->session_pool, inet_ntoa (addr.sin_addr));
659 }
660
661 const vector
662 _ml_get_sessions (pool pool)
663 {
664   return shash_keys_in_pool (sessions, pool);
665 }
666
667 ml_session
668 _ml_get_session (const char *sessionid)
669 {
670   ml_session session;
671
672   if (shash_get (sessions, sessionid, session))
673     return session;
674   else
675     return 0;
676 }
677
678 int
679 _ml_session_get_hits (ml_session session)
680 {
681   return session->hits;
682 }
683
684 reactor_time_t
685 _ml_session_get_last_access (ml_session session)
686 {
687   return session->last_access;
688 }
689
690 reactor_time_t
691 _ml_session_get_created (ml_session session)
692 {
693   return session->created;
694 }
695
696 struct sockaddr_in
697 _ml_session_get_original_ip (ml_session session)
698 {
699   return session->original_ip;
700 }
701
702 void *
703 _ml_session_get_app_main (ml_session session)
704 {
705   return session->app_main;
706 }
707
708 const vector
709 _ml_session_get_windows (ml_session session, pool pool)
710 {
711   return shash_keys_in_pool (session->windows, pool);
712 }
713
714 ml_window
715 _ml_session_get_window (ml_session session, const char *windowid)
716 {
717   ml_window win;
718
719   if (shash_get (session->windows, windowid, win))
720     return win;
721   else
722     return 0;
723 }
724
725 const vector
726 _ml_session_get_actions (ml_session session, pool pool)
727 {
728   return shash_keys_in_pool (session->actions, pool);
729 }
730
731 int
732 _ml_session_get_action (ml_session session, const char *actionid,
733                         void **fn_rtn, void **data_rtn)
734 {
735   struct action action;
736
737   if (shash_get (session->actions, actionid, action))
738     {
739       *fn_rtn = action.callback_fn;
740       *data_rtn = action.data;
741       return 1;
742     }
743   else
744     return 0;
745 }
746
747 const vector
748 _ml_session_get_dbhs (ml_session session, pool pool)
749 {
750   return hash_keys_in_pool (session->dbhs, pool);
751 }
752
753 void
754 ml_session_release_lock (ml_session session)
755 {
756   mutex_leave (session->lock);
757 }
758
759 void
760 ml_session_acquire_lock (ml_session session)
761 {
762   mutex_enter (session->lock);
763
764   /* We've probably been sleeping for a while, so update the last access
765    * time to reflect this.
766    */
767   session->last_access = reactor_time;
768 }
769
770 cgi
771 _ml_session_submitted_args (ml_session session)
772 {
773   return session->submitted_args;
774 }
775
776 static void get_auth_dbf (ml_session);
777 static const char *parse_expires_header (pool pool, const char *expires);
778
779 void
780 ml_session_login (ml_session session, int userid,
781                   const char *path, const char *expires)
782 {
783   pool thread_pool = pth_get_pool (current_pth);
784   db_handle dbh;
785   st_handle sth;
786   const char *cookie;
787
788   /* Parse the expires header. */
789   expires = parse_expires_header (thread_pool, expires);
790
791   get_auth_dbf (session);
792   dbh = get_db_handle (auth_dbf, DBI_THROW_ERRORS);
793
794   /* Generate a suitable cookie and insert it into the database. */
795   cookie = generate_sessionid (thread_pool);
796   sth = st_prepare_cached
797     (dbh,
798      "delete from ml_user_cookie where userid = ?",
799      DBI_INT);
800   st_execute (sth, userid);
801   sth = st_prepare_cached
802     (dbh,
803      "insert into ml_user_cookie (userid, cookie) values (?, ?)",
804      DBI_INT, DBI_STRING);
805   st_execute (sth, userid, cookie);
806   db_commit (dbh);
807   put_db_handle (dbh);
808
809   /* User is logged in. */
810   session->userid = userid;
811
812   /* Remember to send back a cookie. */
813   session->auth_cookie = cookie;
814   session->auth_cookie_path = path;
815   session->auth_cookie_expires = expires;
816 }
817
818 void
819 ml_session_logout (ml_session session, const char *path)
820 {
821   pool thread_pool = pth_get_pool (current_pth);
822   db_handle dbh;
823   st_handle sth;
824   int old_userid = session->userid;
825   const char *expires;
826
827   /* Set the expires header. */
828   expires = parse_expires_header (thread_pool, "+1y");
829
830   /* If no one is actually logged in, do nothing. */
831   if (!old_userid) return;
832
833   get_auth_dbf (session);
834   dbh = get_db_handle (auth_dbf, DBI_THROW_ERRORS);
835
836   sth = st_prepare_cached
837     (dbh,
838      "delete from ml_user_cookie where userid = ?",
839      DBI_INT);
840   st_execute (sth, old_userid);
841   db_commit (dbh);
842   put_db_handle (dbh);
843
844   /* User is logged out. */
845   session->userid = 0;
846
847   /* Remember to send back the poison cookie. */
848   session->auth_cookie = "poison";
849   session->auth_cookie_path = path;
850   session->auth_cookie_expires = expires;
851 }
852
853 /* Convert auth cookie to userid, if possible. If not valid, returns 0. */
854 static int
855 auth_to_userid (ml_session session, const char *auth)
856 {
857   db_handle dbh;
858   st_handle sth;
859   int userid, fetched;
860
861   get_auth_dbf (session);
862   dbh = get_db_handle (auth_dbf, DBI_THROW_ERRORS);
863
864   sth = st_prepare_cached
865     (dbh,
866      "select userid from ml_user_cookie where cookie = ?",
867      DBI_STRING);
868   st_execute (sth, auth);
869
870   st_bind (sth, 0, userid, DBI_INT);
871
872   fetched = st_fetch (sth);
873
874   put_db_handle (dbh);
875
876   if (fetched)
877     return userid;
878   else
879     return 0;
880 }
881
882 static void
883 get_auth_dbf (ml_session session)
884 {
885   /* Check monolith is configured to handle user authentication. */
886   if (!auth_dbf)
887     {
888       auth_dbf = ml_cfg_get_string (session, "monolith user database", 0);
889       if (!auth_dbf)
890         pth_die ("missing 'monolith user database' key "
891                  "in the rws configuration file");
892     }
893 }
894
895 /* XXX We should share this code with rws. */
896 static const char *
897 parse_expires_header (pool pool, const char *expires)
898 {
899   char pm, unit;
900   int length;
901
902   if (expires == 0) return 0;
903
904   /* Is it like "+1y"? */
905   if (sscanf (expires, "%c%d%c", &pm, &length, &unit) == 3 &&
906       (pm == '+' || pm == '-') &&
907       length > 0 &&
908       (unit == 's' || unit == 'm' || unit == 'h' ||
909        unit == 'd' || unit == 'y'))
910     {
911       time_t t;
912       struct tm *tm;
913       char header[64];
914
915       time (&t);
916
917       if (pm == '+')
918         {
919           switch (unit)
920             {
921             case 's': t += length; break;
922             case 'm': t += length * 60; break;
923             case 'h': t += length * (60 * 60); break;
924             case 'd': t += length * (60 * 60 * 24); break;
925             case 'y': t += length * (60 * 60 * 24 * 366); break;
926             }
927         }
928       else
929         {
930           switch (unit)
931             {
932             case 's': t -= length; break;
933             case 'm': t -= length * 60; break;
934             case 'h': t -= length * (60 * 60); break;
935             case 'd': t -= length * (60 * 60 * 24); break;
936             case 'y': t -= length * (60 * 60 * 24 * 366); break;
937             }
938         }
939
940       tm = gmtime (&t);
941       strftime (header, sizeof header, "%a, %d %b %Y %H:%M:%S GMT", tm);
942
943       return pstrdup (pool, header);
944     }
945
946   /* Otherwise, assume it's in RFC 2616 format. */
947   return expires;
948 }
949
950 int
951 ml_session_userid (ml_session session)
952 {
953   return session->userid;
954 }
955
956 const char *
957 ml_cfg_get_string (ml_session session,
958                    const char *key, const char *default_value)
959 {
960   return rws_request_cfg_get_string (session->rws_rq, key, default_value);
961 }
962
963 int
964 ml_cfg_get_int (ml_session session, const char *key, int default_value)
965 {
966   return rws_request_cfg_get_int (session->rws_rq, key, default_value);
967 }
968
969 int
970 ml_cfg_get_bool (ml_session session, const char *key, int default_value)
971 {
972   return rws_request_cfg_get_bool (session->rws_rq, key, default_value);
973 }
974
975 void
976 _ml_session_set_current_window (ml_session session, ml_window window,
977                                 const char *windowid)
978 {
979   shash_insert (session->windows, windowid, window);
980   session->current_window = window;
981 }
982
983 static void
984 run_action (ml_session session, const char *action_id)
985 {
986   struct action a;
987
988   /* Ignore unknown action IDs. */
989   if (shash_get (session->actions, action_id, a))
990     a.callback_fn (session, a.data);
991 }
992
993 const char *
994 ml_register_action (ml_session session,
995                     void (*callback_fn) (ml_session, void *data), void *data)
996 {
997   static int action_id = 1;
998   const char *action_str;
999   struct action a;
1000
1001   action_str = pitoa (session->session_pool, action_id);
1002   action_id++;
1003
1004   a.callback_fn = callback_fn;
1005   a.data = data;
1006
1007   shash_insert (session->actions, action_str, a);
1008
1009   return action_str;
1010 }
1011
1012 void
1013 ml_unregister_action (ml_session session, const char *action_id)
1014 {
1015   shash_erase (session->actions, action_id);
1016 }
1017
1018 #define CRLF "\015\012"
1019
1020 static int
1021 bad_request_error (rws_request rq, const char *text)
1022 {
1023   pool thread_pool = pth_get_pool (current_pth);
1024   io_handle io = rws_request_io (rq);
1025   http_request http_request = rws_request_http_request (rq);
1026   const char *canonical_path = rws_request_canonical_path (rq);
1027   http_response http_response;
1028   int close;
1029
1030   http_response = new_http_response (thread_pool, http_request, io,
1031                                      500, "Internal server error");
1032   http_response_send_headers (http_response,
1033                               /* Content type. */
1034                               "Content-Type", "text/html",
1035                               NO_CACHE_HEADERS,
1036                               /* End of headers. */
1037                               NULL);
1038   close = http_response_end_headers (http_response);
1039
1040   if (http_request_is_HEAD (http_request)) return close;
1041
1042   io_fprintf (io,
1043               "<html><head><title>Internal server error</title></head>" CRLF
1044               "<body bgcolor=\"#ffffff\">" CRLF
1045               "<h1>500 Internal server error</h1>" CRLF
1046               "<p>There was an error serving this request: %s</p>" CRLF
1047               "<hr>" CRLF
1048               "<p><a href=\"%s?ml_reset=1\">Restart your session</a></p>" CRLF
1049               "</body></html>",
1050               text, canonical_path);
1051
1052   return close;
1053 }