moved whitespace around
[citadel.git] / citadel / server / modules / imap / imap_tools.c
1 /*
2  * Utility functions for the IMAP module.
3  *
4  * Copyright (c) 2001-2017 by the citadel.org team and others, except for
5  * most of the UTF7 and UTF8 handling code which was lifted from Evolution.
6  *
7  * This program is open source software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #define SHOW_ME_VAPPEND_PRINTF
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <stdio.h>
26 #include <ctype.h>
27 #include <string.h>
28 #include <stdarg.h>
29 #include <libcitadel.h>
30 #include "../../citadel_defs.h"
31 #include "../../sysdep_decls.h"
32 #include "../../internet_addressing.h"
33 #include "serv_imap.h"
34 #include "imap_tools.h"
35 #include "../../ctdl_module.h"
36
37 /* String handling helpers */
38
39 /* This code uses some pretty nasty string manipulation. To make everything
40  * manageable, we use this semi-high-level string manipulation API. Strings are
41  * always \0-terminated, despite the fact that we keep track of the size.
42  */
43 struct string {
44         char* buffer;
45         int maxsize;
46         int size;
47 };
48
49 static void string_init(struct string* s, char* buf, int bufsize)
50 {
51         s->buffer = buf;
52         s->maxsize = bufsize-1;
53         s->size = strlen(buf);
54 }
55
56 static char* string_end(struct string* s)
57 {
58         return s->buffer + s->size;
59 }
60
61 /* Append a UTF8 string of a particular length (in bytes). -1 to autocalculate. */
62
63 static void string_append_sn(struct string* s, char* p, int len)
64 {
65         if (len == -1)
66                 len = strlen(p);
67         if ((s->size+len) > s->maxsize)
68                 len = s->maxsize - s->size;
69         memcpy(s->buffer + s->size, p, len);
70         s->size += len;
71         s->buffer[s->size] = '\0';
72 }
73
74 /* As above, always autocalculate. */
75
76 #define string_append_s(s, p) string_append_sn((s), (p), -1)
77
78 /* Appends a UTF8 character --- which may make the size change by more than 1!
79  * If the string overflows, the last character may become invalid. */
80
81 static void string_append_c(struct string* s, int c)
82 {
83         char UmlChar[5];
84         int len = 0;
85
86         /* Don't do anything if there's no room. */
87
88         if (s->size == s->maxsize)
89                 return;
90
91         if (c <= 0x7F)
92         {
93                 /* This is the most common case, so we optimise it. */
94
95                 s->buffer[s->size++] = c;
96                 s->buffer[s->size] = 0;
97                 return;
98         }
99         else if (c <= 0x7FF)
100         {
101                 UmlChar[0] = 0xC0 | (c >> 6);
102                 UmlChar[1] = 0x80 | (c & 0x3F);
103                 len = 2;
104         }
105         else if (c <= 0xFFFF)
106         {
107                 UmlChar[0] = 0xE0 | (c >> 12);
108                 UmlChar[1] = 0x80 | ((c >> 6) & 0x3f);
109                 UmlChar[2] = 0x80 | (c & 0x3f);
110                 len = 3;
111         }
112         else
113         {
114                 UmlChar[0] = 0xf0 | c >> 18;
115                 UmlChar[1] = 0x80 | ((c >> 12) & 0x3f);
116                 UmlChar[2] = 0x80 | ((c >> 6) & 0x3f);
117                 UmlChar[3] = 0x80 | (c & 0x3f);
118                 len = 4;
119         }
120
121         string_append_sn(s, UmlChar, len);
122 }       
123
124 /* Reads a UTF8 character from a char*, advancing the pointer. */
125
126 int utf8_getc(char** ptr)
127 {
128         unsigned char* p = (unsigned char*) *ptr;
129         unsigned char c, r;
130         int v, m;
131
132         for (;;)
133         {
134                 r = *p++;
135         loop:
136                 if (r < 0x80)
137                 {
138                         *ptr = (char*) p;
139                         v = r;
140                         break;
141                 }
142                 else if (r < 0xf8)
143                 {
144                         /* valid start char? (max 4 octets) */
145                         v = r;
146                         m = 0x7f80;     /* used to mask out the length bits */
147                         do {
148                                 c = *p++;
149                                 if ((c & 0xc0) != 0x80)
150                                 {
151                                         r = c;
152                                         goto loop;
153                                 }
154                                 v = (v<<6) | (c & 0x3f);
155                                 r<<=1;
156                                 m<<=5;
157                         } while (r & 0x40);
158                         
159                         *ptr = (char*)p;
160
161                         v &= ~m;
162                         break;
163                 }
164         }
165
166         return v;
167 }
168
169 /* IMAP name safety */
170
171 /* IMAP has certain special requirements in its character set, which means we
172  * have to do a fair bit of work to convert Citadel's UTF8 strings to IMAP
173  * strings. The next two routines (and their data tables) do that.
174  */
175
176 static char *utf7_alphabet =
177         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
178
179 static unsigned char utf7_rank[256] = {
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,0x3E,0x3F,0xFF,0xFF,0xFF,
183         0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
184         0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
185         0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
186         0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
187         0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,
188         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
189         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
190         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
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,0xFF,0xFF,0xFF,0xFF,0xFF,
194         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
195         0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
196 };
197
198 /* Base64 helpers. */
199
200 static void utf7_closeb64(struct string* out, int v, int i)
201 {
202         int x;
203
204         if (i > 0)
205         {
206                 x = (v << (6-i)) & 0x3F;
207                 string_append_c(out, utf7_alphabet[x]);
208         }
209         string_append_c(out, '-');
210 }
211
212 /* Convert from a Citadel name to an IMAP-safe name. Returns the end
213  * of the destination.
214  */
215 static char* toimap(char* destp, char* destend, char* src)
216 {
217         struct string dest;
218         int state = 0;
219         int v = 0;
220         int i = 0;
221
222         *destp = 0;
223         string_init(&dest, destp, destend-destp);
224         /* syslog(LOG_DEBUG, "toimap %s", src); */
225
226         for (;;)
227         {
228                 int c = utf8_getc(&src);
229                 if (c == '\0')
230                         break;
231
232                 if (c >= 0x20 && c <= 0x7e)
233                 {
234                         if (state == 1)
235                         {
236                                 utf7_closeb64(&dest, v, i);
237                                 state = 0;
238                                 i = 0;
239                         }
240
241                         switch (c)
242                         {
243                                 case '&':
244                                         string_append_sn(&dest, "&-", 2);
245                                         break;
246
247                                 case '/':
248                                         /* Citadel extension: / becomes |, because /
249                                          * isn't valid as part of an IMAP name. */
250
251                                         c = '|';
252                                         goto defaultcase;
253
254                                 case '\\':
255                                         /* Citadel extension: backslashes mark folder
256                                          * seperators in the IMAP subfolder emulation
257                                          * hack. We turn them into / characters,
258                                          * *except* if it's the last character in the
259                                          * string. */
260
261                                         if (*src != '\0')
262                                                 c = '/';
263                                         /* fall through */
264
265                                 default:
266                                 defaultcase:
267                                         string_append_c(&dest, c);
268                         }
269                 }
270                 else
271                 {
272                         if (state == 0)
273                         {
274                                 string_append_c(&dest, '&');
275                                 state = 1;
276                         }
277                         v = (v << 16) | c;
278                         i += 16;
279                         while (i >= 6)
280                         {
281                                 int x = (v >> (i-6)) & 0x3f;
282                                 string_append_c(&dest, utf7_alphabet[x]);
283                                 i -= 6;
284                         }
285                 }
286         }
287
288         if (state == 1)
289                 utf7_closeb64(&dest, v, i);
290         /* syslog(LOG_DEBUG, "    -> %s", destp); */
291         return string_end(&dest);
292 }
293
294 /* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
295
296 static int cfrommap(int c);
297 static char* fromimap(char* destp, char* destend, const char* src)
298 {
299         struct string dest;
300         unsigned const char *p = (unsigned const char*) src;
301         int v = 0;
302         int i = 0;
303         int state = 0;
304         int c;
305
306         *destp = 0;
307         string_init(&dest, destp, destend-destp);
308         /* syslog(LOG_DEBUG, "fromimap %s", src); */
309
310         do {
311                 c = *p++;
312                 switch (state)
313                 {
314                         case 0:
315                                 /* US-ASCII characters. */
316                                 
317                                 if (c == '&')
318                                         state = 1;
319                                 else
320                                         string_append_c(&dest, cfrommap(c));
321                                 break;
322
323                         case 1:
324                                 if (c == '-')
325                                 {
326                                         string_append_c(&dest, '&');
327                                         state = 0;
328                                 }
329                                 else if (utf7_rank[c] != 0xff)
330                                 {
331                                         v = utf7_rank[c];
332                                         i = 6;
333                                         state = 2;
334                                 }
335                                 else
336                                 {
337                                         /* invalid char */
338                                         string_append_sn(&dest, "&-", 2);
339                                         state = 0;
340                                 }
341                                 break;
342                                 
343                         case 2:
344                                 if (c == '-')
345                                         state = 0;
346                                 else if (utf7_rank[c] != 0xFF)
347                                 {
348                                         v = (v<<6) | utf7_rank[c];
349                                         i += 6;
350                                         if (i >= 16)
351                                         {
352                                                 int x = (v >> (i-16)) & 0xFFFF;
353                                                 string_append_c(&dest, cfrommap(x));
354                                                 i -= 16;
355                                         }
356                                 }
357                                 else
358                                 {
359                                         string_append_c(&dest, cfrommap(c));
360                                         state = 0;
361                                 }
362                                 break;
363                         }
364         } while (c != '\0');
365
366         /* syslog(LOG_DEBUG, "      -> %s", destp); */
367         return string_end(&dest);
368 }
369
370 /* Undoes the special character conversion. */
371 static int cfrommap(int c)
372 {
373         switch (c)
374         {
375                 case '|':       return '/';
376                 case '/':       return '\\';
377         }
378         return c;               
379 }
380
381
382 /* Break a command down into tokens, unquoting any escaped characters. */
383 void MakeStringOf(StrBuf *Buf, int skip)
384 {
385         int i;
386         citimap_command *Cmd = &IMAP->Cmd;
387
388         for (i=skip; i<Cmd->num_parms; ++i) {
389                 StrBufAppendBufPlain(Buf, Cmd->Params[i].Key, Cmd->Params[i].len, 0);
390                 if (i < (Cmd->num_parms-1)) StrBufAppendBufPlain(Buf, HKEY(" "), 0);
391         }
392 }
393
394
395 void TokenCutRight(citimap_command *Cmd, 
396                    ConstStr *CutMe,
397                    int n)
398 {
399         const char *CutAt;
400
401         if (CutMe->len < n) {
402                 CutAt = CutMe->Key;
403                 CutMe->len = 0;
404         }
405         else {
406                 CutAt = CutMe->Key + CutMe->len - n;
407                 CutMe->len -= n;
408         }
409         StrBufPeek(Cmd->CmdBuf, CutAt, -1, '\0');
410 }
411
412 void TokenCutLeft(citimap_command *Cmd, 
413                   ConstStr *CutMe,
414                   int n)
415 {
416         if (CutMe->len < n) {
417                 CutMe->Key += CutMe->len;
418                 CutMe->len = 0;
419         }
420         else {
421                 CutMe->Key += n;
422                 CutMe->len -= n;
423         }
424 }
425
426
427
428 int CmdAdjust(citimap_command *Cmd, 
429               int nArgs,
430               int Realloc)
431 {
432         ConstStr *Params;
433         if (nArgs > Cmd->avail_parms) {
434                 Params = (ConstStr*) malloc(sizeof(ConstStr) * nArgs);
435                 if (Realloc) {
436                         memcpy(Params, 
437                                Cmd->Params, 
438                                sizeof(ConstStr) * Cmd->avail_parms);
439
440                         memset(Cmd->Params + 
441                                sizeof(ConstStr) * Cmd->avail_parms,
442                                0, 
443                                sizeof(ConstStr) * nArgs - 
444                                sizeof(ConstStr) * Cmd->avail_parms 
445                                 );
446                 }
447                 else {
448                         Cmd->num_parms = 0;
449                         memset(Params, 0, 
450                                sizeof(ConstStr) * nArgs);
451                 }
452                 Cmd->avail_parms = nArgs;
453                 if (Cmd->Params != NULL)
454                         free (Cmd->Params);
455                 Cmd->Params = Params;
456         }
457         else {
458                 if (!Realloc) {
459                         memset(Cmd->Params, 
460                                0,
461                                sizeof(ConstStr) * Cmd->avail_parms);
462                         Cmd->num_parms = 0;
463                 }
464         }
465         return Cmd->avail_parms;
466 }
467
468 int imap_parameterize(citimap_command *Cmd)
469 {
470         int nArgs;
471         const char *In, *End;
472
473         In = ChrPtr(Cmd->CmdBuf);
474         End = In + StrLength(Cmd->CmdBuf);
475
476         /* we start with 10 chars per arg, maybe we need to realloc later. */
477         nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
478         nArgs = CmdAdjust(Cmd, nArgs, 0);
479         while (In < End)
480         {
481                 /* Skip whitespace. */
482                 while (isspace(*In))
483                         In++;
484                 if (*In == '\0')
485                         break;
486
487                 /* Found the start of a token. */
488                 
489                 Cmd->Params[Cmd->num_parms].Key = In;
490
491                 /* Read in the token. */
492
493                 for (;;)
494                 {
495                         if (isspace(*In))
496                                 break;
497                         
498                         if (*In == '\"')
499                         {
500                                 /* Found a quoted section. */
501
502                                 Cmd->Params[Cmd->num_parms].Key++; 
503                                 //In++;
504                                 for (;;)
505                                 {
506                                         In++;
507                                         if (*In == '\"') {
508                                                 StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
509                                                 break;
510                                         }
511                                         else if (*In == '\\')
512                                                 In++;
513
514                                         if (*In == '\0') {
515                                                 Cmd->Params[Cmd->num_parms].len = 
516                                                         In - Cmd->Params[Cmd->num_parms].Key;
517                                                 Cmd->num_parms++;
518                                                 return Cmd->num_parms;
519                                         }
520                                 }
521                                 break;
522                         }
523                         else if (*In == '\\')
524                         {
525                                 In++;
526                         }
527
528                         if (*In == '\0') {
529                                 Cmd->Params[Cmd->num_parms].len = 
530                                         In - Cmd->Params[Cmd->num_parms].Key;
531                                 Cmd->num_parms++;
532                                 return Cmd->num_parms;
533                         }
534                         In++;
535                 }
536                 StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
537                 Cmd->Params[Cmd->num_parms].len = 
538                         In - Cmd->Params[Cmd->num_parms].Key;
539                 if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
540                         nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
541                 }
542                 Cmd->num_parms ++;
543                 In++;
544         }
545         return Cmd->num_parms;
546 }
547
548
549 /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
550 long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
551 {
552         char* bufend = buf+bufsize;
553         struct floor *fl;
554         char* p = buf;
555         const char *pend;
556
557         /* For mailboxes, just do it straight.
558          * Do the Cyrus-compatible thing: all private folders are
559          * subfolders of INBOX. */
560
561         if (qrbuf->QRflags & QR_MAILBOX)
562         {
563                 if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
564                 {
565                         pend = toimap(p, bufend, "INBOX");
566                         return pend - buf;
567                 }
568                 else
569                 {
570                         p = toimap(p, bufend, "INBOX");
571                         if (p < bufend)
572                                 *p++ = '/';
573                         pend = toimap(p, bufend, qrbuf->QRname+11);
574                         return pend - buf;
575                 }
576         }
577         else
578         {
579                 /* Otherwise, prefix the floor name as a "public folders" moniker. */
580
581                 fl = CtdlGetCachedFloor(qrbuf->QRfloor);
582                 p = toimap(p, bufend, fl->f_name);
583                 if (p < bufend)
584                         *p++ = '/';
585                 pend = toimap(p, bufend, qrbuf->QRname);
586                 return pend - buf;
587         }
588 }
589
590 /*
591  * Convert an inputted folder name to our best guess as to what an equivalent
592  * room name should be.
593  *
594  * If an error occurs, it returns -1.  Otherwise...
595  *
596  * The lower eight bits of the return value are the floor number on which the
597  * room most likely resides.   The upper eight bits may contain flags,
598  * including IR_MAILBOX if we're dealing with a personal room.
599  *
600  */
601 int imap_roomname(char *rbuf, int bufsize, const char *foldername)
602 {
603         int levels;
604         char floorname[ROOMNAMELEN*2];
605         char roomname[ROOMNAMELEN];
606         int i;
607         struct floor *fl;
608         int ret = (-1);
609
610         if (foldername == NULL)
611                 return(-1);
612
613         /* Unmunge the entire string into the output buffer. */
614
615         fromimap(rbuf, rbuf+bufsize, foldername);
616
617         /* Is this an IMAP inbox? */
618
619         if (strncasecmp(rbuf, "INBOX", 5) == 0)
620         {
621                 if (rbuf[5] == 0)
622                 {
623                         /* It's the system inbox. */
624
625                         safestrncpy(rbuf, MAILROOM, bufsize);
626                         ret = (0 | IR_MAILBOX);
627                         goto exit;
628                 }
629                 else if (rbuf[5] == FDELIM)
630                 {
631                         /* It's another personal mail folder. */
632
633                         safestrncpy(rbuf, rbuf+6, bufsize);
634                         ret = (0 | IR_MAILBOX);
635                         goto exit;
636                 }
637
638                 /* If we get here, the folder just happens to start with INBOX
639                  * --- fall through. */
640         }
641
642         /* Is this a multi-level room name? */
643
644         levels = num_tokens(rbuf, FDELIM);
645         if (levels > 1)
646         {
647                 long len;
648                 /* Extract the main room name. */
649                 
650                 len = extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
651                 if (len < 0) len = 0;
652                 safestrncpy(roomname, &rbuf[len  + 1], sizeof(roomname));
653
654                 /* Try and find it on any floor. */
655                 
656                 for (i = 0; i < MAXFLOORS; ++i)
657                 {
658                         fl = CtdlGetCachedFloor(i);
659                         if (fl->f_flags & F_INUSE)
660                         {
661                                 if (strcasecmp(floorname, fl->f_name) == 0)
662                                 {
663                                         /* Got it! */
664
665                                         safestrncpy(rbuf, roomname, bufsize);
666                                         ret = i;
667                                         goto exit;
668                                 }
669                         }
670                 }
671         }
672
673         /* Meh. It's either not a multi-level room name, or else we
674          * couldn't find it.
675          */
676         ret = (0 | IR_MAILBOX);
677
678 exit:
679         syslog(LOG_DEBUG, "(That translates to \"%s\")", rbuf);
680         return(ret);
681 }
682
683
684 /*
685  * Determine whether the supplied string is a valid message set.
686  * If the string contains only numbers, colons, commas, and asterisks,
687  * return 1 for a valid message set.  If any other character is found, 
688  * return 0.
689  */
690 int imap_is_message_set(const char *buf)
691 {
692         int i;
693
694         if (buf == NULL)
695                 return (0);     /* stupidity checks */
696         if (IsEmptyStr(buf))
697                 return (0);
698
699         if (!strcasecmp(buf, "ALL"))
700                 return (1);     /* macro?  why?  */
701
702         for (i = 0; buf[i]; ++i) {      /* now start the scan */
703                 if (
704                            (!isdigit(buf[i]))
705                            && (buf[i] != ':')
706                            && (buf[i] != ',')
707                            && (buf[i] != '*')
708                     )
709                         return (0);
710         }
711
712         return (1);             /* looks like we're good */
713 }
714
715
716 /*
717  * imap_match.c, based on wildmat.c from INN
718  * hacked for Citadel/IMAP by Daniel Malament
719  */
720
721 /* note: not all return statements use these; don't change them */
722 #define WILDMAT_TRUE    1
723 #define WILDMAT_FALSE   0
724 #define WILDMAT_ABORT   -1
725 #define WILDMAT_DELIM   '/'
726
727 /*
728  * Match text and p, return TRUE, FALSE, or ABORT.
729  */
730 static int do_imap_match(const char *supplied_text, const char *supplied_p)
731 {
732         int matched, i;
733         char lcase_text[SIZ], lcase_p[SIZ];
734         char *text;
735         char *p;
736         
737         /* Copy both strings and lowercase them, in order to
738          * make this entire operation case-insensitive.
739          */
740         for (i=0; 
741              ((supplied_text[i] != '\0') && 
742               (i < sizeof(lcase_text)));
743              ++i)
744                 lcase_text[i] = tolower(supplied_text[i]);
745         lcase_text[i] = '\0';
746
747         for (i=0; 
748              ((supplied_p[i] != '\0') && 
749               (i < sizeof(lcase_p))); 
750              ++i)
751                 lcase_p[i] = tolower(supplied_p[i]);
752         lcase_p[i] = '\0';
753
754         /* Start matching */
755         for (p = lcase_p, text = lcase_text; 
756              !IsEmptyStr(p) && !IsEmptyStr(text); 
757              text++, p++) {
758                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
759                         return WILDMAT_ABORT;
760                 }
761                 switch (*p) {
762                 default:
763                         if (*text != *p) {
764                                 return WILDMAT_FALSE;
765                         }
766                         continue;
767                 case '*':
768 star:
769                         while (++p, ((*p == '*') || (*p == '%'))) {
770                                 /* Consecutive stars or %'s act
771                                  * just like one star.
772                                  */
773                                 continue;
774                         }
775                         if (*p == '\0') {
776                                 /* Trailing star matches everything. */
777                                 return WILDMAT_TRUE;
778                         }
779                         while (*text) {
780                                 if ((matched = do_imap_match(text++, p))
781                                    != WILDMAT_FALSE) {
782                                         return matched;
783                                 }
784                         }
785                         return WILDMAT_ABORT;
786                 case '%':
787                         while (++p, (!IsEmptyStr(p) && ((*p == '*') || (*p == '%'))))
788                         {
789                                 /* Consecutive %'s act just like one, but even
790                                  * a single star makes the sequence act like
791                                  * one star, instead.
792                                  */
793                                 if (*p == '*') {
794                                         goto star;
795                                 }
796                                 continue;
797                         }
798                         if (*p == '\0') {
799                                 /*
800                                  * Trailing % matches everything
801                                  * without a delimiter.
802                                  */
803                                 while (!IsEmptyStr(text)) {
804                                         if (*text == WILDMAT_DELIM) {
805                                                 return WILDMAT_FALSE;
806                                         }
807                                         text++;
808                                 }
809                                 return WILDMAT_TRUE;
810                         }
811                         while (!IsEmptyStr(text) &&
812                                /* make sure text - 1 isn't before lcase_p */
813                                ((text == lcase_text) || (*(text - 1) != WILDMAT_DELIM)))
814                         {
815                                 if ((matched = do_imap_match(text++, p))
816                                    != WILDMAT_FALSE) {
817                                         return matched;
818                                 }
819                         }
820                         return WILDMAT_ABORT;
821                 }
822         }
823
824         if ((*text == '\0') && (*p == '\0')) return WILDMAT_TRUE;
825         else return WILDMAT_FALSE;
826 }
827
828
829 /*
830  * Support function for mailbox pattern name matching in LIST and LSUB
831  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
832  */
833 int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname)
834 {
835         /* handle just-star case quickly */
836         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
837                 return WILDMAT_TRUE;
838         }
839         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
840 }
841
842
843
844 /*
845  * Compare an IMAP date string (date only, no time) to the date found in
846  * a Unix timestamp.
847  */
848 int imap_datecmp(const char *datestr, time_t msgtime) {
849         char daystr[256];
850         char monthstr[256];
851         char yearstr[256];
852         int i;
853         int day, month, year;
854         int msgday, msgmonth, msgyear;
855         struct tm msgtm;
856
857         char *imap_datecmp_ascmonths[12] = {
858                 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
859         };
860
861         if (datestr == NULL) return(0);
862
863         /* Expecting a date in the form dd-Mmm-yyyy */
864         extract_token(daystr, datestr, 0, '-', sizeof daystr);
865         extract_token(monthstr, datestr, 1, '-', sizeof monthstr);
866         extract_token(yearstr, datestr, 2, '-', sizeof yearstr);
867
868         day = atoi(daystr);
869         year = atoi(yearstr);
870         month = 0;
871         for (i=0; i<12; ++i) {
872                 if (!strcasecmp(monthstr, imap_datecmp_ascmonths[i])) {
873                         month = i;
874                 }
875         }
876
877         /* Extract day/month/year from message timestamp */
878         localtime_r(&msgtime, &msgtm);
879         msgday = msgtm.tm_mday;
880         msgmonth = msgtm.tm_mon;
881         msgyear = msgtm.tm_year + 1900;
882
883         /* Now start comparing */
884
885         if (year < msgyear) return(+1);
886         if (year > msgyear) return(-1);
887
888         if (month < msgmonth) return(+1);
889         if (month > msgmonth) return(-1);
890
891         if (day < msgday) return(+1);
892         if (day > msgday) return(-1);
893
894         return(0);
895 }
896
897
898 void IAPrintf(const char *Format, ...)
899 {
900         va_list arg_ptr;
901         
902         va_start(arg_ptr, Format);
903         StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
904         va_end(arg_ptr);
905 }
906
907
908 void iaputs(const char *Str, long Len)
909 {
910         StrBufAppendBufPlain(IMAP->Reply, Str, Len, 0);
911 }
912
913
914 void ireply(const char *Msg, long len)
915 {
916         citimap *Imap = IMAP;
917
918         StrBufAppendBufPlain(Imap->Reply, 
919                              CKEY(Imap->Cmd.Params[0]), 0);
920         StrBufAppendBufPlain(Imap->Reply, 
921                              HKEY(" "), 0);
922         StrBufAppendBufPlain(Imap->Reply, 
923                              Msg, len, 0);
924         
925         StrBufAppendBufPlain(Imap->Reply, 
926                              HKEY("\r\n"), 0);
927         
928 }
929
930
931 void IReplyPrintf(const char *Format, ...)
932 {
933         citimap *Imap = IMAP;
934         va_list arg_ptr;
935         
936
937         StrBufAppendBufPlain(Imap->Reply, 
938                              CKEY(Imap->Cmd.Params[0]), 0);
939
940         StrBufAppendBufPlain(Imap->Reply, 
941                              HKEY(" "), 0);
942
943         va_start(arg_ptr, Format);
944         StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
945         va_end(arg_ptr);
946         
947         StrBufAppendBufPlain(Imap->Reply, 
948                              HKEY("\r\n"), 0);
949         
950 }
951
952
953 /* Output a string to the IMAP client, either as a literal or quoted.
954  * (We do a literal if it has any double-quotes or backslashes.) */
955 void IPutStr(const char *Msg, long Len)
956 {
957         int i;
958         int is_literal = 0;
959         citimap *Imap = IMAP;
960
961         
962         if ((Msg == NULL) || (Len == 0))
963         {       /* yeah, we handle this */
964                 StrBufAppendBufPlain(Imap->Reply, HKEY("NIL"), 0);
965                 return;
966         }
967
968         for (i = 0; i < Len; ++i) {
969                 if ((Msg[i] == '\"') || (Msg[i] == '\\'))
970                         is_literal = 1;
971         }
972
973         if (is_literal) {
974                 StrBufAppendPrintf(Imap->Reply, "{%ld}\r\n", Len);
975                 StrBufAppendBufPlain(Imap->Reply, Msg, Len, 0);
976         } else {
977                 StrBufAppendBufPlain(Imap->Reply, 
978                                      HKEY("\""), 0);
979                 StrBufAppendBufPlain(Imap->Reply, 
980                                      Msg, Len, 0);
981                 StrBufAppendBufPlain(Imap->Reply, 
982                                      HKEY("\""), 0);
983         }
984 }
985
986 void IUnbuffer (void)
987 {
988         citimap *Imap = IMAP;
989
990         cputbuf(Imap->Reply);
991         FlushStrBuf(Imap->Reply);
992 }