080f9db7c0da74855888c081c1a321708207d763
[citadel.git] / citadel / modules / imap / 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 = (char*) 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 = (char*)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         long len;
380
381         if (buf == NULL) {      /* yeah, we handle this */
382                 cprintf("NIL");
383                 return;
384         }
385
386         len = strlen(buf);
387         for (i = 0; i < len; ++i) {
388                 if ((buf[i] == '\"') || (buf[i] == '\\'))
389                         is_literal = 1;
390         }
391
392         if (is_literal) {
393                 cprintf("{%ld}\r\n%s", len, buf);
394         } else {
395                 cprintf("\"%s\"", buf);
396         }
397 }
398
399 /* Break a command down into tokens, unquoting any escaped characters. */
400
401 int imap_parameterize(char** args, char* in)
402 {
403         char* out = in;
404         int num = 0;
405
406         for (;;)
407         {
408                 /* Skip whitespace. */
409
410                 while (isspace(*in))
411                         in++;
412                 if (*in == 0)
413                         break;
414
415                 /* Found the start of a token. */
416                 
417                 args[num++] = out;
418
419                 /* Read in the token. */
420
421                 for (;;)
422                 {
423                         int c = *in++;
424                         if (isspace(c))
425                                 break;
426                         
427                         if (c == '\"')
428                         {
429                                 /* Found a quoted section. */
430
431                                 for (;;)
432                                 {
433                                         c = *in++;
434                                         if (c == '\"')
435                                                 break;
436                                         else if (c == '\\')
437                                                 c = *in++;
438
439                                         *out++ = c;
440                                         if (c == 0)
441                                                 return num;
442                                 }
443                         }
444                         else if (c == '\\')
445                         {
446                                 c = *in++;
447                                 *out++ = c;
448                         }
449                         else
450                                 *out++ = c;
451
452                         if (c == 0)
453                                 return num;
454                 }
455                 *out++ = '\0';
456         }
457
458         return num;
459 }
460
461 /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
462
463 void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
464 {
465         char* bufend = buf+bufsize;
466         struct floor *fl;
467         char* p = buf;
468
469         /* For mailboxes, just do it straight.
470          * Do the Cyrus-compatible thing: all private folders are
471          * subfolders of INBOX. */
472
473         if (qrbuf->QRflags & QR_MAILBOX)
474         {
475                 if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
476                         p = toimap(p, bufend, "INBOX");
477                 else
478                 {
479                         p = toimap(p, bufend, "INBOX");
480                         if (p < bufend)
481                                 *p++ = '/';
482                         p = toimap(p, bufend, qrbuf->QRname+11);
483                 }
484         }
485         else
486         {
487                 /* Otherwise, prefix the floor name as a "public folders" moniker. */
488
489                 fl = cgetfloor(qrbuf->QRfloor);
490                 p = toimap(p, bufend, fl->f_name);
491                 if (p < bufend)
492                         *p++ = '/';
493                 p = toimap(p, bufend, qrbuf->QRname);
494         }
495 }
496
497 /*
498  * Convert an inputted folder name to our best guess as to what an equivalent
499  * room name should be.
500  *
501  * If an error occurs, it returns -1.  Otherwise...
502  *
503  * The lower eight bits of the return value are the floor number on which the
504  * room most likely resides.   The upper eight bits may contain flags,
505  * including IR_MAILBOX if we're dealing with a personal room.
506  *
507  */
508
509 int imap_roomname(char *rbuf, int bufsize, char *foldername)
510 {
511         int levels;
512         char floorname[256];
513         char roomname[ROOMNAMELEN];
514         int i;
515         struct floor *fl;
516         int ret = (-1);
517
518         if (foldername == NULL)
519                 return(-1);
520
521         /* Unmunge the entire string into the output buffer. */
522
523         fromimap(rbuf, rbuf+bufsize, foldername);
524
525         /* Is this an IMAP inbox? */
526
527         if (strncasecmp(rbuf, "INBOX", 5) == 0)
528         {
529                 if (rbuf[5] == 0)
530                 {
531                         /* It's the system inbox. */
532
533                         safestrncpy(rbuf, MAILROOM, bufsize);
534                         ret = (0 | IR_MAILBOX);
535                         goto exit;
536                 }
537                 else if (rbuf[5] == FDELIM)
538                 {
539                         /* It's another personal mail folder. */
540
541                         safestrncpy(rbuf, rbuf+6, bufsize);
542                         ret = (0 | IR_MAILBOX);
543                         goto exit;
544                 }
545
546                 /* If we get here, the folder just happens to start with INBOX
547                  * --- fall through. */
548         }
549
550         /* Is this a multi-level room name? */
551
552         levels = num_tokens(rbuf, FDELIM);
553         if (levels > 1)
554         {
555                 /* Extract the main room name. */
556                 
557                 extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
558                 strcpy(roomname, &rbuf[strlen(floorname)+1]);
559
560                 /* Try and find it on any floor. */
561                 
562                 for (i = 0; i < MAXFLOORS; ++i)
563                 {
564                         fl = cgetfloor(i);
565                         if (fl->f_flags & F_INUSE)
566                         {
567                                 if (strcasecmp(floorname, fl->f_name) == 0)
568                                 {
569                                         /* Got it! */
570
571                                         safestrncpy(rbuf, roomname, bufsize);
572                                         ret = i;
573                                         goto exit;
574                                 }
575                         }
576                 }
577         }
578
579         /* Meh. It's either not a multi-level room name, or else we
580          * couldn't find it.
581          */
582         ret = (0 | IR_MAILBOX);
583
584 exit:
585         lprintf(CTDL_DEBUG, "(That translates to \"%s\")\n", rbuf);
586         return(ret);
587 }
588
589 /*
590  * Output a struct internet_address_list in the form an IMAP client wants
591  */
592 void imap_ial_out(struct internet_address_list *ialist)
593 {
594         struct internet_address_list *iptr;
595
596         if (ialist == NULL) {
597                 cprintf("NIL");
598                 return;
599         }
600         cprintf("(");
601
602         for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
603                 cprintf("(");
604                 imap_strout(iptr->ial_name);
605                 cprintf(" NIL ");
606                 imap_strout(iptr->ial_user);
607                 cprintf(" ");
608                 imap_strout(iptr->ial_node);
609                 cprintf(")");
610         }
611
612         cprintf(")");
613 }
614
615
616
617 /*
618  * Determine whether the supplied string is a valid message set.
619  * If the string contains only numbers, colons, commas, and asterisks,
620  * return 1 for a valid message set.  If any other character is found, 
621  * return 0.
622  */
623 int imap_is_message_set(char *buf)
624 {
625         int i;
626
627         if (buf == NULL)
628                 return (0);     /* stupidity checks */
629         if (IsEmptyStr(buf))
630                 return (0);
631
632         if (!strcasecmp(buf, "ALL"))
633                 return (1);     /* macro?  why?  */
634
635         for (i = 0; buf[i]; ++i) {      /* now start the scan */
636                 if (
637                            (!isdigit(buf[i]))
638                            && (buf[i] != ':')
639                            && (buf[i] != ',')
640                            && (buf[i] != '*')
641                     )
642                         return (0);
643         }
644
645         return (1);             /* looks like we're good */
646 }
647
648
649 /*
650  * imap_match.c, based on wildmat.c from INN
651  * hacked for Citadel/IMAP by Daniel Malament
652  */
653
654 /* note: not all return statements use these; don't change them */
655 #define WILDMAT_TRUE    1
656 #define WILDMAT_FALSE   0
657 #define WILDMAT_ABORT   -1
658 #define WILDMAT_DELIM   '/'
659
660 /*
661  * Match text and p, return TRUE, FALSE, or ABORT.
662  */
663 static int do_imap_match(const char *supplied_text, const char *supplied_p)
664 {
665         int matched, i;
666         char lcase_text[SIZ], lcase_p[SIZ];
667         char *text = lcase_text;
668         char *p = lcase_p;
669
670         /* Copy both strings and lowercase them, in order to
671          * make this entire operation case-insensitive.
672          */
673         for (i=0; i<=strlen(supplied_text); ++i)
674                 lcase_text[i] = tolower(supplied_text[i]);
675         for (i=0; i<=strlen(supplied_p); ++i)
676                 p[i] = tolower(supplied_p[i]);
677
678         /* Start matching */
679         for (; *p; text++, p++) {
680                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
681                         return WILDMAT_ABORT;
682                 }
683                 switch (*p) {
684                 default:
685                         if (*text != *p) {
686                                 return WILDMAT_FALSE;
687                         }
688                         continue;
689                 case '*':
690 star:
691                         while (++p, ((*p == '*') || (*p == '%'))) {
692                                 /* Consecutive stars or %'s act
693                                  * just like one star.
694                                  */
695                                 continue;
696                         }
697                         if (*p == '\0') {
698                                 /* Trailing star matches everything. */
699                                 return WILDMAT_TRUE;
700                         }
701                         while (*text) {
702                                 if ((matched = do_imap_match(text++, p))
703                                    != WILDMAT_FALSE) {
704                                         return matched;
705                                 }
706                         }
707                         return WILDMAT_ABORT;
708                 case '%':
709                         while (++p, ((*p == '*') || (*p == '%'))) {
710                                 /* Consecutive %'s act just like one, but even
711                                  * a single star makes the sequence act like
712                                  * one star, instead.
713                                  */
714                                 if (*p == '*') {
715                                         goto star;
716                                 }
717                                 continue;
718                         }
719                         if (*p == '\0') {
720                                 /*
721                                  * Trailing % matches everything
722                                  * without a delimiter.
723                                  */
724                                 while (*text) {
725                                         if (*text == WILDMAT_DELIM) {
726                                                 return WILDMAT_FALSE;
727                                         }
728                                         text++;
729                                 }
730                                 return WILDMAT_TRUE;
731                         }
732                         while (*text && (*(text - 1) != WILDMAT_DELIM)) {
733                                 if ((matched = do_imap_match(text++, p))
734                                    != WILDMAT_FALSE) {
735                                         return matched;
736                                 }
737                         }
738                         return WILDMAT_ABORT;
739                 }
740         }
741
742         return (*text == '\0');
743 }
744
745
746
747 /*
748  * Support function for mailbox pattern name matching in LIST and LSUB
749  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
750  */
751 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
752 {
753         /* handle just-star case quickly */
754         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
755                 return WILDMAT_TRUE;
756         }
757         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
758 }
759
760
761
762 /*
763  * Compare an IMAP date string (date only, no time) to the date found in
764  * a Unix timestamp.
765  */
766 int imap_datecmp(char *datestr, time_t msgtime) {
767         char daystr[256];
768         char monthstr[256];
769         char yearstr[256];
770         int i;
771         int day, month, year;
772         int msgday, msgmonth, msgyear;
773         struct tm msgtm;
774
775         if (datestr == NULL) return(0);
776
777         /* Expecting a date in the form dd-Mmm-yyyy */
778         extract_token(daystr, datestr, 0, '-', sizeof daystr);
779         extract_token(monthstr, datestr, 1, '-', sizeof monthstr);
780         extract_token(yearstr, datestr, 2, '-', sizeof yearstr);
781
782         day = atoi(daystr);
783         year = atoi(yearstr);
784         month = 0;
785         for (i=0; i<12; ++i) {
786                 if (!strcasecmp(monthstr, ascmonths[i])) {
787                         month = i;
788                 }
789         }
790
791         /* Extract day/month/year from message timestamp */
792         localtime_r(&msgtime, &msgtm);
793         msgday = msgtm.tm_mday;
794         msgmonth = msgtm.tm_mon;
795         msgyear = msgtm.tm_year + 1900;
796
797         /* Now start comparing */
798
799         if (year < msgyear) return(+1);
800         if (year > msgyear) return(-1);
801
802         if (month < msgmonth) return(+1);
803         if (month > msgmonth) return(-1);
804
805         if (day < msgday) return(+1);
806         if (day > msgday) return(-1);
807
808         return(0);
809 }
810