comparison dpi/cookies.c @ 0:6ee11bf9e3ea

Initial revision
author jcid
date Sun, 07 Oct 2007 00:36:34 +0200
parents
children d9e7b35430de
comparison
equal deleted inserted replaced
-1:000000000000 0:6ee11bf9e3ea
1 /*
2 * File: cookies.c
3 * Cookies server.
4 *
5 * Copyright 2001 Lars Clausen <lrclause@cs.uiuc.edu>
6 * Jörgen Viksell <jorgen.viksell@telia.com>
7 * Copyright 2002-2006 Jorge Arellano Cid <jcid@dillo.org>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 */
15
16 /* Handling of cookies takes place here.
17 * This implementation aims to follow RFC 2965:
18 * http://www.cis.ohio-state.edu/cs/Services/rfc/rfc-text/rfc2965.txt
19 */
20
21 /*
22 * TODO: Cleanup this code. Shorten some functions, order things,
23 * add comments, remove leaks, etc.
24 */
25
26 /* Todo: this server is not assembling the received packets.
27 * This means it currently expects dillo to send full dpi tags
28 * within the socket; if that fails, everything stops.
29 */
30
31 #ifdef DISABLE_COOKIES
32
33 int main(void)
34 {
35 return 0; /* never called */
36 }
37
38 #else
39
40
41 #include <sys/types.h>
42 #include <sys/socket.h>
43 #include <sys/stat.h>
44 #include <sys/un.h>
45 #include <fcntl.h>
46 #include <unistd.h>
47 #include <errno.h>
48 #include <stddef.h>
49 #include <string.h>
50 #include <stdlib.h>
51 #include <stdio.h>
52 #include <time.h> /* for time() and time_t */
53 #include <ctype.h>
54 #include <netdb.h>
55 #include <signal.h>
56 #include "dpiutil.h"
57 #include "../dpip/dpip.h"
58
59
60 /*
61 * Debugging macros
62 */
63 #define _MSG(...)
64 #define MSG(...) printf("[cookies dpi]: " __VA_ARGS__)
65
66
67 /* This one is tricky, some sources state it should include the byte
68 * for the terminating NULL, and others say it shouldn't. */
69 # define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \
70 + strlen ((ptr)->sun_path))
71
72
73 /*
74 * a_List_add()
75 *
76 * Make sure there's space for 'num_items' items within the list
77 * (First, allocate an 'alloc_step' sized chunk, after that, double the
78 * list size --to make it faster)
79 */
80 #define a_List_add(list,num_items,alloc_step) \
81 if (!list) { \
82 list = dMalloc(alloc_step * sizeof((*list))); \
83 } \
84 if (num_items >= alloc_step){ \
85 while ( num_items >= alloc_step ) \
86 alloc_step <<= 1; \
87 list = dRealloc(list, alloc_step * sizeof((*list))); \
88 }
89
90 /* The maximum length of a line in the cookie file */
91 #define LINE_MAXLEN 4096
92
93 typedef enum {
94 COOKIE_ACCEPT,
95 COOKIE_ACCEPT_SESSION,
96 COOKIE_DENY
97 } CookieControlAction;
98
99 typedef struct {
100 char *domain;
101 CookieControlAction action;
102 } CookieControl;
103
104 typedef struct {
105 char *domain;
106 Dlist *dlist;
107 } CookieNode;
108
109 typedef struct {
110 char *name;
111 char *value;
112 char *domain;
113 char *path;
114 time_t expires_at;
115 uint_t version;
116 char *comment;
117 char *comment_url;
118 bool_t secure;
119 bool_t session_only;
120 Dlist *ports;
121 } CookieData_t;
122
123 /*
124 * Local data
125 */
126
127 /* List of CookieNode. Each node holds a domain and its list of cookies */
128 static Dlist *cookies;
129
130 /* Variables for access control */
131 static CookieControl *ccontrol = NULL;
132 static int num_ccontrol = 0;
133 static int num_ccontrol_max = 1;
134 static CookieControlAction default_action = COOKIE_DENY;
135
136 static bool_t disabled;
137 static FILE *file_stream;
138 static char *cookies_txt_header_str =
139 "# HTTP Cookie File\n"
140 "# http://www.netscape.com/newsref/std/cookie_spec.html\n"
141 "# This is a generated file! Do not edit.\n\n";
142
143
144 /*
145 * Forward declarations
146 */
147
148 static FILE *Cookies_fopen(const char *file, char *init_str);
149 static CookieControlAction Cookies_control_check_domain(const char *domain);
150 static int Cookie_control_init(void);
151 static void Cookies_parse_ports(int url_port, CookieData_t *cookie,
152 const char *port_str);
153 static char *Cookies_build_ports_str(CookieData_t *cookie);
154 static char *Cookies_strip_path(const char *path);
155 static void Cookies_add_cookie(CookieData_t *cookie);
156 static void Cookies_remove_cookie(CookieData_t *cookie);
157 static int Cookies_cmp(const void *a, const void *b);
158
159 /*
160 * Compare function for searching a cookie node
161 */
162 int Cookie_node_cmp(const void *v1, const void *v2)
163 {
164 const CookieNode *n1 = v1, *n2 = v2;
165
166 return strcmp(n1->domain, n2->domain);
167 }
168
169 /*
170 * Compare function for searching a cookie node by domain
171 */
172 int Cookie_node_by_domain_cmp(const void *v1, const void *v2)
173 {
174 const CookieNode *node = v1;
175 const char *domain = v2;
176
177 return strcmp(node->domain, domain);
178 }
179
180 /*
181 * Return a file pointer. If the file doesn't exist, try to create it,
182 * with the optional 'init_str' as its content.
183 */
184 static FILE *Cookies_fopen(const char *filename, char *init_str)
185 {
186 FILE *F_in;
187 int fd;
188
189 if ((F_in = fopen(filename, "r+")) == NULL) {
190 /* Create the file */
191 fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
192 if (fd != -1) {
193 if (init_str)
194 write(fd, init_str, strlen(init_str));
195 close(fd);
196
197 MSG("Created file: %s\n", filename);
198 F_in = Cookies_fopen(filename, NULL);
199 } else {
200 MSG("Could not create file: %s!\n", filename);
201 }
202 }
203
204 /* set close on exec */
205 fcntl(fileno(F_in), F_SETFD, FD_CLOEXEC | fcntl(fileno(F_in), F_GETFD));
206
207 return F_in;
208 }
209
210 static void Cookies_free_cookie(CookieData_t *cookie)
211 {
212 dFree(cookie->name);
213 dFree(cookie->value);
214 dFree(cookie->domain);
215 dFree(cookie->path);
216 dFree(cookie->comment);
217 dFree(cookie->comment_url);
218 dList_free(cookie->ports);
219 dFree(cookie);
220 }
221
222 /*
223 * Initialize the cookies module
224 * (The 'disabled' variable is writable only within Cookies_init)
225 */
226 void Cookies_init()
227 {
228 CookieData_t *cookie;
229 char *filename;
230 char line[LINE_MAXLEN];
231 #ifndef HAVE_LOCKF
232 struct flock lck;
233 #endif
234 FILE *old_cookies_file_stream;
235
236 /* Default setting */
237 disabled = TRUE;
238
239 /* Read and parse the cookie control file (cookiesrc) */
240 if (Cookie_control_init() != 0) {
241 MSG("Disabling cookies.\n");
242 return;
243 }
244
245 /* Get a stream for the cookies file */
246 filename = dStrconcat(dGethomedir(), "/.dillo/cookies.txt", NULL);
247 file_stream = Cookies_fopen(filename, cookies_txt_header_str);
248
249 dFree(filename);
250
251 if (!file_stream) {
252 MSG("ERROR: Can't open ~/.dillo/cookies.txt, disabling cookies\n");
253 return;
254 }
255
256 /* Try to get a lock from the file descriptor */
257 #ifdef HAVE_LOCKF
258 disabled = (lockf(fileno(file_stream), F_TLOCK, 0) == -1);
259 #else /* POSIX lock */
260 lck.l_start = 0; /* start at beginning of file */
261 lck.l_len = 0; /* lock entire file */
262 lck.l_type = F_WRLCK;
263 lck.l_whence = SEEK_SET; /* absolute offset */
264
265 disabled = (fcntl(fileno(file_stream), F_SETLK, &lck) == -1);
266 #endif
267 if (disabled) {
268 MSG("The cookies file has a file lock: disabling cookies!\n");
269 fclose(file_stream);
270 return;
271 }
272
273 MSG("Enabling cookies as from cookiesrc...\n");
274
275 cookies = dList_new(32);
276
277 /* Get all lines in the file */
278 while (!feof(file_stream)) {
279 line[0] = '\0';
280 fgets(line, LINE_MAXLEN, file_stream);
281
282 /* Remove leading and trailing whitespaces */
283 dStrstrip(line);
284
285 if ((line[0] != '\0') && (line[0] != '#')) {
286 /*
287 * Split the row into pieces using a tab as the delimiter.
288 * pieces[0] The domain name
289 * pieces[1] TRUE/FALSE: is the domain a suffix, or a full domain?
290 * pieces[2] The path
291 * pieces[3] Is the cookie unsecure or secure (TRUE/FALSE)
292 * pieces[4] Timestamp of expire date
293 * pieces[5] Name of the cookie
294 * pieces[6] Value of the cookie
295 */
296 CookieControlAction action;
297 char *piece;
298 char *line_marker = line;
299
300 cookie = dNew0(CookieData_t, 1);
301
302 cookie->session_only = FALSE;
303 cookie->version = 0;
304 cookie->domain = dStrdup(dStrsep(&line_marker, "\t"));
305 dStrsep(&line_marker, "\t"); /* we use domain always as sufix */
306 cookie->path = dStrdup(dStrsep(&line_marker, "\t"));
307 piece = dStrsep(&line_marker, "\t");
308 if (piece != NULL && piece[0] == 'T')
309 cookie->secure = TRUE;
310 piece = dStrsep(&line_marker, "\t");
311 if (piece != NULL)
312 cookie->expires_at = (time_t) strtol(piece, NULL, 10);
313 cookie->name = dStrdup(dStrsep(&line_marker, "\t"));
314 cookie->value = dStrdup(dStrsep(&line_marker, "\t"));
315
316 if (!cookie->domain || cookie->domain[0] == '\0' ||
317 !cookie->path || cookie->path[0] != '/' ||
318 !cookie->name || cookie->name[0] == '\0' ||
319 !cookie->value) {
320 MSG("Malformed line in cookies.txt file!\n");
321 Cookies_free_cookie(cookie);
322 continue;
323 }
324
325 action = Cookies_control_check_domain(cookie->domain);
326 if (action == COOKIE_DENY) {
327 Cookies_free_cookie(cookie);
328 continue;
329 } else if (action == COOKIE_ACCEPT_SESSION) {
330 cookie->session_only = TRUE;
331 }
332
333 /* Save cookie in memory */
334 Cookies_add_cookie(cookie);
335 }
336 }
337
338 filename = dStrconcat(dGethomedir(), "/.dillo/cookies", NULL);
339 if ((old_cookies_file_stream = fopen(filename, "r")) != NULL) {
340 dFree(filename);
341 MSG("WARNING: Reading old cookies file ~/.dillo/cookies too\n");
342
343 /* Get all lines in the file */
344 while (!feof(old_cookies_file_stream)) {
345 line[0] = '\0';
346 fgets(line, LINE_MAXLEN, old_cookies_file_stream);
347
348 /* Remove leading and trailing whitespaces */
349 dStrstrip(line);
350
351 if (line[0] != '\0') {
352 /*
353 * Split the row into pieces using a tab as the delimiter.
354 * pieces[0] The version this cookie was set as (0 / 1)
355 * pieces[1] The domain name
356 * pieces[2] A comma separated list of accepted ports
357 * pieces[3] The path
358 * pieces[4] Is the cookie unsecure or secure (0 / 1)
359 * pieces[5] Timestamp of expire date
360 * pieces[6] Name of the cookie
361 * pieces[7] Value of the cookie
362 * pieces[8] Comment
363 * pieces[9] Comment url
364 */
365 CookieControlAction action;
366 char *piece;
367 char *line_marker = line;
368
369 cookie = dNew0(CookieData_t, 1);
370
371 cookie->session_only = FALSE;
372 piece = dStrsep(&line_marker, "\t");
373 if (piece != NULL)
374 cookie->version = strtol(piece, NULL, 10);
375 cookie->domain = dStrdup(dStrsep(&line_marker, "\t"));
376 Cookies_parse_ports(0, cookie, dStrsep(&line_marker, "\t"));
377 cookie->path = dStrdup(dStrsep(&line_marker, "\t"));
378 piece = dStrsep(&line_marker, "\t");
379 if (piece != NULL && piece[0] == '1')
380 cookie->secure = TRUE;
381 piece = dStrsep(&line_marker, "\t");
382 if (piece != NULL)
383 cookie->expires_at = (time_t) strtol(piece, NULL, 10);
384 cookie->name = dStrdup(dStrsep(&line_marker, "\t"));
385 cookie->value = dStrdup(dStrsep(&line_marker, "\t"));
386 cookie->comment = dStrdup(dStrsep(&line_marker, "\t"));
387 cookie->comment_url = dStrdup(dStrsep(&line_marker, "\t"));
388
389 if (!cookie->domain || cookie->domain[0] == '\0' ||
390 !cookie->path || cookie->path[0] != '/' ||
391 !cookie->name || cookie->name[0] == '\0' ||
392 !cookie->value) {
393 MSG("Malformed line in cookies file!\n");
394 Cookies_free_cookie(cookie);
395 continue;
396 }
397
398 action = Cookies_control_check_domain(cookie->domain);
399 if (action == COOKIE_DENY) {
400 Cookies_free_cookie(cookie);
401 continue;
402 } else if (action == COOKIE_ACCEPT_SESSION) {
403 cookie->session_only = TRUE;
404 }
405
406 /* Save cookie in memory */
407 Cookies_add_cookie(cookie);
408 }
409 }
410 fclose(old_cookies_file_stream);
411 } else {
412 dFree(filename);
413 }
414
415 }
416
417 /*
418 * Flush cookies to disk and free all the memory allocated.
419 */
420 void Cookies_save_and_free()
421 {
422 int i, fd;
423 CookieNode *node;
424 CookieData_t *cookie;
425
426 #ifndef HAVE_LOCKF
427 struct flock lck;
428 #endif
429
430 if (disabled)
431 return;
432
433 rewind(file_stream);
434 fd = fileno(file_stream);
435 ftruncate(fd, 0);
436 fprintf(file_stream, cookies_txt_header_str);
437
438 /* Iterate cookies per domain, saving and freeing */
439 while ((node = dList_nth_data(cookies, 0))) {
440 for (i = 0; (cookie = dList_nth_data(node->dlist, i)); ++i) {
441 if (!cookie->session_only) {
442 /* char * ports_str = Cookies_build_ports_str(cookie); */
443 fprintf(file_stream, "%s\tTRUE\t%s\t%s\t%ld\t%s\t%s\n",
444 cookie->domain,
445 cookie->path,
446 cookie->secure ? "TRUE" : "FALSE",
447 (long)cookie->expires_at,
448 cookie->name,
449 cookie->value);
450 /* dFree(ports_str); */
451 }
452
453 Cookies_free_cookie(cookie);
454 }
455 dList_remove(cookies, node);
456 dFree(node->domain);
457 dList_free(node->dlist);
458 dFree(node);
459 }
460
461 #ifdef HAVE_LOCKF
462 lockf(fd, F_ULOCK, 0);
463 #else /* POSIX file lock */
464 lck.l_start = 0; /* start at beginning of file */
465 lck.l_len = 0; /* lock entire file */
466 lck.l_type = F_UNLCK;
467 lck.l_whence = SEEK_SET; /* absolute offset */
468
469 fcntl(fileno(file_stream), F_SETLKW, &lck);
470 #endif
471 fclose(file_stream);
472 }
473
474 static char *months[] =
475 { "",
476 "Jan", "Feb", "Mar",
477 "Apr", "May", "Jun",
478 "Jul", "Aug", "Sep",
479 "Oct", "Nov", "Dec"
480 };
481
482 /*
483 * Take a months name and return a number between 1-12.
484 * E.g. 'April' -> 4
485 */
486 static int Cookies_get_month(const char *month_name)
487 {
488 int i;
489
490 for (i = 1; i <= 12; i++) {
491 if (!dStrncasecmp(months[i], month_name, 3))
492 return i;
493 }
494 return 0;
495 }
496
497 /*
498 * Return a local timestamp from a GMT date string
499 * Accept: RFC-1123 | RFC-850 | ANSI asctime | Old Netscape format.
500 *
501 * Wdy, DD-Mon-YY HH:MM:SS GMT
502 * Wdy, DD-Mon-YYYY HH:MM:SS GMT
503 * Weekday, DD-Mon-YY HH:MM:SS GMT
504 * Weekday, DD-Mon-YYYY HH:MM:SS GMT
505 * Tue May 21 13:46:22 1991\n
506 * Tue May 21 13:46:22 1991
507 *
508 * (return 0 on malformed date string syntax)
509 */
510 static time_t Cookies_create_timestamp(const char *expires)
511 {
512 time_t ret;
513 int day, month, year, hour, minutes, seconds;
514 char *cp;
515 char *E_msg =
516 "Expire date is malformed!\n"
517 " (should be RFC-1123 | RFC-850 | ANSI asctime)\n"
518 " Ignoring cookie: ";
519
520 cp = strchr(expires, ',');
521 if (!cp && (strlen(expires) == 24 || strlen(expires) == 25)) {
522 /* Looks like ANSI asctime format... */
523 cp = (char *)expires;
524 day = strtol(cp + 8, NULL, 10); /* day */
525 month = Cookies_get_month(cp + 4); /* month */
526 year = strtol(cp + 20, NULL, 10); /* year */
527 hour = strtol(cp + 11, NULL, 10); /* hour */
528 minutes = strtol(cp + 14, NULL, 10); /* minutes */
529 seconds = strtol(cp + 17, NULL, 10); /* seconds */
530
531 } else if (cp && (cp - expires == 3 || cp - expires > 5) &&
532 (strlen(cp) == 24 || strlen(cp) == 26)) {
533 /* RFC-1123 | RFC-850 format | Old Netscape format */
534 day = strtol(cp + 2, NULL, 10);
535 month = Cookies_get_month(cp + 5);
536 year = strtol(cp + 9, &cp, 10);
537 /* todo: tricky, because two digits for year IS ambiguous! */
538 year += (year < 70) ? 2000 : ((year < 100) ? 1900 : 0);
539 hour = strtol(cp + 1, NULL, 10);
540 minutes = strtol(cp + 4, NULL, 10);
541 seconds = strtol(cp + 7, NULL, 10);
542
543 } else {
544 MSG("%s%s\n", E_msg, expires);
545 return (time_t) 0;
546 }
547
548 /* Error checks --this may be overkill */
549 if (!(day > 0 && day < 32 && month > 0 && month < 13 && year > 1970 &&
550 hour >= 0 && hour < 24 && minutes >= 0 && minutes < 60 &&
551 seconds >= 0 && seconds < 60)) {
552 MSG("%s%s\n", E_msg, expires);
553 return (time_t) 0;
554 }
555
556 /* Calculate local timestamp.
557 * [stolen from Lynx... (http://lynx.browser.org)] */
558 month -= 3;
559 if (month < 0) {
560 month += 12;
561 year--;
562 }
563
564 day += (year - 1968) * 1461 / 4;
565 day += ((((month * 153) + 2) / 5) - 672);
566 ret = (time_t)((day * 60 * 60 * 24) +
567 (hour * 60 * 60) +
568 (minutes * 60) +
569 seconds);
570
571 MSG("Expires in %ld seconds, at %s",
572 (long)ret - time(NULL), ctime(&ret));
573
574 return ret;
575 }
576
577 /*
578 * Parse a string containing a list of port numbers.
579 */
580 static void Cookies_parse_ports(int url_port, CookieData_t *cookie,
581 const char *port_str)
582 {
583 if ((!port_str || !port_str[0]) && url_port != 0) {
584 /* There was no list, so only the calling urls port should be allowed. */
585 if (!cookie->ports)
586 cookie->ports = dList_new(1);
587 dList_append(cookie->ports, INT2VOIDP(url_port));
588 } else if (port_str[0] == '"' && port_str[1] != '"') {
589 char *tok, *str;
590 int port;
591
592 str = dStrdup(port_str + 1);
593 while ((tok = dStrsep(&str, ","))) {
594 port = strtol(tok, NULL, 10);
595 if (port > 0) {
596 if (!cookie->ports)
597 cookie->ports = dList_new(1);
598 dList_append(cookie->ports, INT2VOIDP(port));
599 }
600 }
601 dFree(str);
602 }
603 }
604
605 /*
606 * Build a string of the ports in 'cookie'.
607 */
608 static char *Cookies_build_ports_str(CookieData_t *cookie)
609 {
610 Dstr *dstr;
611 char *ret;
612 void *data;
613 int i;
614
615 dstr = dStr_new("\"");
616 for (i = 0; (data = dList_nth_data(cookie->ports, i)); ++i) {
617 dStr_sprintfa(dstr, "%d,", VOIDP2INT(data));
618 }
619 /* Remove any trailing comma */
620 if (dstr->len > 1)
621 dStr_erase(dstr, dstr->len - 1, 1);
622 dStr_append(dstr, "\"");
623
624 ret = dstr->str;
625 dStr_free(dstr, FALSE);
626
627 return ret;
628 }
629
630 static void Cookies_add_cookie(CookieData_t *cookie)
631 {
632 Dlist *domain_cookies;
633 CookieData_t *c;
634 CookieNode *node;
635
636 /* Don't add an expired cookie */
637 if (!cookie->session_only && cookie->expires_at < time(NULL)) {
638 Cookies_free_cookie(cookie);
639 return;
640 }
641
642 node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp);
643 domain_cookies = (node) ? node->dlist : NULL;
644
645 if (domain_cookies) {
646 /* Respect the limit of 20 cookies per domain */
647 if (dList_length(domain_cookies) >= 20) {
648 MSG("There are too many cookies for this domain (%s)\n",
649 cookie->domain);
650 Cookies_free_cookie(cookie);
651 return;
652 }
653
654 /* Remove any cookies with the same name and path */
655 while ((c = dList_find_custom(domain_cookies, cookie, Cookies_cmp))){
656 Cookies_remove_cookie(c);
657 }
658 }
659
660 /* add the cookie into the respective domain list */
661 node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp);
662 domain_cookies = (node) ? node->dlist : NULL;
663 if (!domain_cookies) {
664 domain_cookies = dList_new(5);
665 dList_append(domain_cookies, cookie);
666 node = dNew(CookieNode, 1);
667 node->domain = dStrdup(cookie->domain);
668 node->dlist = domain_cookies;
669 dList_insert_sorted(cookies, node, Cookie_node_cmp);
670 } else {
671 dList_append(domain_cookies, cookie);
672 }
673 }
674
675 /*
676 * Remove the cookie from the domain list.
677 * If the domain list is empty, remove the node too.
678 * Free the cookie.
679 */
680 static void Cookies_remove_cookie(CookieData_t *cookie)
681 {
682 CookieNode *node;
683
684 node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp);
685 if (node) {
686 dList_remove(node->dlist, cookie);
687 if (dList_length(node->dlist) == 0) {
688 dList_remove(cookies, node);
689 dFree(node->domain);
690 dList_free(node->dlist);
691 }
692 } else {
693 MSG("Attempting to remove a cookie that doesn't exist!\n");
694 }
695
696 Cookies_free_cookie(cookie);
697 }
698
699 /*
700 * Return the attribute that is present at *cookie_str. This function
701 * will also attempt to advance cookie_str past any equal-sign.
702 */
703 static char *Cookies_parse_attr(char **cookie_str)
704 {
705 char *str = *cookie_str;
706 uint_t i, end = 0;
707 bool_t got_attr = FALSE;
708
709 for (i = 0; ; i++) {
710 switch (str[i]) {
711 case ' ':
712 case '\t':
713 case '=':
714 case ';':
715 got_attr = TRUE;
716 if (end == 0)
717 end = i;
718 break;
719 case ',':
720 *cookie_str = str + i;
721 return dStrndup(str, i);
722 break;
723 case '\0':
724 if (!got_attr) {
725 end = i;
726 got_attr = TRUE;
727 }
728 /* fall through! */
729 default:
730 if (got_attr) {
731 *cookie_str = str + i;
732 return dStrndup(str, end);
733 }
734 break;
735 }
736 }
737
738 return NULL;
739 }
740
741 /*
742 * Get the value starting at *cookie_str.
743 * broken_syntax: watch out for stupid syntax (comma in unquoted string...)
744 */
745 static char *Cookies_parse_value(char **cookie_str,
746 bool_t broken_syntax,
747 bool_t keep_quotes)
748 {
749 uint_t i, end;
750 char *str = *cookie_str;
751
752 for (i = end = 0; !end; ++i) {
753 switch (str[i]) {
754 case ' ':
755 case '\t':
756 if (!broken_syntax && str[0] != '\'' && str[0] != '"') {
757 *cookie_str = str + i + 1;
758 end = 1;
759 }
760 break;
761 case '\'':
762 case '"':
763 if (i != 0 && str[i] == str[0]) {
764 char *tmp = str + i;
765
766 while (*tmp != '\0' && *tmp != ';' && *tmp != ',')
767 tmp++;
768
769 *cookie_str = (*tmp == ';') ? tmp + 1 : tmp;
770
771 if (keep_quotes)
772 i++;
773 end = 1;
774 }
775 break;
776 case '\0':
777 *cookie_str = str + i;
778 end = 1;
779 break;
780 case ',':
781 if (str[0] != '\'' && str[0] != '"' && !broken_syntax) {
782 /* A new cookie starts here! */
783 *cookie_str = str + i;
784 end = 1;
785 }
786 break;
787 case ';':
788 if (str[0] != '\'' && str[0] != '"') {
789 *cookie_str = str + i + 1;
790 end = 1;
791 }
792 break;
793 default:
794 break;
795 }
796 }
797 /* keep i as an index to the last char */
798 --i;
799
800 if ((str[0] == '\'' || str[0] == '"') && !keep_quotes) {
801 return i > 1 ? dStrndup(str + 1, i - 1) : NULL;
802 } else {
803 return dStrndup(str, i);
804 }
805 }
806
807 /*
808 * Parse one cookie...
809 */
810 static CookieData_t *Cookies_parse_one(int url_port, char **cookie_str)
811 {
812 CookieData_t *cookie;
813 char *str = *cookie_str;
814 char *attr;
815 char *value;
816 int num_attr = 0;
817 bool_t max_age = FALSE;
818 bool_t discard = FALSE;
819
820 cookie = dNew0(CookieData_t, 1);
821 cookie->session_only = TRUE;
822
823 /* Iterate until there is nothing left of the string OR we come
824 * across a comma representing the start of another cookie */
825 while (*str != '\0' && *str != ',') {
826 /* Skip whitespace */
827 while (isspace(*str))
828 str++;
829
830 /* Get attribute */
831 attr = Cookies_parse_attr(&str);
832 if (!attr) {
833 MSG("Failed to parse cookie attribute!\n");
834 Cookies_free_cookie(cookie);
835 return NULL;
836 }
837
838 /* Get the value for the attribute and store it */
839 if (num_attr == 0) {
840 /* The first attr, which always is the user supplied attr, may
841 * have the same name as an ordinary attr. Hence this workaround. */
842 cookie->name = dStrdup(attr);
843 cookie->value = Cookies_parse_value(&str, FALSE, TRUE);
844 } else if (dStrcasecmp(attr, "Path") == 0) {
845 value = Cookies_parse_value(&str, FALSE, FALSE);
846 cookie->path = value;
847 } else if (dStrcasecmp(attr, "Domain") == 0) {
848 value = Cookies_parse_value(&str, FALSE, FALSE);
849 cookie->domain = value;
850 } else if (dStrcasecmp(attr, "Discard") == 0) {
851 cookie->session_only = TRUE;
852 discard = TRUE;
853 } else if (dStrcasecmp(attr, "Max-Age") == 0) {
854 if (!discard) {
855 value = Cookies_parse_value(&str, FALSE, FALSE);
856
857 if (value) {
858 cookie->expires_at = time(NULL) + strtol(value, NULL, 10);
859 cookie->session_only = FALSE;
860 max_age = TRUE;
861 dFree(value);
862 } else {
863 MSG("Failed to parse cookie value!\n");
864 Cookies_free_cookie(cookie);
865 return NULL;
866 }
867 }
868 } else if (dStrcasecmp(attr, "Expires") == 0) {
869 if (!max_age && !discard) {
870 MSG("Old netscape-style cookie...\n");
871 value = Cookies_parse_value(&str, TRUE, FALSE);
872 if (value) {
873 cookie->expires_at = Cookies_create_timestamp(value);
874 cookie->session_only = FALSE;
875 dFree(value);
876 } else {
877 MSG("Failed to parse cookie value!\n");
878 Cookies_free_cookie(cookie);
879 return NULL;
880 }
881 }
882 } else if (dStrcasecmp(attr, "Port") == 0) {
883 value = Cookies_parse_value(&str, FALSE, TRUE);
884 Cookies_parse_ports(url_port, cookie, value);
885 dFree(value);
886 } else if (dStrcasecmp(attr, "Comment") == 0) {
887 value = Cookies_parse_value(&str, FALSE, FALSE);
888 cookie->comment = value;
889 } else if (dStrcasecmp(attr, "CommentURL") == 0) {
890 value = Cookies_parse_value(&str, FALSE, FALSE);
891 cookie->comment_url = value;
892 } else if (dStrcasecmp(attr, "Version") == 0) {
893 value = Cookies_parse_value(&str, FALSE, FALSE);
894
895 if (value) {
896 cookie->version = strtol(value, NULL, 10);
897 dFree(value);
898 } else {
899 MSG("Failed to parse cookie value!\n");
900 Cookies_free_cookie(cookie);
901 return NULL;
902 }
903 } else if (dStrcasecmp(attr, "Secure") == 0) {
904 cookie->secure = TRUE;
905 } else {
906 /* Oops! this can't be good... */
907 dFree(attr);
908 Cookies_free_cookie(cookie);
909 MSG("Cookie contains illegal attribute!\n");
910 return NULL;
911 }
912
913 dFree(attr);
914 num_attr++;
915 }
916
917 *cookie_str = (*str == ',') ? str + 1 : str;
918
919 if (cookie->name && cookie->value) {
920 return cookie;
921 } else {
922 MSG("Cookie missing name and/or value!\n");
923 Cookies_free_cookie(cookie);
924 return NULL;
925 }
926 }
927
928 /*
929 * Iterate the cookie string until we catch all cookies.
930 * Return Value: a list with all the cookies! (or NULL upon error)
931 */
932 static Dlist *Cookies_parse_string(int url_port, char *cookie_string)
933 {
934 CookieData_t *cookie;
935 Dlist *ret = NULL;
936 char *str = cookie_string;
937
938 /* The string may contain several cookies separated by comma.
939 * We'll iterate until we've catched them all */
940 while (*str) {
941 cookie = Cookies_parse_one(url_port, &str);
942
943 if (cookie) {
944 if (!ret)
945 ret = dList_new(4);
946 dList_append(ret, cookie);
947 } else {
948 MSG("Malformed cookie field, ignoring cookie: %s\n", cookie_string);
949 }
950 }
951
952 return ret;
953 }
954
955 /*
956 * Compare cookies by name and path (return 0 if equal)
957 */
958 static int Cookies_cmp(const void *a, const void *b)
959 {
960 const CookieData_t *ca = a, *cb = b;
961 int ret;
962
963 if (!(ret = strcmp(ca->name, cb->name)))
964 ret = strcmp(ca->path, cb->path);
965 return ret;
966 }
967
968 /*
969 * Validate cookies domain against some security checks.
970 */
971 static bool_t Cookies_validate_domain(CookieData_t *cookie, char *host,
972 char *url_path)
973 {
974 int dots, diff, i;
975 bool_t is_ip;
976
977 /* Make sure that the path is set to something */
978 if (!cookie->path || cookie->path[0] != '/') {
979 dFree(cookie->path);
980 cookie->path = Cookies_strip_path(url_path);
981 }
982
983 /* If the server never set a domain, or set one without a leading
984 * dot (which isn't allowed), we use the calling URL's hostname. */
985 if (cookie->domain == NULL || cookie->domain[0] != '.') {
986 dFree(cookie->domain);
987 cookie->domain = dStrdup(host);
988 return TRUE;
989 }
990
991 /* Count the number of dots and also find out if it is an IP-address */
992 is_ip = TRUE;
993 for (i = 0, dots = 0; cookie->domain[i] != '\0'; i++) {
994 if (cookie->domain[i] == '.')
995 dots++;
996 else if (!isdigit(cookie->domain[i]))
997 is_ip = FALSE;
998 }
999
1000 /* A valid domain must have at least two dots in it */
1001 /* NOTE: this breaks cookies on localhost... */
1002 if (dots < 2) {
1003 return FALSE;
1004 }
1005
1006 /* Now see if the url matches the domain */
1007 diff = strlen(host) - i;
1008 if (diff > 0) {
1009 if (dStrcasecmp(host + diff, cookie->domain))
1010 return FALSE;
1011
1012 if (!is_ip) {
1013 /* "x.y.test.com" is not allowed to set cookies for ".test.com";
1014 * only an url of the form "y.test.com" would be. */
1015 while ( diff-- )
1016 if (host[diff] == '.')
1017 return FALSE;
1018 }
1019 }
1020
1021 return TRUE;
1022 }
1023
1024 /*
1025 * Strip of the filename from a full path
1026 */
1027 static char *Cookies_strip_path(const char *path)
1028 {
1029 char *ret;
1030 uint_t len;
1031
1032 if (path) {
1033 len = strlen(path);
1034
1035 while (len && path[len] != '/')
1036 len--;
1037 ret = dStrndup(path, len + 1);
1038 } else {
1039 ret = dStrdup("/");
1040 }
1041
1042 return ret;
1043 }
1044
1045 /*
1046 * Set the value corresponding to the cookie string
1047 */
1048 void Cookies_set(char *cookie_string, char *url_host,
1049 char *url_path, int url_port)
1050 {
1051 CookieControlAction action;
1052 CookieData_t *cookie;
1053 Dlist *list;
1054 int i;
1055
1056 if (disabled)
1057 return;
1058
1059 action = Cookies_control_check_domain(url_host);
1060 if (action == COOKIE_DENY) {
1061 MSG("denied SET for %s\n", url_host);
1062 return;
1063 }
1064
1065 if ((list = Cookies_parse_string(url_port, cookie_string))) {
1066 for (i = 0; (cookie = dList_nth_data(list, i)); ++i) {
1067 if (Cookies_validate_domain(cookie, url_host, url_path)) {
1068 if (action == COOKIE_ACCEPT_SESSION)
1069 cookie->session_only = TRUE;
1070 Cookies_add_cookie(cookie);
1071 } else {
1072 MSG("Rejecting cookie for %s from host %s path %s\n",
1073 cookie->domain, url_host, url_path);
1074 Cookies_free_cookie(cookie);
1075 }
1076 }
1077 dList_free(list);
1078 }
1079 }
1080
1081 /*
1082 * Compare the cookie with the supplied data to see if it matches
1083 */
1084 static bool_t Cookies_match(CookieData_t *cookie, int port,
1085 const char *path, bool_t is_ssl)
1086 {
1087 void *data;
1088 int i;
1089
1090 /* Insecure cookies matches both secure and insecure urls, secure
1091 cookies matches only secure urls */
1092 if (cookie->secure && !is_ssl)
1093 return FALSE;
1094
1095 /* Check that the cookie path is a subpath of the current path */
1096 if (strncmp(cookie->path, path, strlen(cookie->path)) != 0)
1097 return FALSE;
1098
1099 /* Check if the port of the request URL matches any
1100 * of those set in the cookie */
1101 if (cookie->ports) {
1102 for (i = 0; (data = dList_nth_data(cookie->ports, i)); ++i) {
1103 if (VOIDP2INT(data) == port)
1104 return TRUE;
1105 }
1106 return FALSE;
1107 }
1108
1109 /* It's a match */
1110 return TRUE;
1111 }
1112
1113 /*
1114 * Return a string that contains all relevant cookies as headers.
1115 */
1116 char *Cookies_get(char *url_host, char *url_path,
1117 char *url_scheme, int url_port)
1118 {
1119 char *domain_str, *q, *str, *path;
1120 CookieData_t *cookie;
1121 Dlist *matching_cookies;
1122 CookieNode *node;
1123 Dlist *domain_cookies;
1124 bool_t is_ssl;
1125 Dstr *cookie_dstring;
1126 int i;
1127
1128 if (disabled)
1129 return dStrdup("");
1130
1131 matching_cookies = dList_new(8);
1132
1133 path = Cookies_strip_path(url_path);
1134
1135 /* Check if the protocol is secure or not */
1136 is_ssl = (!dStrcasecmp(url_scheme, "https"));
1137
1138 for (domain_str = (char *) url_host;
1139 domain_str != NULL && *domain_str;
1140 domain_str = strchr(domain_str+1, '.')) {
1141
1142 node = dList_find_sorted(cookies, domain_str, Cookie_node_by_domain_cmp);
1143 domain_cookies = (node) ? node->dlist : NULL;
1144
1145 for (i = 0; (cookie = dList_nth_data(domain_cookies, i)); ++i) {
1146 /* Remove expired cookie. */
1147 if (!cookie->session_only && cookie->expires_at < time(NULL)) {
1148 Cookies_remove_cookie(cookie);
1149 --i; continue;
1150 }
1151 /* Check if the cookie matches the requesting URL */
1152 if (Cookies_match(cookie, url_port, path, is_ssl)) {
1153 dList_append(matching_cookies, cookie);
1154 }
1155 }
1156 }
1157
1158 /* Found the cookies, now make the string */
1159 cookie_dstring = dStr_new("");
1160 if (dList_length(matching_cookies) > 0) {
1161 CookieData_t *first_cookie = dList_nth_data(matching_cookies, 0);
1162
1163 dStr_sprintfa(cookie_dstring, "Cookie: ");
1164
1165 if (first_cookie->version != 0)
1166 dStr_sprintfa(cookie_dstring, "$Version=\"%d\"; ",
1167 first_cookie->version);
1168
1169
1170 for (i = 0; (cookie = dList_nth_data(matching_cookies, i)); ++i) {
1171 q = (cookie->version == 0 ? "" : "\"");
1172 dStr_sprintfa(cookie_dstring,
1173 "%s=%s; $Path=%s%s%s; $Domain=%s%s%s",
1174 cookie->name, cookie->value,
1175 q, cookie->path, q, q, cookie->domain, q);
1176 if (cookie->ports) {
1177 char *ports_str = Cookies_build_ports_str(cookie);
1178 dStr_sprintfa(cookie_dstring, "; $Port=%s", ports_str);
1179 dFree(ports_str);
1180 }
1181
1182 dStr_append(cookie_dstring,
1183 dList_length(matching_cookies) > i + 1 ? "; " : "\r\n");
1184 }
1185 }
1186
1187 dList_free(matching_cookies);
1188 dFree(path);
1189 str = cookie_dstring->str;
1190 dStr_free(cookie_dstring, FALSE);
1191 return str;
1192 }
1193
1194 /* -------------------------------------------------------------
1195 * Access control routines
1196 * ------------------------------------------------------------- */
1197
1198
1199 /*
1200 * Get the cookie control rules (from cookiesrc).
1201 * Return value:
1202 * 0 = Parsed OK, with cookies enabled
1203 * 1 = Parsed OK, with cookies disabled
1204 * 2 = Can't open the control file
1205 */
1206 static int Cookie_control_init(void)
1207 {
1208 CookieControl cc;
1209 FILE *stream;
1210 char *filename;
1211 char line[LINE_MAXLEN];
1212 char domain[LINE_MAXLEN];
1213 char rule[LINE_MAXLEN];
1214 int i, j;
1215 bool_t enabled = FALSE;
1216
1217 /* Get a file pointer */
1218 filename = dStrconcat(dGethomedir(), "/.dillo/cookiesrc", NULL);
1219 stream = Cookies_fopen(filename, "DEFAULT DENY\n");
1220 dFree(filename);
1221
1222 if (!stream)
1223 return 2;
1224
1225 /* Get all lines in the file */
1226 while (!feof(stream)) {
1227 line[0] = '\0';
1228 fgets(line, LINE_MAXLEN, stream);
1229
1230 /* Remove leading and trailing whitespaces */
1231 dStrstrip(line);
1232
1233 if (line[0] != '\0' && line[0] != '#') {
1234 i = 0;
1235 j = 0;
1236
1237 /* Get the domain */
1238 while (!isspace(line[i]))
1239 domain[j++] = line[i++];
1240 domain[j] = '\0';
1241
1242 /* Skip past whitespaces */
1243 i++;
1244 while (isspace(line[i]))
1245 i++;
1246
1247 /* Get the rule */
1248 j = 0;
1249 while (line[i] != '\0' && !isspace(line[i]))
1250 rule[j++] = line[i++];
1251 rule[j] = '\0';
1252
1253 if (dStrcasecmp(rule, "ACCEPT") == 0)
1254 cc.action = COOKIE_ACCEPT;
1255 else if (dStrcasecmp(rule, "ACCEPT_SESSION") == 0)
1256 cc.action = COOKIE_ACCEPT_SESSION;
1257 else if (dStrcasecmp(rule, "DENY") == 0)
1258 cc.action = COOKIE_DENY;
1259 else {
1260 MSG("Cookies: rule '%s' for domain '%s' is not recognised.\n",
1261 rule, domain);
1262 continue;
1263 }
1264
1265 cc.domain = dStrdup(domain);
1266 if (dStrcasecmp(cc.domain, "DEFAULT") == 0) {
1267 /* Set the default action */
1268 default_action = cc.action;
1269 dFree(cc.domain);
1270 } else {
1271 a_List_add(ccontrol, num_ccontrol, num_ccontrol_max);
1272 ccontrol[num_ccontrol++] = cc;
1273 }
1274
1275 if (cc.action != COOKIE_DENY)
1276 enabled = TRUE;
1277 }
1278 }
1279
1280 fclose(stream);
1281
1282 return (enabled ? 0 : 1);
1283 }
1284
1285 /*
1286 * Check the rules for an appropriate action for this domain
1287 */
1288 static CookieControlAction Cookies_control_check_domain(const char *domain)
1289 {
1290 int i, diff;
1291
1292 for (i = 0; i < num_ccontrol; i++) {
1293 if (ccontrol[i].domain[0] == '.') {
1294 diff = strlen(domain) - strlen(ccontrol[i].domain);
1295 if (diff >= 0) {
1296 if (dStrcasecmp(domain + diff, ccontrol[i].domain) != 0)
1297 continue;
1298 } else {
1299 continue;
1300 }
1301 } else {
1302 if (dStrcasecmp(domain, ccontrol[i].domain) != 0)
1303 continue;
1304 }
1305
1306 /* If we got here we have a match */
1307 return( ccontrol[i].action );
1308 }
1309
1310 return default_action;
1311 }
1312
1313 /* -- Dpi parser ----------------------------------------------------------- */
1314
1315 /*
1316 * Parse a data stream (dpi protocol)
1317 * Note: Buf is a zero terminated string
1318 * Return code: { 0:OK, 1:Abort, 2:Close }
1319 */
1320 static int srv_parse_buf(SockHandler *sh, char *Buf, size_t BufSize)
1321 {
1322 char *p, *cmd, *cookie, *host, *path, *scheme;
1323 int port;
1324
1325 if (!(p = strchr(Buf, '>'))) {
1326 /* Haven't got a full tag */
1327 MSG("Haven't got a full tag!\n");
1328 return 1;
1329 }
1330
1331 cmd = a_Dpip_get_attr(Buf, BufSize, "cmd");
1332
1333 if (cmd && strcmp(cmd, "DpiBye") == 0) {
1334 dFree(cmd);
1335 MSG("Cookies dpi (pid %d): Got DpiBye.\n", (int)getpid());
1336 exit(0);
1337
1338 } else if (cmd && strcmp(cmd, "set_cookie") == 0) {
1339 dFree(cmd);
1340 cookie = a_Dpip_get_attr(Buf, BufSize, "cookie");
1341 host = a_Dpip_get_attr(Buf, BufSize, "host");
1342 path = a_Dpip_get_attr(Buf, BufSize, "path");
1343 p = a_Dpip_get_attr(Buf, BufSize, "port");
1344 port = strtol(p, NULL, 10);
1345 dFree(p);
1346
1347 Cookies_set(cookie, host, path, port);
1348
1349 dFree(path);
1350 dFree(host);
1351 dFree(cookie);
1352 return 2;
1353
1354 } else if (cmd && strcmp(cmd, "get_cookie") == 0) {
1355 dFree(cmd);
1356 scheme = a_Dpip_get_attr(Buf, BufSize, "scheme");
1357 host = a_Dpip_get_attr(Buf, BufSize, "host");
1358 path = a_Dpip_get_attr(Buf, BufSize, "path");
1359 p = a_Dpip_get_attr(Buf, BufSize, "port");
1360 port = strtol(p, NULL, 10);
1361 dFree(p);
1362
1363 cookie = Cookies_get(host, path, scheme, port);
1364 dFree(scheme);
1365 dFree(path);
1366 dFree(host);
1367
1368 cmd = a_Dpip_build_cmd("cmd=%s cookie=%s", "get_cookie_answer", cookie);
1369
1370 if (sock_handler_write_str(sh, 1, cmd)) {
1371 dFree(cookie);
1372 dFree(cmd);
1373 return 1;
1374 }
1375 dFree(cookie);
1376 dFree(cmd);
1377
1378 return 2;
1379 }
1380
1381 return 0;
1382 }
1383
1384 /* -- Termination handlers ----------------------------------------------- */
1385 /*
1386 * (was to delete the local namespace socket),
1387 * but this is handled by 'dpid' now.
1388 */
1389 static void cleanup(void)
1390 {
1391 Cookies_save_and_free();
1392 MSG("cleanup\n");
1393 /* no more cleanup required */
1394 }
1395
1396 /*
1397 * Perform any necessary cleanups upon abnormal termination
1398 */
1399 static void termination_handler(int signum)
1400 {
1401 exit(signum);
1402 }
1403
1404
1405 /*
1406 * -- MAIN -------------------------------------------------------------------
1407 */
1408 int main (void) {
1409 struct sockaddr_un spun;
1410 int temp_sock_descriptor;
1411 socklen_t address_size;
1412 char *buf;
1413 int code;
1414 SockHandler *sh;
1415
1416 /* Arrange the cleanup function for terminations via exit() */
1417 atexit(cleanup);
1418
1419 /* Arrange the cleanup function for abnormal terminations */
1420 if (signal (SIGINT, termination_handler) == SIG_IGN)
1421 signal (SIGINT, SIG_IGN);
1422 if (signal (SIGHUP, termination_handler) == SIG_IGN)
1423 signal (SIGHUP, SIG_IGN);
1424 if (signal (SIGTERM, termination_handler) == SIG_IGN)
1425 signal (SIGTERM, SIG_IGN);
1426
1427 Cookies_init();
1428 MSG("(v.1) accepting connections...\n");
1429
1430 if (disabled)
1431 exit(1);
1432
1433 /* some OSes may need this... */
1434 address_size = sizeof(struct sockaddr_un);
1435
1436 while (1) {
1437 temp_sock_descriptor =
1438 accept(STDIN_FILENO, (struct sockaddr *)&spun, &address_size);
1439 if (temp_sock_descriptor == -1) {
1440 perror("[accept]");
1441 exit(1);
1442 }
1443
1444 /* create the SockHandler structure */
1445 sh = sock_handler_new(temp_sock_descriptor,temp_sock_descriptor,8*1024);
1446
1447 while (1) {
1448 code = 1;
1449 if ((buf = sock_handler_read(sh)) != NULL) {
1450 /* Let's see what we fished... */
1451 code = srv_parse_buf(sh, buf, strlen(buf));
1452 }
1453 if (code == 1)
1454 exit(1);
1455 else if (code == 2)
1456 break;
1457 }
1458
1459 _MSG("Closing SockHandler\n");
1460 sock_handler_close(sh);
1461 sock_handler_free(sh);
1462
1463 }/*while*/
1464 }
1465
1466 #endif /* !DISABLE_COOKIES */