citadel
About: Citadel is an advanced messaging and collaboration system for groupware and BBS applications (preferred OS: Linux).
  Fossies Dox: citadel.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

imap_search.c
Go to the documentation of this file.
1/*
2 * Implements IMAP's gratuitously complex SEARCH command.
3 *
4 * Copyright (c) 2001-2020 by the citadel.org team
5 *
6 * This program is open source software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15#include "ctdl_module.h"
16
17
18#include "sysdep.h"
19#include <stdlib.h>
20#include <unistd.h>
21#include <stdio.h>
22#include <fcntl.h>
23#include <signal.h>
24#include <pwd.h>
25#include <errno.h>
26#include <sys/types.h>
27#include <time.h>
28#include <sys/wait.h>
29#include <ctype.h>
30#include <string.h>
31#include <limits.h>
32#include <libcitadel.h>
33#include "citadel.h"
34#include "server.h"
35#include "sysdep_decls.h"
36#include "citserver.h"
37#include "support.h"
38#include "config.h"
39#include "user_ops.h"
40#include "database.h"
41#include "msgbase.h"
42#include "internet_addressing.h"
43#include "serv_imap.h"
44#include "imap_tools.h"
45#include "imap_fetch.h"
46#include "imap_search.h"
47#include "genstamp.h"
48
49
50/*
51 * imap_do_search() calls imap_do_search_msg() to search an individual
52 * message after it has been fetched from the disk. This function returns
53 * nonzero if there is a match.
54 *
55 * supplied_msg MAY be used to pass a pointer to the message in memory,
56 * if for some reason it's already been loaded. If not, the message will
57 * be loaded only if one or more search criteria require it.
58 */
59int imap_do_search_msg(int seq, struct CtdlMessage *supplied_msg,
60 int num_items, ConstStr *itemlist, int is_uid) {
61
62 citimap *Imap = IMAP;
63 int match = 0;
64 int is_not = 0;
65 int is_or = 0;
66 int pos = 0;
67 int i;
68 char *fieldptr;
69 struct CtdlMessage *msg = NULL;
70 int need_to_free_msg = 0;
71
72 if (num_items == 0) {
73 return(0);
74 }
75 msg = supplied_msg;
76
77 /* Initially we start at the beginning. */
78 pos = 0;
79
80 /* Check for the dreaded NOT criterion. */
81 if (!strcasecmp(itemlist[0].Key, "NOT")) {
82 is_not = 1;
83 pos = 1;
84 }
85
86 /* Check for the dreaded OR criterion. */
87 if (!strcasecmp(itemlist[0].Key, "OR")) {
88 is_or = 1;
89 pos = 1;
90 }
91
92 /* Now look for criteria. */
93 if (!strcasecmp(itemlist[pos].Key, "ALL")) {
94 match = 1;
95 ++pos;
96 }
97
98 else if (!strcasecmp(itemlist[pos].Key, "ANSWERED")) {
99 if (Imap->flags[seq-1] & IMAP_ANSWERED) {
100 match = 1;
101 }
102 ++pos;
103 }
104
105 else if (!strcasecmp(itemlist[pos].Key, "BCC")) {
106 if (msg == NULL) {
107 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
108 need_to_free_msg = 1;
109 }
110 if (msg != NULL) {
111 fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Bcc");
112 if (fieldptr != NULL) {
113 if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
114 match = 1;
115 }
116 free(fieldptr);
117 }
118 }
119 pos += 2;
120 }
121
122 else if (!strcasecmp(itemlist[pos].Key, "BEFORE")) {
123 if (msg == NULL) {
124 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
125 need_to_free_msg = 1;
126 }
127 if (msg != NULL) {
128 if (!CM_IsEmpty(msg, eTimestamp)) {
129 if (imap_datecmp(itemlist[pos+1].Key,
130 atol(msg->cm_fields[eTimestamp])) < 0) {
131 match = 1;
132 }
133 }
134 }
135 pos += 2;
136 }
137
138 else if (!strcasecmp(itemlist[pos].Key, "BODY")) {
139
140 /* If fulltext indexing is active, on this server,
141 * all messages have already been qualified.
142 */
143 if (CtdlGetConfigInt("c_enable_fulltext")) {
144 match = 1;
145 }
146
147 /* Otherwise, we have to do a slow search. */
148 else {
149 if (msg == NULL) {
150 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
151 need_to_free_msg = 1;
152 }
153 if (msg != NULL) {
154 if (bmstrcasestr(msg->cm_fields[eMesageText], itemlist[pos+1].Key)) {
155 match = 1;
156 }
157 }
158 }
159
160 pos += 2;
161 }
162
163 else if (!strcasecmp(itemlist[pos].Key, "CC")) {
164 if (msg == NULL) {
165 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
166 need_to_free_msg = 1;
167 }
168 if (msg != NULL) {
169 fieldptr = msg->cm_fields[eCarbonCopY];
170 if (fieldptr != NULL) {
171 if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
172 match = 1;
173 }
174 }
175 else {
176 fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Cc");
177 if (fieldptr != NULL) {
178 if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) {
179 match = 1;
180 }
181 free(fieldptr);
182 }
183 }
184 }
185 pos += 2;
186 }
187
188 else if (!strcasecmp(itemlist[pos].Key, "DELETED")) {
189 if (Imap->flags[seq-1] & IMAP_DELETED) {
190 match = 1;
191 }
192 ++pos;
193 }
194
195 else if (!strcasecmp(itemlist[pos].Key, "DRAFT")) {
196 if (Imap->flags[seq-1] & IMAP_DRAFT) {
197 match = 1;
198 }
199 ++pos;
200 }
201
202 else if (!strcasecmp(itemlist[pos].Key, "FLAGGED")) {
203 if (Imap->flags[seq-1] & IMAP_FLAGGED) {
204 match = 1;
205 }
206 ++pos;
207 }
208
209 else if (!strcasecmp(itemlist[pos].Key, "FROM")) {
210 if (msg == NULL) {
211 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
212 need_to_free_msg = 1;
213 }
214 if (msg != NULL) {
215 if (bmstrcasestr(msg->cm_fields[eAuthor], itemlist[pos+1].Key)) {
216 match = 1;
217 }
218 if (bmstrcasestr(msg->cm_fields[erFc822Addr], itemlist[pos+1].Key)) {
219 match = 1;
220 }
221 }
222 pos += 2;
223 }
224
225 else if (!strcasecmp(itemlist[pos].Key, "HEADER")) {
226
227 /* We've got to do a slow search for this because the client
228 * might be asking for an RFC822 header field that has not been
229 * converted into a Citadel header field. That requires
230 * examining the message body.
231 */
232 if (msg == NULL) {
233 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
234 need_to_free_msg = 1;
235 }
236
237 if (msg != NULL) {
238
239 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
241
242 fieldptr = rfc822_fetch_field(ChrPtr(CC->redirect_buffer), itemlist[pos+1].Key);
243 if (fieldptr != NULL) {
244 if (bmstrcasestr(fieldptr, itemlist[pos+2].Key)) {
245 match = 1;
246 }
247 free(fieldptr);
248 }
249
250 FreeStrBuf(&CC->redirect_buffer);
251 }
252
253 pos += 3; /* Yes, three */
254 }
255
256 else if (!strcasecmp(itemlist[pos].Key, "KEYWORD")) {
257 /* not implemented */
258 pos += 2;
259 }
260
261 else if (!strcasecmp(itemlist[pos].Key, "LARGER")) {
262 if (msg == NULL) {
263 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
264 need_to_free_msg = 1;
265 }
266 if (msg != NULL) {
267 if (msg->cm_lengths[eMesageText] > atoi(itemlist[pos+1].Key)) {
268 match = 1;
269 }
270 }
271 pos += 2;
272 }
273
274 else if (!strcasecmp(itemlist[pos].Key, "NEW")) {
275 if ( (Imap->flags[seq-1] & IMAP_RECENT) && (!(Imap->flags[seq-1] & IMAP_SEEN))) {
276 match = 1;
277 }
278 ++pos;
279 }
280
281 else if (!strcasecmp(itemlist[pos].Key, "OLD")) {
282 if (!(Imap->flags[seq-1] & IMAP_RECENT)) {
283 match = 1;
284 }
285 ++pos;
286 }
287
288 else if (!strcasecmp(itemlist[pos].Key, "ON")) {
289 if (msg == NULL) {
290 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
291 need_to_free_msg = 1;
292 }
293 if (msg != NULL) {
294 if (!CM_IsEmpty(msg, eTimestamp)) {
295 if (imap_datecmp(itemlist[pos+1].Key,
296 atol(msg->cm_fields[eTimestamp])) == 0) {
297 match = 1;
298 }
299 }
300 }
301 pos += 2;
302 }
303
304 else if (!strcasecmp(itemlist[pos].Key, "RECENT")) {
305 if (Imap->flags[seq-1] & IMAP_RECENT) {
306 match = 1;
307 }
308 ++pos;
309 }
310
311 else if (!strcasecmp(itemlist[pos].Key, "SEEN")) {
312 if (Imap->flags[seq-1] & IMAP_SEEN) {
313 match = 1;
314 }
315 ++pos;
316 }
317
318 else if (!strcasecmp(itemlist[pos].Key, "SENTBEFORE")) {
319 if (msg == NULL) {
320 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
321 need_to_free_msg = 1;
322 }
323 if (msg != NULL) {
324 if (!CM_IsEmpty(msg, eTimestamp)) {
325 if (imap_datecmp(itemlist[pos+1].Key,
326 atol(msg->cm_fields[eTimestamp])) < 0) {
327 match = 1;
328 }
329 }
330 }
331 pos += 2;
332 }
333
334 else if (!strcasecmp(itemlist[pos].Key, "SENTON")) {
335 if (msg == NULL) {
336 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
337 need_to_free_msg = 1;
338 }
339 if (msg != NULL) {
340 if (!CM_IsEmpty(msg, eTimestamp)) {
341 if (imap_datecmp(itemlist[pos+1].Key,
342 atol(msg->cm_fields[eTimestamp])) == 0) {
343 match = 1;
344 }
345 }
346 }
347 pos += 2;
348 }
349
350 else if (!strcasecmp(itemlist[pos].Key, "SENTSINCE")) {
351 if (msg == NULL) {
352 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
353 need_to_free_msg = 1;
354 }
355 if (msg != NULL) {
356 if (!CM_IsEmpty(msg, eTimestamp)) {
357 if (imap_datecmp(itemlist[pos+1].Key,
358 atol(msg->cm_fields[eTimestamp])) >= 0) {
359 match = 1;
360 }
361 }
362 }
363 pos += 2;
364 }
365
366 else if (!strcasecmp(itemlist[pos].Key, "SINCE")) {
367 if (msg == NULL) {
368 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
369 need_to_free_msg = 1;
370 }
371 if (msg != NULL) {
372 if (!CM_IsEmpty(msg, eTimestamp)) {
373 if (imap_datecmp(itemlist[pos+1].Key,
374 atol(msg->cm_fields[eTimestamp])) >= 0) {
375 match = 1;
376 }
377 }
378 }
379 pos += 2;
380 }
381
382 else if (!strcasecmp(itemlist[pos].Key, "SMALLER")) {
383 if (msg == NULL) {
384 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
385 need_to_free_msg = 1;
386 }
387 if (msg != NULL) {
388 if (msg->cm_lengths[eMesageText] < atoi(itemlist[pos+1].Key)) {
389 match = 1;
390 }
391 }
392 pos += 2;
393 }
394
395 else if (!strcasecmp(itemlist[pos].Key, "SUBJECT")) {
396 if (msg == NULL) {
397 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
398 need_to_free_msg = 1;
399 }
400 if (msg != NULL) {
401 if (bmstrcasestr(msg->cm_fields[eMsgSubject], itemlist[pos+1].Key)) {
402 match = 1;
403 }
404 }
405 pos += 2;
406 }
407
408 else if (!strcasecmp(itemlist[pos].Key, "TEXT")) {
409 if (msg == NULL) {
410 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
411 need_to_free_msg = 1;
412 }
413 if (msg != NULL) {
414 for (i='A'; i<='Z'; ++i) {
415 if (bmstrcasestr(msg->cm_fields[i], itemlist[pos+1].Key)) {
416 match = 1;
417 }
418 }
419 }
420 pos += 2;
421 }
422
423 else if (!strcasecmp(itemlist[pos].Key, "TO")) {
424 if (msg == NULL) {
425 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
426 need_to_free_msg = 1;
427 }
428 if (msg != NULL) {
429 if (bmstrcasestr(msg->cm_fields[eRecipient], itemlist[pos+1].Key)) {
430 match = 1;
431 }
432 }
433 pos += 2;
434 }
435
436 /* FIXME this is b0rken. fix it. */
437 else if (imap_is_message_set(itemlist[pos].Key)) {
438 if (is_msg_in_sequence_set(itemlist[pos].Key, seq)) {
439 match = 1;
440 }
441 pos += 1;
442 }
443
444 /* FIXME this is b0rken. fix it. */
445 else if (!strcasecmp(itemlist[pos].Key, "UID")) {
446 if (is_msg_in_sequence_set(itemlist[pos+1].Key, Imap->msgids[seq-1])) {
447 match = 1;
448 }
449 pos += 2;
450 }
451
452 /* Now here come the 'UN' criteria. Why oh why do we have to
453 * implement *both* the 'UN' criteria *and* the 'NOT' keyword? Why
454 * can't there be *one* way to do things? More gratuitous complexity.
455 */
456
457 else if (!strcasecmp(itemlist[pos].Key, "UNANSWERED")) {
458 if ((Imap->flags[seq-1] & IMAP_ANSWERED) == 0) {
459 match = 1;
460 }
461 ++pos;
462 }
463
464 else if (!strcasecmp(itemlist[pos].Key, "UNDELETED")) {
465 if ((Imap->flags[seq-1] & IMAP_DELETED) == 0) {
466 match = 1;
467 }
468 ++pos;
469 }
470
471 else if (!strcasecmp(itemlist[pos].Key, "UNDRAFT")) {
472 if ((Imap->flags[seq-1] & IMAP_DRAFT) == 0) {
473 match = 1;
474 }
475 ++pos;
476 }
477
478 else if (!strcasecmp(itemlist[pos].Key, "UNFLAGGED")) {
479 if ((Imap->flags[seq-1] & IMAP_FLAGGED) == 0) {
480 match = 1;
481 }
482 ++pos;
483 }
484
485 else if (!strcasecmp(itemlist[pos].Key, "UNKEYWORD")) {
486 /* FIXME */
487 pos += 2;
488 }
489
490 else if (!strcasecmp(itemlist[pos].Key, "UNSEEN")) {
491 if ((Imap->flags[seq-1] & IMAP_SEEN) == 0) {
492 match = 1;
493 }
494 ++pos;
495 }
496
497 /* Remember to negate if we were told to */
498 if (is_not) {
499 match = !match;
500 }
501
502 /* Keep going if there are more criteria! */
503 if (pos < num_items) {
504
505 if (is_or) {
506 match = (match || imap_do_search_msg(seq, msg,
507 num_items - pos, &itemlist[pos], is_uid));
508 }
509 else {
510 match = (match && imap_do_search_msg(seq, msg,
511 num_items - pos, &itemlist[pos], is_uid));
512 }
513
514 }
515
516 if (need_to_free_msg) {
517 CM_Free(msg);
518 }
519 return(match);
520}
521
522
523/*
524 * imap_search() calls imap_do_search() to do its actual work, once it's
525 * validated and boiled down the request a bit.
526 */
527void imap_do_search(int num_items, ConstStr *itemlist, int is_uid) {
528 citimap *Imap = IMAP;
529 int i, j, k;
530 int fts_num_msgs = 0;
531 long *fts_msgs = NULL;
532 int is_in_list = 0;
533 int num_results = 0;
534
535 /* Strip parentheses. We realize that this method will not work
536 * in all cases, but it seems to work with all currently available
537 * client software. Revisit later...
538 */
539 for (i=0; i<num_items; ++i) {
540 if (itemlist[i].Key[0] == '(') {
541
542 TokenCutLeft(&Imap->Cmd,
543 &itemlist[i],
544 1);
545 }
546 if (itemlist[i].Key[itemlist[i].len-1] == ')') {
547 TokenCutRight(&Imap->Cmd,
548 &itemlist[i],
549 1);
550 }
551 }
552
553 /* If there is a BODY search criterion in the query, use our full
554 * text index to disqualify messages that don't have any chance of
555 * matching. (Only do this if the index is enabled!!)
556 */
557 if (CtdlGetConfigInt("c_enable_fulltext")) for (i=0; i<(num_items-1); ++i) {
558 if (!strcasecmp(itemlist[i].Key, "BODY")) {
559 CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, itemlist[i+1].Key, "fulltext");
560 if (fts_num_msgs > 0) {
561 for (j=0; j < Imap->num_msgs; ++j) {
562 if (Imap->flags[j] & IMAP_SELECTED) {
563 is_in_list = 0;
564 for (k=0; k<fts_num_msgs; ++k) {
565 if (Imap->msgids[j] == fts_msgs[k]) {
566 ++is_in_list;
567 }
568 }
569 }
570 if (!is_in_list) {
571 Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED;
572 }
573 }
574 }
575 else { /* no hits on the index; disqualify every message */
576 for (j=0; j < Imap->num_msgs; ++j) {
577 Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED;
578 }
579 }
580 if (fts_msgs) {
581 free(fts_msgs);
582 }
583 }
584 }
585
586 /* Now go through the messages and apply all search criteria. */
588 IAPuts("* SEARCH ");
589 if (Imap->num_msgs > 0)
590 for (i = 0; i < Imap->num_msgs; ++i)
591 if (Imap->flags[i] & IMAP_SELECTED) {
592 if (imap_do_search_msg(i+1, NULL, num_items, itemlist, is_uid)) {
593 if (num_results != 0) {
594 IAPuts(" ");
595 }
596 if (is_uid) {
597 IAPrintf("%ld", Imap->msgids[i]);
598 }
599 else {
600 IAPrintf("%d", i+1);
601 }
602 ++num_results;
603 }
604 }
605 IAPuts("\r\n");
607}
608
609
610/*
611 * This function is called by the main command loop.
612 */
613void imap_search(int num_parms, ConstStr *Params) {
614 int i;
615
616 if (num_parms < 3) {
617 IReply("BAD invalid parameters");
618 return;
619 }
620
621 for (i = 0; i < IMAP->num_msgs; ++i) {
622 IMAP->flags[i] |= IMAP_SELECTED;
623 }
624
625 imap_do_search(num_parms-2, &Params[2], 0);
626 IReply("OK SEARCH completed");
627}
628
629/*
630 * This function is called by the main command loop.
631 */
632void imap_uidsearch(int num_parms, ConstStr *Params) {
633 int i;
634
635 if (num_parms < 4) {
636 IReply("BAD invalid parameters");
637 return;
638 }
639
640 for (i = 0; i < IMAP->num_msgs; ++i) {
641 IMAP->flags[i] |= IMAP_SELECTED;
642 }
643
644 imap_do_search(num_parms-3, &Params[3], 1);
645 IReply("OK UID SEARCH completed");
646}
647
648
int CtdlGetConfigInt(char *key)
Definition: config.c:417
#define CC
Definition: context.h:140
void CtdlModuleDoSearch(int *num_msgs, long **search_msgs, const char *search_string, const char *func_name)
int imap_do_search_msg(int seq, struct CtdlMessage *supplied_msg, int num_items, ConstStr *itemlist, int is_uid)
Definition: imap_search.c:59
void imap_uidsearch(int num_parms, ConstStr *Params)
Definition: imap_search.c:632
void imap_do_search(int num_items, ConstStr *itemlist, int is_uid)
Definition: imap_search.c:527
void imap_search(int num_parms, ConstStr *Params)
Definition: imap_search.c:613
int imap_is_message_set(const char *buf)
Definition: imap_tools.c:690
void TokenCutLeft(citimap_command *Cmd, ConstStr *CutMe, int n)
Definition: imap_tools.c:412
void IAPrintf(const char *Format,...)
Definition: imap_tools.c:898
void TokenCutRight(citimap_command *Cmd, ConstStr *CutMe, int n)
Definition: imap_tools.c:395
int imap_datecmp(const char *datestr, time_t msgtime)
Definition: imap_tools.c:848
#define IAPuts(Msg)
Definition: imap_tools.h:39
#define IReply(msg)
Definition: imap_tools.h:45
char * rfc822_fetch_field(const char *rfc822, const char *fieldname)
void CM_Free(struct CtdlMessage *msg)
Definition: msgbase.c:310
int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage, int mode, int headers_only, int do_proto, int crlf, int flags)
Definition: msgbase.c:1969
struct CtdlMessage * CtdlFetchMessage(long msgnum, int with_body)
Definition: msgbase.c:1135
int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which)
Definition: msgbase.c:137
#define HEADERS_FAST
Definition: msgbase.h:42
void free(void *)
#define IMAP_SELECTED
Definition: serv_imap.h:90
#define IMAP_ANSWERED
Definition: serv_imap.h:81
#define IMAP_DELETED
Definition: serv_imap.h:83
#define IMAP_DRAFT
Definition: serv_imap.h:84
#define IMAP_RECENT
Definition: serv_imap.h:91
#define IMAP
Definition: serv_imap.h:104
#define IMAP_FLAGGED
Definition: serv_imap.h:82
#define IMAP_SEEN
Definition: serv_imap.h:85
@ eMesageText
Definition: server.h:315
@ erFc822Addr
Definition: server.h:310
@ eAuthor
Definition: server.h:307
@ eCarbonCopY
Definition: server.h:323
@ eTimestamp
Definition: server.h:319
@ eMsgSubject
Definition: server.h:320
@ eRecipient
Definition: server.h:318
@ MT_RFC822
Definition: server.h:167
long cm_lengths[256]
Definition: server.h:38
char * cm_fields[256]
Definition: server.h:37
citimap_command Cmd
Definition: serv_imap.h:51
unsigned int * flags
Definition: serv_imap.h:47
int num_msgs
Definition: serv_imap.h:43
long * msgids
Definition: serv_imap.h:46
#define SIZ
Definition: sysconfig.h:33
void buffer_output(void)
Definition: sysdep.c:267
void unbuffer_output(void)
Definition: sysdep.c:276