* Added support for "Cc" and "Bcc" IMAP SEARCHes
[citadel.git] / citadel / imap_search.c
1 /*
2  * $Id$
3  *
4  * Implements the SEARCH command in IMAP.
5  * This command is way too convoluted.  Marc Crispin is a fscking idiot.
6  *
7  * NOTE: this is a partial implementation.  It is NOT FINISHED.
8  *
9  */
10
11
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17 #include <signal.h>
18 #include <pwd.h>
19 #include <errno.h>
20 #include <sys/types.h>
21
22 #if TIME_WITH_SYS_TIME
23 # include <sys/time.h>
24 # include <time.h>
25 #else
26 # if HAVE_SYS_TIME_H
27 #  include <sys/time.h>
28 # else
29 #  include <time.h>
30 # endif
31 #endif
32
33 #include <sys/wait.h>
34 #include <ctype.h>
35 #include <string.h>
36 #include <limits.h>
37 #include "citadel.h"
38 #include "server.h"
39 #include "sysdep_decls.h"
40 #include "citserver.h"
41 #include "support.h"
42 #include "config.h"
43 #include "serv_extensions.h"
44 #include "room_ops.h"
45 #include "user_ops.h"
46 #include "policy.h"
47 #include "database.h"
48 #include "msgbase.h"
49 #include "tools.h"
50 #include "internet_addressing.h"
51 #include "serv_imap.h"
52 #include "imap_tools.h"
53 #include "imap_fetch.h"
54 #include "imap_search.h"
55 #include "genstamp.h"
56
57
58
59 /*
60  * imap_do_search() calls imap_do_search_msg() to search an individual
61  * message after it has been fetched from the disk.  This function returns
62  * nonzero if there is a match.
63  */
64 int imap_do_search_msg(int seq, struct CtdlMessage *msg,
65                         int num_items, char **itemlist, int is_uid) {
66
67         int match = 0;
68         int is_not = 0;
69         int is_or = 0;
70         int pos = 0;
71         int i;
72         char *fieldptr;
73
74         if (num_items == 0) return(0);
75
76         /* Initially we start at the beginning. */
77         pos = 0;
78
79         /* Check for the dreaded NOT criterion. */
80         if (!strcasecmp(itemlist[0], "NOT")) {
81                 is_not = 1;
82                 pos = 1;
83         }
84
85         /* Check for the dreaded OR criterion. */
86         if (!strcasecmp(itemlist[0], "OR")) {
87                 is_or = 1;
88                 pos = 1;
89         }
90
91         /* Now look for criteria. */
92         if (!strcasecmp(itemlist[pos], "ALL")) {
93                 match = 1;
94                 ++pos;
95         }
96         
97         else if (!strcasecmp(itemlist[pos], "ANSWERED")) {
98                 if (IMAP->flags[seq-1] & IMAP_ANSWERED) {
99                         match = 1;
100                 }
101                 ++pos;
102         }
103
104         else if (!strcasecmp(itemlist[pos], "BCC")) {
105                 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
106                 if (fieldptr != NULL) {
107                         if (bmstrstr(fieldptr, itemlist[pos+1], strncasecmp)) {
108                                 match = 1;
109                         }
110                         phree(fieldptr);
111                 }
112                 pos += 2;
113         }
114
115         else if (!strcasecmp(itemlist[pos], "BEFORE")) {
116                 if (msg->cm_fields['T'] != NULL) {
117                         if (imap_datecmp(itemlist[pos+1],
118                                         atol(msg->cm_fields['T'])) < 0) {
119                                 match = 1;
120                         }
121                 }
122                 pos += 2;
123         }
124
125         else if (!strcasecmp(itemlist[pos], "BODY")) {
126                 if (bmstrstr(msg->cm_fields['M'], itemlist[pos+1], strncasecmp)) {
127                         match = 1;
128                 }
129                 pos += 2;
130         }
131
132         else if (!strcasecmp(itemlist[pos], "CC")) {
133                 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
134                 if (fieldptr != NULL) {
135                         if (bmstrstr(fieldptr, itemlist[pos+1], strncasecmp)) {
136                                 match = 1;
137                         }
138                         phree(fieldptr);
139                 }
140                 pos += 2;
141         }
142
143         else if (!strcasecmp(itemlist[pos], "DELETED")) {
144                 if (IMAP->flags[seq-1] & IMAP_DELETED) {
145                         match = 1;
146                 }
147                 ++pos;
148         }
149
150         else if (!strcasecmp(itemlist[pos], "DRAFT")) {
151                 if (IMAP->flags[seq-1] & IMAP_DRAFT) {
152                         match = 1;
153                 }
154                 ++pos;
155         }
156
157         else if (!strcasecmp(itemlist[pos], "FLAGGED")) {
158                 if (IMAP->flags[seq-1] & IMAP_FLAGGED) {
159                         match = 1;
160                 }
161                 ++pos;
162         }
163
164         else if (!strcasecmp(itemlist[pos], "FROM")) {
165                 if (bmstrstr(msg->cm_fields['A'], itemlist[pos+1], strncasecmp)) {
166                         match = 1;
167                 }
168                 pos += 2;
169         }
170
171         else if (!strcasecmp(itemlist[pos], "HEADER")) {
172                 /* FIXME */
173                 pos += 3;       /* Yes, three */
174         }
175
176         else if (!strcasecmp(itemlist[pos], "KEYWORD")) {
177                 /* FIXME */
178                 pos += 2;
179         }
180
181         else if (!strcasecmp(itemlist[pos], "LARGER")) {
182                 if (strlen(msg->cm_fields['M']) > atoi(itemlist[pos+1])) {
183                         match = 1;
184                 }
185                 pos += 2;
186         }
187
188         else if (!strcasecmp(itemlist[pos], "NEW")) {
189                 /* FIXME */
190                 ++pos;
191         }
192
193         else if (!strcasecmp(itemlist[pos], "OLD")) {
194                 /* FIXME */
195                 ++pos;
196         }
197
198         else if (!strcasecmp(itemlist[pos], "ON")) {
199                 if (msg->cm_fields['T'] != NULL) {
200                         if (imap_datecmp(itemlist[pos+1],
201                                         atol(msg->cm_fields['T'])) == 0) {
202                                 match = 1;
203                         }
204                 }
205                 pos += 2;
206         }
207
208         else if (!strcasecmp(itemlist[pos], "RECENT")) {
209                 /* FIXME */
210                 ++pos;
211         }
212
213         else if (!strcasecmp(itemlist[pos], "SEEN")) {
214                 if (IMAP->flags[seq-1] & IMAP_SEEN) {
215                         match = 1;
216                 }
217                 ++pos;
218         }
219
220         else if (!strcasecmp(itemlist[pos], "SENTBEFORE")) {
221                 if (msg->cm_fields['T'] != NULL) {
222                         if (imap_datecmp(itemlist[pos+1],
223                                         atol(msg->cm_fields['T'])) < 0) {
224                                 match = 1;
225                         }
226                 }
227                 pos += 2;
228         }
229
230         else if (!strcasecmp(itemlist[pos], "SENTON")) {
231                 if (msg->cm_fields['T'] != NULL) {
232                         if (imap_datecmp(itemlist[pos+1],
233                                         atol(msg->cm_fields['T'])) == 0) {
234                                 match = 1;
235                         }
236                 }
237                 pos += 2;
238         }
239
240         else if (!strcasecmp(itemlist[pos], "SENTSINCE")) {
241                 if (msg->cm_fields['T'] != NULL) {
242                         if (imap_datecmp(itemlist[pos+1],
243                                         atol(msg->cm_fields['T'])) >= 0) {
244                                 match = 1;
245                         }
246                 }
247                 pos += 2;
248         }
249
250         else if (!strcasecmp(itemlist[pos], "SINCE")) {
251                 if (msg->cm_fields['T'] != NULL) {
252                         if (imap_datecmp(itemlist[pos+1],
253                                         atol(msg->cm_fields['T'])) >= 0) {
254                                 match = 1;
255                         }
256                 }
257                 pos += 2;
258         }
259
260         else if (!strcasecmp(itemlist[pos], "SMALLER")) {
261                 if (strlen(msg->cm_fields['M']) < atoi(itemlist[pos+1])) {
262                         match = 1;
263                 }
264                 pos += 2;
265         }
266
267         else if (!strcasecmp(itemlist[pos], "SUBJECT")) {
268                 if (bmstrstr(msg->cm_fields['U'], itemlist[pos+1], strncasecmp)) {
269                         match = 1;
270                 }
271                 pos += 2;
272         }
273
274         else if (!strcasecmp(itemlist[pos], "TEXT")) {
275                 for (i='A'; i<='Z'; ++i) {
276                         if (bmstrstr(msg->cm_fields[i], itemlist[pos+1], strncasecmp)) {
277                                 match = 1;
278                         }
279                 }
280                 pos += 2;
281         }
282
283         else if (!strcasecmp(itemlist[pos], "TO")) {
284                 if (bmstrstr(msg->cm_fields['R'], itemlist[pos+1], strncasecmp)) {
285                         match = 1;
286                 }
287                 pos += 2;
288         }
289
290         else if (!strcasecmp(itemlist[pos], "UID")) {
291                 if (is_msg_in_mset(itemlist[pos+1], IMAP->msgids[seq-1])) {
292                         match = 1;
293                 }
294                 pos += 2;
295         }
296
297         /* Now here come the 'UN' criteria.  Why oh why do we have to
298          * implement *both* the 'UN' criteria *and* the 'NOT' keyword?  Why
299          * can't there be *one* way to do things?  Answer: because Mark
300          * Crispin is an idiot.
301          */
302
303         else if (!strcasecmp(itemlist[pos], "UNANSWERED")) {
304                 if ((IMAP->flags[seq-1] & IMAP_ANSWERED) == 0) {
305                         match = 1;
306                 }
307                 ++pos;
308         }
309
310         else if (!strcasecmp(itemlist[pos], "UNDELETED")) {
311                 if ((IMAP->flags[seq-1] & IMAP_DELETED) == 0) {
312                         match = 1;
313                 }
314                 ++pos;
315         }
316
317         else if (!strcasecmp(itemlist[pos], "UNDRAFT")) {
318                 if ((IMAP->flags[seq-1] & IMAP_DRAFT) == 0) {
319                         match = 1;
320                 }
321                 ++pos;
322         }
323
324         else if (!strcasecmp(itemlist[pos], "UNFLAGGED")) {
325                 if ((IMAP->flags[seq-1] & IMAP_FLAGGED) == 0) {
326                         match = 1;
327                 }
328                 ++pos;
329         }
330
331         else if (!strcasecmp(itemlist[pos], "UNKEYWORD")) {
332                 /* FIXME */
333                 pos += 2;
334         }
335
336         else if (!strcasecmp(itemlist[pos], "UNSEEN")) {
337                 if ((IMAP->flags[seq-1] & IMAP_SEEN) == 0) {
338                         match = 1;
339                 }
340                 ++pos;
341         }
342
343         /* Remember to negate if we were told to */
344         if (is_not) {
345                 match = !match;
346         }
347
348         /* Keep going if there are more criteria! */
349         if (pos < num_items) {
350
351                 if (is_or) {
352                         match = (match || imap_do_search_msg(seq, msg,
353                                 num_items - pos, &itemlist[pos], is_uid));
354                 }
355                 else {
356                         match = (match && imap_do_search_msg(seq, msg,
357                                 num_items - pos, &itemlist[pos], is_uid));
358                 }
359
360         }
361
362         return(match);
363 }
364
365
366 /*
367  * imap_search() calls imap_do_search() to do its actual work, once it's
368  * validated and boiled down the request a bit.
369  */
370 void imap_do_search(int num_items, char **itemlist, int is_uid) {
371         int i;
372         struct CtdlMessage *msg;
373
374         cprintf("* SEARCH ");
375         if (IMAP->num_msgs > 0)
376          for (i = 0; i < IMAP->num_msgs; ++i)
377           if (IMAP->flags[i] && IMAP_SELECTED) {
378                 msg = CtdlFetchMessage(IMAP->msgids[i]);
379                 if (msg != NULL) {
380                         if (imap_do_search_msg(i+1, msg, num_items,
381                            itemlist, is_uid)) {
382                                 if (is_uid) {
383                                         cprintf("%ld ", IMAP->msgids[i]);
384                                 }
385                                 else {
386                                         cprintf("%d ", i+1);
387                                 }
388                         }
389                         CtdlFreeMessage(msg);
390                 }
391                 else {
392                         lprintf(1, "SEARCH internal error\n");
393                 }
394         }
395         cprintf("\r\n");
396 }
397
398
399 /*
400  * This function is called by the main command loop.
401  */
402 void imap_search(int num_parms, char *parms[]) {
403         int i;
404
405         if (num_parms < 3) {
406                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
407                 return;
408         }
409
410         for (i=1; i<num_parms; ++i) {
411                 if (imap_is_message_set(parms[i])) {
412                         imap_pick_range(parms[i], 0);
413                 }
414         }
415
416         imap_do_search(num_parms-2, &parms[2], 0);
417         cprintf("%s OK SEARCH completed\r\n", parms[0]);
418 }
419
420 /*
421  * This function is called by the main command loop.
422  */
423 void imap_uidsearch(int num_parms, char *parms[]) {
424         int i;
425
426         if (num_parms < 4) {
427                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
428                 return;
429         }
430
431         for (i=1; i<num_parms; ++i) {
432                 if (imap_is_message_set(parms[i])) {
433                         imap_pick_range(parms[i], 1);
434                 }
435         }
436
437         imap_do_search(num_parms-3, &parms[3], 1);
438         cprintf("%s OK UID SEARCH completed\r\n", parms[0]);
439 }
440
441