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)  

Loading...
Searching...
No Matches
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_defs.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, num_items - pos, &itemlist[pos], is_uid));
507 }
508 else {
509 match = (match && imap_do_search_msg(seq, msg, num_items - pos, &itemlist[pos], is_uid));
510 }
511
512 }
513
514 if (need_to_free_msg) {
515 CM_Free(msg);
516 }
517 return(match);
518}
519
520
521/*
522 * imap_search() calls imap_do_search() to do its actual work, once it's
523 * validated and boiled down the request a bit.
524 */
525void imap_do_search(int num_items, ConstStr *itemlist, int is_uid) {
526 citimap *Imap = IMAP;
527 int i, j, k;
528 int fts_num_msgs = 0;
529 long *fts_msgs = NULL;
530 int is_in_list = 0;
531 int num_results = 0;
532
533 /* Strip parentheses. We realize that this method will not work
534 * in all cases, but it seems to work with all currently available
535 * client software. Revisit later...
536 */
537 for (i=0; i<num_items; ++i) {
538 if (itemlist[i].Key[0] == '(') {
539 TokenCutLeft(&Imap->Cmd, &itemlist[i], 1);
540 }
541 if (itemlist[i].Key[itemlist[i].len-1] == ')') {
542 TokenCutRight(&Imap->Cmd, &itemlist[i], 1);
543 }
544 }
545
546 /* If there is a BODY search criterion in the query, use our full
547 * text index to disqualify messages that don't have any chance of
548 * matching. (Only do this if the index is enabled!!)
549 */
550 if (CtdlGetConfigInt("c_enable_fulltext")) for (i=0; i<(num_items-1); ++i) {
551 if (!strcasecmp(itemlist[i].Key, "BODY")) {
552 CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, itemlist[i+1].Key, "fulltext");
553 if (fts_num_msgs > 0) {
554 for (j=0; j < Imap->num_msgs; ++j) {
555 if (Imap->flags[j] & IMAP_SELECTED) {
556 is_in_list = 0;
557 for (k=0; k<fts_num_msgs; ++k) {
558 if (Imap->msgids[j] == fts_msgs[k]) {
559 ++is_in_list;
560 }
561 }
562 }
563 if (!is_in_list) {
564 Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED;
565 }
566 }
567 }
568 else { /* no hits on the index; disqualify every message */
569 for (j=0; j < Imap->num_msgs; ++j) {
570 Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED;
571 }
572 }
573 if (fts_msgs) {
574 free(fts_msgs);
575 }
576 }
577 }
578
579 /* Now go through the messages and apply all search criteria. */
581 IAPuts("* SEARCH ");
582 if (Imap->num_msgs > 0)
583 for (i = 0; i < Imap->num_msgs; ++i)
584 if (Imap->flags[i] & IMAP_SELECTED) {
585 if (imap_do_search_msg(i+1, NULL, num_items, itemlist, is_uid)) {
586 if (num_results != 0) {
587 IAPuts(" ");
588 }
589 if (is_uid) {
590 IAPrintf("%ld", Imap->msgids[i]);
591 }
592 else {
593 IAPrintf("%d", i+1);
594 }
595 ++num_results;
596 }
597 }
598 IAPuts("\r\n");
600}
601
602
603/*
604 * This function is called by the main command loop.
605 */
606void imap_search(int num_parms, ConstStr *Params) {
607 int i;
608
609 if (num_parms < 3) {
610 IReply("BAD invalid parameters");
611 return;
612 }
613
614 for (i = 0; i < IMAP->num_msgs; ++i) {
615 IMAP->flags[i] |= IMAP_SELECTED;
616 }
617
618 imap_do_search(num_parms-2, &Params[2], 0);
619 IReply("OK SEARCH completed");
620}
621
622/*
623 * This function is called by the main command loop.
624 */
625void imap_uidsearch(int num_parms, ConstStr *Params) {
626 int i;
627
628 if (num_parms < 4) {
629 IReply("BAD invalid parameters");
630 return;
631 }
632
633 for (i = 0; i < IMAP->num_msgs; ++i) {
634 IMAP->flags[i] |= IMAP_SELECTED;
635 }
636
637 imap_do_search(num_parms-3, &Params[3], 1);
638 IReply("OK UID SEARCH completed");
639}
640
641
@ eMesageText
Definition: citadel_defs.h:263
@ erFc822Addr
Definition: citadel_defs.h:258
@ eAuthor
Definition: citadel_defs.h:255
@ eCarbonCopY
Definition: citadel_defs.h:271
@ eTimestamp
Definition: citadel_defs.h:267
@ eMsgSubject
Definition: citadel_defs.h:268
@ eRecipient
Definition: citadel_defs.h:266
@ MT_RFC822
Definition: citadel_defs.h:180
int CtdlGetConfigInt(char *key)
Definition: config.c:390
#define CC
Definition: context.h:136
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:625
void imap_do_search(int num_items, ConstStr *itemlist, int is_uid)
Definition: imap_search.c:525
void imap_search(int num_parms, ConstStr *Params)
Definition: imap_search.c:606
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:305
int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage, int mode, int headers_only, int do_proto, int crlf, int flags)
Definition: msgbase.c:1960
struct CtdlMessage * CtdlFetchMessage(long msgnum, int with_body)
Definition: msgbase.c:1130
int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which)
Definition: msgbase.c:132
#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
long cm_lengths[256]
Definition: server.h:30
char * cm_fields[256]
Definition: server.h:29
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:259
void unbuffer_output(void)
Definition: sysdep.c:268