Mercurial > dillo_port1.3
view dpi/cookies.c @ 0:6ee11bf9e3ea
Initial revision
author | jcid |
---|---|
date | Sun, 07 Oct 2007 00:36:34 +0200 |
parents | |
children | d9e7b35430de |
line wrap: on
line source
/* * File: cookies.c * Cookies server. * * Copyright 2001 Lars Clausen <lrclause@cs.uiuc.edu> * Jörgen Viksell <jorgen.viksell@telia.com> * Copyright 2002-2006 Jorge Arellano Cid <jcid@dillo.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * */ /* Handling of cookies takes place here. * This implementation aims to follow RFC 2965: * http://www.cis.ohio-state.edu/cs/Services/rfc/rfc-text/rfc2965.txt */ /* * TODO: Cleanup this code. Shorten some functions, order things, * add comments, remove leaks, etc. */ /* Todo: this server is not assembling the received packets. * This means it currently expects dillo to send full dpi tags * within the socket; if that fails, everything stops. */ #ifdef DISABLE_COOKIES int main(void) { return 0; /* never called */ } #else #include <sys/types.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/un.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <stddef.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <time.h> /* for time() and time_t */ #include <ctype.h> #include <netdb.h> #include <signal.h> #include "dpiutil.h" #include "../dpip/dpip.h" /* * Debugging macros */ #define _MSG(...) #define MSG(...) printf("[cookies dpi]: " __VA_ARGS__) /* This one is tricky, some sources state it should include the byte * for the terminating NULL, and others say it shouldn't. */ # define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + strlen ((ptr)->sun_path)) /* * a_List_add() * * Make sure there's space for 'num_items' items within the list * (First, allocate an 'alloc_step' sized chunk, after that, double the * list size --to make it faster) */ #define a_List_add(list,num_items,alloc_step) \ if (!list) { \ list = dMalloc(alloc_step * sizeof((*list))); \ } \ if (num_items >= alloc_step){ \ while ( num_items >= alloc_step ) \ alloc_step <<= 1; \ list = dRealloc(list, alloc_step * sizeof((*list))); \ } /* The maximum length of a line in the cookie file */ #define LINE_MAXLEN 4096 typedef enum { COOKIE_ACCEPT, COOKIE_ACCEPT_SESSION, COOKIE_DENY } CookieControlAction; typedef struct { char *domain; CookieControlAction action; } CookieControl; typedef struct { char *domain; Dlist *dlist; } CookieNode; typedef struct { char *name; char *value; char *domain; char *path; time_t expires_at; uint_t version; char *comment; char *comment_url; bool_t secure; bool_t session_only; Dlist *ports; } CookieData_t; /* * Local data */ /* List of CookieNode. Each node holds a domain and its list of cookies */ static Dlist *cookies; /* Variables for access control */ static CookieControl *ccontrol = NULL; static int num_ccontrol = 0; static int num_ccontrol_max = 1; static CookieControlAction default_action = COOKIE_DENY; static bool_t disabled; static FILE *file_stream; static char *cookies_txt_header_str = "# HTTP Cookie File\n" "# http://www.netscape.com/newsref/std/cookie_spec.html\n" "# This is a generated file! Do not edit.\n\n"; /* * Forward declarations */ static FILE *Cookies_fopen(const char *file, char *init_str); static CookieControlAction Cookies_control_check_domain(const char *domain); static int Cookie_control_init(void); static void Cookies_parse_ports(int url_port, CookieData_t *cookie, const char *port_str); static char *Cookies_build_ports_str(CookieData_t *cookie); static char *Cookies_strip_path(const char *path); static void Cookies_add_cookie(CookieData_t *cookie); static void Cookies_remove_cookie(CookieData_t *cookie); static int Cookies_cmp(const void *a, const void *b); /* * Compare function for searching a cookie node */ int Cookie_node_cmp(const void *v1, const void *v2) { const CookieNode *n1 = v1, *n2 = v2; return strcmp(n1->domain, n2->domain); } /* * Compare function for searching a cookie node by domain */ int Cookie_node_by_domain_cmp(const void *v1, const void *v2) { const CookieNode *node = v1; const char *domain = v2; return strcmp(node->domain, domain); } /* * Return a file pointer. If the file doesn't exist, try to create it, * with the optional 'init_str' as its content. */ static FILE *Cookies_fopen(const char *filename, char *init_str) { FILE *F_in; int fd; if ((F_in = fopen(filename, "r+")) == NULL) { /* Create the file */ fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fd != -1) { if (init_str) write(fd, init_str, strlen(init_str)); close(fd); MSG("Created file: %s\n", filename); F_in = Cookies_fopen(filename, NULL); } else { MSG("Could not create file: %s!\n", filename); } } /* set close on exec */ fcntl(fileno(F_in), F_SETFD, FD_CLOEXEC | fcntl(fileno(F_in), F_GETFD)); return F_in; } static void Cookies_free_cookie(CookieData_t *cookie) { dFree(cookie->name); dFree(cookie->value); dFree(cookie->domain); dFree(cookie->path); dFree(cookie->comment); dFree(cookie->comment_url); dList_free(cookie->ports); dFree(cookie); } /* * Initialize the cookies module * (The 'disabled' variable is writable only within Cookies_init) */ void Cookies_init() { CookieData_t *cookie; char *filename; char line[LINE_MAXLEN]; #ifndef HAVE_LOCKF struct flock lck; #endif FILE *old_cookies_file_stream; /* Default setting */ disabled = TRUE; /* Read and parse the cookie control file (cookiesrc) */ if (Cookie_control_init() != 0) { MSG("Disabling cookies.\n"); return; } /* Get a stream for the cookies file */ filename = dStrconcat(dGethomedir(), "/.dillo/cookies.txt", NULL); file_stream = Cookies_fopen(filename, cookies_txt_header_str); dFree(filename); if (!file_stream) { MSG("ERROR: Can't open ~/.dillo/cookies.txt, disabling cookies\n"); return; } /* Try to get a lock from the file descriptor */ #ifdef HAVE_LOCKF disabled = (lockf(fileno(file_stream), F_TLOCK, 0) == -1); #else /* POSIX lock */ lck.l_start = 0; /* start at beginning of file */ lck.l_len = 0; /* lock entire file */ lck.l_type = F_WRLCK; lck.l_whence = SEEK_SET; /* absolute offset */ disabled = (fcntl(fileno(file_stream), F_SETLK, &lck) == -1); #endif if (disabled) { MSG("The cookies file has a file lock: disabling cookies!\n"); fclose(file_stream); return; } MSG("Enabling cookies as from cookiesrc...\n"); cookies = dList_new(32); /* Get all lines in the file */ while (!feof(file_stream)) { line[0] = '\0'; fgets(line, LINE_MAXLEN, file_stream); /* Remove leading and trailing whitespaces */ dStrstrip(line); if ((line[0] != '\0') && (line[0] != '#')) { /* * Split the row into pieces using a tab as the delimiter. * pieces[0] The domain name * pieces[1] TRUE/FALSE: is the domain a suffix, or a full domain? * pieces[2] The path * pieces[3] Is the cookie unsecure or secure (TRUE/FALSE) * pieces[4] Timestamp of expire date * pieces[5] Name of the cookie * pieces[6] Value of the cookie */ CookieControlAction action; char *piece; char *line_marker = line; cookie = dNew0(CookieData_t, 1); cookie->session_only = FALSE; cookie->version = 0; cookie->domain = dStrdup(dStrsep(&line_marker, "\t")); dStrsep(&line_marker, "\t"); /* we use domain always as sufix */ cookie->path = dStrdup(dStrsep(&line_marker, "\t")); piece = dStrsep(&line_marker, "\t"); if (piece != NULL && piece[0] == 'T') cookie->secure = TRUE; piece = dStrsep(&line_marker, "\t"); if (piece != NULL) cookie->expires_at = (time_t) strtol(piece, NULL, 10); cookie->name = dStrdup(dStrsep(&line_marker, "\t")); cookie->value = dStrdup(dStrsep(&line_marker, "\t")); if (!cookie->domain || cookie->domain[0] == '\0' || !cookie->path || cookie->path[0] != '/' || !cookie->name || cookie->name[0] == '\0' || !cookie->value) { MSG("Malformed line in cookies.txt file!\n"); Cookies_free_cookie(cookie); continue; } action = Cookies_control_check_domain(cookie->domain); if (action == COOKIE_DENY) { Cookies_free_cookie(cookie); continue; } else if (action == COOKIE_ACCEPT_SESSION) { cookie->session_only = TRUE; } /* Save cookie in memory */ Cookies_add_cookie(cookie); } } filename = dStrconcat(dGethomedir(), "/.dillo/cookies", NULL); if ((old_cookies_file_stream = fopen(filename, "r")) != NULL) { dFree(filename); MSG("WARNING: Reading old cookies file ~/.dillo/cookies too\n"); /* Get all lines in the file */ while (!feof(old_cookies_file_stream)) { line[0] = '\0'; fgets(line, LINE_MAXLEN, old_cookies_file_stream); /* Remove leading and trailing whitespaces */ dStrstrip(line); if (line[0] != '\0') { /* * Split the row into pieces using a tab as the delimiter. * pieces[0] The version this cookie was set as (0 / 1) * pieces[1] The domain name * pieces[2] A comma separated list of accepted ports * pieces[3] The path * pieces[4] Is the cookie unsecure or secure (0 / 1) * pieces[5] Timestamp of expire date * pieces[6] Name of the cookie * pieces[7] Value of the cookie * pieces[8] Comment * pieces[9] Comment url */ CookieControlAction action; char *piece; char *line_marker = line; cookie = dNew0(CookieData_t, 1); cookie->session_only = FALSE; piece = dStrsep(&line_marker, "\t"); if (piece != NULL) cookie->version = strtol(piece, NULL, 10); cookie->domain = dStrdup(dStrsep(&line_marker, "\t")); Cookies_parse_ports(0, cookie, dStrsep(&line_marker, "\t")); cookie->path = dStrdup(dStrsep(&line_marker, "\t")); piece = dStrsep(&line_marker, "\t"); if (piece != NULL && piece[0] == '1') cookie->secure = TRUE; piece = dStrsep(&line_marker, "\t"); if (piece != NULL) cookie->expires_at = (time_t) strtol(piece, NULL, 10); cookie->name = dStrdup(dStrsep(&line_marker, "\t")); cookie->value = dStrdup(dStrsep(&line_marker, "\t")); cookie->comment = dStrdup(dStrsep(&line_marker, "\t")); cookie->comment_url = dStrdup(dStrsep(&line_marker, "\t")); if (!cookie->domain || cookie->domain[0] == '\0' || !cookie->path || cookie->path[0] != '/' || !cookie->name || cookie->name[0] == '\0' || !cookie->value) { MSG("Malformed line in cookies file!\n"); Cookies_free_cookie(cookie); continue; } action = Cookies_control_check_domain(cookie->domain); if (action == COOKIE_DENY) { Cookies_free_cookie(cookie); continue; } else if (action == COOKIE_ACCEPT_SESSION) { cookie->session_only = TRUE; } /* Save cookie in memory */ Cookies_add_cookie(cookie); } } fclose(old_cookies_file_stream); } else { dFree(filename); } } /* * Flush cookies to disk and free all the memory allocated. */ void Cookies_save_and_free() { int i, fd; CookieNode *node; CookieData_t *cookie; #ifndef HAVE_LOCKF struct flock lck; #endif if (disabled) return; rewind(file_stream); fd = fileno(file_stream); ftruncate(fd, 0); fprintf(file_stream, cookies_txt_header_str); /* Iterate cookies per domain, saving and freeing */ while ((node = dList_nth_data(cookies, 0))) { for (i = 0; (cookie = dList_nth_data(node->dlist, i)); ++i) { if (!cookie->session_only) { /* char * ports_str = Cookies_build_ports_str(cookie); */ fprintf(file_stream, "%s\tTRUE\t%s\t%s\t%ld\t%s\t%s\n", cookie->domain, cookie->path, cookie->secure ? "TRUE" : "FALSE", (long)cookie->expires_at, cookie->name, cookie->value); /* dFree(ports_str); */ } Cookies_free_cookie(cookie); } dList_remove(cookies, node); dFree(node->domain); dList_free(node->dlist); dFree(node); } #ifdef HAVE_LOCKF lockf(fd, F_ULOCK, 0); #else /* POSIX file lock */ lck.l_start = 0; /* start at beginning of file */ lck.l_len = 0; /* lock entire file */ lck.l_type = F_UNLCK; lck.l_whence = SEEK_SET; /* absolute offset */ fcntl(fileno(file_stream), F_SETLKW, &lck); #endif fclose(file_stream); } static char *months[] = { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; /* * Take a months name and return a number between 1-12. * E.g. 'April' -> 4 */ static int Cookies_get_month(const char *month_name) { int i; for (i = 1; i <= 12; i++) { if (!dStrncasecmp(months[i], month_name, 3)) return i; } return 0; } /* * Return a local timestamp from a GMT date string * Accept: RFC-1123 | RFC-850 | ANSI asctime | Old Netscape format. * * Wdy, DD-Mon-YY HH:MM:SS GMT * Wdy, DD-Mon-YYYY HH:MM:SS GMT * Weekday, DD-Mon-YY HH:MM:SS GMT * Weekday, DD-Mon-YYYY HH:MM:SS GMT * Tue May 21 13:46:22 1991\n * Tue May 21 13:46:22 1991 * * (return 0 on malformed date string syntax) */ static time_t Cookies_create_timestamp(const char *expires) { time_t ret; int day, month, year, hour, minutes, seconds; char *cp; char *E_msg = "Expire date is malformed!\n" " (should be RFC-1123 | RFC-850 | ANSI asctime)\n" " Ignoring cookie: "; cp = strchr(expires, ','); if (!cp && (strlen(expires) == 24 || strlen(expires) == 25)) { /* Looks like ANSI asctime format... */ cp = (char *)expires; day = strtol(cp + 8, NULL, 10); /* day */ month = Cookies_get_month(cp + 4); /* month */ year = strtol(cp + 20, NULL, 10); /* year */ hour = strtol(cp + 11, NULL, 10); /* hour */ minutes = strtol(cp + 14, NULL, 10); /* minutes */ seconds = strtol(cp + 17, NULL, 10); /* seconds */ } else if (cp && (cp - expires == 3 || cp - expires > 5) && (strlen(cp) == 24 || strlen(cp) == 26)) { /* RFC-1123 | RFC-850 format | Old Netscape format */ day = strtol(cp + 2, NULL, 10); month = Cookies_get_month(cp + 5); year = strtol(cp + 9, &cp, 10); /* todo: tricky, because two digits for year IS ambiguous! */ year += (year < 70) ? 2000 : ((year < 100) ? 1900 : 0); hour = strtol(cp + 1, NULL, 10); minutes = strtol(cp + 4, NULL, 10); seconds = strtol(cp + 7, NULL, 10); } else { MSG("%s%s\n", E_msg, expires); return (time_t) 0; } /* Error checks --this may be overkill */ if (!(day > 0 && day < 32 && month > 0 && month < 13 && year > 1970 && hour >= 0 && hour < 24 && minutes >= 0 && minutes < 60 && seconds >= 0 && seconds < 60)) { MSG("%s%s\n", E_msg, expires); return (time_t) 0; } /* Calculate local timestamp. * [stolen from Lynx... (http://lynx.browser.org)] */ month -= 3; if (month < 0) { month += 12; year--; } day += (year - 1968) * 1461 / 4; day += ((((month * 153) + 2) / 5) - 672); ret = (time_t)((day * 60 * 60 * 24) + (hour * 60 * 60) + (minutes * 60) + seconds); MSG("Expires in %ld seconds, at %s", (long)ret - time(NULL), ctime(&ret)); return ret; } /* * Parse a string containing a list of port numbers. */ static void Cookies_parse_ports(int url_port, CookieData_t *cookie, const char *port_str) { if ((!port_str || !port_str[0]) && url_port != 0) { /* There was no list, so only the calling urls port should be allowed. */ if (!cookie->ports) cookie->ports = dList_new(1); dList_append(cookie->ports, INT2VOIDP(url_port)); } else if (port_str[0] == '"' && port_str[1] != '"') { char *tok, *str; int port; str = dStrdup(port_str + 1); while ((tok = dStrsep(&str, ","))) { port = strtol(tok, NULL, 10); if (port > 0) { if (!cookie->ports) cookie->ports = dList_new(1); dList_append(cookie->ports, INT2VOIDP(port)); } } dFree(str); } } /* * Build a string of the ports in 'cookie'. */ static char *Cookies_build_ports_str(CookieData_t *cookie) { Dstr *dstr; char *ret; void *data; int i; dstr = dStr_new("\""); for (i = 0; (data = dList_nth_data(cookie->ports, i)); ++i) { dStr_sprintfa(dstr, "%d,", VOIDP2INT(data)); } /* Remove any trailing comma */ if (dstr->len > 1) dStr_erase(dstr, dstr->len - 1, 1); dStr_append(dstr, "\""); ret = dstr->str; dStr_free(dstr, FALSE); return ret; } static void Cookies_add_cookie(CookieData_t *cookie) { Dlist *domain_cookies; CookieData_t *c; CookieNode *node; /* Don't add an expired cookie */ if (!cookie->session_only && cookie->expires_at < time(NULL)) { Cookies_free_cookie(cookie); return; } node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp); domain_cookies = (node) ? node->dlist : NULL; if (domain_cookies) { /* Respect the limit of 20 cookies per domain */ if (dList_length(domain_cookies) >= 20) { MSG("There are too many cookies for this domain (%s)\n", cookie->domain); Cookies_free_cookie(cookie); return; } /* Remove any cookies with the same name and path */ while ((c = dList_find_custom(domain_cookies, cookie, Cookies_cmp))){ Cookies_remove_cookie(c); } } /* add the cookie into the respective domain list */ node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp); domain_cookies = (node) ? node->dlist : NULL; if (!domain_cookies) { domain_cookies = dList_new(5); dList_append(domain_cookies, cookie); node = dNew(CookieNode, 1); node->domain = dStrdup(cookie->domain); node->dlist = domain_cookies; dList_insert_sorted(cookies, node, Cookie_node_cmp); } else { dList_append(domain_cookies, cookie); } } /* * Remove the cookie from the domain list. * If the domain list is empty, remove the node too. * Free the cookie. */ static void Cookies_remove_cookie(CookieData_t *cookie) { CookieNode *node; node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp); if (node) { dList_remove(node->dlist, cookie); if (dList_length(node->dlist) == 0) { dList_remove(cookies, node); dFree(node->domain); dList_free(node->dlist); } } else { MSG("Attempting to remove a cookie that doesn't exist!\n"); } Cookies_free_cookie(cookie); } /* * Return the attribute that is present at *cookie_str. This function * will also attempt to advance cookie_str past any equal-sign. */ static char *Cookies_parse_attr(char **cookie_str) { char *str = *cookie_str; uint_t i, end = 0; bool_t got_attr = FALSE; for (i = 0; ; i++) { switch (str[i]) { case ' ': case '\t': case '=': case ';': got_attr = TRUE; if (end == 0) end = i; break; case ',': *cookie_str = str + i; return dStrndup(str, i); break; case '\0': if (!got_attr) { end = i; got_attr = TRUE; } /* fall through! */ default: if (got_attr) { *cookie_str = str + i; return dStrndup(str, end); } break; } } return NULL; } /* * Get the value starting at *cookie_str. * broken_syntax: watch out for stupid syntax (comma in unquoted string...) */ static char *Cookies_parse_value(char **cookie_str, bool_t broken_syntax, bool_t keep_quotes) { uint_t i, end; char *str = *cookie_str; for (i = end = 0; !end; ++i) { switch (str[i]) { case ' ': case '\t': if (!broken_syntax && str[0] != '\'' && str[0] != '"') { *cookie_str = str + i + 1; end = 1; } break; case '\'': case '"': if (i != 0 && str[i] == str[0]) { char *tmp = str + i; while (*tmp != '\0' && *tmp != ';' && *tmp != ',') tmp++; *cookie_str = (*tmp == ';') ? tmp + 1 : tmp; if (keep_quotes) i++; end = 1; } break; case '\0': *cookie_str = str + i; end = 1; break; case ',': if (str[0] != '\'' && str[0] != '"' && !broken_syntax) { /* A new cookie starts here! */ *cookie_str = str + i; end = 1; } break; case ';': if (str[0] != '\'' && str[0] != '"') { *cookie_str = str + i + 1; end = 1; } break; default: break; } } /* keep i as an index to the last char */ --i; if ((str[0] == '\'' || str[0] == '"') && !keep_quotes) { return i > 1 ? dStrndup(str + 1, i - 1) : NULL; } else { return dStrndup(str, i); } } /* * Parse one cookie... */ static CookieData_t *Cookies_parse_one(int url_port, char **cookie_str) { CookieData_t *cookie; char *str = *cookie_str; char *attr; char *value; int num_attr = 0; bool_t max_age = FALSE; bool_t discard = FALSE; cookie = dNew0(CookieData_t, 1); cookie->session_only = TRUE; /* Iterate until there is nothing left of the string OR we come * across a comma representing the start of another cookie */ while (*str != '\0' && *str != ',') { /* Skip whitespace */ while (isspace(*str)) str++; /* Get attribute */ attr = Cookies_parse_attr(&str); if (!attr) { MSG("Failed to parse cookie attribute!\n"); Cookies_free_cookie(cookie); return NULL; } /* Get the value for the attribute and store it */ if (num_attr == 0) { /* The first attr, which always is the user supplied attr, may * have the same name as an ordinary attr. Hence this workaround. */ cookie->name = dStrdup(attr); cookie->value = Cookies_parse_value(&str, FALSE, TRUE); } else if (dStrcasecmp(attr, "Path") == 0) { value = Cookies_parse_value(&str, FALSE, FALSE); cookie->path = value; } else if (dStrcasecmp(attr, "Domain") == 0) { value = Cookies_parse_value(&str, FALSE, FALSE); cookie->domain = value; } else if (dStrcasecmp(attr, "Discard") == 0) { cookie->session_only = TRUE; discard = TRUE; } else if (dStrcasecmp(attr, "Max-Age") == 0) { if (!discard) { value = Cookies_parse_value(&str, FALSE, FALSE); if (value) { cookie->expires_at = time(NULL) + strtol(value, NULL, 10); cookie->session_only = FALSE; max_age = TRUE; dFree(value); } else { MSG("Failed to parse cookie value!\n"); Cookies_free_cookie(cookie); return NULL; } } } else if (dStrcasecmp(attr, "Expires") == 0) { if (!max_age && !discard) { MSG("Old netscape-style cookie...\n"); value = Cookies_parse_value(&str, TRUE, FALSE); if (value) { cookie->expires_at = Cookies_create_timestamp(value); cookie->session_only = FALSE; dFree(value); } else { MSG("Failed to parse cookie value!\n"); Cookies_free_cookie(cookie); return NULL; } } } else if (dStrcasecmp(attr, "Port") == 0) { value = Cookies_parse_value(&str, FALSE, TRUE); Cookies_parse_ports(url_port, cookie, value); dFree(value); } else if (dStrcasecmp(attr, "Comment") == 0) { value = Cookies_parse_value(&str, FALSE, FALSE); cookie->comment = value; } else if (dStrcasecmp(attr, "CommentURL") == 0) { value = Cookies_parse_value(&str, FALSE, FALSE); cookie->comment_url = value; } else if (dStrcasecmp(attr, "Version") == 0) { value = Cookies_parse_value(&str, FALSE, FALSE); if (value) { cookie->version = strtol(value, NULL, 10); dFree(value); } else { MSG("Failed to parse cookie value!\n"); Cookies_free_cookie(cookie); return NULL; } } else if (dStrcasecmp(attr, "Secure") == 0) { cookie->secure = TRUE; } else { /* Oops! this can't be good... */ dFree(attr); Cookies_free_cookie(cookie); MSG("Cookie contains illegal attribute!\n"); return NULL; } dFree(attr); num_attr++; } *cookie_str = (*str == ',') ? str + 1 : str; if (cookie->name && cookie->value) { return cookie; } else { MSG("Cookie missing name and/or value!\n"); Cookies_free_cookie(cookie); return NULL; } } /* * Iterate the cookie string until we catch all cookies. * Return Value: a list with all the cookies! (or NULL upon error) */ static Dlist *Cookies_parse_string(int url_port, char *cookie_string) { CookieData_t *cookie; Dlist *ret = NULL; char *str = cookie_string; /* The string may contain several cookies separated by comma. * We'll iterate until we've catched them all */ while (*str) { cookie = Cookies_parse_one(url_port, &str); if (cookie) { if (!ret) ret = dList_new(4); dList_append(ret, cookie); } else { MSG("Malformed cookie field, ignoring cookie: %s\n", cookie_string); } } return ret; } /* * Compare cookies by name and path (return 0 if equal) */ static int Cookies_cmp(const void *a, const void *b) { const CookieData_t *ca = a, *cb = b; int ret; if (!(ret = strcmp(ca->name, cb->name))) ret = strcmp(ca->path, cb->path); return ret; } /* * Validate cookies domain against some security checks. */ static bool_t Cookies_validate_domain(CookieData_t *cookie, char *host, char *url_path) { int dots, diff, i; bool_t is_ip; /* Make sure that the path is set to something */ if (!cookie->path || cookie->path[0] != '/') { dFree(cookie->path); cookie->path = Cookies_strip_path(url_path); } /* If the server never set a domain, or set one without a leading * dot (which isn't allowed), we use the calling URL's hostname. */ if (cookie->domain == NULL || cookie->domain[0] != '.') { dFree(cookie->domain); cookie->domain = dStrdup(host); return TRUE; } /* Count the number of dots and also find out if it is an IP-address */ is_ip = TRUE; for (i = 0, dots = 0; cookie->domain[i] != '\0'; i++) { if (cookie->domain[i] == '.') dots++; else if (!isdigit(cookie->domain[i])) is_ip = FALSE; } /* A valid domain must have at least two dots in it */ /* NOTE: this breaks cookies on localhost... */ if (dots < 2) { return FALSE; } /* Now see if the url matches the domain */ diff = strlen(host) - i; if (diff > 0) { if (dStrcasecmp(host + diff, cookie->domain)) return FALSE; if (!is_ip) { /* "x.y.test.com" is not allowed to set cookies for ".test.com"; * only an url of the form "y.test.com" would be. */ while ( diff-- ) if (host[diff] == '.') return FALSE; } } return TRUE; } /* * Strip of the filename from a full path */ static char *Cookies_strip_path(const char *path) { char *ret; uint_t len; if (path) { len = strlen(path); while (len && path[len] != '/') len--; ret = dStrndup(path, len + 1); } else { ret = dStrdup("/"); } return ret; } /* * Set the value corresponding to the cookie string */ void Cookies_set(char *cookie_string, char *url_host, char *url_path, int url_port) { CookieControlAction action; CookieData_t *cookie; Dlist *list; int i; if (disabled) return; action = Cookies_control_check_domain(url_host); if (action == COOKIE_DENY) { MSG("denied SET for %s\n", url_host); return; } if ((list = Cookies_parse_string(url_port, cookie_string))) { for (i = 0; (cookie = dList_nth_data(list, i)); ++i) { if (Cookies_validate_domain(cookie, url_host, url_path)) { if (action == COOKIE_ACCEPT_SESSION) cookie->session_only = TRUE; Cookies_add_cookie(cookie); } else { MSG("Rejecting cookie for %s from host %s path %s\n", cookie->domain, url_host, url_path); Cookies_free_cookie(cookie); } } dList_free(list); } } /* * Compare the cookie with the supplied data to see if it matches */ static bool_t Cookies_match(CookieData_t *cookie, int port, const char *path, bool_t is_ssl) { void *data; int i; /* Insecure cookies matches both secure and insecure urls, secure cookies matches only secure urls */ if (cookie->secure && !is_ssl) return FALSE; /* Check that the cookie path is a subpath of the current path */ if (strncmp(cookie->path, path, strlen(cookie->path)) != 0) return FALSE; /* Check if the port of the request URL matches any * of those set in the cookie */ if (cookie->ports) { for (i = 0; (data = dList_nth_data(cookie->ports, i)); ++i) { if (VOIDP2INT(data) == port) return TRUE; } return FALSE; } /* It's a match */ return TRUE; } /* * Return a string that contains all relevant cookies as headers. */ char *Cookies_get(char *url_host, char *url_path, char *url_scheme, int url_port) { char *domain_str, *q, *str, *path; CookieData_t *cookie; Dlist *matching_cookies; CookieNode *node; Dlist *domain_cookies; bool_t is_ssl; Dstr *cookie_dstring; int i; if (disabled) return dStrdup(""); matching_cookies = dList_new(8); path = Cookies_strip_path(url_path); /* Check if the protocol is secure or not */ is_ssl = (!dStrcasecmp(url_scheme, "https")); for (domain_str = (char *) url_host; domain_str != NULL && *domain_str; domain_str = strchr(domain_str+1, '.')) { node = dList_find_sorted(cookies, domain_str, Cookie_node_by_domain_cmp); domain_cookies = (node) ? node->dlist : NULL; for (i = 0; (cookie = dList_nth_data(domain_cookies, i)); ++i) { /* Remove expired cookie. */ if (!cookie->session_only && cookie->expires_at < time(NULL)) { Cookies_remove_cookie(cookie); --i; continue; } /* Check if the cookie matches the requesting URL */ if (Cookies_match(cookie, url_port, path, is_ssl)) { dList_append(matching_cookies, cookie); } } } /* Found the cookies, now make the string */ cookie_dstring = dStr_new(""); if (dList_length(matching_cookies) > 0) { CookieData_t *first_cookie = dList_nth_data(matching_cookies, 0); dStr_sprintfa(cookie_dstring, "Cookie: "); if (first_cookie->version != 0) dStr_sprintfa(cookie_dstring, "$Version=\"%d\"; ", first_cookie->version); for (i = 0; (cookie = dList_nth_data(matching_cookies, i)); ++i) { q = (cookie->version == 0 ? "" : "\""); dStr_sprintfa(cookie_dstring, "%s=%s; $Path=%s%s%s; $Domain=%s%s%s", cookie->name, cookie->value, q, cookie->path, q, q, cookie->domain, q); if (cookie->ports) { char *ports_str = Cookies_build_ports_str(cookie); dStr_sprintfa(cookie_dstring, "; $Port=%s", ports_str); dFree(ports_str); } dStr_append(cookie_dstring, dList_length(matching_cookies) > i + 1 ? "; " : "\r\n"); } } dList_free(matching_cookies); dFree(path); str = cookie_dstring->str; dStr_free(cookie_dstring, FALSE); return str; } /* ------------------------------------------------------------- * Access control routines * ------------------------------------------------------------- */ /* * Get the cookie control rules (from cookiesrc). * Return value: * 0 = Parsed OK, with cookies enabled * 1 = Parsed OK, with cookies disabled * 2 = Can't open the control file */ static int Cookie_control_init(void) { CookieControl cc; FILE *stream; char *filename; char line[LINE_MAXLEN]; char domain[LINE_MAXLEN]; char rule[LINE_MAXLEN]; int i, j; bool_t enabled = FALSE; /* Get a file pointer */ filename = dStrconcat(dGethomedir(), "/.dillo/cookiesrc", NULL); stream = Cookies_fopen(filename, "DEFAULT DENY\n"); dFree(filename); if (!stream) return 2; /* Get all lines in the file */ while (!feof(stream)) { line[0] = '\0'; fgets(line, LINE_MAXLEN, stream); /* Remove leading and trailing whitespaces */ dStrstrip(line); if (line[0] != '\0' && line[0] != '#') { i = 0; j = 0; /* Get the domain */ while (!isspace(line[i])) domain[j++] = line[i++]; domain[j] = '\0'; /* Skip past whitespaces */ i++; while (isspace(line[i])) i++; /* Get the rule */ j = 0; while (line[i] != '\0' && !isspace(line[i])) rule[j++] = line[i++]; rule[j] = '\0'; if (dStrcasecmp(rule, "ACCEPT") == 0) cc.action = COOKIE_ACCEPT; else if (dStrcasecmp(rule, "ACCEPT_SESSION") == 0) cc.action = COOKIE_ACCEPT_SESSION; else if (dStrcasecmp(rule, "DENY") == 0) cc.action = COOKIE_DENY; else { MSG("Cookies: rule '%s' for domain '%s' is not recognised.\n", rule, domain); continue; } cc.domain = dStrdup(domain); if (dStrcasecmp(cc.domain, "DEFAULT") == 0) { /* Set the default action */ default_action = cc.action; dFree(cc.domain); } else { a_List_add(ccontrol, num_ccontrol, num_ccontrol_max); ccontrol[num_ccontrol++] = cc; } if (cc.action != COOKIE_DENY) enabled = TRUE; } } fclose(stream); return (enabled ? 0 : 1); } /* * Check the rules for an appropriate action for this domain */ static CookieControlAction Cookies_control_check_domain(const char *domain) { int i, diff; for (i = 0; i < num_ccontrol; i++) { if (ccontrol[i].domain[0] == '.') { diff = strlen(domain) - strlen(ccontrol[i].domain); if (diff >= 0) { if (dStrcasecmp(domain + diff, ccontrol[i].domain) != 0) continue; } else { continue; } } else { if (dStrcasecmp(domain, ccontrol[i].domain) != 0) continue; } /* If we got here we have a match */ return( ccontrol[i].action ); } return default_action; } /* -- Dpi parser ----------------------------------------------------------- */ /* * Parse a data stream (dpi protocol) * Note: Buf is a zero terminated string * Return code: { 0:OK, 1:Abort, 2:Close } */ static int srv_parse_buf(SockHandler *sh, char *Buf, size_t BufSize) { char *p, *cmd, *cookie, *host, *path, *scheme; int port; if (!(p = strchr(Buf, '>'))) { /* Haven't got a full tag */ MSG("Haven't got a full tag!\n"); return 1; } cmd = a_Dpip_get_attr(Buf, BufSize, "cmd"); if (cmd && strcmp(cmd, "DpiBye") == 0) { dFree(cmd); MSG("Cookies dpi (pid %d): Got DpiBye.\n", (int)getpid()); exit(0); } else if (cmd && strcmp(cmd, "set_cookie") == 0) { dFree(cmd); cookie = a_Dpip_get_attr(Buf, BufSize, "cookie"); host = a_Dpip_get_attr(Buf, BufSize, "host"); path = a_Dpip_get_attr(Buf, BufSize, "path"); p = a_Dpip_get_attr(Buf, BufSize, "port"); port = strtol(p, NULL, 10); dFree(p); Cookies_set(cookie, host, path, port); dFree(path); dFree(host); dFree(cookie); return 2; } else if (cmd && strcmp(cmd, "get_cookie") == 0) { dFree(cmd); scheme = a_Dpip_get_attr(Buf, BufSize, "scheme"); host = a_Dpip_get_attr(Buf, BufSize, "host"); path = a_Dpip_get_attr(Buf, BufSize, "path"); p = a_Dpip_get_attr(Buf, BufSize, "port"); port = strtol(p, NULL, 10); dFree(p); cookie = Cookies_get(host, path, scheme, port); dFree(scheme); dFree(path); dFree(host); cmd = a_Dpip_build_cmd("cmd=%s cookie=%s", "get_cookie_answer", cookie); if (sock_handler_write_str(sh, 1, cmd)) { dFree(cookie); dFree(cmd); return 1; } dFree(cookie); dFree(cmd); return 2; } return 0; } /* -- Termination handlers ----------------------------------------------- */ /* * (was to delete the local namespace socket), * but this is handled by 'dpid' now. */ static void cleanup(void) { Cookies_save_and_free(); MSG("cleanup\n"); /* no more cleanup required */ } /* * Perform any necessary cleanups upon abnormal termination */ static void termination_handler(int signum) { exit(signum); } /* * -- MAIN ------------------------------------------------------------------- */ int main (void) { struct sockaddr_un spun; int temp_sock_descriptor; socklen_t address_size; char *buf; int code; SockHandler *sh; /* Arrange the cleanup function for terminations via exit() */ atexit(cleanup); /* Arrange the cleanup function for abnormal terminations */ if (signal (SIGINT, termination_handler) == SIG_IGN) signal (SIGINT, SIG_IGN); if (signal (SIGHUP, termination_handler) == SIG_IGN) signal (SIGHUP, SIG_IGN); if (signal (SIGTERM, termination_handler) == SIG_IGN) signal (SIGTERM, SIG_IGN); Cookies_init(); MSG("(v.1) accepting connections...\n"); if (disabled) exit(1); /* some OSes may need this... */ address_size = sizeof(struct sockaddr_un); while (1) { temp_sock_descriptor = accept(STDIN_FILENO, (struct sockaddr *)&spun, &address_size); if (temp_sock_descriptor == -1) { perror("[accept]"); exit(1); } /* create the SockHandler structure */ sh = sock_handler_new(temp_sock_descriptor,temp_sock_descriptor,8*1024); while (1) { code = 1; if ((buf = sock_handler_read(sh)) != NULL) { /* Let's see what we fished... */ code = srv_parse_buf(sh, buf, strlen(buf)); } if (code == 1) exit(1); else if (code == 2) break; } _MSG("Closing SockHandler\n"); sock_handler_close(sh); sock_handler_free(sh); }/*while*/ } #endif /* !DISABLE_COOKIES */