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