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