* Renamed "struct user" to "struct ctdluser"
[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          */
102         if (qrbuf->QRflags & QR_MAILBOX) {
103                 safestrncpy(buf, qrbuf->QRname, bufsize);
104                 strcpy(buf, &buf[11]);
105                 if (!strcasecmp(buf, MAILROOM))
106                         strcpy(buf, "INBOX");
107         }
108         /*
109          * Otherwise, prefix the floor name as a "public folders" moniker
110          */
111         else {
112                 fl = cgetfloor(qrbuf->QRfloor);
113                 snprintf(buf, bufsize, "%s|%s",
114                          fl->f_name,
115                          qrbuf->QRname);
116         }
117
118         /*
119          * Replace delimiter characters with "|" for pseudo-folder-delimiting
120          */
121         for (i=0; i<strlen(buf); ++i) {
122                 if (buf[i] == FDELIM) buf[i] = '|';
123         }
124 }
125
126
127 /*
128  * Convert an inputted folder name to our best guess as to what an equivalent
129  * room name should be.
130  *
131  * If an error occurs, it returns -1.  Otherwise...
132  *
133  * The lower eight bits of the return value are the floor number on which the
134  * room most likely resides.   The upper eight bits may contain flags,
135  * including IR_MAILBOX if we're dealing with a personal room.
136  *
137  */
138 int imap_roomname(char *rbuf, int bufsize, char *foldername)
139 {
140         int levels;
141         char floorname[SIZ];
142         char roomname[SIZ];
143         int i;
144         struct floor *fl;
145         int ret = (-1);
146
147         if (foldername == NULL) return(-1);
148         levels = num_parms(foldername);
149
150         /*
151          * Convert the crispy idiot's reserved names to our reserved names.
152          */
153         if (!strcasecmp(foldername, "INBOX")) {
154                 safestrncpy(rbuf, MAILROOM, bufsize);
155                 ret = (0 | IR_MAILBOX);
156         }
157         else if (levels > 1) {
158                 extract(floorname, foldername, 0);
159                 strcpy(roomname, &foldername[strlen(floorname)+1]);
160                 for (i = 0; i < MAXFLOORS; ++i) {
161                         fl = cgetfloor(i);
162                         if (fl->f_flags & F_INUSE) {
163                                 if (!strcasecmp(floorname, fl->f_name)) {
164                                         strcpy(rbuf, roomname);
165                                         ret = i;
166                                 }
167                         }
168                 }
169
170                 if (ret < 0) {
171                         /* No subfolderificationalisticism on this one... */
172                         safestrncpy(rbuf, foldername, bufsize);
173                         ret = (0 | IR_MAILBOX);
174                 }
175
176         }
177         else {
178                 safestrncpy(rbuf, foldername, bufsize);
179                 ret = (0 | IR_MAILBOX);
180         }
181
182         /* Undelimiterizationalize the room name (change '|') */
183         for (i=0; i<strlen(rbuf); ++i) {
184                 if (rbuf[i] == '|') rbuf[i] = FDELIM;
185         }
186
187
188 /*** This doesn't work.
189         char buf[SIZ];
190         if (ret & IR_MAILBOX) {
191                 if (atol(rbuf) == 0L) {
192                         strcpy(buf, rbuf);
193                         sprintf(rbuf, "%010ld.%s", CC->user.usernum, buf);
194                 }
195         }
196  ***/
197
198         lprintf(9, "(That translates to \"%s\")\n", rbuf);
199         return(ret);
200 }
201
202
203
204
205
206 /*
207  * Output a struct internet_address_list in the form an IMAP client wants
208  */
209 void imap_ial_out(struct internet_address_list *ialist)
210 {
211         struct internet_address_list *iptr;
212
213         if (ialist == NULL) {
214                 cprintf("NIL");
215                 return;
216         }
217         cprintf("(");
218
219         for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
220                 cprintf("(");
221                 imap_strout(iptr->ial_name);
222                 cprintf(" NIL ");
223                 imap_strout(iptr->ial_user);
224                 cprintf(" ");
225                 imap_strout(iptr->ial_node);
226                 cprintf(")");
227         }
228
229         cprintf(")");
230 }
231
232
233
234 /*
235  * Determine whether the supplied string is a valid message set.
236  * If the string contains only numbers, colons, commas, and asterisks,
237  * return 1 for a valid message set.  If any other character is found, 
238  * return 0.
239  */
240 int imap_is_message_set(char *buf)
241 {
242         int i;
243
244         if (buf == NULL)
245                 return (0);     /* stupidity checks */
246         if (strlen(buf) == 0)
247                 return (0);
248
249         if (!strcasecmp(buf, "ALL"))
250                 return (1);     /* macro?  why?  */
251
252         for (i = 0; i < strlen(buf); ++i) {     /* now start the scan */
253                 if (
254                            (!isdigit(buf[i]))
255                            && (buf[i] != ':')
256                            && (buf[i] != ',')
257                            && (buf[i] != '*')
258                     )
259                         return (0);
260         }
261
262         return (1);             /* looks like we're good */
263 }
264
265
266 /*
267  * imap_match.c, based on wildmat.c from INN
268  * hacked for Citadel/IMAP by Daniel Malament
269  */
270
271 /* note: not all return statements use these; don't change them */
272 #define WILDMAT_TRUE    1
273 #define WILDMAT_FALSE   0
274 #define WILDMAT_ABORT   -1
275 #define WILDMAT_DELIM   '|'
276
277 /*
278  * Match text and p, return TRUE, FALSE, or ABORT.
279  */
280 static int do_imap_match(const char *supplied_text, const char *supplied_p)
281 {
282         int matched, i;
283         char lcase_text[SIZ], lcase_p[SIZ];
284         char *text = lcase_text;
285         char *p = lcase_p;
286
287         /* Copy both strings and lowercase them, in order to
288          * make this entire operation case-insensitive.
289          */
290         for (i=0; i<=strlen(supplied_text); ++i)
291                 lcase_text[i] = tolower(supplied_text[i]);
292         for (i=0; i<=strlen(supplied_p); ++i)
293                 p[i] = tolower(supplied_p[i]);
294
295         /* Start matching */
296         for (; *p; text++, p++) {
297                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
298                         return WILDMAT_ABORT;
299                 }
300                 switch (*p) {
301                 default:
302                         if (*text != *p) {
303                                 return WILDMAT_FALSE;
304                         }
305                         continue;
306                 case '*':
307 star:
308                         while (++p, ((*p == '*') || (*p == '%'))) {
309                                 /* Consecutive stars or %'s act
310                                  * just like one star.
311                                  */
312                                 continue;
313                         }
314                         if (*p == '\0') {
315                                 /* Trailing star matches everything. */
316                                 return WILDMAT_TRUE;
317                         }
318                         while (*text) {
319                                 if ((matched = do_imap_match(text++, p))
320                                    != WILDMAT_FALSE) {
321                                         return matched;
322                                 }
323                         }
324                         return WILDMAT_ABORT;
325                 case '%':
326                         while (++p, ((*p == '*') || (*p == '%'))) {
327                                 /* Consecutive %'s act just like one, but even
328                                  * a single star makes the sequence act like
329                                  * one star, instead.
330                                  */
331                                 if (*p == '*') {
332                                         goto star;
333                                 }
334                                 continue;
335                         }
336                         if (*p == '\0') {
337                                 /*
338                                  * Trailing % matches everything
339                                  * without a delimiter.
340                                  */
341                                 while (*text) {
342                                         if (*text == WILDMAT_DELIM) {
343                                                 return WILDMAT_FALSE;
344                                         }
345                                         text++;
346                                 }
347                                 return WILDMAT_TRUE;
348                         }
349                         while (*text && (*(text - 1) != WILDMAT_DELIM)) {
350                                 if ((matched = do_imap_match(text++, p))
351                                    != WILDMAT_FALSE) {
352                                         return matched;
353                                 }
354                         }
355                         return WILDMAT_ABORT;
356                 }
357         }
358
359         return (*text == '\0');
360 }
361
362
363
364 /*
365  * Support function for mailbox pattern name matching in LIST and LSUB
366  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
367  */
368 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
369 {
370         /* handle just-star case quickly */
371         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
372                 return WILDMAT_TRUE;
373         }
374         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
375 }