4c86dcfe465a73634d408e68b398994f593345be
[citadel.git] / citadel / imap_tools.c
1 /*
2  * $Id$
3  *
4  * Utility functions for the IMAP module.
5  *
6  */
7
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <stdio.h>
11 #include <ctype.h>
12 #include <string.h>
13 #include "citadel.h"
14 #include "sysdep_decls.h"
15 #include "tools.h"
16 #include "room_ops.h"
17 #include "internet_addressing.h"
18 #include "imap_tools.h"
19
20
21 #ifndef HAVE_SNPRINTF
22 #include "snprintf.h"
23 #endif
24
25 /*
26  * Output a string to the IMAP client, either as a literal or quoted.
27  * (We do a literal if it has any double-quotes or backslashes.)
28  */
29 void imap_strout(char *buf)
30 {
31         int i;
32         int is_literal = 0;
33
34         if (buf == NULL) {      /* yeah, we handle this */
35                 cprintf("NIL");
36                 return;
37         }
38         for (i = 0; i < strlen(buf); ++i) {
39                 if ((buf[i] == '\"') || (buf[i] == '\\'))
40                         is_literal = 1;
41         }
42
43         if (is_literal) {
44                 cprintf("{%ld}\r\n%s", (long)strlen(buf), buf);
45         } else {
46                 cprintf("\"%s\"", buf);
47         }
48 }
49
50
51
52
53
54 /*
55  * Break a command down into tokens, taking into consideration the
56  * possibility of escaping spaces using quoted tokens
57  */
58 int imap_parameterize(char **args, char *buf)
59 {
60         int num = 0;
61         int start = 0;
62         int i;
63         int in_quote = 0;
64         int original_len;
65
66         strcat(buf, " ");
67
68         original_len = strlen(buf);
69
70         for (i = 0; i < original_len; ++i) {
71
72                 if ((isspace(buf[i])) && (!in_quote)) {
73                         buf[i] = 0;
74                         args[num] = &buf[start];
75                         start = i + 1;
76                         if (args[num][0] == '\"') {
77                                 ++args[num];
78                                 args[num][strlen(args[num]) - 1] = 0;
79                         }
80                         ++num;
81                 } else if ((buf[i] == '\"') && (!in_quote)) {
82                         in_quote = 1;
83                 } else if ((buf[i] == '\"') && (in_quote)) {
84                         in_quote = 0;
85                 }
86         }
87
88         return (num);
89 }
90
91 /*
92  * Convert a struct ctdlroom to an IMAP-compatible mailbox name.
93  */
94 void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
95 {
96         struct floor *fl;
97         int i;
98         char buf2[SIZ];
99
100         /*
101          * For mailboxes, just do it straight.
102          * Do the Cyrus-compatible thing: all private folders are
103          * subfolders of INBOX.
104          */
105         if (qrbuf->QRflags & QR_MAILBOX) {
106                 safestrncpy(buf, qrbuf->QRname, bufsize);
107                 strcpy(buf, &buf[11]);
108                 if (!strcasecmp(buf, MAILROOM)) {
109                         strcpy(buf, "INBOX");
110                 }
111                 else {
112                         sprintf(buf2, "INBOX|%s", buf);
113                         strcpy(buf, buf2);
114                 }
115         }
116         /*
117          * Otherwise, prefix the floor name as a "public folders" moniker
118          */
119         else {
120                 fl = cgetfloor(qrbuf->QRfloor);
121                 snprintf(buf, bufsize, "%s|%s",
122                          fl->f_name,
123                          qrbuf->QRname);
124         }
125
126         /*
127          * Replace delimiter characters with "|" for pseudo-folder-delimiting
128          */
129         for (i=0; i<strlen(buf); ++i) {
130                 if (buf[i] == FDELIM) buf[i] = '|';
131         }
132 }
133
134
135 /*
136  * Convert an inputted folder name to our best guess as to what an equivalent
137  * room name should be.
138  *
139  * If an error occurs, it returns -1.  Otherwise...
140  *
141  * The lower eight bits of the return value are the floor number on which the
142  * room most likely resides.   The upper eight bits may contain flags,
143  * including IR_MAILBOX if we're dealing with a personal room.
144  *
145  */
146 int imap_roomname(char *rbuf, int bufsize, char *foldername)
147 {
148         int levels;
149         char floorname[SIZ];
150         char roomname[SIZ];
151         int i;
152         struct floor *fl;
153         int ret = (-1);
154
155         if (foldername == NULL) return(-1);
156         levels = num_parms(foldername);
157
158         /*
159          * Convert the crispy idiot's reserved names to our reserved names.
160          * Also handle Cyrus-compatible folder names.
161          */
162         if (!strcasecmp(foldername, "INBOX")) {
163                 safestrncpy(rbuf, MAILROOM, bufsize);
164                 ret = (0 | IR_MAILBOX);
165         }
166         else if (!strncasecmp(foldername, "INBOX|", 6)) {
167                 safestrncpy(rbuf, &foldername[6], bufsize);
168                 ret = (0 | IR_MAILBOX);
169         }
170         else if (levels > 1) {
171                 extract(floorname, foldername, 0);
172                 strcpy(roomname, &foldername[strlen(floorname)+1]);
173                 for (i = 0; i < MAXFLOORS; ++i) {
174                         fl = cgetfloor(i);
175                         if (fl->f_flags & F_INUSE) {
176                                 if (!strcasecmp(floorname, fl->f_name)) {
177                                         strcpy(rbuf, roomname);
178                                         ret = i;
179                                 }
180                         }
181                 }
182
183                 if (ret < 0) {
184                         /* No subfolderificationalisticism on this one... */
185                         safestrncpy(rbuf, foldername, bufsize);
186                         ret = (0 | IR_MAILBOX);
187                 }
188
189         }
190         else {
191                 safestrncpy(rbuf, foldername, bufsize);
192                 ret = (0 | IR_MAILBOX);
193         }
194
195         /* Undelimiterizationalisticize the room name (change '|') */
196         for (i=0; i<strlen(rbuf); ++i) {
197                 if (rbuf[i] == '|') rbuf[i] = FDELIM;
198         }
199
200
201 /*** This doesn't work.
202         char buf[SIZ];
203         if (ret & IR_MAILBOX) {
204                 if (atol(rbuf) == 0L) {
205                         strcpy(buf, rbuf);
206                         sprintf(rbuf, "%010ld.%s", CC->user.usernum, buf);
207                 }
208         }
209  ***/
210
211         lprintf(9, "(That translates to \"%s\")\n", rbuf);
212         return(ret);
213 }
214
215
216
217
218
219 /*
220  * Output a struct internet_address_list in the form an IMAP client wants
221  */
222 void imap_ial_out(struct internet_address_list *ialist)
223 {
224         struct internet_address_list *iptr;
225
226         if (ialist == NULL) {
227                 cprintf("NIL");
228                 return;
229         }
230         cprintf("(");
231
232         for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
233                 cprintf("(");
234                 imap_strout(iptr->ial_name);
235                 cprintf(" NIL ");
236                 imap_strout(iptr->ial_user);
237                 cprintf(" ");
238                 imap_strout(iptr->ial_node);
239                 cprintf(")");
240         }
241
242         cprintf(")");
243 }
244
245
246
247 /*
248  * Determine whether the supplied string is a valid message set.
249  * If the string contains only numbers, colons, commas, and asterisks,
250  * return 1 for a valid message set.  If any other character is found, 
251  * return 0.
252  */
253 int imap_is_message_set(char *buf)
254 {
255         int i;
256
257         if (buf == NULL)
258                 return (0);     /* stupidity checks */
259         if (strlen(buf) == 0)
260                 return (0);
261
262         if (!strcasecmp(buf, "ALL"))
263                 return (1);     /* macro?  why?  */
264
265         for (i = 0; i < strlen(buf); ++i) {     /* now start the scan */
266                 if (
267                            (!isdigit(buf[i]))
268                            && (buf[i] != ':')
269                            && (buf[i] != ',')
270                            && (buf[i] != '*')
271                     )
272                         return (0);
273         }
274
275         return (1);             /* looks like we're good */
276 }
277
278
279 /*
280  * imap_match.c, based on wildmat.c from INN
281  * hacked for Citadel/IMAP by Daniel Malament
282  */
283
284 /* note: not all return statements use these; don't change them */
285 #define WILDMAT_TRUE    1
286 #define WILDMAT_FALSE   0
287 #define WILDMAT_ABORT   -1
288 #define WILDMAT_DELIM   '|'
289
290 /*
291  * Match text and p, return TRUE, FALSE, or ABORT.
292  */
293 static int do_imap_match(const char *supplied_text, const char *supplied_p)
294 {
295         int matched, i;
296         char lcase_text[SIZ], lcase_p[SIZ];
297         char *text = lcase_text;
298         char *p = lcase_p;
299
300         /* Copy both strings and lowercase them, in order to
301          * make this entire operation case-insensitive.
302          */
303         for (i=0; i<=strlen(supplied_text); ++i)
304                 lcase_text[i] = tolower(supplied_text[i]);
305         for (i=0; i<=strlen(supplied_p); ++i)
306                 p[i] = tolower(supplied_p[i]);
307
308         /* Start matching */
309         for (; *p; text++, p++) {
310                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
311                         return WILDMAT_ABORT;
312                 }
313                 switch (*p) {
314                 default:
315                         if (*text != *p) {
316                                 return WILDMAT_FALSE;
317                         }
318                         continue;
319                 case '*':
320 star:
321                         while (++p, ((*p == '*') || (*p == '%'))) {
322                                 /* Consecutive stars or %'s act
323                                  * just like one star.
324                                  */
325                                 continue;
326                         }
327                         if (*p == '\0') {
328                                 /* Trailing star matches everything. */
329                                 return WILDMAT_TRUE;
330                         }
331                         while (*text) {
332                                 if ((matched = do_imap_match(text++, p))
333                                    != WILDMAT_FALSE) {
334                                         return matched;
335                                 }
336                         }
337                         return WILDMAT_ABORT;
338                 case '%':
339                         while (++p, ((*p == '*') || (*p == '%'))) {
340                                 /* Consecutive %'s act just like one, but even
341                                  * a single star makes the sequence act like
342                                  * one star, instead.
343                                  */
344                                 if (*p == '*') {
345                                         goto star;
346                                 }
347                                 continue;
348                         }
349                         if (*p == '\0') {
350                                 /*
351                                  * Trailing % matches everything
352                                  * without a delimiter.
353                                  */
354                                 while (*text) {
355                                         if (*text == WILDMAT_DELIM) {
356                                                 return WILDMAT_FALSE;
357                                         }
358                                         text++;
359                                 }
360                                 return WILDMAT_TRUE;
361                         }
362                         while (*text && (*(text - 1) != WILDMAT_DELIM)) {
363                                 if ((matched = do_imap_match(text++, p))
364                                    != WILDMAT_FALSE) {
365                                         return matched;
366                                 }
367                         }
368                         return WILDMAT_ABORT;
369                 }
370         }
371
372         return (*text == '\0');
373 }
374
375
376
377 /*
378  * Support function for mailbox pattern name matching in LIST and LSUB
379  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
380  */
381 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
382 {
383         /* handle just-star case quickly */
384         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
385                 return WILDMAT_TRUE;
386         }
387         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
388 }
389
390
391
392 /*
393  * Compare an IMAP date string (date only, no time) to the date found in
394  * a Unix timestamp.
395  */
396 int imap_datecmp(char *datestr, time_t msgtime) {
397         char daystr[SIZ];
398         char monthstr[SIZ];
399         char yearstr[SIZ];
400         int i;
401         int day, month, year;
402         int msgday, msgmonth, msgyear;
403         struct tm msgtm;
404
405         if (datestr == NULL) return(0);
406
407         /* Expecting a date in the form dd-Mmm-yyyy */
408         extract_token(daystr, datestr, 0, '-');
409         extract_token(monthstr, datestr, 1, '-');
410         extract_token(yearstr, datestr, 2, '-');
411
412         day = atoi(daystr);
413         year = atoi(yearstr);
414         month = 0;
415         for (i=0; i<12; ++i) {
416                 if (!strcasecmp(monthstr, ascmonths[i])) {
417                         month = i;
418                 }
419         }
420
421         /* Extract day/month/year from message timestamp */
422         memcpy(&msgtm, localtime(&msgtime), sizeof(struct tm));
423         msgday = msgtm.tm_mday;
424         msgmonth = msgtm.tm_mon;
425         msgyear = msgtm.tm_year + 1900;
426
427         /* Now start comparing */
428
429         if (year < msgyear) return(+1);
430         if (year > msgyear) return(-1);
431
432         if (month < msgmonth) return(+1);
433         if (month > msgmonth) return(-1);
434
435         if (day < msgday) return(+1);
436         if (day > msgday) return(-1);
437
438         return(0);
439 }