* The groupware folders "Calendar", "Contacts", "Notes", and "Tasks" are
[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
99         /*
100          * For mailboxes, just do it straight.
101          * Also handle Kolab-compatible groupware folder names.
102          */
103         if (qrbuf->QRflags & QR_MAILBOX) {
104                 safestrncpy(buf, qrbuf->QRname, bufsize);
105                 strcpy(buf, &buf[11]);
106                 if (!strcasecmp(buf, MAILROOM)) {
107                         strcpy(buf, "INBOX");
108                 }
109                 if (!strcasecmp(buf, USERCALENDARROOM)) {
110                         sprintf(buf, "INBOX|%s", USERCALENDARROOM);
111                 }
112                 if (!strcasecmp(buf, USERCONTACTSROOM)) {
113                         sprintf(buf, "INBOX|%s", USERCONTACTSROOM);
114                 }
115                 if (!strcasecmp(buf, USERTASKSROOM)) {
116                         sprintf(buf, "INBOX|%s", USERTASKSROOM);
117                 }
118                 if (!strcasecmp(buf, USERNOTESROOM)) {
119                         sprintf(buf, "INBOX|%s", USERNOTESROOM);
120                 }
121         }
122         /*
123          * Otherwise, prefix the floor name as a "public folders" moniker
124          */
125         else {
126                 fl = cgetfloor(qrbuf->QRfloor);
127                 snprintf(buf, bufsize, "%s|%s",
128                          fl->f_name,
129                          qrbuf->QRname);
130         }
131
132         /*
133          * Replace delimiter characters with "|" for pseudo-folder-delimiting
134          */
135         for (i=0; i<strlen(buf); ++i) {
136                 if (buf[i] == FDELIM) buf[i] = '|';
137         }
138 }
139
140
141 /*
142  * Convert an inputted folder name to our best guess as to what an equivalent
143  * room name should be.
144  *
145  * If an error occurs, it returns -1.  Otherwise...
146  *
147  * The lower eight bits of the return value are the floor number on which the
148  * room most likely resides.   The upper eight bits may contain flags,
149  * including IR_MAILBOX if we're dealing with a personal room.
150  *
151  */
152 int imap_roomname(char *rbuf, int bufsize, char *foldername)
153 {
154         int levels;
155         char floorname[SIZ];
156         char roomname[SIZ];
157         int i;
158         struct floor *fl;
159         int ret = (-1);
160
161         if (foldername == NULL) return(-1);
162         levels = num_parms(foldername);
163
164         /*
165          * Convert the crispy idiot's reserved names to our reserved names.
166          * Also handle Kolab-compatible groupware folder names.
167          */
168         if (!strcasecmp(foldername, "INBOX")) {
169                 safestrncpy(rbuf, MAILROOM, bufsize);
170                 ret = (0 | IR_MAILBOX);
171         }
172         else if ( (!strncasecmp(foldername, "INBOX", 5))
173               && (!strcasecmp(&foldername[6], USERCALENDARROOM)) ) {
174                 safestrncpy(rbuf, USERCALENDARROOM, bufsize);
175                 ret = (0 | IR_MAILBOX);
176         }
177         else if ( (!strncasecmp(foldername, "INBOX", 5))
178               && (!strcasecmp(&foldername[6], USERCONTACTSROOM)) ) {
179                 safestrncpy(rbuf, USERCONTACTSROOM, bufsize);
180                 ret = (0 | IR_MAILBOX);
181         }
182         else if ( (!strncasecmp(foldername, "INBOX", 5))
183               && (!strcasecmp(&foldername[6], USERTASKSROOM)) ) {
184                 safestrncpy(rbuf, USERTASKSROOM, bufsize);
185                 ret = (0 | IR_MAILBOX);
186         }
187         else if ( (!strncasecmp(foldername, "INBOX", 5))
188               && (!strcasecmp(&foldername[6], USERNOTESROOM)) ) {
189                 safestrncpy(rbuf, USERNOTESROOM, bufsize);
190                 ret = (0 | IR_MAILBOX);
191         }
192         else if (levels > 1) {
193                 extract(floorname, foldername, 0);
194                 strcpy(roomname, &foldername[strlen(floorname)+1]);
195                 for (i = 0; i < MAXFLOORS; ++i) {
196                         fl = cgetfloor(i);
197                         if (fl->f_flags & F_INUSE) {
198                                 if (!strcasecmp(floorname, fl->f_name)) {
199                                         strcpy(rbuf, roomname);
200                                         ret = i;
201                                 }
202                         }
203                 }
204
205                 if (ret < 0) {
206                         /* No subfolderificationalisticism on this one... */
207                         safestrncpy(rbuf, foldername, bufsize);
208                         ret = (0 | IR_MAILBOX);
209                 }
210
211         }
212         else {
213                 safestrncpy(rbuf, foldername, bufsize);
214                 ret = (0 | IR_MAILBOX);
215         }
216
217         /* Undelimiterizationalize the room name (change '|') */
218         for (i=0; i<strlen(rbuf); ++i) {
219                 if (rbuf[i] == '|') rbuf[i] = FDELIM;
220         }
221
222
223 /*** This doesn't work.
224         char buf[SIZ];
225         if (ret & IR_MAILBOX) {
226                 if (atol(rbuf) == 0L) {
227                         strcpy(buf, rbuf);
228                         sprintf(rbuf, "%010ld.%s", CC->user.usernum, buf);
229                 }
230         }
231  ***/
232
233         lprintf(9, "(That translates to \"%s\")\n", rbuf);
234         return(ret);
235 }
236
237
238
239
240
241 /*
242  * Output a struct internet_address_list in the form an IMAP client wants
243  */
244 void imap_ial_out(struct internet_address_list *ialist)
245 {
246         struct internet_address_list *iptr;
247
248         if (ialist == NULL) {
249                 cprintf("NIL");
250                 return;
251         }
252         cprintf("(");
253
254         for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
255                 cprintf("(");
256                 imap_strout(iptr->ial_name);
257                 cprintf(" NIL ");
258                 imap_strout(iptr->ial_user);
259                 cprintf(" ");
260                 imap_strout(iptr->ial_node);
261                 cprintf(")");
262         }
263
264         cprintf(")");
265 }
266
267
268
269 /*
270  * Determine whether the supplied string is a valid message set.
271  * If the string contains only numbers, colons, commas, and asterisks,
272  * return 1 for a valid message set.  If any other character is found, 
273  * return 0.
274  */
275 int imap_is_message_set(char *buf)
276 {
277         int i;
278
279         if (buf == NULL)
280                 return (0);     /* stupidity checks */
281         if (strlen(buf) == 0)
282                 return (0);
283
284         if (!strcasecmp(buf, "ALL"))
285                 return (1);     /* macro?  why?  */
286
287         for (i = 0; i < strlen(buf); ++i) {     /* now start the scan */
288                 if (
289                            (!isdigit(buf[i]))
290                            && (buf[i] != ':')
291                            && (buf[i] != ',')
292                            && (buf[i] != '*')
293                     )
294                         return (0);
295         }
296
297         return (1);             /* looks like we're good */
298 }
299
300
301 /*
302  * imap_match.c, based on wildmat.c from INN
303  * hacked for Citadel/IMAP by Daniel Malament
304  */
305
306 /* note: not all return statements use these; don't change them */
307 #define WILDMAT_TRUE    1
308 #define WILDMAT_FALSE   0
309 #define WILDMAT_ABORT   -1
310 #define WILDMAT_DELIM   '|'
311
312 /*
313  * Match text and p, return TRUE, FALSE, or ABORT.
314  */
315 static int do_imap_match(const char *supplied_text, const char *supplied_p)
316 {
317         int matched, i;
318         char lcase_text[SIZ], lcase_p[SIZ];
319         char *text = lcase_text;
320         char *p = lcase_p;
321
322         /* Copy both strings and lowercase them, in order to
323          * make this entire operation case-insensitive.
324          */
325         for (i=0; i<=strlen(supplied_text); ++i)
326                 lcase_text[i] = tolower(supplied_text[i]);
327         for (i=0; i<=strlen(supplied_p); ++i)
328                 p[i] = tolower(supplied_p[i]);
329
330         /* Start matching */
331         for (; *p; text++, p++) {
332                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
333                         return WILDMAT_ABORT;
334                 }
335                 switch (*p) {
336                 default:
337                         if (*text != *p) {
338                                 return WILDMAT_FALSE;
339                         }
340                         continue;
341                 case '*':
342 star:
343                         while (++p, ((*p == '*') || (*p == '%'))) {
344                                 /* Consecutive stars or %'s act
345                                  * just like one star.
346                                  */
347                                 continue;
348                         }
349                         if (*p == '\0') {
350                                 /* Trailing star matches everything. */
351                                 return WILDMAT_TRUE;
352                         }
353                         while (*text) {
354                                 if ((matched = do_imap_match(text++, p))
355                                    != WILDMAT_FALSE) {
356                                         return matched;
357                                 }
358                         }
359                         return WILDMAT_ABORT;
360                 case '%':
361                         while (++p, ((*p == '*') || (*p == '%'))) {
362                                 /* Consecutive %'s act just like one, but even
363                                  * a single star makes the sequence act like
364                                  * one star, instead.
365                                  */
366                                 if (*p == '*') {
367                                         goto star;
368                                 }
369                                 continue;
370                         }
371                         if (*p == '\0') {
372                                 /*
373                                  * Trailing % matches everything
374                                  * without a delimiter.
375                                  */
376                                 while (*text) {
377                                         if (*text == WILDMAT_DELIM) {
378                                                 return WILDMAT_FALSE;
379                                         }
380                                         text++;
381                                 }
382                                 return WILDMAT_TRUE;
383                         }
384                         while (*text && (*(text - 1) != WILDMAT_DELIM)) {
385                                 if ((matched = do_imap_match(text++, p))
386                                    != WILDMAT_FALSE) {
387                                         return matched;
388                                 }
389                         }
390                         return WILDMAT_ABORT;
391                 }
392         }
393
394         return (*text == '\0');
395 }
396
397
398
399 /*
400  * Support function for mailbox pattern name matching in LIST and LSUB
401  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
402  */
403 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
404 {
405         /* handle just-star case quickly */
406         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
407                 return WILDMAT_TRUE;
408         }
409         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
410 }
411
412
413
414 /*
415  * Compare an IMAP date string (date only, no time) to the date found in
416  * a Unix timestamp.
417  */
418 int imap_datecmp(char *datestr, time_t msgtime) {
419         char daystr[SIZ];
420         char monthstr[SIZ];
421         char yearstr[SIZ];
422         int i;
423         int day, month, year;
424         int msgday, msgmonth, msgyear;
425         struct tm msgtm;
426
427         if (datestr == NULL) return(0);
428
429         /* Expecting a date in the form dd-Mmm-yyyy */
430         extract_token(daystr, datestr, 0, '-');
431         extract_token(monthstr, datestr, 1, '-');
432         extract_token(yearstr, datestr, 2, '-');
433
434         day = atoi(daystr);
435         year = atoi(yearstr);
436         month = 0;
437         for (i=0; i<12; ++i) {
438                 if (!strcasecmp(monthstr, ascmonths[i])) {
439                         month = i;
440                 }
441         }
442
443         /* Extract day/month/year from message timestamp */
444         memcpy(&msgtm, localtime(&msgtime), sizeof(struct tm));
445         msgday = msgtm.tm_mday;
446         msgmonth = msgtm.tm_mon;
447         msgyear = msgtm.tm_year + 1900;
448
449         /* Now start comparing */
450
451         if (year < msgyear) return(+1);
452         if (year > msgyear) return(-1);
453
454         if (month < msgmonth) return(+1);
455         if (month > msgmonth) return(-1);
456
457         if (day < msgday) return(+1);
458         if (day > msgday) return(-1);
459
460         return(0);
461 }