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