Kannel: Open Source WAP and SMS gateway  $Revision: 5037 $
cookies.c
Go to the documentation of this file.
1 /* ====================================================================
2  * The Kannel Software License, Version 1.0
3  *
4  * Copyright (c) 2001-2016 Kannel Group
5  * Copyright (c) 1998-2001 WapIT Ltd.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  * notice, this list of conditions and the following disclaimer in
17  * the documentation and/or other materials provided with the
18  * distribution.
19  *
20  * 3. The end-user documentation included with the redistribution,
21  * if any, must include the following acknowledgment:
22  * "This product includes software developed by the
23  * Kannel Group (http://www.kannel.org/)."
24  * Alternately, this acknowledgment may appear in the software itself,
25  * if and wherever such third-party acknowledgments normally appear.
26  *
27  * 4. The names "Kannel" and "Kannel Group" must not be used to
28  * endorse or promote products derived from this software without
29  * prior written permission. For written permission, please
30  * contact org@kannel.org.
31  *
32  * 5. Products derived from this software may not be called "Kannel",
33  * nor may "Kannel" appear in their name, without prior written
34  * permission of the Kannel Group.
35  *
36  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39  * DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS
40  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
41  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
42  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
43  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
44  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
45  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
46  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Kannel Group. For more information on
51  * the Kannel Group, please see <http://www.kannel.org/>.
52  *
53  * Portions of this software are based upon software originally written at
54  * WapIT Ltd., Helsinki, Finland for the Kannel project.
55  */
56 
57 /*
58  * Module: cookies.c
59  *
60  * Description: Implements a minimal cookie handler for session persistence.
61  *
62  * References: RFC 2109
63  *
64  * Author: Paul Keogh, ANAM Wireless Internet Solutions
65  *
66  * Date: May 2000
67  */
68 
69 #include <string.h>
70 #include <ctype.h>
71 
72 #include "gwlib/gwlib.h"
73 #include "wsp.h"
74 #include "cookies.h"
75 
76 /* Statics */
77 
79 static Cookie *parse_cookie(Octstr*);
80 static void add_cookie_to_cache(const WSPMachine*, Cookie*);
81 static void expire_cookies(List*);
82 static void cookie_destroy(void*);
83 static int have_cookie(List*, Cookie*);
84 static int parse_http_date(const char*);
86 
87 
89 {
90  Cookie *p;
91 
92  p = gw_malloc(sizeof(Cookie)); /* Never returns NULL */
93 
94  *p = emptyCookie;
95  p -> max_age = -1;
96  time (&p -> birth);
97  return p;
98 }
99 
100 void cookies_destroy(List *cookies)
101 {
103 
104  if (cookies == NULL)
105  return;
106 
107  gwlist_destroy(cookies, cookie_destroy);
108 }
109 
110 
111 int get_cookies(List *headers, const WSPMachine *sm)
112 {
113  Octstr *header = NULL;
114  Octstr *value = NULL;
115  Cookie *cookie = NULL;
116  long pos = 0;
117 
118  /*
119  * This can happen if the user aborts while the HTTP request is pending from the server.
120  * In that case, the session machine is destroyed and is not available to this function
121  * for cookie caching.
122  */
123 
124  if (sm == NULL) {
125  info (0, "No session machine for cookie retrieval");
126  return 0;
127  }
128 
129  for (pos = 0; pos < gwlist_len(headers); pos++) {
130  header = gwlist_get(headers, pos);
131  /* debug ("wap.wsp.http", 0, "get_cookies: Examining header (%s)", octstr_get_cstr (header)); */
132  if (strncasecmp ("set-cookie", octstr_get_cstr (header),10) == 0) {
133  debug ("wap.wsp.http", 0, "Caching cookie (%s)", octstr_get_cstr (header));
134 
135  if ((value = get_header_value (header)) == NULL) {
136  error (0, "get_cookies: No value in (%s)", octstr_get_cstr(header));
137  continue;
138  }
139 
140  /* Parse the received cookie */
141  if ((cookie = parse_cookie(value)) != NULL) {
142 
143  /* Check to see if this cookie is already present */
144  if (have_cookie(sm->cookies, cookie) == 1) {
145  debug("wap.wsp.http", 0, "parse_cookie: Cookie present");
146  cookie_destroy(cookie);
147  continue;
148  } else {
149  add_cookie_to_cache(sm, cookie);
150  debug("wap.wsp.http", 0, "get_cookies: Added (%s)",
151  octstr_get_cstr(cookie -> name));
152  }
153  }
154  }
155  }
156 
157  debug("wap.wsp.http", 0, "get_cookies: End");
158  return 0;
159 }
160 
161 
162 int set_cookies(List *headers, WSPMachine *sm)
163 {
164  Cookie *value = NULL;
165  Octstr *cookie = NULL;
166  long pos = 0;
167 
168  if (headers == NULL || sm == NULL) {
169  error (0, "set_cookies: Null argument(s) - no headers, WSPMachine or both");
170  return -1;
171  }
172 
173  /* Expire cookies that have timed out */
174  expire_cookies(sm->cookies);
175 
176  /* Walk through the cookie cache, adding the cookie to the request headers */
177  if (gwlist_len(sm->cookies) > 0) {
178  debug("wap.wsp.http", 0, "set_cookies: Cookies in cache");
179 
180  for (pos = 0; pos < gwlist_len(sm->cookies); pos++) {
181  value = gwlist_get(sm->cookies, pos);
182 
183  cookie = octstr_create("Cookie: ");
184  if (value->version)
185  octstr_append(cookie, value->version);
186  octstr_append(cookie, value->name);
187  octstr_append_char(cookie, '=');
188  octstr_append(cookie, value->value);
189 
190  if (value->path) {
191  octstr_append_char(cookie, ';');
192  octstr_append(cookie, value->path);
193  }
194  if (value->domain) {
195  octstr_append_char(cookie, ';');
196  octstr_append(cookie, value->domain);
197  }
198 
199  gwlist_append(headers, cookie);
200  debug("wap.wsp.http", 0, "set_cookies: Added (%s)", octstr_get_cstr (cookie));
201  }
202  } else
203  debug("wap.wsp.http", 0, "set_cookies: No cookies in cache");
204 
205  return 0;
206 }
207 
208 /*
209  * Private interface functions
210  */
211 
212 /*
213  * Function: get_header_value
214  *
215  * Description: Gets the header value as an Octstr.
216  */
217 
218 static Octstr *get_header_value(Octstr *header)
219 {
220  Octstr *h = NULL;
221  long colon = -1;
222 
223  if (header == NULL) {
224  error(0, "get_header_value: NULL argument");
225  return NULL;
226  }
227 
228  octstr_strip_blanks(header);
229  colon = octstr_search_char(header, ':', 0);
230  if (colon == -1) {
231  error(0, "get_header_value: Malformed header (%s)", octstr_get_cstr (header));
232  return NULL;
233  } else {
234  h = octstr_copy(header, colon + 1, octstr_len(header));
236  }
237 
238  debug("wap.wsp.http", 0, "get_header_value: Value (%s)", octstr_get_cstr (h));
239  return h;
240 }
241 
242 /*
243  * Function: parse_cookie
244  *
245  * Description: Parses the received cookie and rewrites it for sending.
246  */
247 
248 static Cookie *parse_cookie(Octstr *cookiestr)
249 {
250  char *v = NULL;
251  char *p = NULL;
252  int delta = 0;
253  Cookie *c = NULL;
254  Octstr **f = NULL;
255 
256  if (cookiestr == NULL) {
257  error(0, "parse_cookie: NULL argument");
258  return NULL;
259  }
260 
261  v = gw_strdup(octstr_get_cstr (cookiestr));
262  p = strtok(v, ";");
263 
264  c = cookie_create(); /* Never returns NULL */
265 
266  while (p != NULL) {
267  while (isspace((int)*p)) p++; /* Skip leading whitespace */
268 
269  if (strncasecmp("version", p, 7) == 0)
270  f = &c -> version;
271  else if (strncasecmp("path", p, 4) == 0)
272  f = &c -> path;
273  else if (strncasecmp("domain", p, 6) == 0)
274  f = &c -> domain; /* XXX DAVI: Shouldn't we check if domain is similar
275  * to real domain, and to set domain to
276  * real domain if not set by header ??? */
277  else if (strncasecmp("max-age", p, 7) == 0) {
278  c -> max_age = atol(strrchr (p, '=') + 1);
279  p = strtok(NULL, ";");
280  continue;
281  }
282  else if (strncasecmp("expires", p, 7) == 0) {
283  delta = parse_http_date(p);
284  if (delta != -1)
285  c->max_age = delta;
286  p = strtok(NULL, ";");
287  continue;
288  }
289  else if (strncasecmp("comment", p, 7) == 0 ) { /* Ignore comments */
290  p = strtok(NULL, ";");
291  continue;
292  }
293  else if (strncasecmp("secure", p, 6) == 0 ) { /* XXX DAVI: this should processed */
294  p = strtok(NULL, ";");
295  continue;
296  }
297  else { /* Name value pair - this should be first */
298  char *equals = NULL;
299 
300  if ((equals = strchr(p, '=')) != NULL) {
301  *equals = '\0';
302 
303  c->name = octstr_create(p);
304  c->value = octstr_create(equals + 1);
305  } else {
306  error(0, "parse_cookie: Bad name=value cookie component (%s)", p);
307  cookie_destroy(c);
308  return NULL;
309  }
310  p = strtok(NULL, ";");
311  continue;
312  }
313 
314  if (*f != NULL) { /* Undefined behaviour - 4.2.2 */
315  error(0, "parse_cookie: Duplicate cookie field (%s), discarding", p);
316  p = strtok(NULL, ";");
317  continue;
318  }
319 
320  *f = octstr_create("$");
321  octstr_append_cstr(*f, p);
322  p = strtok(NULL, ";");
323  }
324 
325  /* Process version - 4.3.4
326  * XXX DAVI: Altough it seems to be "MUST" in RFC, no one sends a Version
327  * tag when it's value is "0"
328  if (c->version == NULL) {
329  c->version = octstr_create("");
330  octstr_append_cstr(c->version, "$Version=\"0\";");
331  }
332  */
333 
334  gw_free (v);
335  return c;
336 }
337 
338 /*
339  * Function: add_cookie_to_cache
340  *
341  * Description: Adds the cookie to the WSPMachine cookie cache.
342  */
343 
344 static void add_cookie_to_cache(const WSPMachine *sm, Cookie *value)
345 {
346  gw_assert(sm != NULL);
347  gw_assert(sm->cookies != NULL);
348  gw_assert(value != NULL);
349 
350  gwlist_append(sm->cookies, value);
351 
352  return;
353 }
354 
355 /*
356  * Function: have_cookie
357  *
358  * Description: Checks to see if the cookie is present in the list.
359  */
360 
361 static int have_cookie(List *cookies, Cookie *cookie)
362 {
363  Cookie *value = NULL;
364  long pos = 0;
365 
366  if (cookies == NULL || cookie == NULL) {
367  error(0, "have_cookie: Null argument(s) - no Cookie list, Cookie or both");
368  return 0;
369  }
370 
371  /* Walk through the cookie cache, comparing cookie */
372  while (pos < gwlist_len(cookies)) {
373  value = gwlist_get(cookies, pos);
374 
375  /* octstr_compare() now only returns 0 on an exact match or if both args are 0 */
376  debug ("wap.wsp.http", 0, "have_cookie: Comparing name (%s:%s), path (%s:%s), domain (%s:%s)",
377  octstr_get_cstr(cookie->name), octstr_get_cstr(value->name),
378  octstr_get_cstr(cookie->path), octstr_get_cstr(value->path),
379  octstr_get_cstr(cookie->domain), octstr_get_cstr(value->domain));
380 
381  /* Match on no value or value and value equality for name, path and domain */
382  if (
383  (value->name == NULL ||
384  ((value->name != NULL && cookie->name != NULL) && octstr_compare(value->name, cookie->name) == 0)) &&
385  (value->path == NULL ||
386  ((value->path != NULL && cookie->path != NULL) && octstr_compare(value->path, cookie->path) == 0)) &&
387  (value->domain == NULL ||
388  ((value->domain != NULL && cookie->domain != NULL) && octstr_compare(value->domain, cookie->domain) == 0))
389  ) {
390 
391  /* We have a match according to 4.3.3 - discard the old one */
392  cookie_destroy(value);
393  gwlist_delete(cookies, pos, 1);
394 
395  /* Discard the new cookie also if max-age is 0 - set if expiry date is up */
396  if (cookie->max_age == 0) {
397  debug("wap.wsp.http", 0, "have_cookie: Discarding expired cookie (%s)",
398  octstr_get_cstr(cookie->name));
399  return 1;
400  }
401 
402  debug("wap.wsp.http", 0, "have_cookie: Updating cached cookie (%s)",
403  octstr_get_cstr (cookie->name));
404  break;
405  } else {
406  pos++;
407  }
408  }
409 
410  return 0;
411 }
412 
413 /*
414  * Function: expire_cookies
415  *
416  * Description: Walks thru the cookie list and checking for expired cookies.
417  */
418 
419 static void expire_cookies(List *cookies)
420 {
421  Cookie *value = NULL;
422  time_t now = 0;
423  long pos = 0;
424 
425  if (cookies == NULL) {
426  error(0, "expire_cookies: Null argument(s) - no Cookie list");
427  return;
428  }
429 
430  /* Walk through the cookie cache */
431 
432  time(&now);
433 
434  if (gwlist_len(cookies) > 0) {
435  debug("wap.wsp.http", 0, "expire_cookies: Cookies in cache");
436  for (pos = 0; pos < gwlist_len(cookies); pos++) {
437  value = gwlist_get(cookies, pos);
438  gw_assert(value != NULL);
439 
440  if (value->max_age != -1) { /* Interesting value */
441  if (value->max_age + value->birth < now) {
442  debug("wap.wsp.http", 0, "expire_cookies: Expired cookie (%s)",
443  octstr_get_cstr(value->name));
444  cookie_destroy(value);
445  gwlist_delete(cookies, pos, 1);
446  }
447  }
448  }
449  } else
450  debug("wap.wsp.http", 0, "expire_cookies: No cookies in cache");
451 
452  return;
453 }
454 
455 static void cookie_destroy(void *p)
456 {
457  Cookie *cookie;
458 
459  if (p == NULL)
460  return;
461  cookie = p;
462 
463  octstr_destroy(cookie->name);
464  octstr_destroy(cookie->value);
465  octstr_destroy(cookie->version);
466  octstr_destroy(cookie->domain);
467  octstr_destroy(cookie->path);
468 
469  gw_free(cookie);
470  debug("wap.wsp.http", 0, "cookie_destroy: Destroyed cookie");
471  return;
472 }
473 
474 /*
475  * Function: parse_http_date
476  *
477  * Description: Parses an HTTP date format as used by the Expires: header. See RFC 2616
478  * section 3.3.1 for more information.
479  * HTTP applications have historically allowed three different formats
480  * for the representation of date/time stamps:
481  *
482  * Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
483  * Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
484  * Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
485  *
486  * The first format is preferred as an Internet standard and represents
487  * a fixed-length subset of that defined by RFC 1123 [8] (an update to
488  * RFC 822 [9]). The second format is in common use, but is based on the
489  * obsolete RFC 850 [12] date format and lacks a four-digit year.
490  * HTTP/1.1 clients and servers that parse the date value MUST accept
491  * all three formats (for compatibility with HTTP/1.0), though they MUST
492  * only generate the RFC 1123 format for representing HTTP-date values
493  * in header fields.
494  *
495  * Returns: -1 on success, max-age sematic value on success.
496  */
497 
498 /*
499  * TODO: This is obviously the same as gwlib/date.c:date_http_parse().
500  * Need to unify this to one pulic function.
501  */
502 
503 static const char* months[] = {
504  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
505  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL
506 };
507 
508 static int month_index(const char *s)
509 {
510  const char **p = &months[0];
511  int i = 0;
512 
513  while (*p != NULL) {
514  if (strcmp(s, *p) == 0)
515  return i;
516  p++, i++;
517  }
518 
519  return -1;
520 }
521 
522 static int parse_http_date(const char *expires)
523 {
524  struct tm ti;
525  char *p = NULL;
526  char *date = NULL;
527  char month[MAX_HTTP_DATE_LENGTH];
528  time_t rv;
529  time_t now;
530 
531  memset(&ti, 0, sizeof(struct tm));
532 
533  /* Break up the Expires: header */
534  if (!(date = strchr(expires, '='))) {
535  error(0, "parse_http_date: Bogus expires type=value header (%s)", expires);
536  return -1;
537  } else {
538  date++;
539  while (isspace((int)*date))
540  ++date;
541  }
542 
543  /* Onto the date value */
544  if (!(p = strchr (date,' '))) {
545  error(0, "parse_http_date: Bogus date string (%s)", date);
546  return -1;
547  } else
548  while (isspace((int)*p))
549  ++p;
550 
551  if (MAX_HTTP_DATE_LENGTH < strlen(p)) {
552  error(0, "parse_http_date: %s blows length limit (%d)", date, MAX_HTTP_DATE_LENGTH);
553  return -1;
554  }
555 
556  if (isalpha((int)*p)) {
557  /* ctime */
558  sscanf(p, (strstr(p, "DST") ? "%s %d %d:%d:%d %*s %d" : "%s %d %d:%d:%d %d"),
559  month, &ti.tm_mday, &ti.tm_hour, &ti.tm_min,
560  &ti.tm_sec, &ti.tm_year);
561  ti.tm_year -= 1900;
562 
563  } else if (p[2] == '-') {
564  /* RFC 850 (normal HTTP) */
565  char buf[MAX_HTTP_DATE_LENGTH];
566 
567  sscanf(p, "%s %d:%d:%d", buf, &ti.tm_hour, &ti.tm_min, &ti.tm_sec);
568  buf[2] = '\0';
569  ti.tm_mday = atoi(buf);
570  buf[6] = '\0';
571 
572  strcpy(month, &buf[3]);
573  ti.tm_year = atoi(&buf[7]);
574 
575  /* Prevent wraparound from ambiguity */
576 
577  if (ti.tm_year < 70) {
578  ti.tm_year += 100;
579  } else if (ti.tm_year > 1900) {
580  ti.tm_year -= 1900;
581  }
582  } else {
583  /* RFC 822 */
584  sscanf(p,"%d %s %d %d:%d:%d",&ti.tm_mday, month, &ti.tm_year,
585  &ti.tm_hour, &ti.tm_min, &ti.tm_sec);
586 
587  /*
588  * since tm_year is years since 1900 and the year we parsed
589  * is absolute, we need to subtract 1900 years from it
590  */
591  ti.tm_year -= 1900;
592  }
593 
594  ti.tm_mon = month_index(month);
595  if (ti.tm_mon == -1) {
596  error(0, "parse_http_date () failed on bad month value (%s)", month);
597  return -1;
598  }
599 
600  ti.tm_isdst = -1;
601 
602  rv = gw_mktime(&ti);
603  if (ti.tm_isdst)
604  rv -= 3600;
605 
606  if (rv == -1) {
607  error(0, "parse_http_date(): mktime() was unable to resolve date/time: %s",
608  asctime(&ti));
609  return -1;
610  }
611 
612  debug("parse_http_date", 0, "Parsed date (%s) as: %s", date, asctime(&ti));
613 
614  /*
615  * If rv is valid, it should be some time in the (near) future. Normalise this to
616  * a max-age semantic so we can use the same expiry mechanism
617  */
618  now = time(NULL);
619 
620  if (rv - now < 0) {
621  /* This is bad - set the delta to 0 so we expire next time around */
622  error(0, "parse_http_date () Expiry time (%s) (delta=%ld) is in the past !",
623  asctime(&ti), rv-now);
624  return 0;
625  }
626 
627  return rv - now;
628 }
629 
int get_cookies(List *headers, const WSPMachine *sm)
Definition: cookies.c:111
void error(int err, const char *fmt,...)
Definition: log.c:612
void info(int err, const char *fmt,...)
Definition: log.c:636
static int month_index(const char *s)
Definition: cookies.c:508
void gwlist_append(List *list, void *item)
Definition: list.c:179
void octstr_append(Octstr *ostr1, const Octstr *ostr2)
Definition: octstr.c:1502
long gwlist_len(List *list)
Definition: list.c:166
static const char * months[]
Definition: cookies.c:503
void * gwlist_get(List *list, long pos)
Definition: list.c:292
void octstr_append_char(Octstr *ostr, int ch)
Definition: octstr.c:1515
void octstr_append_cstr(Octstr *ostr, const char *cstr)
Definition: octstr.c:1509
void octstr_strip_blanks(Octstr *text)
Definition: octstr.c:1344
#define octstr_get_cstr(ostr)
Definition: octstr.h:233
#define octstr_copy(ostr, from, len)
Definition: octstr.h:178
long octstr_search_char(const Octstr *ostr, int ch, long pos)
Definition: octstr.c:1010
static void expire_cookies(List *)
Definition: cookies.c:419
void cookies_destroy(List *cookies)
Definition: cookies.c:100
static int have_cookie(List *, Cookie *)
Definition: cookies.c:361
static Octstr * get_header_value(Octstr *)
Definition: cookies.c:218
time_t gw_mktime(struct tm *tm)
Definition: protected.c:153
static void cookie_destroy(void *)
Definition: cookies.c:455
void gwlist_delete(List *list, long pos, long count)
Definition: list.c:232
#define MAX_HTTP_DATE_LENGTH
Definition: cookies.h:106
static Cookie * parse_cookie(Octstr *)
Definition: cookies.c:248
static Cookie emptyCookie
Definition: cookies.c:85
char * name
Definition: smsc_cimd2.c:212
static int parse_http_date(const char *)
Definition: cookies.c:522
void octstr_destroy(Octstr *ostr)
Definition: octstr.c:322
Cookie * cookie_create(void)
Definition: cookies.c:88
#define octstr_create(cstr)
Definition: octstr.h:125
gw_assert(wtls_machine->packet_to_send!=NULL)
static Octstr * colon
Definition: smsc_smasi.c:218
long octstr_len(const Octstr *ostr)
Definition: octstr.c:340
Definition: octstr.c:118
void debug(const char *place, int err, const char *fmt,...)
Definition: log.c:690
void() gwlib_assert_init(void)
Definition: gwlib.c:72
int set_cookies(List *headers, WSPMachine *sm)
Definition: cookies.c:162
static int date(int hex)
static void add_cookie_to_cache(const WSPMachine *, Cookie *)
Definition: cookies.c:344
Definition: list.c:102
int octstr_compare(const Octstr *ostr1, const Octstr *ostr2)
Definition: octstr.c:869
void gwlist_destroy(List *list, gwlist_item_destructor_t *destructor)
Definition: list.c:145
See file LICENSE for details about the license agreement for using, modifying, copying or deriving work from this software.