ceca387e26fe69a689986966ff15cb04a0ab07b0
[citadel.git] / webcit / webcit.c
1 /*
2  * $Id$
3  *
4  * This is the main transaction loop of the web service.  It maintains a
5  * persistent session to the Citadel server, handling HTTP WebCit requests as
6  * they arrive and presenting a user interface.
7  */
8
9 #include "webcit.h"
10 #include "groupdav.h"
11 #include "webserver.h"
12
13 #include <stdio.h>
14 #include <stdarg.h>
15
16 /*
17  * String to unset the cookie.
18  * Any date "in the past" will work, so I chose my birthday, right down to
19  * the exact minute.  :)
20  */
21 static char *unset = "; expires=28-May-1971 18:10:00 GMT";
22
23 /**   
24  * \brief remove escaped strings from i.e. the url string (like %20 for blanks)
25  * \param buf the buffer to examine
26  */
27 long unescape_input(char *buf)
28 {
29         int a, b;
30         char hex[3];
31         long buflen;
32         long len;
33
34         buflen = strlen(buf);
35
36         while ((buflen > 0) && (isspace(buf[buflen - 1]))){
37                 buf[buflen - 1] = 0;
38                 buflen --;
39         }
40
41         a = 0; 
42         while (a < buflen) {
43                 if (buf[a] == '+')
44                         buf[a] = ' ';
45                 if (buf[a] == '%') {
46                         /* don't let % chars through, rather truncate the input. */
47                         if (a + 2 > buflen) {
48                                 buf[a] = '\0';
49                                 buflen = a;
50                         }
51                         else {                  
52                                 hex[0] = buf[a + 1];
53                                 hex[1] = buf[a + 2];
54                                 hex[2] = 0;
55                                 b = 0;
56                                 sscanf(hex, "%02x", &b);
57                                 buf[a] = (char) b;
58                                 len = buflen - a - 2;
59                                 if (len > 0)
60                                         memmove(&buf[a + 1], &buf[a + 3], len);
61                         
62                                 buflen -=2;
63                         }
64                 }
65                 a++;
66         }
67         return a;
68 }
69
70 void free_url(void *U)
71 {
72         urlcontent *u = (urlcontent*) U;
73         free(u->url_data);
74         free(u);
75 }
76
77 /**
78  * \brief Extract variables from the URL.
79  * \param url URL supplied by the HTTP parser
80  */
81 void addurls(char *url)
82 {
83         char *aptr, *bptr, *eptr;
84         char *up;
85         char buf[SIZ] = "";
86         int len, n, keylen;
87         urlcontent *u;
88         struct wcsession *WCC = WC;
89
90         if (WCC->urlstrings == NULL)
91                 WCC->urlstrings = NewHash();
92         eptr = buf + sizeof (buf);
93         up = url;
94         /** locate the = sign */
95         n = safestrncpy(buf, up, sizeof buf);
96         if (n < 0) /** hm, we exceeded the buffer... hmmm what todo now? */
97                 n = -n;
98         up = buf;
99 //      while ((up < eptr) && (*up != '?') && (*up != '&'))
100 //              up++;
101         while (!IsEmptyStr(up)) {
102                 aptr = up;
103                 while ((aptr < eptr) && (*aptr != '\0') && (*aptr != '='))
104                         aptr++;
105                 if (*aptr != '=')
106                         return;
107                 *aptr = '\0';
108                 aptr++;
109                 bptr = aptr;
110                 while ((bptr < eptr) && (*bptr != '\0')
111                       && (*bptr != '&') && (*bptr != '?') && (*bptr != ' ')) {
112                         bptr++;
113                 }
114                 *bptr = '\0';
115                 u = (urlcontent *) malloc(sizeof(urlcontent));
116
117                 keylen = safestrncpy(u->url_key, up, sizeof u->url_key);
118                 if (keylen < 0){
119                         lprintf(1, "URLkey to long! [%s]", up);
120                         continue;
121                 }
122
123                 Put(WCC->urlstrings, u->url_key, keylen, u, free_url);
124                 len = bptr - aptr;
125                 u->url_data = malloc(len + 2);
126                 safestrncpy(u->url_data, aptr, len + 2);
127                 u->url_data_size = unescape_input(u->url_data);
128                 u->url_data[u->url_data_size] = '\0';
129                 up = bptr;
130                 ++up;
131 /*
132                 lprintf(9, "%s = [%ld]  %s\n", u->url_key, u->url_data_size, u->url_data); 
133 */
134         }
135 }
136
137 /**
138  * \brief free urlstring memory
139  */
140 void free_urls(void)
141 {
142         DeleteHash(&WC->urlstrings);
143 }
144
145 /**
146  * \brief Diagnostic function to display the contents of all variables
147  */
148
149 void dump_vars(void)
150 {
151         struct wcsession *WCC = WC;
152         urlcontent *u;
153         void *U;
154         long HKLen;
155         char *HKey;
156         HashPos *Cursor;
157         
158         Cursor = GetNewHashPos ();
159         while (GetNextHashPos(WCC->urlstrings, Cursor, &HKLen, &HKey, &U)) {
160                 u = (urlcontent*) U;
161                 wprintf("%38s = %s\n", u->url_key, u->url_data);
162         }
163 }
164
165 /**
166  * \brief Return the value of a variable supplied to the current web page (from the url or a form)
167  * \param key The name of the variable we want
168  */
169
170 const char *XBstr(char *key, size_t keylen, size_t *len)
171 {
172         void *U;
173
174         if ((WC->urlstrings != NULL) && 
175             GetHash(WC->urlstrings, key, keylen, &U)) {
176                 *len = ((urlcontent *)U)->url_data_size;
177                 return ((urlcontent *)U)->url_data;
178         }
179         else {
180                 *len = 0;
181                 return ("");
182         }
183 }
184
185 const char *XBSTR(char *key, size_t *len)
186 {
187         void *U;
188
189         if ((WC->urlstrings != NULL) &&
190             GetHash(WC->urlstrings, key, strlen (key), &U)){
191                 *len = ((urlcontent *)U)->url_data_size;
192                 return ((urlcontent *)U)->url_data;
193         }
194         else {
195                 *len = 0;
196                 return ("");
197         }
198 }
199
200
201 const char *BSTR(char *key)
202 {
203         void *U;
204
205         if ((WC->urlstrings != NULL) &&
206             GetHash(WC->urlstrings, key, strlen (key), &U))
207                 return ((urlcontent *)U)->url_data;
208         else    
209                 return ("");
210 }
211
212 const char *Bstr(char *key, size_t keylen)
213 {
214         void *U;
215
216         if ((WC->urlstrings != NULL) && 
217             GetHash(WC->urlstrings, key, keylen, &U))
218                 return ((urlcontent *)U)->url_data;
219         else    
220                 return ("");
221 }
222
223 long LBstr(char *key, size_t keylen)
224 {
225         void *U;
226
227         if ((WC->urlstrings != NULL) && 
228             GetHash(WC->urlstrings, key, keylen, &U))
229                 return atol(((urlcontent *)U)->url_data);
230         else    
231                 return (0);
232 }
233
234 long LBSTR(char *key)
235 {
236         void *U;
237
238         if ((WC->urlstrings != NULL) && 
239             GetHash(WC->urlstrings, key, strlen(key), &U))
240                 return atol(((urlcontent *)U)->url_data);
241         else    
242                 return (0);
243 }
244
245 int IBstr(char *key, size_t keylen)
246 {
247         void *U;
248
249         if ((WC->urlstrings != NULL) && 
250             GetHash(WC->urlstrings, key, keylen, &U))
251                 return atoi(((urlcontent *)U)->url_data);
252         else    
253                 return (0);
254 }
255
256 int IBSTR(char *key)
257 {
258         void *U;
259
260         if ((WC->urlstrings != NULL) && 
261             GetHash(WC->urlstrings, key, strlen(key), &U))
262                 return atoi(((urlcontent *)U)->url_data);
263         else    
264                 return (0);
265 }
266
267 int HaveBstr(char *key, size_t keylen)
268 {
269         void *U;
270
271         if ((WC->urlstrings != NULL) && 
272             GetHash(WC->urlstrings, key, keylen, &U))
273                 return ((urlcontent *)U)->url_data_size != 0;
274         else    
275                 return (0);
276 }
277
278 int HAVEBSTR(char *key)
279 {
280         void *U;
281
282         if ((WC->urlstrings != NULL) && 
283             GetHash(WC->urlstrings, key, strlen(key), &U))
284                 return ((urlcontent *)U)->url_data_size != 0;
285         else    
286                 return (0);
287 }
288
289
290 int YesBstr(char *key, size_t keylen)
291 {
292         void *U;
293
294         if ((WC->urlstrings != NULL) && 
295             GetHash(WC->urlstrings, key, keylen, &U))
296                 return strcmp( ((urlcontent *)U)->url_data, "yes") == 0;
297         else    
298                 return (0);
299 }
300
301 int YESBSTR(char *key)
302 {
303         void *U;
304
305         if ((WC->urlstrings != NULL) && 
306             GetHash(WC->urlstrings, key, strlen(key), &U))
307                 return strcmp( ((urlcontent *)U)->url_data, "yes") == 0;
308         else    
309                 return (0);
310 }
311
312 /**
313  * \brief web-printing funcion. uses our vsnprintf wrapper
314  * \param format printf format string 
315  * \param ... the varargs to put into formatstring
316  */
317 void wprintf(const char *format,...)
318 {
319         va_list arg_ptr;
320         char wbuf[4096];
321
322         va_start(arg_ptr, format);
323         vsnprintf(wbuf, sizeof wbuf, format, arg_ptr);
324         va_end(arg_ptr);
325
326         client_write(wbuf, strlen(wbuf));
327 }
328
329
330 /**
331  * \brief wrap up an HTTP session, closes tags, etc.
332  * \todo multiline params?
333  * \param print_standard_html_footer should be set to 0 to transmit only, 1 to
334  * append the main menu and closing tags, or 2 to
335  * append the closing tags only.
336  */
337 void wDumpContent(int print_standard_html_footer)
338 {
339         if (print_standard_html_footer) {
340                 wprintf("</div>\n");    /* end of "text" div */
341                 do_template("trailing");
342         }
343
344         /* If we've been saving it all up for one big output burst,
345          * go ahead and do that now.
346          */
347         end_burst();
348 }
349
350
351 /**
352  * \brief Copy a string, escaping characters which have meaning in HTML.  
353  * \param target target buffer
354  * \param strbuf source buffer
355  * \param nbsp If nonzero, spaces are converted to non-breaking spaces.
356  * \param nolinebreaks if set, linebreaks are removed from the string.
357  */
358 long stresc(char *target, long tSize, char *strbuf, int nbsp, int nolinebreaks)
359 {
360         char *aptr, *bptr, *eptr;
361
362         *target = '\0';
363         aptr = strbuf;
364         bptr = target;
365         eptr = target + tSize - 6; // our biggest unit to put in... 
366
367         while ((bptr < eptr) && !IsEmptyStr(aptr) ){
368                 if (*aptr == '<') {
369                         memcpy(bptr, "&lt;", 4);
370                         bptr += 4;
371                 }
372                 else if (*aptr == '>') {
373                         memcpy(bptr, "&gt;", 4);
374                         bptr += 4;
375                 }
376                 else if (*aptr == '&') {
377                         memcpy(bptr, "&amp;", 5);
378                         bptr += 5;
379                 }
380                 else if (*aptr == '\"') {
381                         memcpy(bptr, "&quot;", 6);
382                         bptr += 6;
383                 }
384                 else if (*aptr == '\'') {
385                         memcpy(bptr, "&#39;", 5);
386                         bptr += 5;
387                 }
388                 else if (*aptr == LB) {
389                         *bptr = '<';
390                         bptr ++;
391                 }
392                 else if (*aptr == RB) {
393                         *bptr = '>';
394                         bptr ++;
395                 }
396                 else if (*aptr == QU) {
397                         *bptr ='"';
398                         bptr ++;
399                 }
400                 else if ((*aptr == 32) && (nbsp == 1)) {
401                         memcpy(bptr, "&nbsp;", 6);
402                         bptr += 6;
403                 }
404                 else if ((*aptr == '\n') && (nolinebreaks)) {
405                         *bptr='\0';     /* nothing */
406                 }
407                 else if ((*aptr == '\r') && (nolinebreaks)) {
408                         *bptr='\0';     /* nothing */
409                 }
410                 else{
411                         *bptr = *aptr;
412                         bptr++;
413                 }
414                 aptr ++;
415         }
416         *bptr = '\0';
417         if ((bptr = eptr - 1 ) && !IsEmptyStr(aptr) )
418                 return -1;
419         return (bptr - target);
420 }
421
422 /**
423  * \brief WHAT???
424  * \param strbuf what???
425  * \param nbsp If nonzero, spaces are converted to non-breaking spaces.
426  * \param nolinebreaks if set, linebreaks are removed from the string.
427  */ 
428 void escputs1(char *strbuf, int nbsp, int nolinebreaks)
429 {
430         char *buf;
431         long Siz;
432
433         if (strbuf == NULL) return;
434         Siz = (3 * strlen(strbuf)) + SIZ ;
435         buf = malloc(Siz);
436         stresc(buf, Siz, strbuf, nbsp, nolinebreaks);
437         wprintf("%s", buf);
438         free(buf);
439 }
440
441 /** 
442  * \brief static wrapper for ecsputs1
443  * \param strbuf buffer to print escaped to client
444  */
445 void escputs(char *strbuf)
446 {
447         escputs1(strbuf, 0, 0);
448 }
449
450
451 /**
452  * \brief urlescape buffer and print it to the client
453  * \param strbuf buffer to urlescape
454  */
455 void urlescputs(char *strbuf)
456 {
457         char outbuf[SIZ];
458         
459         urlesc(outbuf, SIZ, strbuf);
460         wprintf("%s", outbuf);
461 }
462
463
464 /**
465  * \brief Copy a string, escaping characters for JavaScript strings.
466  * \param target output string
467  * \param strbuf input string
468  */
469 void jsesc(char *target, size_t tlen, char *strbuf)
470 {
471         int len;
472         char *tend;
473         char *send;
474         char *tptr;
475         char *sptr;
476
477         target[0]='\0';
478         len = strlen (strbuf);
479         send = strbuf + len;
480         tend = target + tlen;
481         sptr = strbuf;
482         tptr = target;
483         
484         while (!IsEmptyStr(sptr) && 
485                (sptr < send) &&
486                (tptr < tend)) {
487                
488                 if (*sptr == '<')
489                         *tptr = '[';
490                 else if (*sptr == '>')
491                         *tptr = ']';
492                 else if (*sptr == '\'') {
493                         if (tend - tptr < 3)
494                                 return;
495                         *(tptr++) = '\\';
496                         *tptr = '\'';
497                 }
498                 else if (*sptr == '"') {
499                         if (tend - tptr < 8)
500                                 return;
501                         *(tptr++) = '&';
502                         *(tptr++) = 'q';
503                         *(tptr++) = 'u';
504                         *(tptr++) = 'o';
505                         *(tptr++) = 't';
506                         *tptr = ';';
507                 }
508                 else if (*sptr == '&') {
509                         if (tend - tptr < 7)
510                                 return;
511                         *(tptr++) = '&';
512                         *(tptr++) = 'a';
513                         *(tptr++) = 'm';
514                         *(tptr++) = 'p';
515                         *tptr = ';';
516                 } else {
517                         *tptr = *sptr;
518                 }
519                 tptr++; sptr++;
520         }
521         *tptr = '\0';
522 }
523
524 /**
525  * \brief escape and print java script
526  * \param strbuf the js code
527  */
528 void jsescputs(char *strbuf)
529 {
530         char outbuf[SIZ];
531         
532         jsesc(outbuf, SIZ, strbuf);
533         wprintf("%s", outbuf);
534 }
535
536 /**
537  * \brief Copy a string, escaping characters for message text hold
538  * \param target target buffer
539  * \param strbuf source buffer
540  */
541 void msgesc(char *target, size_t tlen, char *strbuf)
542 {
543         int len;
544         char *tend;
545         char *send;
546         char *tptr;
547         char *sptr;
548
549         target[0]='\0';
550         len = strlen (strbuf);
551         send = strbuf + len;
552         tend = target + tlen;
553         sptr = strbuf;
554         tptr = target;
555
556         while (!IsEmptyStr(sptr) && 
557                (sptr < send) &&
558                (tptr < tend)) {
559                
560                 if (*sptr == '\n')
561                         *tptr = ' ';
562                 else if (*sptr == '\r')
563                         *tptr = ' ';
564                 else if (*sptr == '\'') {
565                         if (tend - tptr < 8)
566                                 return;
567                         *(tptr++) = '&';
568                         *(tptr++) = '#';
569                         *(tptr++) = '3';
570                         *(tptr++) = '9';
571                         *tptr = ';';
572                 } else {
573                         *tptr = *sptr;
574                 }
575                 tptr++; sptr++;
576         }
577         *tptr = '\0';
578 }
579
580 /**
581  * \brief print a string to the client after cleaning it with msgesc() and stresc()
582  * \param strbuf string to be printed
583  */
584 void msgescputs1( char *strbuf)
585 {
586         char *outbuf;
587         char *outbuf2;
588         int buflen;
589
590         if (strbuf == NULL) return;
591         buflen = 3 * strlen(strbuf) + SIZ;
592         outbuf = malloc( buflen);
593         outbuf2 = malloc( buflen);
594         msgesc(outbuf, buflen, strbuf);
595         stresc(outbuf2, buflen, outbuf, 0, 0);
596         wprintf("%s", outbuf2);
597         free(outbuf);
598         free(outbuf2);
599 }
600
601 /**
602  * \brief print a string to the client after cleaning it with msgesc()
603  * \param strbuf string to be printed
604  */
605 void msgescputs(char *strbuf) {
606         char *outbuf;
607         size_t len;
608
609         if (strbuf == NULL) return;
610         len =  (3 * strlen(strbuf)) + SIZ;
611         outbuf = malloc(len);
612         msgesc(outbuf, len, strbuf);
613         wprintf("%s", outbuf);
614         free(outbuf);
615 }
616
617
618
619
620 /**
621  * \brief Output all that important stuff that the browser will want to see
622  */
623 void output_headers(    int do_httpheaders,     /**< 1 = output HTTP headers                          */
624                         int do_htmlhead,        /**< 1 = output HTML <head> section and <body> opener */
625
626                         int do_room_banner,     /**< 0=no, 1=yes,                                     
627                                                                  * 2 = I'm going to embed my own, so don't open the 
628                                                                  *     <div id="content"> either.                   
629                                                                  */
630
631                         int unset_cookies,      /**< 1 = session is terminating, so unset the cookies */
632                         int suppress_check,     /**< 1 = suppress check for instant messages          */
633                         int cache               /**< 1 = allow browser to cache this page             */
634 ) {
635         char cookie[1024];
636         char httpnow[128];
637
638         wprintf("HTTP/1.1 200 OK\n");
639         http_datestring(httpnow, sizeof httpnow, time(NULL));
640
641         if (do_httpheaders) {
642                 wprintf("Content-type: text/html; charset=utf-8\r\n"
643                         "Server: %s / %s\n"
644                         "Connection: close\r\n",
645                         PACKAGE_STRING, serv_info.serv_software
646                 );
647         }
648
649         if (cache) {
650                 wprintf("Pragma: public\r\n"
651                         "Cache-Control: max-age=3600, must-revalidate\r\n"
652                         "Last-modified: %s\r\n",
653                         httpnow
654                 );
655         }
656         else {
657                 wprintf("Pragma: no-cache\r\n"
658                         "Cache-Control: no-store\r\n"
659                         "Expires: -1\r\n"
660                 );
661         }
662
663         stuff_to_cookie(cookie, 1024, WC->wc_session, WC->wc_username,
664                         WC->wc_password, WC->wc_roomname);
665
666         if (unset_cookies) {
667                 wprintf("Set-cookie: webcit=%s; path=/\r\n", unset);
668         } else {
669                 wprintf("Set-cookie: webcit=%s; path=/\r\n", cookie);
670                 if (server_cookie != NULL) {
671                         wprintf("%s\n", server_cookie);
672                 }
673         }
674
675         if (do_htmlhead) {
676                 begin_burst();
677                 if (!access("static.local/webcit.css", R_OK)) {
678                         svprintf("CSSLOCAL", WCS_STRING,
679                            "<link href=\"static.local/webcit.css\" rel=\"stylesheet\" type=\"text/css\">"
680                         );
681                 }
682                 do_template("head");
683         }
684
685         /** ICONBAR */
686         if (do_htmlhead) {
687
688
689                 /** check for ImportantMessages (these display in a div overlaying the main screen) */
690                 if (!IsEmptyStr(WC->ImportantMessage)) {
691                         wprintf("<div id=\"important_message\">\n"
692                                 "<span class=\"imsg\">");
693                         escputs(WC->ImportantMessage);
694                         wprintf("</span><br />\n"
695                                 "</div>\n"
696                                 "<script type=\"text/javascript\">\n"
697                                 "        setTimeout('hide_imsg_popup()', 5000); \n"
698                                 "</script>\n");
699                         WC->ImportantMessage[0] = 0;
700                 }
701
702                 if ( (WC->logged_in) && (!unset_cookies) ) {
703                         wprintf("<div id=\"iconbar\">");
704                         do_selected_iconbar();
705                         /** check for instant messages (these display in a new window) */
706                         page_popup();
707                         wprintf("</div>");
708                 }
709
710                 if (do_room_banner == 1) {
711                         wprintf("<div id=\"banner\">\n");
712                         embed_room_banner(NULL, navbar_default);
713                         wprintf("</div>\n");
714                 }
715         }
716
717         if (do_room_banner == 1) {
718                 wprintf("<div id=\"content\">\n");
719         }
720 }
721
722
723 /**
724  * \brief Generic function to do an HTTP redirect.  Easy and fun.
725  * \param whichpage target url to 302 to
726  */
727 void http_redirect(char *whichpage) {
728         wprintf("HTTP/1.1 302 Moved Temporarily\n");
729         wprintf("Location: %s\r\n", whichpage);
730         wprintf("URI: %s\r\n", whichpage);
731         wprintf("Content-type: text/html; charset=utf-8\r\n\r\n");
732         wprintf("<html><body>");
733         wprintf("Go <a href=\"%s\">here</A>.", whichpage);
734         wprintf("</body></html>\n");
735 }
736
737
738
739 /**
740  * \brief Output a piece of content to the web browser
741  */
742 void http_transmit_thing(char *thing, size_t length, const char *content_type,
743                          int is_static) {
744
745         output_headers(0, 0, 0, 0, 0, is_static);
746
747         wprintf("Content-type: %s\r\n"
748                 "Server: %s\r\n"
749                 "Connection: close\r\n",
750                 content_type,
751                 PACKAGE_STRING);
752
753 #ifdef HAVE_ZLIB
754         /** If we can send the data out compressed, please do so. */
755         if (WC->gzip_ok) {
756                 char *compressed_data = NULL;
757                 size_t compressed_len;
758
759                 compressed_len =  ((length * 101) / 100) + 100;
760                 compressed_data = malloc(compressed_len);
761
762                 if (compress_gzip((Bytef *) compressed_data,
763                                   &compressed_len,
764                                   (Bytef *) thing,
765                                   (uLongf) length, Z_BEST_SPEED) == Z_OK) {
766                         wprintf("Content-encoding: gzip\r\n"
767                                 "Content-length: %ld\r\n"
768                                 "\r\n",
769                                 (long) compressed_len
770                         );
771                         client_write(compressed_data, (size_t)compressed_len);
772                         free(compressed_data);
773                         return;
774                 }
775         }
776 #endif
777
778         /** No compression ... just send it out as-is */
779         wprintf("Content-length: %ld\r\n"
780                 "\r\n",
781                 (long) length
782         );
783         client_write(thing, (size_t)length);
784 }
785
786 /**
787  * \brief print menu box like used in the floor view or admin interface.
788  * This function takes pair of strings as va_args, 
789  * \param Title Title string of the box
790  * \param Class CSS Class for the box
791  * \param nLines How many string pairs should we print? (URL, UrlText)
792  * \param ... Pairs of URL Strings and their Names
793  */
794 void print_menu_box(char* Title, char *Class, int nLines, ...)
795 {
796         va_list arg_list;
797         long i;
798         
799         svprintf("BOXTITLE", WCS_STRING, Title);
800         do_template("beginbox");
801         
802         wprintf("<ul class=\"%s\">", Class);
803         
804         va_start(arg_list, nLines);
805         for (i = 0; i < nLines; ++i)
806         { 
807                 wprintf("<li><a href=\"%s\">", va_arg(arg_list, char *));
808                 wprintf((char *) va_arg(arg_list, char *));
809                 wprintf("</a></li>\n");
810         }
811         va_end (arg_list);
812         
813         wprintf("</a></li>\n");
814         
815         wprintf("</ul>");
816         
817         do_template("endbox");
818 }
819
820
821 /**
822  * \brief dump out static pages from disk
823  * \param what the file urs to print
824  */
825 void output_static(char *what)
826 {
827         FILE *fp;
828         struct stat statbuf;
829         off_t bytes;
830         off_t count = 0;
831         size_t res;
832         char *bigbuffer;
833         const char *content_type;
834         int len;
835
836         fp = fopen(what, "rb");
837         if (fp == NULL) {
838                 lprintf(9, "output_static('%s')  -- NOT FOUND --\n", what);
839                 wprintf("HTTP/1.1 404 %s\r\n", strerror(errno));
840                 wprintf("Content-Type: text/plain\r\n");
841                 wprintf("\r\n");
842                 wprintf("Cannot open %s: %s\r\n", what, strerror(errno));
843         } else {
844                 len = strlen (what);
845                 content_type = GuessMimeByFilename(what, len);
846
847                 if (fstat(fileno(fp), &statbuf) == -1) {
848                         lprintf(9, "output_static('%s')  -- FSTAT FAILED --\n", what);
849                         wprintf("HTTP/1.1 404 %s\r\n", strerror(errno));
850                         wprintf("Content-Type: text/plain\r\n");
851                         wprintf("\r\n");
852                         wprintf("Cannot fstat %s: %s\n", what, strerror(errno));
853                         return;
854                 }
855
856                 count = 0;
857                 bytes = statbuf.st_size;
858                 if ((bigbuffer = malloc(bytes + 2)) == NULL) {
859                         lprintf(9, "output_static('%s')  -- MALLOC FAILED (%s) --\n", what, strerror(errno));
860                         wprintf("HTTP/1.1 500 internal server error\r\n");
861                         wprintf("Content-Type: text/plain\r\n");
862                         wprintf("\r\n");
863                         return;
864                 }
865                 while (count < bytes) {
866                         if ((res = fread(bigbuffer + count, 1, bytes - count, fp)) == 0) {
867                                 lprintf(9, "output_static('%s')  -- FREAD FAILED (%s) %zu bytes of %zu --\n", what, strerror(errno), bytes - count, bytes);
868                                 wprintf("HTTP/1.1 500 internal server error \r\n");
869                                 wprintf("Content-Type: text/plain\r\n");
870                                 wprintf("\r\n");
871                                 return;
872                         }
873                         count += res;
874                 }
875
876                 fclose(fp);
877
878                 lprintf(9, "output_static('%s')  %s\n", what, content_type);
879                 http_transmit_thing(bigbuffer, (size_t)bytes, content_type, 1);
880                 free(bigbuffer);
881         }
882         if (yesbstr("force_close_session")) {
883                 end_webcit_session();
884         }
885 }
886
887 /**
888  * \brief When the browser requests an image file from the Citadel server,
889  * this function is called to transmit it.
890  */
891 void output_image()
892 {
893         char buf[SIZ];
894         char *xferbuf = NULL;
895         off_t bytes;
896         const char *MimeType;
897
898         serv_printf("OIMG %s|%s", bstr("name"), bstr("parm"));
899         serv_getln(buf, sizeof buf);
900         if (buf[0] == '2') {
901                 bytes = extract_long(&buf[4], 0);
902                 xferbuf = malloc(bytes + 2);
903
904                 /** Read it from the server */
905                 read_server_binary(xferbuf, bytes);
906                 serv_puts("CLOS");
907                 serv_getln(buf, sizeof buf);
908
909                 MimeType = GuessMimeType (xferbuf, bytes);
910                 /** Write it to the browser */
911                 if (!IsEmptyStr(MimeType))
912                 {
913                         http_transmit_thing(xferbuf, 
914                                             (size_t)bytes, 
915                                             MimeType, 
916                                             0);
917                         free(xferbuf);
918                         return;
919                 }
920                 /* hm... unknown mimetype? fallback to blank gif */
921                 free(xferbuf);
922         } 
923
924         
925         /**
926          * Instead of an ugly 404, send a 1x1 transparent GIF
927          * when there's no such image on the server.
928          */
929         char blank_gif[SIZ];
930         snprintf (blank_gif, SIZ, "%s%s", static_dirs[0], "/blank.gif");
931         output_static(blank_gif);
932 }
933
934 /**
935  * \brief Generic function to output an arbitrary MIME part from an arbitrary
936  *        message number on the server.
937  *
938  * \param msgnum                Number of the item on the citadel server
939  * \param partnum               The MIME part to be output
940  * \param force_download        Nonzero to force set the Content-Type: header
941  *                              to "application/octet-stream"
942  */
943 void mimepart(char *msgnum, char *partnum, int force_download)
944 {
945         char buf[256];
946         off_t bytes;
947         char content_type[256];
948         char *content = NULL;
949         
950         serv_printf("OPNA %s|%s", msgnum, partnum);
951         serv_getln(buf, sizeof buf);
952         if (buf[0] == '2') {
953                 bytes = extract_long(&buf[4], 0);
954                 content = malloc(bytes + 2);
955                 if (force_download) {
956                         strcpy(content_type, "application/octet-stream");
957                 }
958                 else {
959                         extract_token(content_type, &buf[4], 3, '|', sizeof content_type);
960                 }
961                 output_headers(0, 0, 0, 0, 0, 0);
962                 read_server_binary(content, bytes);
963                 serv_puts("CLOS");
964                 serv_getln(buf, sizeof buf);
965                 http_transmit_thing(content, bytes, content_type, 0);
966                 free(content);
967         } else {
968                 wprintf("HTTP/1.1 404 %s\n", &buf[4]);
969                 output_headers(0, 0, 0, 0, 0, 0);
970                 wprintf("Content-Type: text/plain\r\n");
971                 wprintf("\r\n");
972                 wprintf(_("An error occurred while retrieving this part: %s\n"), &buf[4]);
973         }
974
975 }
976
977
978 /**
979  * \brief Read any MIME part of a message, from the server, into memory.
980  * \param msgnum number of the message on the citadel server
981  * \param partnum the MIME part to be loaded
982  */
983 char *load_mimepart(long msgnum, char *partnum)
984 {
985         char buf[SIZ];
986         off_t bytes;
987         char content_type[SIZ];
988         char *content;
989         
990         serv_printf("DLAT %ld|%s", msgnum, partnum);
991         serv_getln(buf, sizeof buf);
992         if (buf[0] == '6') {
993                 bytes = extract_long(&buf[4], 0);
994                 extract_token(content_type, &buf[4], 3, '|', sizeof content_type);
995
996                 content = malloc(bytes + 2);
997                 serv_read(content, bytes);
998
999                 content[bytes] = 0;     /* null terminate for good measure */
1000                 return(content);
1001         }
1002         else {
1003                 return(NULL);
1004         }
1005
1006 }
1007
1008
1009 /**
1010  * \brief Convenience functions to display a page containing only a string
1011  * \param titlebarcolor color of the titlebar of the frame
1012  * \param titlebarmsg text to display in the title bar
1013  * \param messagetext body of the box
1014  */
1015 void convenience_page(char *titlebarcolor, char *titlebarmsg, char *messagetext)
1016 {
1017         wprintf("HTTP/1.1 200 OK\n");
1018         output_headers(1, 1, 2, 0, 0, 0);
1019         wprintf("<div id=\"banner\">\n");
1020         wprintf("<table width=100%% border=0 bgcolor=\"#%s\"><tr><td>", titlebarcolor);
1021         wprintf("<span class=\"titlebar\">%s</span>\n", titlebarmsg);
1022         wprintf("</td></tr></table>\n");
1023         wprintf("</div>\n<div id=\"content\">\n");
1024         escputs(messagetext);
1025
1026         wprintf("<hr />\n");
1027         wDumpContent(1);
1028 }
1029
1030
1031 /**
1032  * \brief Display a blank page.
1033  */
1034 void blank_page(void) {
1035         output_headers(1, 1, 0, 0, 0, 0);
1036         wDumpContent(2);
1037 }
1038
1039
1040 /**
1041  * \brief A template has been requested
1042  */
1043 void url_do_template(void) {
1044         do_template(bstr("template"));
1045 }
1046
1047
1048
1049 /**
1050  * \brief Offer to make any page the user's "start page."
1051  */
1052 void offer_start_page(void) {
1053         wprintf("<a href=\"change_start_page?startpage=");
1054         urlescputs(WC->this_page);
1055         wprintf("\">");
1056         wprintf(_("Make this my start page"));
1057         wprintf("</a>");
1058 #ifdef TECH_PREVIEW
1059         wprintf("<br/><a href=\"rss?room=");
1060         urlescputs(WC->wc_roomname);
1061         wprintf("\" title=\"RSS 2.0 feed for ");
1062         escputs(WC->wc_roomname);
1063         wprintf("\"><img alt=\"RSS\" border=\"0\" src=\"static/xml_button.gif\"/></a>\n");
1064 #endif
1065 }
1066
1067
1068 /**
1069  * \brief Change the user's start page
1070  */
1071 void change_start_page(void) {
1072
1073         if (bstr("startpage") == NULL) {
1074                 safestrncpy(WC->ImportantMessage,
1075                         _("You no longer have a start page selected."),
1076                         sizeof WC->ImportantMessage);
1077                 display_main_menu();
1078                 return;
1079         }
1080
1081         set_preference("startpage", bstr("startpage"), 1);
1082
1083         output_headers(1, 1, 0, 0, 0, 0);
1084         do_template("newstartpage");
1085         wDumpContent(1);
1086 }
1087
1088
1089
1090 /**
1091  * \brief convenience function to indicate success
1092  * \param successmessage the mesage itself
1093  */
1094 void display_success(char *successmessage)
1095 {
1096         convenience_page("007700", "OK", successmessage);
1097 }
1098
1099
1100 /**
1101  * \brief Authorization required page 
1102  * This is probably temporary and should be revisited 
1103  * \param message message to put in header
1104 */
1105 void authorization_required(const char *message)
1106 {
1107         wprintf("HTTP/1.1 401 Authorization Required\r\n");
1108         wprintf("WWW-Authenticate: Basic realm=\"\"\r\n", serv_info.serv_humannode);
1109         wprintf("Content-Type: text/html\r\n\r\n");
1110         wprintf("<h1>");
1111         wprintf(_("Authorization Required"));
1112         wprintf("</h1>\r\n");
1113         wprintf(_("The resource you requested requires a valid username and password. "
1114                 "You could not be logged in: %s\n"), message);
1115         wDumpContent(0);
1116 }
1117
1118 /**
1119  * \brief This function is called by the MIME parser to handle data uploaded by
1120  *        the browser.  Form data, uploaded files, and the data from HTTP PUT
1121  *        operations (such as those found in GroupDAV) all arrive this way.
1122  *
1123  * \param name Name of the item being uploaded
1124  * \param filename Filename of the item being uploaded
1125  * \param partnum MIME part identifier (not needed)
1126  * \param disp MIME content disposition (not needed)
1127  * \param content The actual data
1128  * \param cbtype MIME content-type
1129  * \param cbcharset Character set
1130  * \param length Content length
1131  * \param encoding MIME encoding type (not needed)
1132  * \param userdata Not used here
1133  */
1134 void upload_handler(char *name, char *filename, char *partnum, char *disp,
1135                         void *content, char *cbtype, char *cbcharset,
1136                         size_t length, char *encoding, void *userdata)
1137 {
1138         urlcontent *u;
1139 /*
1140         lprintf(9, "upload_handler() name=%s, type=%s, len=%d\n", name, cbtype, length);
1141 */
1142         if (WC->urlstrings == NULL)
1143                 WC->urlstrings = NewHash();
1144
1145         /* Form fields */
1146         if ( (length > 0) && (IsEmptyStr(cbtype)) ) {
1147                 u = (urlcontent *) malloc(sizeof(urlcontent));
1148                 
1149                 safestrncpy(u->url_key, name, sizeof(u->url_key));
1150                 u->url_data = malloc(length + 1);
1151                 u->url_data_size = length;
1152                 memcpy(u->url_data, content, length);
1153                 u->url_data[length] = 0;
1154                 Put(WC->urlstrings, u->url_key, strlen(u->url_key), u, free_url);
1155
1156 /*              lprintf(9, "Key: <%s> len: [%ld] Data: <%s>\n", u->url_key, u->url_data_size, u->url_data);*/
1157         }
1158
1159         /** Uploaded files */
1160         if ( (length > 0) && (!IsEmptyStr(cbtype)) ) {
1161                 WC->upload = malloc(length);
1162                 if (WC->upload != NULL) {
1163                         WC->upload_length = length;
1164                         safestrncpy(WC->upload_filename, filename,
1165                                         sizeof(WC->upload_filename));
1166                         safestrncpy(WC->upload_content_type, cbtype,
1167                                         sizeof(WC->upload_content_type));
1168                         memcpy(WC->upload, content, length);
1169                 }
1170                 else {
1171                         lprintf(3, "malloc() failed: %s\n", strerror(errno));
1172                 }
1173         }
1174
1175 }
1176
1177 /**
1178  * \brief Convenience functions to wrap around asynchronous ajax responses
1179  */
1180 void begin_ajax_response(void) {
1181         output_headers(0, 0, 0, 0, 0, 0);
1182
1183         wprintf("Content-type: text/html; charset=UTF-8\r\n"
1184                 "Server: %s\r\n"
1185                 "Connection: close\r\n"
1186                 "Pragma: no-cache\r\n"
1187                 "Cache-Control: no-cache\r\n"
1188                 "Expires: -1\r\n"
1189                 ,
1190                 PACKAGE_STRING);
1191         begin_burst();
1192 }
1193
1194 /**
1195  * \brief print ajax response footer 
1196  */
1197 void end_ajax_response(void) {
1198         wprintf("\r\n");
1199         wDumpContent(0);
1200 }
1201
1202 /**
1203  * \brief Wraps a Citadel server command in an AJAX transaction.
1204  */
1205 void ajax_servcmd(void)
1206 {
1207         char buf[1024];
1208         char gcontent[1024];
1209         char *junk;
1210         size_t len;
1211
1212         begin_ajax_response();
1213
1214         serv_printf("%s", bstr("g_cmd"));
1215         serv_getln(buf, sizeof buf);
1216         wprintf("%s\n", buf);
1217
1218         if (buf[0] == '8') {
1219                 serv_printf("\n\n000");
1220         }
1221         if ((buf[0] == '1') || (buf[0] == '8')) {
1222                 while (serv_getln(gcontent, sizeof gcontent), strcmp(gcontent, "000")) {
1223                         wprintf("%s\n", gcontent);
1224                 }
1225                 wprintf("000");
1226         }
1227         if (buf[0] == '4') {
1228                 text_to_server(bstr("g_input"));
1229                 serv_puts("000");
1230         }
1231         if (buf[0] == '6') {
1232                 len = atol(&buf[4]);
1233                 junk = malloc(len);
1234                 serv_read(junk, len);
1235                 free(junk);
1236         }
1237         if (buf[0] == '7') {
1238                 len = atol(&buf[4]);
1239                 junk = malloc(len);
1240                 memset(junk, 0, len);
1241                 serv_write(junk, len);
1242                 free(junk);
1243         }
1244
1245         end_ajax_response();
1246         
1247         /**
1248          * This is kind of an ugly hack, but this is the only place it can go.
1249          * If the command was GEXP, then the instant messenger window must be
1250          * running, so reset the "last_pager_check" watchdog timer so
1251          * that page_popup() doesn't try to open it a second time.
1252          */
1253         if (!strncasecmp(bstr("g_cmd"), "GEXP", 4)) {
1254                 WC->last_pager_check = time(NULL);
1255         }
1256 }
1257
1258
1259 /**
1260  * \brief Helper function for the asynchronous check to see if we need
1261  * to open the instant messenger window.
1262  */
1263 void seconds_since_last_gexp(void)
1264 {
1265         char buf[256];
1266
1267         begin_ajax_response();
1268         if ( (time(NULL) - WC->last_pager_check) < 30) {
1269                 wprintf("NO\n");
1270         }
1271         else {
1272                 serv_puts("NOOP");
1273                 serv_getln(buf, sizeof buf);
1274                 if (buf[3] == '*') {
1275                         wprintf("YES");
1276                 }
1277                 else {
1278                         wprintf("NO");
1279                 }
1280         }
1281         end_ajax_response();
1282 }
1283
1284
1285
1286
1287 /**
1288  * \brief Entry point for WebCit transaction
1289  */
1290 void session_loop(struct httprequest *req)
1291 {
1292         char cmd[1024];
1293         char action[1024];
1294         char arg[8][128];
1295         size_t sizes[10];
1296         char *index[10];
1297         char buf[SIZ];
1298         char request_method[128];
1299         char pathname[1024];
1300         int a, b, nBackDots, nEmpty;
1301         int ContentLength = 0;
1302         int BytesRead = 0;
1303         char ContentType[512];
1304         char *content = NULL;
1305         char *content_end = NULL;
1306         struct httprequest *hptr;
1307         char browser_host[256];
1308         char user_agent[256];
1309         int body_start = 0;
1310         int is_static = 0;
1311         int n_static = 0;
1312         int len = 0;
1313         /**
1314          * We stuff these with the values coming from the client cookies,
1315          * so we can use them to reconnect a timed out session if we have to.
1316          */
1317         char c_username[SIZ];
1318         char c_password[SIZ];
1319         char c_roomname[SIZ];
1320         char c_httpauth_string[SIZ];
1321         char c_httpauth_user[SIZ];
1322         char c_httpauth_pass[SIZ];
1323         char cookie[SIZ];
1324
1325         safestrncpy(c_username, "", sizeof c_username);
1326         safestrncpy(c_password, "", sizeof c_password);
1327         safestrncpy(c_roomname, "", sizeof c_roomname);
1328         safestrncpy(c_httpauth_string, "", sizeof c_httpauth_string);
1329         safestrncpy(c_httpauth_user, DEFAULT_HTTPAUTH_USER, sizeof c_httpauth_user);
1330         safestrncpy(c_httpauth_pass, DEFAULT_HTTPAUTH_PASS, sizeof c_httpauth_pass);
1331         strcpy(browser_host, "");
1332
1333         WC->upload_length = 0;
1334         WC->upload = NULL;
1335         WC->vars = NULL;
1336         WC->is_wap = 0;
1337
1338         hptr = req;
1339         if (hptr == NULL) return;
1340
1341         safestrncpy(cmd, hptr->line, sizeof cmd);
1342         hptr = hptr->next;
1343         extract_token(request_method, cmd, 0, ' ', sizeof request_method);
1344         extract_token(pathname, cmd, 1, ' ', sizeof pathname);
1345
1346         /** Figure out the action */
1347         index[0] = action;
1348         sizes[0] = sizeof action;
1349         for (a=1; a<9; a++)
1350         {
1351                 index[a] = arg[a-1];
1352                 sizes[a] = sizeof arg[a-1];
1353         }
1354 ////    index[9] = &foo; todo
1355         nBackDots = 0;
1356         nEmpty = 0;
1357         for ( a = 0; a < 9; ++a)
1358         {
1359                 extract_token(index[a], pathname, a + 1, '/', sizes[a]);
1360                 if (strstr(index[a], "?")) *strstr(index[a], "?") = 0;
1361                 if (strstr(index[a], "&")) *strstr(index[a], "&") = 0;
1362                 if (strstr(index[a], " ")) *strstr(index[a], " ") = 0;
1363                 if ((index[a][0] == '.') && (index[a][1] == '.'))
1364                         nBackDots++;
1365                 if (index[a][0] == '\0')
1366                         nEmpty++;
1367         }
1368
1369         while (hptr != NULL) {
1370                 safestrncpy(buf, hptr->line, sizeof buf);
1371                 /* lprintf(9, "HTTP HEADER: %s\n", buf); */
1372                 hptr = hptr->next;
1373
1374                 if (!strncasecmp(buf, "Cookie: webcit=", 15)) {
1375                         safestrncpy(cookie, &buf[15], sizeof cookie);
1376                         cookie_to_stuff(cookie, NULL,
1377                                         c_username, sizeof c_username,
1378                                         c_password, sizeof c_password,
1379                                         c_roomname, sizeof c_roomname);
1380                 }
1381                 else if (!strncasecmp(buf, "Authorization: Basic ", 21)) {
1382                         CtdlDecodeBase64(c_httpauth_string, &buf[21], strlen(&buf[21]));
1383                         extract_token(c_httpauth_user, c_httpauth_string, 0, ':', sizeof c_httpauth_user);
1384                         extract_token(c_httpauth_pass, c_httpauth_string, 1, ':', sizeof c_httpauth_pass);
1385                 }
1386                 else if (!strncasecmp(buf, "Content-length: ", 16)) {
1387                         ContentLength = atoi(&buf[16]);
1388                 }
1389                 else if (!strncasecmp(buf, "Content-type: ", 14)) {
1390                         safestrncpy(ContentType, &buf[14], sizeof ContentType);
1391                 }
1392                 else if (!strncasecmp(buf, "User-agent: ", 12)) {
1393                         safestrncpy(user_agent, &buf[12], sizeof user_agent);
1394                 }
1395                 else if (!strncasecmp(buf, "X-Forwarded-Host: ", 18)) {
1396                         if (follow_xff) {
1397                                 safestrncpy(WC->http_host, &buf[18], sizeof WC->http_host);
1398                         }
1399                 }
1400                 else if (!strncasecmp(buf, "Host: ", 6)) {
1401                         if (IsEmptyStr(WC->http_host)) {
1402                                 safestrncpy(WC->http_host, &buf[6], sizeof WC->http_host);
1403                         }
1404                 }
1405                 else if (!strncasecmp(buf, "X-Forwarded-For: ", 17)) {
1406                         safestrncpy(browser_host, &buf[17], sizeof browser_host);
1407                         while (num_tokens(browser_host, ',') > 1) {
1408                                 remove_token(browser_host, 0, ',');
1409                         }
1410                         striplt(browser_host);
1411                 }
1412                 /** Only WAP gateways explicitly name this content-type */
1413                 else if (strstr(buf, "text/vnd.wap.wml")) {
1414                         WC->is_wap = 1;
1415                 }
1416         }
1417
1418         if (ContentLength > 0) {
1419                 content = malloc(ContentLength + SIZ);
1420                 memset(content, 0, ContentLength + SIZ);
1421                 snprintf(content,  ContentLength + SIZ, "Content-type: %s\n"
1422                                 "Content-length: %d\n\n",
1423                                 ContentType, ContentLength);
1424                 body_start = strlen(content);
1425
1426                 /** Read the entire input data at once. */
1427                 client_read(WC->http_sock, &content[BytesRead+body_start], ContentLength);
1428
1429                 if (!strncasecmp(ContentType, "application/x-www-form-urlencoded", 33)) {
1430                         addurls(&content[body_start]);
1431                 } else if (!strncasecmp(ContentType, "multipart", 9)) {
1432                         content_end = content + ContentLength + body_start;
1433                         mime_parser(content, content_end, *upload_handler, NULL, NULL, NULL, 0);
1434                 }
1435         } else {
1436                 content = NULL;
1437         }
1438
1439         /** make a note of where we are in case the user wants to save it */
1440         safestrncpy(WC->this_page, cmd, sizeof(WC->this_page));
1441         remove_token(WC->this_page, 2, ' ');
1442         remove_token(WC->this_page, 0, ' ');
1443
1444         /** If there are variables in the URL, we must grab them now */
1445         len = strlen(cmd);
1446         for (a = 0; a < len; ++a) {
1447                 if ((cmd[a] == '?') || (cmd[a] == '&')) {
1448                         for (b = a; b < len; ++b) {
1449                                 if (isspace(cmd[b])){
1450                                         cmd[b] = 0;
1451                                         len = b - 1;
1452                                 }
1453                         }
1454                         addurls(&cmd[a + 1]);
1455                         cmd[a] = 0;
1456                         len = a - 1;
1457                 }
1458         }
1459
1460         /** If it's a "force 404" situation then display the error and bail. */
1461         if (!strcmp(action, "404")) {
1462                 wprintf("HTTP/1.1 404 Not found\r\n");
1463                 wprintf("Content-Type: text/plain\r\n");
1464                 wprintf("\r\n");
1465                 wprintf("Not found\r\n");
1466                 goto SKIP_ALL_THIS_CRAP;
1467         }
1468
1469         /** Static content can be sent without connecting to Citadel. */
1470         is_static = 0;
1471         for (a=0; a<ndirs; ++a) {
1472                 if (!strcasecmp(action, (char*)static_content_dirs[a])) { /* map web to disk location */
1473                         is_static = 1;
1474                         n_static = a;
1475                 }
1476         }
1477         if (is_static) {
1478                 if (nBackDots < 2)
1479                 {
1480                         snprintf(buf, sizeof buf, "%s/%s/%s/%s/%s/%s/%s/%s",
1481                                  static_dirs[n_static], 
1482                                  index[1], index[2], index[3], index[4], index[5], index[6], index[7]);
1483                         for (a=0; a<8; ++a) {
1484                                 if (buf[strlen(buf)-1] == '/') {
1485                                         buf[strlen(buf)-1] = 0;
1486                                 }
1487                         }
1488                         for (a = 0; a < strlen(buf); ++a) {
1489                                 if (isspace(buf[a])) {
1490                                         buf[a] = 0;
1491                                 }
1492                         }
1493                         output_static(buf);
1494                 }
1495                 else 
1496                 {
1497                         lprintf(9, "Suspicious request. Ignoring.");
1498                         wprintf("HTTP/1.1 404 Security check failed\r\n");
1499                         wprintf("Content-Type: text/plain\r\n");
1500                         wprintf("\r\n");
1501                         wprintf("You have sent a malformed or invalid request.\r\n");
1502                 }
1503                 goto SKIP_ALL_THIS_CRAP;        /* Don't try to connect */
1504         }
1505
1506         /* If the client sent a nonce that is incorrect, kill the request. */
1507         if (strlen(bstr("nonce")) > 0) {
1508                 lprintf(9, "Comparing supplied nonce %s to session nonce %ld\n", 
1509                         bstr("nonce"), WC->nonce);
1510                 if (ibstr("nonce") != WC->nonce) {
1511                         lprintf(9, "Ignoring request with mismatched nonce.\n");
1512                         wprintf("HTTP/1.1 404 Security check failed\r\n");
1513                         wprintf("Content-Type: text/plain\r\n");
1514                         wprintf("\r\n");
1515                         wprintf("Security check failed.\r\n");
1516                         goto SKIP_ALL_THIS_CRAP;
1517                 }
1518         }
1519
1520         /**
1521          * If we're not connected to a Citadel server, try to hook up the
1522          * connection now.
1523          */
1524         if (!WC->connected) {
1525                 if (!strcasecmp(ctdlhost, "uds")) {
1526                         /* unix domain socket */
1527                         snprintf(buf, SIZ, "%s/citadel.socket", ctdlport);
1528                         WC->serv_sock = uds_connectsock(buf);
1529                 }
1530                 else {
1531                         /* tcp socket */
1532                         WC->serv_sock = tcp_connectsock(ctdlhost, ctdlport);
1533                 }
1534
1535                 if (WC->serv_sock < 0) {
1536                         do_logout();
1537                         goto SKIP_ALL_THIS_CRAP;
1538                 }
1539                 else {
1540                         WC->connected = 1;
1541                         serv_getln(buf, sizeof buf);    /** get the server welcome message */
1542
1543                         /**
1544                          * From what host is our user connecting?  Go with
1545                          * the host at the other end of the HTTP socket,
1546                          * unless we are following X-Forwarded-For: headers
1547                          * and such a header has already turned up something.
1548                          */
1549                         if ( (!follow_xff) || (strlen(browser_host) == 0) ) {
1550                                 locate_host(browser_host, WC->http_sock);
1551                         }
1552
1553                         get_serv_info(browser_host, user_agent);
1554                         if (serv_info.serv_rev_level < MINIMUM_CIT_VERSION) {
1555                                 wprintf(_("You are connected to a Citadel "
1556                                         "server running Citadel %d.%02d. \n"
1557                                         "In order to run this version of WebCit "
1558                                         "you must also have Citadel %d.%02d or"
1559                                         " newer.\n\n\n"),
1560                                                 serv_info.serv_rev_level / 100,
1561                                                 serv_info.serv_rev_level % 100,
1562                                                 MINIMUM_CIT_VERSION / 100,
1563                                                 MINIMUM_CIT_VERSION % 100
1564                                         );
1565                                 end_webcit_session();
1566                                 goto SKIP_ALL_THIS_CRAP;
1567                         }
1568                 }
1569         }
1570
1571         /**
1572          * Functions which can be performed without logging in
1573          */
1574         if (!strcasecmp(action, "listsub")) {
1575                 do_listsub();
1576                 goto SKIP_ALL_THIS_CRAP;
1577         }
1578         if (!strcasecmp(action, "freebusy")) {
1579                 do_freebusy(cmd);
1580                 goto SKIP_ALL_THIS_CRAP;
1581         }
1582
1583         /**
1584          * If we're not logged in, but we have HTTP Authentication data,
1585          * try logging in to Citadel using that.
1586          */
1587         if ((!WC->logged_in)
1588            && (strlen(c_httpauth_user) > 0)
1589            && (strlen(c_httpauth_pass) > 0)) {
1590                 serv_printf("USER %s", c_httpauth_user);
1591                 serv_getln(buf, sizeof buf);
1592                 if (buf[0] == '3') {
1593                         serv_printf("PASS %s", c_httpauth_pass);
1594                         serv_getln(buf, sizeof buf);
1595                         if (buf[0] == '2') {
1596                                 become_logged_in(c_httpauth_user,
1597                                                 c_httpauth_pass, buf);
1598                                 safestrncpy(WC->httpauth_user, c_httpauth_user, sizeof WC->httpauth_user);
1599                                 safestrncpy(WC->httpauth_pass, c_httpauth_pass, sizeof WC->httpauth_pass);
1600                         } else {
1601                                 /** Should only display when password is wrong */
1602                                 authorization_required(&buf[4]);
1603                                 goto SKIP_ALL_THIS_CRAP;
1604                         }
1605                 }
1606         }
1607
1608         /** This needs to run early */
1609 #ifdef TECH_PREVIEW
1610         if (!strcasecmp(action, "rss")) {
1611                 display_rss(bstr("room"), request_method);
1612                 goto SKIP_ALL_THIS_CRAP;
1613         }
1614 #endif
1615
1616         /** 
1617          * The GroupDAV stuff relies on HTTP authentication instead of
1618          * our session's authentication.
1619          */
1620         if (!strncasecmp(action, "groupdav", 8)) {
1621                 groupdav_main(req, ContentType, /* do GroupDAV methods */
1622                         ContentLength, content+body_start);
1623                 if (!WC->logged_in) {
1624                         WC->killthis = 1;       /* If not logged in, don't */
1625                 }                               /* keep the session active */
1626                 goto SKIP_ALL_THIS_CRAP;
1627         }
1628
1629
1630         /**
1631          * Automatically send requests with any method other than GET or
1632          * POST to the GroupDAV code as well.
1633          */
1634         if ((strcasecmp(request_method, "GET")) && (strcasecmp(request_method, "POST"))) {
1635                 groupdav_main(req, ContentType, /** do GroupDAV methods */
1636                         ContentLength, content+body_start);
1637                 if (!WC->logged_in) {
1638                         WC->killthis = 1;       /** If not logged in, don't */
1639                 }                               /** keep the session active */
1640                 goto SKIP_ALL_THIS_CRAP;
1641         }
1642
1643         /**
1644          * If we're not logged in, but we have username and password cookies
1645          * supplied by the browser, try using them to log in.
1646          */
1647         if ((!WC->logged_in)
1648            && (!IsEmptyStr(c_username))
1649            && (!IsEmptyStr(c_password))) {
1650                 serv_printf("USER %s", c_username);
1651                 serv_getln(buf, sizeof buf);
1652                 if (buf[0] == '3') {
1653                         serv_printf("PASS %s", c_password);
1654                         serv_getln(buf, sizeof buf);
1655                         if (buf[0] == '2') {
1656                                 become_logged_in(c_username, c_password, buf);
1657                         }
1658                 }
1659         }
1660         /**
1661          * If we don't have a current room, but a cookie specifying the
1662          * current room is supplied, make an effort to go there.
1663          */
1664         if ((IsEmptyStr(WC->wc_roomname)) && (!IsEmptyStr(c_roomname))) {
1665                 serv_printf("GOTO %s", c_roomname);
1666                 serv_getln(buf, sizeof buf);
1667                 if (buf[0] == '2') {
1668                         safestrncpy(WC->wc_roomname, c_roomname, sizeof WC->wc_roomname);
1669                 }
1670         }
1671
1672         if (!strcasecmp(action, "image")) {
1673                 output_image();
1674         } else if (!strcasecmp(action, "display_mime_icon")) {
1675                 display_mime_icon();
1676
1677                 /**
1678                  * All functions handled below this point ... make sure we log in
1679                  * before doing anything else!
1680                  */
1681         } else if ((!WC->logged_in) && (!strcasecmp(action, "login"))) {
1682                 do_login();
1683         } else if (!WC->logged_in) {
1684                 display_login(NULL);
1685         }
1686
1687         /**
1688          * Various commands...
1689          */
1690
1691         else if (!strcasecmp(action, "do_welcome")) {
1692                 do_welcome();
1693         } else if (!strcasecmp(action, "blank")) {
1694                 blank_page();
1695         } else if (!strcasecmp(action, "do_template")) {
1696                 url_do_template();
1697         } else if (!strcasecmp(action, "display_aide_menu")) {
1698                 display_aide_menu();
1699         } else if (!strcasecmp(action, "server_shutdown")) {
1700                 display_shutdown();
1701         } else if (!strcasecmp(action, "display_main_menu")) {
1702                 display_main_menu();
1703         } else if (!strcasecmp(action, "who")) {
1704                 who();
1705         } else if (!strcasecmp(action, "sslg")) {
1706                 seconds_since_last_gexp();
1707         } else if (!strcasecmp(action, "who_inner_html")) {
1708                 begin_ajax_response();
1709                 who_inner_div();
1710                 end_ajax_response();
1711         } else if (!strcasecmp(action, "wholist_section")) {
1712                 begin_ajax_response();
1713                 wholist_section();
1714                 end_ajax_response();
1715         } else if (!strcasecmp(action, "new_messages_html")) {
1716                 begin_ajax_response();
1717                 new_messages_section();
1718                 end_ajax_response();
1719         } else if (!strcasecmp(action, "tasks_inner_html")) {
1720                 begin_ajax_response();
1721                 tasks_section();
1722                 end_ajax_response();
1723         } else if (!strcasecmp(action, "calendar_inner_html")) {
1724                 begin_ajax_response();
1725                 calendar_section();
1726                 end_ajax_response();
1727         } else if (!strcasecmp(action, "mini_calendar")) {
1728                 begin_ajax_response();
1729                 ajax_mini_calendar();
1730                 end_ajax_response();
1731         } else if (!strcasecmp(action, "iconbar_ajax_menu")) {
1732                 begin_ajax_response();
1733                 do_iconbar();
1734                 end_ajax_response();
1735         } else if (!strcasecmp(action, "iconbar_ajax_rooms")) {
1736                 begin_ajax_response();
1737                 do_iconbar_roomlist();
1738                 end_ajax_response();
1739         } else if (!strcasecmp(action, "knrooms")) {
1740                 knrooms();
1741         } else if (!strcasecmp(action, "gotonext")) {
1742                 slrp_highest();
1743                 gotonext();
1744         } else if (!strcasecmp(action, "skip")) {
1745                 gotonext();
1746         } else if (!strcasecmp(action, "ungoto")) {
1747                 ungoto();
1748         } else if (!strcasecmp(action, "dotgoto")) {
1749                 if (WC->wc_view != VIEW_MAILBOX) {      /* dotgoto acts like dotskip when we're in a mailbox view */
1750                         slrp_highest();
1751                 }
1752                 smart_goto(bstr("room"));
1753         } else if (!strcasecmp(action, "dotskip")) {
1754                 smart_goto(bstr("room"));
1755         } else if (!strcasecmp(action, "termquit")) {
1756                 do_logout();
1757         } else if (!strcasecmp(action, "readnew")) {
1758                 readloop("readnew");
1759         } else if (!strcasecmp(action, "readold")) {
1760                 readloop("readold");
1761         } else if (!strcasecmp(action, "readfwd")) {
1762                 readloop("readfwd");
1763         } else if (!strcasecmp(action, "headers")) {
1764                 readloop("headers");
1765         } else if (!strcasecmp(action, "do_search")) {
1766                 readloop("do_search");
1767         } else if (!strcasecmp(action, "msg")) {
1768                 embed_message(index[1]);
1769         } else if (!strcasecmp(action, "printmsg")) {
1770                 print_message(index[1]);
1771         } else if (!strcasecmp(action, "msgheaders")) {
1772                 display_headers(index[1]);
1773         } else if (!strcasecmp(action, "wiki")) {
1774                 display_wiki_page();
1775         } else if (!strcasecmp(action, "display_enter")) {
1776                 display_enter();
1777         } else if (!strcasecmp(action, "post")) {
1778                 post_message();
1779         } else if (!strcasecmp(action, "move_msg")) {
1780                 move_msg();
1781         } else if (!strcasecmp(action, "delete_msg")) {
1782                 delete_msg();
1783         } else if (!strcasecmp(action, "userlist")) {
1784                 userlist();
1785         } else if (!strcasecmp(action, "showuser")) {
1786                 showuser();
1787         } else if (!strcasecmp(action, "display_page")) {
1788                 display_page();
1789         } else if (!strcasecmp(action, "page_user")) {
1790                 page_user();
1791         } else if (!strcasecmp(action, "chat")) {
1792                 do_chat();
1793         } else if (!strcasecmp(action, "display_private")) {
1794                 display_private("", 0);
1795         } else if (!strcasecmp(action, "goto_private")) {
1796                 goto_private();
1797         } else if (!strcasecmp(action, "zapped_list")) {
1798                 zapped_list();
1799         } else if (!strcasecmp(action, "display_zap")) {
1800                 display_zap();
1801         } else if (!strcasecmp(action, "zap")) {
1802                 zap();
1803         } else if (!strcasecmp(action, "display_entroom")) {
1804                 display_entroom();
1805         } else if (!strcasecmp(action, "entroom")) {
1806                 entroom();
1807         } else if (!strcasecmp(action, "display_whok")) {
1808                 display_whok();
1809         } else if (!strcasecmp(action, "do_invt_kick")) {
1810                 do_invt_kick();
1811         } else if (!strcasecmp(action, "display_editroom")) {
1812                 display_editroom();
1813         } else if (!strcasecmp(action, "netedit")) {
1814                 netedit();
1815         } else if (!strcasecmp(action, "editroom")) {
1816                 editroom();
1817         } else if (!strcasecmp(action, "display_editinfo")) {
1818                 display_edit(_("Room info"), "EINF 0", "RINF", "editinfo", 1);
1819         } else if (!strcasecmp(action, "editinfo")) {
1820                 save_edit(_("Room info"), "EINF 1", 1);
1821         } else if (!strcasecmp(action, "display_editbio")) {
1822                 snprintf(buf, SIZ, "RBIO %s", WC->wc_fullname);
1823                 display_edit(_("Your bio"), "NOOP", buf, "editbio", 3);
1824         } else if (!strcasecmp(action, "editbio")) {
1825                 save_edit(_("Your bio"), "EBIO", 0);
1826         } else if (!strcasecmp(action, "confirm_move_msg")) {
1827                 confirm_move_msg();
1828         } else if (!strcasecmp(action, "delete_room")) {
1829                 delete_room();
1830         } else if (!strcasecmp(action, "validate")) {
1831                 validate();
1832                 /* The users photo display / upload facility */
1833         } else if (!strcasecmp(action, "display_editpic")) {
1834                 display_graphics_upload(_("your photo"),
1835                                         "_userpic_",
1836                                         "editpic");
1837         } else if (!strcasecmp(action, "editpic")) {
1838                 do_graphics_upload("_userpic_");
1839                 /* room picture dispay / upload facility */
1840         } else if (!strcasecmp(action, "display_editroompic")) {
1841                 display_graphics_upload(_("the icon for this room"),
1842                                         "_roompic_",
1843                                         "editroompic");
1844         } else if (!strcasecmp(action, "editroompic")) {
1845                 do_graphics_upload("_roompic_");
1846                 /* the greetingpage hello pic */
1847         } else if (!strcasecmp(action, "display_edithello")) {
1848                 display_graphics_upload(_("the Greetingpicture for the login prompt"),
1849                                         "hello",
1850                                         "edithellopic");
1851         } else if (!strcasecmp(action, "edithellopic")) {
1852                 do_graphics_upload("hello");
1853                 /* the logoff banner */
1854         } else if (!strcasecmp(action, "display_editgoodbyepic")) {
1855                 display_graphics_upload(_("the Logoff banner picture"),
1856                                         "UIMG 0|%s|goodbuye",
1857                                         "editgoodbuyepic");
1858         } else if (!strcasecmp(action, "editgoodbuyepic")) {
1859                 do_graphics_upload("UIMG 1|%s|goodbuye");
1860
1861         } else if (!strcasecmp(action, "delete_floor")) {
1862                 delete_floor();
1863         } else if (!strcasecmp(action, "rename_floor")) {
1864                 rename_floor();
1865         } else if (!strcasecmp(action, "create_floor")) {
1866                 create_floor();
1867         } else if (!strcasecmp(action, "display_editfloorpic")) {
1868                 snprintf(buf, SIZ, "UIMG 0|_floorpic_|%s",
1869                         bstr("which_floor"));
1870                 display_graphics_upload(_("the icon for this floor"),
1871                                         buf,
1872                                         "editfloorpic");
1873         } else if (!strcasecmp(action, "editfloorpic")) {
1874                 snprintf(buf, SIZ, "UIMG 1|_floorpic_|%s",
1875                         bstr("which_floor"));
1876                 do_graphics_upload(buf);
1877         } else if (!strcasecmp(action, "display_reg")) {
1878                 display_reg(0);
1879         } else if (!strcasecmp(action, "display_changepw")) {
1880                 display_changepw();
1881         } else if (!strcasecmp(action, "changepw")) {
1882                 changepw();
1883         } else if (!strcasecmp(action, "display_edit_node")) {
1884                 display_edit_node();
1885         } else if (!strcasecmp(action, "edit_node")) {
1886                 edit_node();
1887         } else if (!strcasecmp(action, "display_netconf")) {
1888                 display_netconf();
1889         } else if (!strcasecmp(action, "display_confirm_delete_node")) {
1890                 display_confirm_delete_node();
1891         } else if (!strcasecmp(action, "delete_node")) {
1892                 delete_node();
1893         } else if (!strcasecmp(action, "display_add_node")) {
1894                 display_add_node();
1895         } else if (!strcasecmp(action, "terminate_session")) {
1896                 slrp_highest();
1897                 terminate_session();
1898         } else if (!strcasecmp(action, "edit_me")) {
1899                 edit_me();
1900         } else if (!strcasecmp(action, "display_siteconfig")) {
1901                 display_siteconfig();
1902         } else if (!strcasecmp(action, "chat_recv")) {
1903                 chat_recv();
1904         } else if (!strcasecmp(action, "chat_send")) {
1905                 chat_send();
1906         } else if (!strcasecmp(action, "siteconfig")) {
1907                 siteconfig();
1908         } else if (!strcasecmp(action, "display_generic")) {
1909                 display_generic();
1910         } else if (!strcasecmp(action, "do_generic")) {
1911                 do_generic();
1912         } else if (!strcasecmp(action, "ajax_servcmd")) {
1913                 ajax_servcmd();
1914         } else if (!strcasecmp(action, "display_menubar")) {
1915                 display_menubar(1);
1916         } else if (!strcasecmp(action, "mimepart")) {
1917                 mimepart(index[1], index[2], 0);
1918         } else if (!strcasecmp(action, "mimepart_download")) {
1919                 mimepart(index[1], index[2], 1);
1920         } else if (!strcasecmp(action, "edit_vcard")) {
1921                 edit_vcard();
1922         } else if (!strcasecmp(action, "submit_vcard")) {
1923                 submit_vcard();
1924         } else if (!strcasecmp(action, "select_user_to_edit")) {
1925                 select_user_to_edit(NULL, NULL);
1926         } else if (!strcasecmp(action, "display_edituser")) {
1927                 display_edituser(NULL, 0);
1928         } else if (!strcasecmp(action, "edituser")) {
1929                 edituser();
1930         } else if (!strcasecmp(action, "create_user")) {
1931                 create_user();
1932         } else if (!strcasecmp(action, "changeview")) {
1933                 change_view();
1934         } else if (!strcasecmp(action, "change_start_page")) {
1935                 change_start_page();
1936         } else if (!strcasecmp(action, "display_floorconfig")) {
1937                 display_floorconfig(NULL);
1938         } else if (!strcasecmp(action, "toggle_self_service")) {
1939                 toggle_self_service();
1940         } else if (!strcasecmp(action, "display_edit_task")) {
1941                 display_edit_task();
1942         } else if (!strcasecmp(action, "save_task")) {
1943                 save_task();
1944         } else if (!strcasecmp(action, "display_edit_event")) {
1945                 display_edit_event();
1946         } else if (!strcasecmp(action, "save_event")) {
1947                 save_event();
1948         } else if (!strcasecmp(action, "respond_to_request")) {
1949                 respond_to_request();
1950         } else if (!strcasecmp(action, "handle_rsvp")) {
1951                 handle_rsvp();
1952         } else if (!strcasecmp(action, "summary")) {
1953                 summary();
1954         } else if (!strcasecmp(action, "summary_inner_div")) {
1955                 begin_ajax_response();
1956                 summary_inner_div();
1957                 end_ajax_response();
1958         } else if (!strcasecmp(action, "display_customize_iconbar")) {
1959                 display_customize_iconbar();
1960         } else if (!strcasecmp(action, "commit_iconbar")) {
1961                 commit_iconbar();
1962         } else if (!strcasecmp(action, "set_room_policy")) {
1963                 set_room_policy();
1964         } else if (!strcasecmp(action, "display_inetconf")) {
1965                 display_inetconf();
1966         } else if (!strcasecmp(action, "save_inetconf")) {
1967                 save_inetconf();
1968         } else if (!strcasecmp(action, "display_smtpqueue")) {
1969                 display_smtpqueue();
1970         } else if (!strcasecmp(action, "display_smtpqueue_inner_div")) {
1971                 display_smtpqueue_inner_div();
1972         } else if (!strcasecmp(action, "display_sieve")) {
1973                 display_sieve();
1974         } else if (!strcasecmp(action, "save_sieve")) {
1975                 save_sieve();
1976         } else if (!strcasecmp(action, "display_pushemail")) {
1977                 display_pushemail();
1978         } else if (!strcasecmp(action, "save_pushemail")) {
1979                 save_pushemail();
1980         } else if (!strcasecmp(action, "display_add_remove_scripts")) {
1981                 display_add_remove_scripts(NULL);
1982         } else if (!strcasecmp(action, "create_script")) {
1983                 create_script();
1984         } else if (!strcasecmp(action, "delete_script")) {
1985                 delete_script();
1986         } else if (!strcasecmp(action, "setup_wizard")) {
1987                 do_setup_wizard();
1988         } else if (!strcasecmp(action, "display_preferences")) {
1989                 display_preferences();
1990         } else if (!strcasecmp(action, "set_preferences")) {
1991                 set_preferences();
1992         } else if (!strcasecmp(action, "recp_autocomplete")) {
1993                 recp_autocomplete(bstr("recp"));
1994         } else if (!strcasecmp(action, "cc_autocomplete")) {
1995                 recp_autocomplete(bstr("cc"));
1996         } else if (!strcasecmp(action, "bcc_autocomplete")) {
1997                 recp_autocomplete(bstr("bcc"));
1998         } else if (!strcasecmp(action, "display_address_book_middle_div")) {
1999                 display_address_book_middle_div();
2000         } else if (!strcasecmp(action, "display_address_book_inner_div")) {
2001                 display_address_book_inner_div();
2002         } else if (!strcasecmp(action, "set_floordiv_expanded")) {
2003                 set_floordiv_expanded(index[1]);
2004         } else if (!strcasecmp(action, "diagnostics")) {
2005                 output_headers(1, 1, 1, 0, 0, 0);
2006                 wprintf("Session: %d<hr />\n", WC->wc_session);
2007                 wprintf("Command: <br /><PRE>\n");
2008                 escputs(cmd);
2009                 wprintf("</PRE><hr />\n");
2010                 wprintf("Variables: <br /><PRE>\n");
2011                 dump_vars();
2012                 wprintf("</PRE><hr />\n");
2013                 wDumpContent(1);
2014         } else if (!strcasecmp(action, "updatenote")) {
2015                 updatenote();
2016         } else if (!strcasecmp(action, "display_room_directory")) {
2017                 display_room_directory();
2018         } else if (!strcasecmp(action, "display_pictureview")) {
2019                 display_pictureview();
2020         } else if (!strcasecmp(action, "download_file")) {
2021                 download_file(index[1]);
2022         } else if (!strcasecmp(action, "upload_file")) {
2023                 upload_file();
2024         }
2025
2026         /** When all else fais, display the main menu. */
2027         else {
2028                 display_main_menu();
2029         }
2030
2031 SKIP_ALL_THIS_CRAP:
2032         fflush(stdout);
2033         if (content != NULL) {
2034                 free(content);
2035                 content = NULL;
2036         }
2037         free_urls();
2038         if (WC->upload_length > 0) {
2039                 free(WC->upload);
2040                 WC->upload_length = 0;
2041         }
2042 }
2043
2044 /**
2045  * \brief Replacement for sleep() that uses select() in order to avoid SIGALRM
2046  * \param seconds how many seconds should we sleep?
2047  */
2048 void sleeeeeeeeeep(int seconds)
2049 {
2050         struct timeval tv;
2051
2052         tv.tv_sec = seconds;
2053         tv.tv_usec = 0;
2054         select(0, NULL, NULL, NULL, &tv);
2055 }
2056
2057
2058 /*@}*/