]> code.citadel.org Git - citadel.git/blob - citadel/imap_tools.c
* Applied a patch sent in by David Given for handling of the "&" character
[citadel.git] / citadel / imap_tools.c
1 /*
2  * $Id$
3  *
4  * Utility functions for the IMAP module.
5  *
6  * Note: most of the UTF7 and UTF8 handling in here was stolen from Evolution.
7  *
8  */
9
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <stdio.h>
13 #include <ctype.h>
14 #include <string.h>
15 #include "citadel.h"
16 #include "sysdep_decls.h"
17 #include "tools.h"
18 #include "room_ops.h"
19 #include "internet_addressing.h"
20 #include "imap_tools.h"
21
22
23 #ifndef HAVE_SNPRINTF
24 #include "snprintf.h"
25 #endif
26
27 /* String handling helpers */
28
29 /* This code uses some pretty narsty string manipulation. To make everything
30  * manageable, we use this semi-high-level string manipulation API. Strings are
31  * always \0-terminated, despite the fact that we keep track of the size. */
32
33 struct string {
34         char* buffer;
35         int maxsize;
36         int size;
37 };
38
39 static void string_init(struct string* s, char* buf, int bufsize)
40 {
41         s->buffer = buf;
42         s->maxsize = bufsize-1;
43         s->size = strlen(buf);
44 }
45
46 static int string_overflow(struct string* s)
47 {
48         return (s->size == s->maxsize);
49 }
50
51 static int string_length(struct string* s)
52 {
53         return s->size;
54 }
55
56 static char* string_ptr(struct string* s)
57 {
58         return s->buffer;
59 }
60
61 static char* string_end(struct string* s)
62 {
63         return s->buffer + s->size;
64 }
65
66 /* Append a UTF8 string of a particular length (in bytes). -1 to autocalculate. */
67
68 static void string_append_sn(struct string* s, char* p, int len)
69 {
70         if (len == -1)
71                 len = strlen(p);
72         if ((s->size+len) > s->maxsize)
73                 len = s->maxsize - s->size;
74         memcpy(s->buffer + s->size, p, len);
75         s->size += len;
76         s->buffer[s->size] = '\0';
77 }
78
79 /* As above, always autocalculate. */
80
81 #define string_append_s(s, p) string_append_sn((s), (p), -1)
82
83 /* Appends a UTF8 character --- which may make the size change by more than 1!
84  * If the string overflows, the last character may become invalid. */
85
86 static void string_append_c(struct string* s, int c)
87 {
88         char buf[5];
89         int len = 0;
90
91         /* Don't do anything if there's no room. */
92
93         if (s->size == s->maxsize)
94                 return;
95
96         if (c <= 0x7F)
97         {
98                 /* This is the most common case, so we optimise it. */
99
100                 s->buffer[s->size++] = c;
101                 s->buffer[s->size] = 0;
102                 return;
103         }
104         else if (c <= 0x7FF)
105         {
106                 buf[0] = 0xC0 | (c >> 6);
107                 buf[1] = 0x80 | (c & 0x3F);
108                 len = 2;
109         }
110         else if (c <= 0xFFFF)
111         {
112                 buf[0] = 0xE0 | (c >> 12);
113                 buf[1] = 0x80 | ((c >> 6) & 0x3f);
114                 buf[2] = 0x80 | (c & 0x3f);
115                 len = 3;
116         }
117         else
118         {
119                 buf[0] = 0xf0 | c >> 18;
120                 buf[1] = 0x80 | ((c >> 12) & 0x3f);
121                 buf[2] = 0x80 | ((c >> 6) & 0x3f);
122                 buf[3] = 0x80 | (c & 0x3f);
123                 len = 4;
124         }
125
126         string_append_sn(s, buf, len);
127 }       
128
129 /* Append another string structure. */
130
131 static void string_append(struct string* dest, struct string* src)
132 {
133         string_append_sn(dest, src->buffer, src->size);
134 }
135
136 /* Reads a UTF8 character from a char*, advancing the pointer. */
137
138 int utf8_getc(char** ptr)
139 {
140         unsigned char* p = (unsigned char*) *ptr;
141         unsigned char c, r;
142         int v, m;
143
144         for (;;)
145         {
146                 r = *p++;
147         loop:
148                 if (r < 0x80)
149                 {
150                         *ptr = p;
151                         v = r;
152                         break;
153                 }
154                 else if (r < 0xf8)
155                 {
156                         /* valid start char? (max 4 octets) */
157                         v = r;
158                         m = 0x7f80;     /* used to mask out the length bits */
159                         do {
160                                 c = *p++;
161                                 if ((c & 0xc0) != 0x80)
162                                 {
163                                         r = c;
164                                         goto loop;
165                                 }
166                                 v = (v<<6) | (c & 0x3f);
167                                 r<<=1;
168                                 m<<=5;
169                         } while (r & 0x40);
170                         
171                         *ptr = p;
172
173                         v &= ~m;
174                         break;
175                 }
176         }
177
178         return v;
179 }
180
181 /* IMAP name safety */
182
183 /* IMAP has certain special requirements in its character set, which means we
184  * have to do a fair bit of work to convert Citadel's UTF8 strings to IMAP
185  * strings. The next two routines (and their data tables) do that. */
186
187 static char *utf7_alphabet =
188         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
189
190 static unsigned char utf7_rank[256] = {
191         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
192         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
193         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0x3F,0xFF,0xFF,0xFF,
194         0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
195         0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
196         0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
197         0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
198         0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,
199         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
200         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
201         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
202         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
203         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
204         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
205         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
206         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
207 };
208
209 /* Base64 helpers. */
210
211 static void utf7_closeb64(struct string* out, int v, int i)
212 {
213         int x;
214
215         if (i > 0)
216         {
217                 x = (v << (6-i)) & 0x3F;
218                 string_append_c(out, utf7_alphabet[x]);
219         }
220         string_append_c(out, '-');
221 }
222
223 /* Convert from a Citadel name to an IMAP-safe name. Returns the end of the destination. */
224
225 static char* toimap(char* destp, char* destend, char* src)
226 {
227         struct string dest;
228         int state = 0;
229         int v = 0;
230         int i = 0;
231
232         *destp = 0;
233         string_init(&dest, destp, destend-destp);
234         lprintf(CTDL_DEBUG, "toimap %s\r\n", src);
235
236         for (;;)
237         {
238                 int c = utf8_getc(&src);
239                 if (c == '\0')
240                         break;
241
242                 if (c >= 0x20 && c <= 0x7e)
243                 {
244                         if (state == 1)
245                         {
246                                 utf7_closeb64(&dest, v, i);
247                                 state = 0;
248                                 i = 0;
249                         }
250
251                         switch (c)
252                         {
253                                 case '&':
254                                         string_append_sn(&dest, "&-", 2);
255                                         break;
256
257                                 case '/':
258                                         /* Citadel extension: / becomes |, because /
259                                          * isn't valid as part of an IMAP name. */
260
261                                         c = '|';
262                                         goto defaultcase;
263
264                                 case '\\':
265                                         /* Citadel extension: backslashes mark folder
266                                          * seperators in the IMAP subfolder emulation
267                                          * hack. We turn them into / characters,
268                                          * *except* if it's the last character in the
269                                          * string. */
270
271                                         if (*src != '\0')
272                                                 c = '/';
273                                         /* fall through */
274
275                                 default:
276                                 defaultcase:
277                                         string_append_c(&dest, c);
278                         }
279                 }
280                 else
281                 {
282                         if (state == 0)
283                         {
284                                 string_append_c(&dest, '&');
285                                 state = 1;
286                         }
287                         v = (v << 16) | c;
288                         i += 16;
289                         while (i >= 6)
290                         {
291                                 int x = (v >> (i-6)) & 0x3f;
292                                 string_append_c(&dest, utf7_alphabet[x]);
293                                 i -= 6;
294                         }
295                 }
296         }
297
298         if (state == 1)
299                 utf7_closeb64(&dest, v, i);
300         lprintf(CTDL_DEBUG, "    -> %s\r\n", destp);
301         return string_end(&dest);
302 }
303
304 /* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
305
306 static int cfrommap(int c);
307 static char* fromimap(char* destp, char* destend, char* src)
308 {
309         struct string dest;
310         unsigned char *p = (unsigned char*) src;
311         int v = 0;
312         int i = 0;
313         int state = 0;
314         int c;
315
316         *destp = 0;
317         string_init(&dest, destp, destend-destp);
318         lprintf(CTDL_DEBUG, "fromimap %s\r\n", src);
319
320         do {
321                 c = *p++;
322                 switch (state)
323                 {
324                         case 0:
325                                 /* US-ASCII characters. */
326                                 
327                                 if (c == '&')
328                                         state = 1;
329                                 else
330                                         string_append_c(&dest, cfrommap(c));
331                                 break;
332
333                         case 1:
334                                 if (c == '-')
335                                 {
336                                         string_append_c(&dest, '&');
337                                         state = 0;
338                                 }
339                                 else if (utf7_rank[c] != 0xff)
340                                 {
341                                         v = utf7_rank[c];
342                                         i = 6;
343                                         state = 2;
344                                 }
345                                 else
346                                 {
347                                         /* invalid char */
348                                         string_append_sn(&dest, "&-", 2);
349                                         state = 0;
350                                 }
351                                 break;
352                                 
353                         case 2:
354                                 if (c == '-')
355                                         state = 0;
356                                 else if (utf7_rank[c] != 0xFF)
357                                 {
358                                         v = (v<<6) | utf7_rank[c];
359                                         i += 6;
360                                         if (i >= 16)
361                                         {
362                                                 int x = (v >> (i-16)) & 0xFFFF;
363                                                 string_append_c(&dest, cfrommap(x));
364                                                 i -= 16;
365                                         }
366                                 }
367                                 else
368                                 {
369                                         string_append_c(&dest, cfrommap(c));
370                                         state = 0;
371                                 }
372                                 break;
373                         }
374         } while (c != '\0');
375
376         lprintf(CTDL_DEBUG, "      -> %s\r\n", destp);
377         return string_end(&dest);
378 }
379
380 /* Undoes the special character conversion. */
381
382 static int cfrommap(int c)
383 {
384         switch (c)
385         {
386                 case '|':       return '/';
387                 case '/':       return '\\';
388         }
389         return c;               
390 }
391
392 /* Output a string to the IMAP client, either as a literal or quoted.
393  * (We do a literal if it has any double-quotes or backslashes.) */
394
395 void imap_strout(char *buf)
396 {
397         int i;
398         int is_literal = 0;
399
400         if (buf == NULL) {      /* yeah, we handle this */
401                 cprintf("NIL");
402                 return;
403         }
404         for (i = 0; i < strlen(buf); ++i) {
405                 if ((buf[i] == '\"') || (buf[i] == '\\'))
406                         is_literal = 1;
407         }
408
409         if (is_literal) {
410                 cprintf("{%ld}\r\n%s", (long)strlen(buf), buf);
411         } else {
412                 cprintf("\"%s\"", buf);
413         }
414 }
415
416 /* Break a command down into tokens, unquoting any escaped characters. */
417
418 int imap_parameterize(char** args, char* in)
419 {
420         char* out = in;
421         int num = 0;
422
423         for (;;)
424         {
425                 /* Skip whitespace. */
426
427                 while (isspace(*in))
428                         in++;
429                 if (*in == 0)
430                         break;
431
432                 /* Found the start of a token. */
433                 
434                 args[num++] = out;
435
436                 /* Read in the token. */
437
438                 for (;;)
439                 {
440                         int c = *in++;
441                         if (isspace(c))
442                                 break;
443                         
444                         if (c == '\"')
445                         {
446                                 /* Found a quoted section. */
447
448                                 for (;;)
449                                 {
450                                         c = *in++;
451                                         if (c == '\"')
452                                                 break;
453                                         else if (c == '\\')
454                                                 c = *in++;
455
456                                         *out++ = c;
457                                         if (c == 0)
458                                                 return num;
459                                 }
460                         }
461                         else if (c == '\\')
462                         {
463                                 c = *in++;
464                                 *out++ = c;
465                         }
466                         else
467                                 *out++ = c;
468
469                         if (c == 0)
470                                 return num;
471                 }
472                 *out++ = '\0';
473         }
474
475         return num;
476 }
477
478 /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
479
480 void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
481 {
482         char* bufend = buf+bufsize;
483         struct floor *fl;
484         char* p = buf;
485
486         /* For mailboxes, just do it straight.
487          * Do the Cyrus-compatible thing: all private folders are
488          * subfolders of INBOX. */
489
490         if (qrbuf->QRflags & QR_MAILBOX)
491         {
492                 if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
493                         p = toimap(p, bufend, "INBOX");
494                 else
495                 {
496                         p = toimap(p, bufend, "INBOX");
497                         if (p < bufend)
498                                 *p++ = '/';
499                         p = toimap(p, bufend, qrbuf->QRname+11);
500                 }
501         }
502         else
503         {
504                 /* Otherwise, prefix the floor name as a "public folders" moniker. */
505
506                 fl = cgetfloor(qrbuf->QRfloor);
507                 p = toimap(p, bufend, fl->f_name);
508                 if (p < bufend)
509                         *p++ = '/';
510                 p = toimap(p, bufend, qrbuf->QRname);
511         }
512 }
513
514 /*
515  * Convert an inputted folder name to our best guess as to what an equivalent
516  * room name should be.
517  *
518  * If an error occurs, it returns -1.  Otherwise...
519  *
520  * The lower eight bits of the return value are the floor number on which the
521  * room most likely resides.   The upper eight bits may contain flags,
522  * including IR_MAILBOX if we're dealing with a personal room.
523  *
524  */
525
526 int imap_roomname(char *rbuf, int bufsize, char *foldername)
527 {
528         int levels;
529         char floorname[SIZ];
530         char roomname[SIZ];
531         int i;
532         struct floor *fl;
533         int ret = (-1);
534
535         if (foldername == NULL)
536                 return(-1);
537
538         /* Unmunge the entire string into the output buffer. */
539
540         fromimap(rbuf, rbuf+bufsize, foldername);
541
542         /* Is this an IMAP inbox? */
543
544         if (strncasecmp(rbuf, "INBOX", 5) == 0)
545         {
546                 if (rbuf[5] == 0)
547                 {
548                         /* It's the system inbox. */
549
550                         safestrncpy(rbuf, MAILROOM, bufsize);
551                         ret = (0 | IR_MAILBOX);
552                         goto exit;
553                 }
554                 else if (rbuf[5] == FDELIM)
555                 {
556                         /* It's another personal mail folder. */
557
558                         safestrncpy(rbuf, rbuf+6, bufsize);
559                         ret = (0 | IR_MAILBOX);
560                         goto exit;
561                 }
562
563                 /* If we get here, the folder just happens to start with INBOX
564                  * --- fall through. */
565         }
566
567         /* Is this a multi-level room name? */
568
569         levels = num_tokens(rbuf, FDELIM);
570         if (levels > 1)
571         {
572                 /* Extract the main room name. */
573                 
574                 extract_token(floorname, rbuf, 0, FDELIM);
575                 strcpy(roomname, &rbuf[strlen(floorname)+1]);
576
577                 /* Try and find it on any floor. */
578                 
579                 for (i = 0; i < MAXFLOORS; ++i)
580                 {
581                         fl = cgetfloor(i);
582                         if (fl->f_flags & F_INUSE)
583                         {
584                                 if (strcasecmp(floorname, fl->f_name) == 0)
585                                 {
586                                         /* Got it! */
587
588                                         strcpy(rbuf, roomname);
589                                         ret = i;
590                                         goto exit;
591                                 }
592                         }
593                 }
594         }
595
596         /* Meh. It's either not a multi-level room name, or else we couldn't find it. */
597
598         ret = (0 | IR_MAILBOX);
599
600 exit:
601         lprintf(CTDL_DEBUG, "(That translates to \"%s\")\n", rbuf);
602         return(ret);
603 }
604
605 /*
606  * Output a struct internet_address_list in the form an IMAP client wants
607  */
608 void imap_ial_out(struct internet_address_list *ialist)
609 {
610         struct internet_address_list *iptr;
611
612         if (ialist == NULL) {
613                 cprintf("NIL");
614                 return;
615         }
616         cprintf("(");
617
618         for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
619                 cprintf("(");
620                 imap_strout(iptr->ial_name);
621                 cprintf(" NIL ");
622                 imap_strout(iptr->ial_user);
623                 cprintf(" ");
624                 imap_strout(iptr->ial_node);
625                 cprintf(")");
626         }
627
628         cprintf(")");
629 }
630
631
632
633 /*
634  * Determine whether the supplied string is a valid message set.
635  * If the string contains only numbers, colons, commas, and asterisks,
636  * return 1 for a valid message set.  If any other character is found, 
637  * return 0.
638  */
639 int imap_is_message_set(char *buf)
640 {
641         int i;
642
643         if (buf == NULL)
644                 return (0);     /* stupidity checks */
645         if (strlen(buf) == 0)
646                 return (0);
647
648         if (!strcasecmp(buf, "ALL"))
649                 return (1);     /* macro?  why?  */
650
651         for (i = 0; i < strlen(buf); ++i) {     /* now start the scan */
652                 if (
653                            (!isdigit(buf[i]))
654                            && (buf[i] != ':')
655                            && (buf[i] != ',')
656                            && (buf[i] != '*')
657                     )
658                         return (0);
659         }
660
661         return (1);             /* looks like we're good */
662 }
663
664
665 /*
666  * imap_match.c, based on wildmat.c from INN
667  * hacked for Citadel/IMAP by Daniel Malament
668  */
669
670 /* note: not all return statements use these; don't change them */
671 #define WILDMAT_TRUE    1
672 #define WILDMAT_FALSE   0
673 #define WILDMAT_ABORT   -1
674 #define WILDMAT_DELIM   '/'
675
676 /*
677  * Match text and p, return TRUE, FALSE, or ABORT.
678  */
679 static int do_imap_match(const char *supplied_text, const char *supplied_p)
680 {
681         int matched, i;
682         char lcase_text[SIZ], lcase_p[SIZ];
683         char *text = lcase_text;
684         char *p = lcase_p;
685
686         /* Copy both strings and lowercase them, in order to
687          * make this entire operation case-insensitive.
688          */
689         for (i=0; i<=strlen(supplied_text); ++i)
690                 lcase_text[i] = tolower(supplied_text[i]);
691         for (i=0; i<=strlen(supplied_p); ++i)
692                 p[i] = tolower(supplied_p[i]);
693
694         /* Start matching */
695         for (; *p; text++, p++) {
696                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
697                         return WILDMAT_ABORT;
698                 }
699                 switch (*p) {
700                 default:
701                         if (*text != *p) {
702                                 return WILDMAT_FALSE;
703                         }
704                         continue;
705                 case '*':
706 star:
707                         while (++p, ((*p == '*') || (*p == '%'))) {
708                                 /* Consecutive stars or %'s act
709                                  * just like one star.
710                                  */
711                                 continue;
712                         }
713                         if (*p == '\0') {
714                                 /* Trailing star matches everything. */
715                                 return WILDMAT_TRUE;
716                         }
717                         while (*text) {
718                                 if ((matched = do_imap_match(text++, p))
719                                    != WILDMAT_FALSE) {
720                                         return matched;
721                                 }
722                         }
723                         return WILDMAT_ABORT;
724                 case '%':
725                         while (++p, ((*p == '*') || (*p == '%'))) {
726                                 /* Consecutive %'s act just like one, but even
727                                  * a single star makes the sequence act like
728                                  * one star, instead.
729                                  */
730                                 if (*p == '*') {
731                                         goto star;
732                                 }
733                                 continue;
734                         }
735                         if (*p == '\0') {
736                                 /*
737                                  * Trailing % matches everything
738                                  * without a delimiter.
739                                  */
740                                 while (*text) {
741                                         if (*text == WILDMAT_DELIM) {
742                                                 return WILDMAT_FALSE;
743                                         }
744                                         text++;
745                                 }
746                                 return WILDMAT_TRUE;
747                         }
748                         while (*text && (*(text - 1) != WILDMAT_DELIM)) {
749                                 if ((matched = do_imap_match(text++, p))
750                                    != WILDMAT_FALSE) {
751                                         return matched;
752                                 }
753                         }
754                         return WILDMAT_ABORT;
755                 }
756         }
757
758         return (*text == '\0');
759 }
760
761
762
763 /*
764  * Support function for mailbox pattern name matching in LIST and LSUB
765  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
766  */
767 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
768 {
769         /* handle just-star case quickly */
770         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
771                 return WILDMAT_TRUE;
772         }
773         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
774 }
775
776
777
778 /*
779  * Compare an IMAP date string (date only, no time) to the date found in
780  * a Unix timestamp.
781  */
782 int imap_datecmp(char *datestr, time_t msgtime) {
783         char daystr[SIZ];
784         char monthstr[SIZ];
785         char yearstr[SIZ];
786         int i;
787         int day, month, year;
788         int msgday, msgmonth, msgyear;
789         struct tm msgtm;
790
791         if (datestr == NULL) return(0);
792
793         /* Expecting a date in the form dd-Mmm-yyyy */
794         extract_token(daystr, datestr, 0, '-');
795         extract_token(monthstr, datestr, 1, '-');
796         extract_token(yearstr, datestr, 2, '-');
797
798         day = atoi(daystr);
799         year = atoi(yearstr);
800         month = 0;
801         for (i=0; i<12; ++i) {
802                 if (!strcasecmp(monthstr, ascmonths[i])) {
803                         month = i;
804                 }
805         }
806
807         /* Extract day/month/year from message timestamp */
808         localtime_r(&msgtime, &msgtm);
809         msgday = msgtm.tm_mday;
810         msgmonth = msgtm.tm_mon;
811         msgyear = msgtm.tm_year + 1900;
812
813         /* Now start comparing */
814
815         if (year < msgyear) return(+1);
816         if (year > msgyear) return(-1);
817
818         if (month < msgmonth) return(+1);
819         if (month > msgmonth) return(-1);
820
821         if (day < msgday) return(+1);
822         if (day > msgday) return(-1);
823
824         return(0);
825 }
826