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