1 /*
2 * Copyright (c) 2015, 2016 YASUOKA Masahiko <yasuoka@yasuoka.net>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 #include "compat.h"
17
18 #include <sys/types.h>
19 #ifdef MONITOR_KQUEUE
20 #include <sys/event.h>
21 #endif
22 #ifdef MONITOR_INOTIFY
23 #include <sys/inotify.h>
24 #endif
25 #include <sys/queue.h>
26 #include <sys/resource.h>
27 #include <sys/socket.h>
28 #include <sys/stat.h>
29 #include <sys/tree.h>
30 #include <sys/mman.h>
31 #include <sys/un.h>
32
33 #include <ctype.h>
34 #include <dirent.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <event.h>
38 #include <fcntl.h>
39 #include <fnmatch.h>
40 #include <fts.h>
41 #include <glob.h>
42 #include <libgen.h>
43 #include <limits.h>
44 #include <paths.h>
45 #include <signal.h>
46 #include <stdarg.h>
47 #include <stdbool.h>
48 #include <stdint.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <sysexits.h>
53 #include <syslog.h>
54 #include <time.h>
55 #include <unistd.h>
56 #ifdef MAILESTD_MT
57 #include <pthread.h>
58 #endif
59
60 #include "bytebuf.h"
61 #include <cabin.h>
62 #include <estraier.h>
63 #ifdef HAVE_LIBESTDRAFT
64 #include <estdraft.h>
65 #else
66 /*
67 * Without estdraft, forking "estdoc" to create a draft message, this
68 * decreases performance so much.
69 */
70 #endif
71
72 #include "defs.h"
73 #include "mailestd.h"
74 #include "mailestd_local.h"
75
76 #ifndef RB_NFIND
77 static struct rfc822 *
rfc822_tree_RB_NFIND(struct rfc822_tree * head,struct rfc822 * elm)78 rfc822_tree_RB_NFIND(struct rfc822_tree *head, struct rfc822 *elm)
79 {
80 struct rfc822 *tmp = RB_ROOT(head);
81 struct rfc822 *res = NULL;
82 int comp;
83 while (tmp) {
84 comp = rfc822_compar(elm, tmp);
85 if (comp < 0) {
86 res = tmp;
87 tmp = RB_LEFT(tmp, tree);
88 }
89 else if (comp > 0)
90 tmp = RB_RIGHT(tmp, tree);
91 else
92 return (tmp);
93 }
94 return (res);
95 }
96 #define RB_NFIND(name, x, y) name##_RB_NFIND(x, y)
97 #endif
98
99 int mailestctl_main(int argc, char *argv[]);
100
101 static void
usage(void)102 usage(void)
103 {
104 extern char *__progname;
105
106 fprintf(stderr, "usage: %s [-dnh] [-S suffix] [-f file] [maildir]\n",
107 __progname);
108 }
109
110 int
main(int argc,char * argv[])111 main(int argc, char *argv[])
112 {
113 int ch, suffixcount = 0;
114 struct mailestd mailestd_s;
115 const char *maildir = NULL, *home, *conf_file = NULL,
116 *suffix[11];
117 char pathtmp[PATH_MAX], maildir0[PATH_MAX];
118 struct mailestd_conf *conf;
119 bool noaction = false;
120 struct stat st;
121 extern char *__progname;
122
123 if (strcmp(__progname, "mailestctl") == 0)
124 return (mailestctl_main(argc, argv));
125
126 memset(suffix, 0, sizeof(suffix));
127 while ((ch = getopt(argc, argv, "+dhS:nf:")) != -1)
128 switch (ch) {
129 case 'S':
130 if (suffixcount + 2 >= (int)nitems(suffix)) {
131 errx(EX_USAGE, "too many suffixes. "
132 "limited %d", (int)nitems(suffix) - 1);
133 }
134 suffix[suffixcount++] = optarg;
135 suffix[suffixcount] = NULL;
136 break;
137
138 case 'd':
139 if (!foreground)
140 foreground = 1;
141 else
142 debug++;
143 break;
144
145 case 'f':
146 conf_file = optarg;
147 break;
148
149 case 'n':
150 noaction = true;
151 break;
152
153 case 'h':
154 usage();
155 exit(EX_USAGE);
156 break;
157
158 default:
159 exit(EX_USAGE);
160 break;
161 }
162 argc -= optind;
163 argv += optind;
164
165 if (argc > 0) {
166 if (argc != 1) {
167 usage();
168 exit(EX_USAGE);
169 }
170 maildir = argv[0];
171 }
172
173 if ((home = getenv("HOME")) == NULL)
174 err(EX_USAGE, "Missing HOME environment variable");
175 strlcpy(pathtmp, home, sizeof(pathtmp));
176 strlcat(pathtmp, "/" MAILESTD_MAIL_DIR, sizeof(pathtmp));
177 if (maildir == NULL) {
178 strlcpy(maildir0, pathtmp, sizeof(maildir0));
179 maildir = maildir0;
180 }
181 if (lstat(maildir, &st) == -1)
182 err(EX_USAGE, "%s", maildir);
183
184 if (conf_file == NULL) {
185 strlcpy(pathtmp, maildir, sizeof(pathtmp));
186 strlcat(pathtmp, "/", sizeof(pathtmp));
187 strlcat(pathtmp, MAILESTD_CONF_PATH, sizeof(pathtmp));
188 conf_file = pathtmp;
189 }
190 if ((conf = parse_config(conf_file, maildir)) == NULL)
191 exit(EXIT_FAILURE);
192 if (noaction) {
193 free_config(conf);
194 fputs("configration OK\n", stderr);
195 exit(EXIT_SUCCESS);
196 }
197
198 mailestd_init(&mailestd_s, conf, (isnull(suffix[0]))? NULL : suffix);
199 free_config(conf);
200 if (!foreground) {
201 if (daemon(1, 0) == -1)
202 err(EX_OSERR, "daemon");
203 }
204
205 mailestd_log_init();
206 if (unlimit_data() == -1)
207 mailestd_log(LOG_ERR, "unlimit_data: %m");
208 if (unlimit_nofile() == -1)
209 mailestd_log(LOG_ERR, "unlimit_nofile: %m");
210 EVENT_INIT();
211 mailestd_start(&mailestd_s, foreground);
212
213 EVENT_LOOP(0);
214 EVENT_BASE_FREE();
215
216 mailestd_fini(&mailestd_s);
217 mailestd_log_fini();
218
219 exit(EXIT_SUCCESS);
220 }
221
222 static void
mailestd_init(struct mailestd * _this,struct mailestd_conf * conf,char const ** suffix)223 mailestd_init(struct mailestd *_this, struct mailestd_conf *conf,
224 char const **suffix)
225 {
226 int sock, i, ns = 0;
227 mode_t oumask;
228 struct sockaddr_un sun;
229 extern char *__progname;
230 const char *deffolder[] = { MAILESTD_DEFAULT_FOLDERS };
231
232 memset(_this, 0, sizeof(struct mailestd));
233
234 realpath(conf->maildir, _this->maildir);
235 _this->lmaildir = strlen(_this->maildir);
236
237 if (debug == 0)
238 debug = conf->debug;
239 RB_INIT(&_this->root);
240 TAILQ_INIT(&_this->rfc822_pendings);
241 TAILQ_INIT(&_this->gather_pendings);
242 TAILQ_INIT(&_this->rfc822_tasks);
243 _this->rfc822_task_max = conf->tasks;
244 TAILQ_INIT(&_this->ctls);
245 TAILQ_INIT(&_this->gathers);
246 strlcpy(_this->logfn, conf->log_path, sizeof(_this->logfn));
247 strlcpy(_this->dbpath, conf->db_path, sizeof(_this->dbpath));
248 _this->logsiz = conf->log_size;
249 _this->logmax = conf->log_count;
250 _this->doc_trimsize = conf->trim_size;
251 if (conf->folders == NULL) {
252 _this->folder = xcalloc(nitems(deffolder) + 1, sizeof(char *));
253 for (i = 0; i < (int)nitems(deffolder); i++)
254 _this->folder[i] = xstrdup(deffolder[i]);
255 _this->folder[i] = NULL;
256 } else {
257 _this->folder = conf->folders;
258 conf->folders = NULL;
259 }
260 _this->monitor = conf->monitor;
261 _this->monitor_delay.tv_sec = conf->monitor_delay / 1000;
262 _this->monitor_delay.tv_nsec = (conf->monitor_delay % 1000) * 1000000UL;
263 _this->paridguess = (conf->paridguess)? true : false;
264
265 for (i = 0; suffix != NULL && !isnull(suffix[i]); i++)
266 /* nothing */;
267 ns += i;
268 for (i = 0; conf->suffixes != NULL && !isnull(conf->suffixes[i]); i++)
269 /* nothing */;
270 ns += i;
271 if (ns == 0) {
272 _this->suffix = xcalloc(2, sizeof(char *));
273 _this->suffix[ns++] = xstrdup(MAILESTD_DEFAULT_SUFFIX);
274 } else {
275 _this->suffix = xcalloc(ns + 1, sizeof(char *));
276 ns = 0;
277 for (i = 0; suffix != NULL && !isnull(suffix[i]); i++)
278 _this->suffix[ns++] = xstrdup(suffix[i]);
279 for (i = 0; conf->suffixes != NULL &&
280 !isnull(conf->suffixes[i]); i++)
281 _this->suffix[ns++] = xstrdup(conf->suffixes[i]);
282 }
283 _this->suffix[ns++] = NULL;
284
285 _thread_spin_init(&_this->id_seq_lock, 0);
286
287 memset(&sun, 0, sizeof(sun));
288 sun.sun_family = AF_UNIX;
289 strlcpy(sun.sun_path, conf->sock_path, sizeof(sun.sun_path));
290
291 _this->sock_ctl = socket(PF_UNIX, SOCK_SEQPACKET, 0);
292 if (_this->sock_ctl < 0)
293 err(EX_OSERR, "socket");
294 sock = socket(PF_UNIX, SOCK_SEQPACKET, 0);
295 oumask = umask(077);
296 if (connect(sock, (struct sockaddr *)&sun, sizeof(sun)) == 0)
297 errx(1, "already running");
298 else if (errno != EEXIST)
299 unlink(conf->sock_path);
300 if (bind(_this->sock_ctl, (struct sockaddr *)&sun, sizeof(sun)) == -1)
301 err(EX_OSERR, "bind");
302 umask(oumask);
303 }
304
305 static void
mailestd_start(struct mailestd * _this,bool fg)306 mailestd_start(struct mailestd *_this, bool fg)
307 {
308 int i;
309 int ntask = 0;
310 struct task *task;
311
312 if (!fg)
313 mailestd_log_rotation(_this->logfn, 0, 0);
314 EVENT_SET(&_this->evsigterm, SIGTERM, EV_SIGNAL | EV_PERSIST,
315 mailestd_on_sigterm, _this);
316 EVENT_SET(&_this->evsigint, SIGINT, EV_SIGNAL | EV_PERSIST,
317 mailestd_on_sigint, _this);
318 EVENT_SET(&_this->evtimer, -1, 0, mailestd_on_timer, _this);
319 signal_add(&_this->evsigterm, NULL);
320 signal_add(&_this->evsigint, NULL);
321 mailestd_on_timer(-1, 0, _this); /* dummy to make a schedule */
322 time(&_this->curr_time);
323
324 /*
325 * prepare limited number of tasks to control the resource usage.
326 * works like kanban method.
327 */
328 for (i = 0; i < _this->rfc822_task_max; i++) {
329 task = xcalloc(1, sizeof(struct task_rfc822));
330 TAILQ_INSERT_TAIL(&_this->rfc822_tasks, task, queue);
331 }
332 mailestd_monitor_init(_this);
333
334 _this->workers[ntask++] = &_this->mainworker;
335 _this->workers[ntask++] = &_this->dbworker;
336 if (_this->monitor)
337 _this->workers[ntask++] = &_this->monitorworker;
338 _this->workers[ntask++] = NULL;
339 for (i = 0; _this->workers[i] != NULL; i++) {
340 task_worker_init(_this->workers[i], _this);
341 #ifndef MAILESTD_MT
342 task_worker_start(_this->workers[i]);
343 #endif
344 }
345 #ifdef MAILESTD_MT
346 task_worker_start(&_this->mainworker); /* this thread */
347 task_worker_run(&_this->dbworker); /* another thread */
348 if (_this->monitor)
349 mailestd_monitor_run(_this); /* another thread */
350 #endif
351
352 if (listen(_this->sock_ctl, 5) == -1)
353 mailestd_log(LOG_ERR, "listen(): %m");
354 mailestc_reset_ctl_event(_this);
355
356 mailestd_log(LOG_INFO, "Started mailestd. Process-Id=%d",
357 (int)getpid());
358 mailestd_db_add_msgid_index(_this);
359 mailestd_schedule_db_sync(_this);
360 }
361
362 static uint64_t
mailestd_new_id(struct mailestd * _this)363 mailestd_new_id(struct mailestd *_this)
364 {
365 uint64_t task_id;
366
367 _thread_spin_lock(&_this->id_seq_lock);
368 task_id = ++(_this->id_seq);
369 _thread_spin_unlock(&_this->id_seq_lock);
370
371 return (task_id);
372 }
373
374 static void
mailestd_stop(struct mailestd * _this)375 mailestd_stop(struct mailestd *_this)
376 {
377 struct mailestc *ctle, *ctlt;
378 int i;
379
380 /* stop the task threads */
381 mailestd_schedule_message_all(_this, MAILESTD_TASK_STOP);
382
383 evtimer_del(&_this->evtimer);
384 /* stop signals and receiving controls */
385 signal_del(&_this->evsigterm);
386 signal_del(&_this->evsigint);
387 event_del(&_this->evsock_ctl);
388 close(_this->sock_ctl);
389 TAILQ_FOREACH_SAFE(ctle, &_this->ctls, queue, ctlt) {
390 mailestc_stop(ctle);
391 }
392
393 for (i = 0; _this->workers[i] != NULL; i++) {
394 if (_thread_self() != _this->workers[i]->thread)
395 _thread_join(_this->workers[i]->thread, NULL);
396 }
397 /* entered single thread */
398
399 mailestd_log(LOG_INFO, "Stopped mailestd");
400 }
401
402 static void
mailestd_fini(struct mailestd * _this)403 mailestd_fini(struct mailestd *_this)
404 {
405 int i;
406 struct rfc822 *msge, *msgt;
407 struct task *tske, *tskt;
408 struct gather *gate, *gatt;
409
410 TAILQ_FOREACH_SAFE(gate, &_this->gathers, queue, gatt) {
411 TAILQ_REMOVE(&_this->gathers, gate, queue);
412 free(gate);
413 }
414 TAILQ_FOREACH_SAFE(tske, &_this->gather_pendings, queue, tskt) {
415 TAILQ_REMOVE(&_this->gather_pendings, tske, queue);
416 free(tske);
417 }
418 TAILQ_FOREACH_SAFE(tske, &_this->rfc822_tasks, queue, tskt) {
419 TAILQ_REMOVE(&_this->rfc822_tasks, tske, queue);
420 free(tske);
421 }
422 TAILQ_FOREACH_SAFE(msge, &_this->rfc822_pendings, queue, msgt) {
423 TAILQ_REMOVE(&_this->rfc822_pendings, msge, queue);
424 }
425 RB_FOREACH_SAFE(msge, rfc822_tree, &_this->root, msgt) {
426 RB_REMOVE(rfc822_tree, &_this->root, msge);
427 rfc822_free(msge);
428 }
429 mailestd_monitor_fini(_this);
430
431 if (_this->suffix != NULL) {
432 for (i = 0; !isnull(_this->suffix[i]); i++)
433 free(_this->suffix[i]);
434 }
435 free(_this->suffix);
436 if (_this->folder != NULL) {
437 for (i = 0; !isnull(_this->folder[i]); i++)
438 free(_this->folder[i]);
439 }
440 free(_this->folder);
441 free(_this->sync_prev);
442
443 _thread_spin_destroy(&_this->id_seq_lock);
444 }
445
446 static struct gather *
mailestd_get_gather(struct mailestd * _this,uint64_t id)447 mailestd_get_gather(struct mailestd *_this, uint64_t id)
448 {
449 struct gather *gathere;
450
451 TAILQ_FOREACH(gathere, &_this->gathers, queue) {
452 if (gathere->id == id)
453 return (gathere);
454 }
455
456 return (NULL);
457 }
458
459 /***********************************************************************
460 * Event handlers
461 ***********************************************************************/
462 static void
mailestd_on_timer(int fd,short evmask,void * ctx)463 mailestd_on_timer(int fd, short evmask, void *ctx)
464 {
465 struct mailestd *_this = ctx;
466 struct timeval next;
467
468 time(&_this->curr_time);
469 if (evmask & EV_TIMEOUT) {
470 mailestd_log_rotation(_this->logfn, _this->logsiz,
471 _this->logmax);
472 }
473
474 next.tv_sec = MAILESTD_LOGROTWHEN -
475 (_this->curr_time % MAILESTD_LOGROTWHEN);
476 next.tv_usec = 0;
477 event_add(&_this->evtimer, &next);
478 }
479
480 static void
mailestd_on_sigterm(int fd,short evmask,void * ctx)481 mailestd_on_sigterm(int fd, short evmask, void *ctx)
482 {
483 struct mailestd *_this = ctx;
484
485 mailestd_log(LOG_INFO, "Received SIGTERM");
486 mailestd_stop(_this);
487 }
488
489 static void
mailestd_on_sigint(int fd,short evmask,void * ctx)490 mailestd_on_sigint(int fd, short evmask, void *ctx)
491 {
492 struct mailestd *_this = ctx;
493
494 mailestd_log(LOG_INFO, "Received SIGINT");
495 mailestd_stop(_this);
496 }
497
498 static void
mailestc_on_ctl_event(int fd,short evmask,void * ctx)499 mailestc_on_ctl_event(int fd, short evmask, void *ctx)
500 {
501 int sock;
502 struct sockaddr_un sun;
503 socklen_t slen;
504 struct mailestd *_this = ctx;
505 struct mailestc *ctl;
506
507 if (evmask & EV_READ) {
508 slen = sizeof(sun);
509 if ((sock = accept(_this->sock_ctl, (struct sockaddr *)&sun,
510 &slen)) < 0) {
511 mailestd_log(LOG_ERR, "accept(): %m");
512 goto out_read;
513 }
514 ctl = xcalloc(1, sizeof(struct mailestc));
515 mailestc_start(ctl, _this, sock);
516 }
517 out_read:
518 mailestc_reset_ctl_event(_this);
519 return;
520 }
521
522 static void
mailestc_reset_ctl_event(struct mailestd * _this)523 mailestc_reset_ctl_event(struct mailestd *_this)
524 {
525 MAILESTD_ASSERT(_this->sock_ctl >= 0);
526
527 EVENT_SET(&_this->evsock_ctl, _this->sock_ctl, EV_READ,
528 mailestc_on_ctl_event, _this);
529 event_add(&_this->evsock_ctl, NULL);
530 }
531
532 static void
mailestd_get_all_folders(struct mailestd * _this,struct folder_tree * tree)533 mailestd_get_all_folders(struct mailestd *_this, struct folder_tree *tree)
534 {
535 int len;
536 char *ps;
537 struct rfc822 *msge, msg0;
538 char path[PATH_MAX];
539 struct folder *fld;
540
541 strlcpy(path, _this->maildir, sizeof(path));
542 path[_this->lmaildir] = '/';
543 path[_this->lmaildir + 1] = '\0';
544 msg0.path = path;
545 msge = RB_NFIND(rfc822_tree, &_this->root, &msg0);
546 while (msge != NULL) {
547 if (!is_parent_dir(_this->maildir, msge->path))
548 break;
549 ps = strrchr(msge->path, '/');
550 MAILESTD_ASSERT(ps != NULL);
551 len = (ps - msge->path);
552 if (len <= 0)
553 break;
554 memcpy(path, msge->path, len);
555 path[len] = '\0';
556 fld = xcalloc(1, sizeof(struct folder));
557 fld->path = xstrdup(path + _this->lmaildir + 1);
558 RB_INSERT(folder_tree, tree, fld);
559 path[len++] = '/' + 1;
560 path[len] = '\0';
561 msg0.path = path;
562 msge = RB_NFIND(rfc822_tree, &_this->root, &msg0);
563 }
564 }
565
566 static const char *
mailestd_folder_name(struct mailestd * _this,const char * dir,char * buf,int lbuf)567 mailestd_folder_name(struct mailestd *_this, const char *dir, char *buf,
568 int lbuf)
569 {
570 if (is_parent_dir(_this->maildir, dir)) {
571 buf[0] = '+';
572 strlcpy(buf + 1, dir + _this->lmaildir + 1, lbuf - 1);
573 } else
574 strlcpy(buf, dir, lbuf);
575
576 return buf;
577 }
578
579 /***********************************************************************
580 * Database operations, gathering messages from file system
581 ***********************************************************************/
582 static ESTDB *
mailestd_db_open_rd(struct mailestd * _this)583 mailestd_db_open_rd(struct mailestd *_this)
584 {
585 ESTDB *db = NULL;
586 int ecode;
587
588 if (_this->db != NULL)
589 return (_this->db);
590
591 if ((db = est_db_open(_this->dbpath, ESTDBREADER, &ecode)) == NULL) {
592 mailestd_log(LOG_ERR, "Opening DB: %s", est_err_msg(ecode));
593 mailestd_db_error(_this);
594 } else {
595 _this->db = db;
596 _this->db_wr = false;
597 mailestd_log(LOG_DEBUG, "Opened DB");
598 }
599
600 return (db);
601 }
602
603 static ESTDB *
mailestd_db_open_wr(struct mailestd * _this)604 mailestd_db_open_wr(struct mailestd *_this)
605 {
606 ESTDB *db = NULL;
607 int ecode;
608
609 if (_this->db != NULL) {
610 if (_this->db_wr)
611 return (_this->db);
612 if (!est_db_close(_this->db, &ecode))
613 mailestd_log(LOG_ERR, "est_db_close: %s",
614 est_err_msg(ecode));
615 _this->db = NULL;
616 }
617
618 if ((db = est_db_open(_this->dbpath,
619 ESTDBWRITER | ESTDBCREAT | ESTDBHUGE, &ecode)) == NULL) {
620 mailestd_log(LOG_ERR, "Opening DB: %s", est_err_msg(ecode));
621 mailestd_db_error(_this);
622 } else {
623 _this->db = db;
624 _this->db_wr = true;
625 mailestd_log(LOG_INFO, "Opened DB for writing");
626 }
627
628 return (db);
629 }
630
631 static void
mailestd_db_close(struct mailestd * _this)632 mailestd_db_close(struct mailestd *_this)
633 {
634 int ecode;
635
636 if (_this->db != NULL) {
637 if (debug > 1)
638 est_db_set_informer(_this->db, mailestd_db_informer,
639 NULL);
640 if (!est_db_close(_this->db, &ecode))
641 mailestd_log(LOG_ERR, "Closing DB: %s",
642 est_err_msg(ecode));
643 _this->db = NULL;
644 }
645 }
646
647 static void
mailestd_db_add_msgid_index(struct mailestd * _this)648 mailestd_db_add_msgid_index(struct mailestd *_this)
649 {
650 int i;
651 CBLIST *exprs;
652 ESTDB *db;
653 bool msgid_found = false, parid_found = false, title_found = false;
654 struct stat st;
655
656 if (lstat(_this->dbpath, &st) == -1 && errno == ENOENT)
657 goto db_noent;
658 if ((db = mailestd_db_open_rd(_this)) == NULL)
659 return;
660 mailestd_log(LOG_INFO, "Opened database(%s) has %d docs",
661 _this->dbpath, db->dnum);
662 if ((exprs = est_db_attr_index_exprs(db)) != NULL) {
663 for (i = 0; i < cblistnum(exprs); i++) {
664 if (!strncasecmp(ATTR_MSGID "=",
665 cblistval(exprs, i, NULL), sizeof(ATTR_MSGID)))
666 msgid_found = true;
667 if (!strncasecmp(ATTR_PARID "=",
668 cblistval(exprs, i, NULL), sizeof(ATTR_PARID)))
669 parid_found = true;
670 if (!strncasecmp(ATTR_TITLE "=",
671 cblistval(exprs, i, NULL), sizeof(ATTR_TITLE)))
672 title_found = true;
673 if (msgid_found && parid_found && title_found)
674 break;
675 }
676 cblistclose(exprs);
677 }
678 db_noent:
679 if (!msgid_found || !parid_found ||
680 (!title_found && _this->paridguess)) {
681 if ((db = mailestd_db_open_wr(_this)) == NULL)
682 return;
683 if (!msgid_found) {
684 mailestd_log(LOG_INFO, "Adding \""ATTR_MSGID"\" index");
685 est_db_add_attr_index(db, ATTR_MSGID, ESTIDXATTRSTR);
686 }
687 if (!parid_found) {
688 mailestd_log(LOG_INFO, "Adding \""ATTR_PARID"\" index");
689 est_db_add_attr_index(db, ATTR_PARID, ESTIDXATTRSTR);
690 }
691 if (!title_found) {
692 mailestd_log(LOG_INFO, "Adding \""ATTR_TITLE"\" index");
693 est_db_add_attr_index(db, ATTR_TITLE, ESTIDXATTRSTR);
694 }
695 mailestd_db_close(_this);
696 }
697 }
698
699 static int
mailestd_db_sync(struct mailestd * _this)700 mailestd_db_sync(struct mailestd *_this)
701 {
702 ESTDB *db;
703 int i, id, ldir, delete;
704 char dir[PATH_MAX + 128];
705 const char *prev, *fn, *uri, *errstr, *folder;
706 ESTDOC *doc;
707 struct rfc822 *msg, msg0;
708 struct tm tm;
709 struct task *tske, *tskt;
710 struct folder *flde, *fldt;
711 struct folder_tree
712 folders;
713
714 MAILESTD_ASSERT(_thread_self() == _this->dbworker.thread);
715
716 if ((db = mailestd_db_open_rd(_this)) == NULL)
717 return (-1);
718
719 prev = _this->sync_prev;
720 est_db_iter_init(db, prev);
721 for (i = 0; (id = est_db_iter_next(db)) > 0; i++) {
722 doc = est_db_get_doc(db, id, ESTGDNOTEXT);
723 if (doc == NULL)
724 continue;
725
726 uri = est_doc_attr(doc, ESTDATTRURI);
727 if (uri == NULL || strncmp(uri, URIFILE "/", 8) != 0)
728 continue;
729 fn = URI2PATH(uri);
730
731 msg0.path = (char *)fn;
732 msg = RB_FIND(rfc822_tree, &_this->root, &msg0);
733 if (msg == NULL) {
734 msg = xcalloc(1, sizeof(struct rfc822));
735 msg->path = xstrdup(fn);
736 RB_INSERT(rfc822_tree, &_this->root, msg);
737 }
738 if (!msg->ontask && msg->db_id == 0) {
739 strptime(est_doc_attr(doc, ESTDATTRMDATE),
740 MAILESTD_TIMEFMT, &tm);
741 msg->db_id = id;
742 msg->mtime = timegm(&tm);
743 msg->size = strtonum(est_doc_attr(doc, ESTDATTRSIZE), 0,
744 INT64_MAX, &errstr);
745 }
746
747 msg->pariddone =
748 (est_doc_attr(doc, ATTR_PARID) != NULL)? true : false;
749 if (_this->paridguess && !msg->pariddone) {
750 if (skip_subject(est_doc_attr(doc, "@title")) == NULL)
751 msg->pariddone = true;
752 /*
753 * Otherwise, let msg->pariddone keep false to
754 * indicate it's a target in mailestd_guess_parid().
755 * The message has no x-mew-parid and subject
756 * seems to replying the other message.
757 */
758 }
759 if (_this->paridguess && !msg->pariddone)
760 _this->paridnotdone++;
761
762 if (i >= MAILESTD_DBSYNC_NITER) {
763 free(_this->sync_prev);
764 _this->sync_prev = xstrdup(uri);
765 est_doc_delete(doc);
766 mailestd_schedule_db_sync(_this); /* schedule again */
767 return (0);
768 }
769 est_doc_delete(doc);
770 }
771 free(_this->sync_prev);
772 _this->sync_prev = NULL;
773
774 mailestd_log(LOG_INFO, "Database cache updated");
775
776 TAILQ_FOREACH_SAFE(tske, &_this->gather_pendings, queue, tskt) {
777 TAILQ_REMOVE(&_this->gather_pendings, tske, queue);
778 delete = 0;
779 folder = ((struct task_gather *)tske)->folder;
780 strlcpy(dir, _this->maildir, sizeof(dir));
781 strlcat(dir, "/", sizeof(dir));
782 strlcat(dir, folder, sizeof(dir));
783 strlcat(dir, "/", sizeof(dir));
784 ldir = strlen(dir);
785 msg0.path = dir;
786 for (msg = RB_NFIND(rfc822_tree, &_this->root, &msg0);
787 msg != NULL; msg = RB_NEXT(rfc822_tree, &_this->root, msg)){
788 if (strncmp(msg->path, dir, ldir) != 0)
789 break;
790 if (msg->fstime == 0 && !msg->ontask) {
791 mailestd_schedule_deldb(_this, NULL, msg);
792 delete++;
793 }
794 }
795 if (delete > 0)
796 mailestd_log(LOG_INFO, "Cleanup %s%s (Remove: %d)",
797 (folder[0] != '/')? "+" : "", folder, delete);
798 free(tske);
799 }
800 _this->db_sync_time = _this->curr_time;
801 if (_this->monitor) {
802 RB_INIT(&folders);
803 mailestd_get_all_folders(_this, &folders);
804 RB_FOREACH_SAFE(flde, folder_tree, &folders, fldt) {
805 RB_REMOVE(folder_tree, &folders, flde);
806 mailestd_schedule_monitor(_this, flde->path);
807 folder_free(flde);
808 }
809 }
810 if (_this->paridguess) {
811 mailestd_guess_parid(_this);
812 _this->paridnotdone = 0;
813 }
814
815 return (0);
816 }
817
818 static int
mailestd_gather_start(struct mailestd * _this,struct task_gather * task)819 mailestd_gather_start(struct mailestd *_this, struct task_gather *task)
820 {
821 char folder[PATH_MAX];
822 int i, len;
823 char path[PATH_MAX];
824 glob_t gl;
825 struct stat st;
826 DIR *dp;
827 struct dirent *de;
828 struct gather *ctx;
829 struct folder_tree folders;
830 struct folder *flde, *fldt, fld0;
831 bool found = false;
832 struct rfc822 msg0;
833 struct task_queue tskq;
834 struct task *tske, *tskt;
835
836 MAILESTD_ASSERT(_thread_self() == _this->dbworker.thread);
837 TAILQ_INIT(&tskq);
838
839 strlcpy(folder, task->folder, sizeof(folder));
840 ctx = xcalloc(1, sizeof(struct gather));
841 ctx->id = task->gather_id;
842
843 if (isnull(folder))
844 strlcpy(ctx->target, "all", sizeof(ctx->target));
845 else
846 mailestd_folder_name(_this, folder, ctx->target,
847 sizeof(ctx->target));
848 TAILQ_INSERT_TAIL(&_this->gathers, ctx, queue);
849
850 if (folder[0] != '\0') {
851 if (folder[0] != '/') {
852 memset(&gl, 0, sizeof(gl));
853 strlcpy(path, _this->maildir, sizeof(path));
854 strlcat(path, "/", sizeof(path));
855 strlcat(path, folder, sizeof(path));
856 if (glob(path, GLOB_BRACE, NULL, &gl) == 0) {
857 for (i = 0; i < gl.gl_pathc; i++) {
858 if (lstat(gl.gl_pathv[i], &st) != 0 ||
859 !S_ISDIR(st.st_mode))
860 continue;
861 if (!mailestd_folder_match(_this,
862 gl.gl_pathv[i] +
863 _this->lmaildir + 1))
864 continue;
865 mailestd_gather_enqueue(&tskq, ctx,
866 gl.gl_pathv[i] + _this->lmaildir
867 + 1);
868 }
869 globfree(&gl);
870 found = true;
871 }
872 } else {
873 if (lstat(folder, &st) == 0 && S_ISDIR(st.st_mode)) {
874 realpath(folder, path);
875 mailestd_gather_enqueue(&tskq, ctx, path);
876 found = true;
877 } else
878 strlcpy(path, folder, sizeof(path));
879 }
880 if (!found) {
881 /* the directory is not found, but it may be cached */
882 len = strlen(path);
883 strlcat(path, "/", sizeof(path));
884 msg0.path = path;
885 if (RB_NFIND(rfc822_tree, &_this->root, &msg0) != NULL){
886 path[len] = '\0';
887 mailestd_gather_enqueue(&tskq, ctx, path);
888 }
889 }
890 } else {
891 if ((dp = opendir(_this->maildir)) == NULL)
892 mailestd_log(LOG_ERR, "%s: %m", _this->maildir);
893 else {
894 RB_INIT(&folders);
895 mailestd_get_all_folders(_this, &folders);
896 while ((de = readdir(dp)) != NULL) {
897 if (de->d_type != DT_DIR ||
898 strcmp(de->d_name, ".") == 0 ||
899 strcmp(de->d_name, "..") == 0)
900 continue;
901 if (!mailestd_folder_match(_this, de->d_name))
902 continue;
903 mailestd_gather_enqueue(&tskq, ctx, de->d_name);
904 fld0.path = de->d_name;
905 flde = RB_FIND(folder_tree, &folders, &fld0);
906 if (flde != NULL) {
907 RB_REMOVE(folder_tree, &folders, flde);
908 folder_free(flde);
909 }
910 }
911 closedir(dp);
912 RB_FOREACH_SAFE(flde, folder_tree, &folders, fldt) {
913 RB_REMOVE(folder_tree, &folders, flde);
914 mailestd_gather_enqueue(&tskq, ctx, flde->path);
915 folder_free(flde);
916 }
917 }
918 if (_this->monitor)
919 mailestd_schedule_monitor(_this, _this->maildir);
920 }
921
922 if (ctx->folders == 0) {
923 MAILESTD_ASSERT(TAILQ_EMPTY(&tskq));
924 strlcpy(ctx->errmsg, "grabing folders", sizeof(ctx->errmsg));
925 mailestd_gather_inform(_this, NULL, ctx); /* ctx is freed */
926 }
927
928 /*
929 * Since this function is called from the main or monitor thread we
930 * need to schedule all tasks at once here to avoid conflicts
931 * accessing "ctx" which is common object in the tasks.
932 */
933 TAILQ_FOREACH_SAFE(tske, &tskq, queue, tskt) {
934 TAILQ_REMOVE(&tskq, tske, queue);
935 task_worker_add_task(&_this->dbworker, tske);
936 }
937
938 return (0);
939 }
940
941 static int
mailestd_gather(struct mailestd * _this,struct task_gather * task)942 mailestd_gather(struct mailestd *_this, struct task_gather *task)
943 {
944 int lrdir, update = 0, delete = 0, total = 0;
945 char rdir[PATH_MAX], buf[PATH_MAX], *paths[2];
946 const char *folder = task->folder;
947 struct gather *ctx;
948 FTS *fts;
949 struct rfc822 *msge, msg0;
950 time_t curr_time;
951 struct folder *flde, *fldt;
952 struct folder_tree
953 folders;
954
955 MAILESTD_ASSERT(_thread_self() == _this->dbworker.thread);
956 RB_INIT(&folders);
957 ctx = mailestd_get_gather(_this, task->gather_id);
958 MAILESTD_ASSERT(ctx != NULL);
959 if (folder[0] == '/')
960 strlcpy(rdir, folder, sizeof(rdir));
961 else {
962 strlcpy(rdir, _this->maildir, sizeof(rdir));
963 strlcat(rdir, "/", sizeof(rdir));
964 strlcat(rdir, folder, sizeof(rdir));
965 }
966 mailestd_log(LOG_DEBUG, "Gathering %s ...", mailestd_folder_name(
967 _this, rdir, buf, sizeof(buf)));
968 paths[0] = rdir;
969 paths[1] = NULL;
970 lrdir = strlen(rdir);
971
972 if ((fts = fts_open(paths, FTS_LOGICAL | FTS_NOCHDIR, NULL)) == NULL) {
973 mailestd_log(LOG_ERR, "fts_open(%s): %m", folder);
974 goto out;
975 }
976 curr_time = _this->curr_time;
977 update = mailestd_fts(_this, ctx, curr_time, fts, fts_read(fts),
978 &folders);
979 fts_close(fts);
980
981 MAILESTD_ASSERT(lrdir + 1 < (int)sizeof(rdir));
982 rdir[lrdir++] = '/';
983 rdir[lrdir] = '\0';
984
985 msg0.path = rdir;
986 for (msge = RB_NFIND(rfc822_tree, &_this->root, &msg0);
987 msge != NULL; msge = RB_NEXT(rfc822_tree, &_this->root, msge)) {
988 if (strncmp(msge->path, rdir, lrdir) != 0)
989 break;
990 total++;
991 if (msge->fstime != curr_time) {
992 delete++;
993 if (msge->ontask)
994 /* other task is running */;
995 else {
996 MAILESTD_ASSERT(msge->db_id != 0);
997 mailestd_schedule_deldb(_this, ctx, msge);
998 }
999 }
1000 }
1001
1002 mailestd_log(LOG_DEBUG, "Gathered %s (Total: %d Remove: %d Update: %d)",
1003 mailestd_folder_name(_this, rdir, buf, sizeof(buf)),
1004 total, delete, update);
1005 out:
1006 if (ctx != NULL) {
1007 if (ctx->puts == ctx->puts_done &&
1008 ctx->dels == ctx->dels_done &&
1009 (update > 0 || delete > 0)) {
1010 strlcpy(ctx->errmsg, "other task exists",
1011 sizeof(ctx->errmsg));
1012 mailestd_gather_inform(_this, NULL, ctx);
1013 } else if (_this->dbworker.suspend) {
1014 strlcpy(ctx->errmsg,
1015 "database tasks are suspended",
1016 sizeof(ctx->errmsg));
1017 mailestd_gather_inform(_this, NULL, ctx);
1018 } else
1019 mailestd_gather_inform(_this, (struct task *)task, ctx);
1020 }
1021 RB_FOREACH_SAFE(flde, folder_tree, &folders, fldt) {
1022 RB_REMOVE(folder_tree, &folders, flde);
1023 if (_this->monitor)
1024 mailestd_schedule_monitor(_this, flde->path);
1025 folder_free(flde);
1026 }
1027
1028 return (0);
1029 }
1030
1031 static void
mailestd_gather_inform(struct mailestd * _this,struct task * task,struct gather * gat)1032 mailestd_gather_inform(struct mailestd *_this, struct task *task,
1033 struct gather *gat)
1034 {
1035 int notice = 0;
1036 struct gather *gather = gat;
1037
1038 if (task != NULL) {
1039 if (gather == NULL && (gather = mailestd_get_gather(_this,
1040 ((struct task_rfc822 *)task)->msg->gather_id)) == NULL)
1041 return;
1042 switch (task->type) {
1043 default:
1044 break;
1045
1046 case MAILESTD_TASK_GATHER:
1047 if (++gather->folders_done == gather->folders && (
1048 gather->dels_done == gather->dels ||
1049 gather->puts_done == gather->puts))
1050 notice++;
1051 break;
1052
1053 case MAILESTD_TASK_RFC822_DELDB:
1054 if (++gather->dels_done == gather->dels &&
1055 gather->folders_done == gather->folders)
1056 notice++;
1057 break;
1058
1059 case MAILESTD_TASK_RFC822_PUTDB:
1060 if (++gather->puts_done == gather->puts &&
1061 gather->folders_done == gather->folders)
1062 notice++;
1063 break;
1064 }
1065 if (notice > 0)
1066 mailestd_schedule_inform(_this, gather->id,
1067 (u_char *)gather, sizeof(struct gather));
1068 if (gather->folders_done == gather->folders &&
1069 gather->dels_done == gather->dels &&
1070 gather->puts_done == gather->puts) {
1071 mailestd_log(LOG_INFO,
1072 "Updated %s (Folders: %d Remove: %d "
1073 "Update: %d)", gather->target,
1074 gather->folders_done, gather->dels_done,
1075 gather->puts_done);
1076 TAILQ_REMOVE(&_this->gathers, gather, queue);
1077 free(gather);
1078 }
1079 } else {
1080 mailestd_log(LOG_INFO,
1081 "Updating %s failed (Folders: %d Remove: %d Update: %d): "
1082 "%s", gather->target, gather->folders_done,
1083 gather->dels_done, gather->puts_done, gather->errmsg);
1084 mailestd_schedule_inform(_this, gather->id,
1085 (u_char *)gather, sizeof(struct gather));
1086 TAILQ_REMOVE(&_this->gathers, gather, queue);
1087 free(gather);
1088 }
1089 }
1090
1091 static int
mailestd_fts(struct mailestd * _this,struct gather * ctx,time_t curr_time,FTS * fts,FTSENT * ftse,struct folder_tree * folders)1092 mailestd_fts(struct mailestd *_this, struct gather *ctx, time_t curr_time,
1093 FTS *fts, FTSENT *ftse, struct folder_tree *folders)
1094 {
1095 int i, j, update = 0;
1096 const char *bn, *errstr;
1097 struct rfc822 *msg, msg0;
1098 bool needupdate = false;
1099 char uri[PATH_MAX + 128];
1100 struct tm tm;
1101 ESTDOC *doc;
1102 int db_id;
1103 struct folder *fld;
1104
1105 strlcpy(uri, URIFILE, sizeof(uri));
1106 do {
1107 needupdate = false;
1108 if (ftse == NULL)
1109 break;
1110 if (_this->monitor && ftse->fts_info == FTS_D) {
1111 fld = xcalloc(1, sizeof(struct folder));
1112 fld->path = xstrdup(ftse->fts_path);
1113 RB_INSERT(folder_tree, folders, fld);
1114 }
1115 if (!(ftse->fts_info == FTS_F || ftse->fts_info == FTS_SL))
1116 continue;
1117
1118 bn = ftse->fts_name;
1119 for (i = 0; bn[i] != '\0'; i++) {
1120 if (!isdigit(bn[i]))
1121 break;
1122 }
1123 if (bn[i] != '\0') {
1124 if (_this->suffix == NULL)
1125 continue;
1126 for (j = 0; !isnull(_this->suffix[j]); j++) {
1127 if (strcmp(bn + i, _this->suffix[j]) == 0)
1128 break;
1129 }
1130 if (isnull(_this->suffix[j]))
1131 continue;
1132 }
1133 msg0.path = ftse->fts_path;
1134 msg = RB_FIND(rfc822_tree, &_this->root, &msg0);
1135 if (msg == NULL) {
1136 msg = xcalloc(1, sizeof(struct rfc822));
1137 msg->path = xstrdup(ftse->fts_path);
1138 RB_INSERT(rfc822_tree, &_this->root, msg);
1139 strlcpy(uri + 7, ftse->fts_path, sizeof(uri) - 7);
1140 if (!mailestd_is_db_sync_done(_this) &&
1141 mailestd_db_open_rd(_this) != NULL &&
1142 (db_id = est_db_uri_to_id(_this->db, uri)) != -1 &&
1143 (doc = est_db_get_doc(_this->db, db_id,
1144 ESTGDNOKWD)) != NULL) {
1145 strptime(est_doc_attr(doc, ESTDATTRMDATE),
1146 MAILESTD_TIMEFMT, &tm);
1147 msg->db_id = db_id;
1148 msg->mtime = timegm(&tm);
1149 msg->size = strtonum(est_doc_attr(doc,
1150 ESTDATTRSIZE), 0, INT64_MAX, &errstr);
1151 est_doc_delete(doc);
1152 }
1153 }
1154 if (msg->db_id == 0 ||
1155 msg->mtime != ftse->fts_statp->st_mtime ||
1156 msg->size != ftse->fts_statp->st_size)
1157 needupdate = true;
1158
1159 msg->fstime = curr_time;
1160 msg->mtime = ftse->fts_statp->st_mtime;
1161 msg->size = ftse->fts_statp->st_size;
1162 if (needupdate) {
1163 update++;
1164 if (!msg->ontask) {
1165 mailestd_schedule_draft(_this, ctx, msg);
1166 if (ctx != NULL)
1167 ctx->puts++;
1168 }
1169 }
1170 } while ((ftse = fts_read(fts)) != NULL);
1171
1172 return (update);
1173 }
1174
1175 static void
mailestd_draft(struct mailestd * _this,struct rfc822 * msg)1176 mailestd_draft(struct mailestd *_this, struct rfc822 *msg)
1177 {
1178 #ifdef HAVE_LIBESTDRAFT
1179 int fd = -1;
1180 struct stat st;
1181 char *msgs = NULL, buf[PATH_MAX + 128];
1182 struct tm tm;
1183
1184 if ((fd = open(msg->path, O_RDONLY)) < 0) {
1185 mailestd_log(LOG_WARNING, "open(%s): %m", msg->path);
1186 goto on_error;
1187 }
1188 if (fstat(fd, &st) == -1) {
1189 mailestd_log(LOG_WARNING, "fstat(%s): %m", msg->path);
1190 goto on_error;
1191 }
1192 if ((msgs = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE | MAP_FILE, fd,
1193 0)) == MAP_FAILED) {
1194 mailestd_log(LOG_WARNING, "mmap(%s): %m", msg->path);
1195 goto on_error;
1196 }
1197 msg->draft = est_doc_new_from_mime(
1198 msgs, st.st_size, NULL, ESTLANGEN, 0);
1199 if (msg->draft == NULL) {
1200 mailestd_log(LOG_WARNING, "est_doc_new_from_mime(%s) failed",
1201 msg->path);
1202 goto on_error;
1203 }
1204 est_doc_slim(msg->draft, MAILESTD_TRIMSIZE);
1205 strlcpy(buf, URIFILE, sizeof(buf));
1206 strlcat(buf, msg->path, sizeof(buf));
1207 est_doc_add_attr(msg->draft, ESTDATTRURI, buf);
1208 gmtime_r(&msg->mtime, &tm);
1209 strftime(buf, sizeof(buf), MAILESTD_TIMEFMT "\n", &tm);
1210 est_doc_add_attr(msg->draft, ESTDATTRMDATE, buf);
1211
1212 on_error:
1213 if (fd >= 0)
1214 close(fd);
1215 if (msgs != NULL)
1216 munmap(msgs, st.st_size);
1217 return;
1218 #else
1219 FILE *fpin, *fpout;
1220 char *draft = NULL, buf[BUFSIZ];
1221 size_t siz, draftsiz = 0;
1222 int status;
1223 struct tm tm;
1224
1225 fpout = open_memstream(&draft, &draftsiz);
1226 snprintf(buf, sizeof(buf), "estcmd draft -fm %s", msg->path);
1227 fpin = popen(buf, "r");
1228 while ((siz = fread(buf, 1, sizeof(buf), fpin)) > 0)
1229 fwrite(buf, 1, siz, fpout);
1230 status = pclose(fpin);
1231 if (status != 0)
1232 mailestd_log(LOG_ERR, "%s returns %d: %m", msg->path, status);
1233 fflush(fpout);
1234 fclose(fpout);
1235 if (draftsiz == 0) {
1236 mailestd_log(LOG_ERR, "couldn not parse %s??", msg->path);
1237 free(draft);
1238 return;
1239 } else {
1240 MAILESTD_ASSERT(msg->draft == NULL);
1241 MAILESTD_ASSERT(draftsiz > 0);
1242 msg->draft = est_doc_new_from_draft(draft);
1243 gmtime_r(&msg->mtime, &tm);
1244 strftime(buf, sizeof(buf), MAILESTD_TIMEFMT "\n", &tm);
1245 est_doc_add_attr(msg->draft, ESTDATTRMDATE, buf);
1246 }
1247 #endif
1248 }
1249
1250 static void
mailestd_putdb(struct mailestd * _this,struct rfc822 * msg)1251 mailestd_putdb(struct mailestd *_this, struct rfc822 *msg)
1252 {
1253 int ecode;
1254
1255 MAILESTD_ASSERT(_thread_self() == _this->dbworker.thread);
1256 MAILESTD_ASSERT(msg->draft != NULL);
1257 MAILESTD_ASSERT(_this->db != NULL);
1258
1259 if (est_db_put_doc(_this->db, msg->draft, 0)) {
1260 msg->db_id = est_doc_id(msg->draft);
1261 if (debug > 2)
1262 mailestd_log(LOG_DEBUG, "put %s successfully. id=%d",
1263 msg->path, msg->db_id);
1264 } else {
1265 ecode = est_db_error(_this->db);
1266 mailestd_log(LOG_WARNING,
1267 "putting %s failed: %s", msg->path, est_err_msg(ecode));
1268 mailestd_db_error(_this);
1269 }
1270
1271 est_doc_delete(msg->draft);
1272 msg->draft = NULL;
1273 }
1274
1275 static void
mailestd_guess(struct mailestd * _this,struct rfc822 * msg)1276 mailestd_guess(struct mailestd *_this, struct rfc822 *msg)
1277 {
1278 ESTDOC *doc = NULL, *docpar;
1279 const char *subj, *cdate, *subj1, *parid = NULL;
1280 ESTCOND *cond;
1281 char condbuf[128];
1282 int *res, rnum;
1283
1284 if (msg->db_id == 0)
1285 goto out;
1286
1287 doc = est_db_get_doc(_this->db, msg->db_id, ESTGDNOTEXT);
1288 if (doc == NULL)
1289 goto out;
1290 subj = est_doc_attr(doc, ATTR_TITLE);
1291 cdate = est_doc_attr(doc, ATTR_CDATE);
1292 MAILESTD_ASSERT(subj != NULL);
1293 subj1 = skip_subject(subj);
1294 MAILESTD_ASSERT(subj1 != NULL);
1295
1296 if (strlen(subj1) < 5) {
1297 mailestd_log(LOG_INFO,
1298 "guessing %s failed: subject too short", msg->path);
1299 goto notexist;
1300 }
1301
1302 cond = est_cond_new();
1303
1304 strlcpy(condbuf, ATTR_TITLE " " ESTOPSTREW " ", sizeof(condbuf));
1305 strlcat(condbuf, subj1, sizeof(condbuf));
1306 est_cond_add_attr(cond, condbuf);
1307
1308 if (cdate != NULL) {
1309 strlcpy(condbuf, ATTR_CDATE" " ESTOPNUMLT " ", sizeof(condbuf));
1310 strlcat(condbuf, cdate, sizeof(condbuf));
1311 est_cond_add_attr(cond, condbuf);
1312 }
1313
1314 strlcpy(condbuf, ATTR_CDATE " " ESTORDNUMD, sizeof(condbuf));
1315 est_cond_set_order(cond, condbuf);
1316
1317 est_cond_set_max(cond, 1);
1318 res = est_db_search(_this->db, cond, &rnum, NULL);
1319 if (rnum != 1) {
1320 mailestd_log(LOG_INFO,
1321 "guess %s's parent message doesn't exist", msg->path);
1322 notexist:
1323 est_doc_add_attr(doc, ATTR_PARID, "notexist");
1324 est_db_edit_doc(_this->db, doc);
1325 goto out;
1326 }
1327
1328 docpar = est_db_get_doc(_this->db, res[0], ESTGDNOTEXT);
1329 if (docpar == NULL) {
1330 mailestd_log(LOG_WARNING,
1331 "guessing %s failed: search result no doc %d", msg->path,
1332 res[0]);
1333 goto out;
1334 }
1335
1336 parid = est_doc_attr(docpar, ATTR_MSGID);
1337 if (parid != NULL) {
1338 mailestd_log(LOG_INFO,
1339 "guess %s's parent message is %s",
1340 msg->path, est_doc_attr(docpar, ESTDATTRURI) + 7);
1341 est_doc_add_attr(doc, ATTR_PARID, parid);
1342 est_db_edit_doc(_this->db, doc);
1343 } else {
1344 mailestd_log(LOG_DEBUG,
1345 "guessing %s failed: guessed parent (doc id=%d) has no "
1346 "msgid ", msg->path, res[0]);
1347 est_doc_add_attr(doc, ATTR_PARID, "noidparent");
1348 est_db_edit_doc(_this->db, doc);
1349 }
1350 est_doc_delete(docpar);
1351 out:
1352 if (doc != NULL)
1353 est_doc_delete(doc);
1354 msg->pariddone = true;
1355 }
1356
1357 static void
mailestd_deldb(struct mailestd * _this,struct rfc822 * msg)1358 mailestd_deldb(struct mailestd *_this, struct rfc822 *msg)
1359 {
1360 int ecode;
1361
1362 MAILESTD_ASSERT(_thread_self() == _this->dbworker.thread);
1363 MAILESTD_ASSERT(msg->db_id != 0);
1364 MAILESTD_ASSERT(_this->db != NULL);
1365
1366 if (est_db_out_doc(_this->db, msg->db_id, 0)) {
1367 if (debug > 2)
1368 mailestd_log(LOG_DEBUG, "delete %s(%d) successfully",
1369 msg->path, msg->db_id);
1370 } else {
1371 ecode = est_db_error(_this->db);
1372 mailestd_log(LOG_WARNING, "deleting %s(%d) failed: %s",
1373 msg->path, msg->db_id, est_err_msg(ecode));
1374 mailestd_db_error(_this);
1375 }
1376 }
1377
1378 static void
mailestd_db_smew(struct mailestd * _this,struct task_smew * smew)1379 mailestd_db_smew(struct mailestd *_this, struct task_smew *smew)
1380 {
1381 int i, cnt = 0, *res, rnum, lfolder;
1382 const char *msgid;
1383 char buf[BUFSIZ], *bufp = NULL;
1384 size_t bufsiz = 0;
1385 FILE *out;
1386 ESTDB *db;
1387 ESTCOND *cond;
1388 ESTDOC *doc;
1389 struct doclist {
1390 ESTDOC *doc;
1391 const char *msgid;
1392 const char *uri;
1393 TAILQ_ENTRY(doclist) queue;
1394 } *doce, *docc, *doct;
1395 TAILQ_HEAD(, doclist) children, ancestors;
1396
1397 lfolder = (isnull(smew->folder))? -1 : (int)strlen(smew->folder);
1398
1399 if ((out = open_memstream(&bufp, &bufsiz)) == NULL)
1400 abort();
1401
1402 if ((db = mailestd_db_open_rd(_this)) == NULL)
1403 goto out;
1404
1405 TAILQ_INIT(&children);
1406 TAILQ_INIT(&ancestors);
1407 /* search ancestors */
1408 for (i = 0, msgid = smew->msgid; msgid != NULL; i++) {
1409 strlcpy(buf, ATTR_MSGID " " ESTOPSTREQ " ", sizeof(buf));
1410 strlcat(buf, msgid, sizeof(buf));
1411 cond = est_cond_new();
1412 est_cond_add_attr(cond, buf);
1413 res = est_db_search(db, cond, &rnum, NULL);
1414 est_cond_delete(cond);
1415 msgid = NULL;
1416 if (rnum > 0) {
1417 if ((doc = est_db_get_doc(db, res[0], ESTGDNOTEXT))
1418 == NULL)
1419 continue;
1420 doce = xcalloc(1, sizeof(struct doclist));
1421 doce->doc = doc;
1422 doce->msgid = est_doc_attr(doc, ATTR_MSGID);
1423 if (i == 0)
1424 TAILQ_INSERT_TAIL(&children, doce, queue);
1425 else
1426 TAILQ_INSERT_HEAD(&ancestors, doce, queue);
1427 msgid = est_doc_attr(doc, ATTR_PARID);
1428 }
1429 }
1430
1431 /* search descendants */
1432 while ((doce = TAILQ_FIRST(&children)) != NULL) {
1433 TAILQ_REMOVE(&children, doce, queue);
1434 TAILQ_FOREACH_SAFE(docc, &ancestors, queue, doct) {
1435 if (doce->msgid != NULL && docc->msgid != NULL &&
1436 strcmp(doce->msgid, docc->msgid) == 0)
1437 break;
1438 }
1439 if (docc != NULL) {
1440 /*
1441 * Keep ancestors list unique.
1442 */
1443 if (docc->uri == NULL)
1444 docc->uri = uri2normalpath(_this,
1445 est_doc_attr(docc->doc, ESTDATTRURI));
1446 if (lfolder > 0 &&
1447 !strncmp(docc->uri, smew->folder, lfolder) &&
1448 docc->uri[lfolder] == '/') {
1449 /*
1450 * existing member, docc, imatches the given
1451 * folder. Keep it.
1452 */
1453 est_doc_delete(doce->doc);
1454 free(doce);
1455 } else {
1456 TAILQ_REMOVE(&ancestors, docc, queue);
1457 est_doc_delete(docc->doc);
1458 free(docc);
1459 TAILQ_INSERT_TAIL(&ancestors, doce, queue);
1460 }
1461 continue;
1462 }
1463 TAILQ_INSERT_TAIL(&ancestors, doce, queue);
1464 if (doce->msgid == NULL)
1465 continue; /* can't become parent */
1466 strlcpy(buf, ATTR_PARID " " ESTOPSTREQ " ", sizeof(buf));
1467 strlcat(buf, doce->msgid, sizeof(buf));
1468 cond = est_cond_new();
1469 est_cond_add_attr(cond, buf);
1470 res = est_db_search(db, cond, &rnum, NULL);
1471 est_cond_delete(cond);
1472 for (i = 0; i < rnum; i++) {
1473 if ((doc = est_db_get_doc(db, res[i], ESTGDNOTEXT))
1474 == NULL)
1475 continue;
1476 docc = xcalloc(1, sizeof(struct doclist));
1477 docc->doc = doc;
1478 docc->msgid = est_doc_attr(doc, ATTR_MSGID);
1479 TAILQ_INSERT_HEAD(&children, docc, queue);
1480 }
1481 }
1482
1483 TAILQ_FOREACH_SAFE(doce, &ancestors, queue, doct) {
1484 TAILQ_REMOVE(&ancestors, doce, queue);
1485 if (doce->uri == NULL)
1486 doce->uri = uri2normalpath(_this,
1487 (const char *)est_doc_attr(doce->doc, ESTDATTRURI));
1488 fprintf(out, "%s\n", doce->uri);
1489 est_doc_delete(doce->doc);
1490 free(doce);
1491 cnt++;
1492 }
1493 out:
1494 mailestd_log(LOG_INFO, "Done smew (%s%s%s) Hit %d",
1495 smew->msgid, (lfolder > 0)? ", +" : "",
1496 (lfolder > 0)? smew->folder : "", cnt);
1497 fclose(out);
1498 mailestd_schedule_inform(_this, smew->id, (u_char *)bufp, bufsiz);
1499 free(bufp);
1500 }
1501
1502 static void
mailestd_search(struct mailestd * _this,uint64_t task_id,const char * searchstr,ESTCOND * cond,enum MAILESTCTL_OUTFORM outform)1503 mailestd_search(struct mailestd *_this, uint64_t task_id, const char *searchstr,
1504 ESTCOND *cond, enum MAILESTCTL_OUTFORM outform)
1505 {
1506 int i, rnum, *res, ecode;
1507 char *bufp = NULL;
1508 size_t bufsiz = 0;
1509 ESTDOC *doc;
1510 FILE *out;
1511 const char *uri;
1512
1513 MAILESTD_ASSERT(_thread_self() == _this->dbworker.thread);
1514 MAILESTD_ASSERT(_this->db != NULL);
1515 if ((out = open_memstream(&bufp, &bufsiz)) == NULL)
1516 abort();
1517 res = est_db_search(_this->db, cond, &rnum, NULL);
1518 if (res == NULL) {
1519 ecode = est_db_error(_this->db);
1520 mailestd_log(LOG_INFO,
1521 "Search(%s) failed: %s", searchstr, est_err_msg(ecode));
1522 mailestd_schedule_inform(_this, task_id, NULL, 0);
1523 } else {
1524 mailestd_log(LOG_INFO,
1525 "Searched(%s). Hit %d", searchstr, rnum);
1526 for (i = 0; i < rnum; i++) {
1527 doc = est_db_get_doc(_this->db, res[i], ESTGDNOKWD);
1528 if (doc == NULL) {
1529 ecode = est_db_error(_this->db);
1530 mailestd_log(LOG_WARNING,
1531 "est_db_get_doc(id=%d) failed: %s",
1532 res[i], est_err_msg(ecode));
1533 } else {
1534 uri = est_doc_attr(doc, ESTDATTRURI);
1535 switch (outform) {
1536 case MAILESTCTL_OUTFORM_SMEW:
1537 if (is_parent_dir(
1538 _this->maildir, URI2PATH(uri)))
1539 fprintf(out, "%s\n",
1540 URI2PATH(uri) +
1541 _this->lmaildir + 1);
1542 else
1543 fprintf(out, "%s\n",
1544 URI2PATH(uri));
1545 break;
1546 case MAILESTCTL_OUTFORM_COMPAT_VU:
1547 fprintf(out, "%d\t%s\n", res[i], uri);
1548 break;
1549 }
1550 }
1551 }
1552 fclose(out);
1553 mailestd_schedule_inform(_this, task_id, (u_char *)bufp,
1554 bufsiz);
1555 free(bufp);
1556 }
1557 }
1558
1559 static void
mailestd_guess_parid(struct mailestd * _this)1560 mailestd_guess_parid(struct mailestd *_this)
1561 {
1562 struct rfc822 *msg;
1563
1564 MAILESTD_ASSERT(_thread_self() == _this->dbworker.thread);
1565 RB_FOREACH(msg, rfc822_tree, &_this->root) {
1566 if (!msg->ontask && !msg->pariddone)
1567 mailestd_schedule_guess_parid(_this, msg);
1568 }
1569 }
1570
1571 static void
mailestd_db_informer(const char * msg,void * opaque)1572 mailestd_db_informer(const char *msg, void *opaque)
1573 {
1574 mailestd_log(LOG_DEBUG, "DB: %s", msg);
1575 }
1576
1577 static void
mailestd_db_error(struct mailestd * _this)1578 mailestd_db_error(struct mailestd *_this)
1579 {
1580 struct gather *gate, *gatt;
1581
1582 TAILQ_FOREACH_SAFE(gate, &_this->gathers, queue, gatt) {
1583 strlcpy(gate->errmsg, "Database broken", sizeof(gate->errmsg));
1584 mailestd_gather_inform(_this, NULL, gate);
1585 }
1586 mailestd_log(LOG_WARNING, "Database may be broken. Operations for "
1587 "the datatabase will be suspended. Try \"estcmd repair -rst %s\", "
1588 "then \"mailestctl resume\". If the error continues, you may "
1589 "need to recreate the database", _this->dbpath);
1590 mailestd_schedule_message_all(_this, MAILESTD_TASK_SUSPEND);
1591 }
1592
1593 static uint64_t
mailestd_schedule_db_sync(struct mailestd * _this)1594 mailestd_schedule_db_sync(struct mailestd *_this)
1595 {
1596 struct task *task;
1597
1598 task = xcalloc(1, sizeof(struct task));
1599 task->type = MAILESTD_TASK_SYNCDB;
1600
1601 return (task_worker_add_task(&_this->dbworker, task));
1602 }
1603
1604 static bool
mailestd_folder_match(struct mailestd * _this,const char * folder)1605 mailestd_folder_match(struct mailestd *_this, const char *folder)
1606 {
1607 int i;
1608 bool neg;
1609 const char *pat;
1610
1611 for (i = 0; _this->folder != NULL && !isnull(_this->folder[i]); i++) {
1612 neg = false;
1613 pat = _this->folder[i];
1614 if (*pat == '!') {
1615 pat++;
1616 neg = true;
1617 }
1618 if (fnmatch(pat, folder, 0) == 0) {
1619 if (neg)
1620 return (false);
1621 break;
1622 }
1623 }
1624
1625 return (true);
1626 }
1627
1628 static uint64_t
mailestd_schedule_gather_start(struct mailestd * _this,const char * folder)1629 mailestd_schedule_gather_start(struct mailestd *_this, const char *folder)
1630 {
1631 struct task_gather *task;
1632
1633 task = xcalloc(1, sizeof(struct task_gather));
1634 task->type = MAILESTD_TASK_GATHER_START;
1635 task->highprio = true;
1636 task->gather_id = mailestd_new_id(_this);
1637 strlcpy(task->folder, folder, sizeof(task->folder));
1638
1639 task_worker_add_task(&_this->dbworker, (struct task *)task);
1640
1641 return (task->gather_id);
1642 }
1643
1644 static void
mailestd_gather_enqueue(struct task_queue * q,struct gather * ctx,const char * folder)1645 mailestd_gather_enqueue(struct task_queue *q, struct gather *ctx,
1646 const char *folder)
1647 {
1648 struct task_gather *task;
1649
1650 task = xcalloc(1, sizeof(struct task_gather));
1651 task->type = MAILESTD_TASK_GATHER;
1652 task->highprio = true;
1653 task->gather_id = ctx->id;
1654 ctx->folders++;
1655 strlcpy(task->folder, folder, sizeof(task->folder));
1656
1657 TAILQ_INSERT_TAIL(q, (struct task *)task, queue);
1658 }
1659
1660 static uint64_t
mailestd_schedule_draft(struct mailestd * _this,struct gather * gather,struct rfc822 * msg)1661 mailestd_schedule_draft(struct mailestd *_this, struct gather *gather,
1662 struct rfc822 *msg)
1663 {
1664 struct task *task;
1665
1666 MAILESTD_ASSERT(_thread_self() == _this->dbworker.thread);
1667 MAILESTD_ASSERT(!msg->ontask);
1668
1669 msg->ontask = true;
1670 msg->gather_id = (gather != NULL)? gather->id : 0;
1671 task = TAILQ_FIRST_ITEM(&_this->rfc822_tasks);
1672 if (task) {
1673 TAILQ_REMOVE(&_this->rfc822_tasks, task, queue);
1674 ((struct task_rfc822 *)task)->msg = msg;
1675 task->type = MAILESTD_TASK_RFC822_DRAFT;
1676 _this->rfc822_ntask++;
1677
1678 return (task_worker_add_task(&_this->mainworker, task));
1679 } else
1680 TAILQ_INSERT_TAIL(&_this->rfc822_pendings, msg, queue);
1681
1682 return (0);
1683 }
1684
1685 static uint64_t
mailestd_reschedule_draft(struct mailestd * _this)1686 mailestd_reschedule_draft(struct mailestd *_this)
1687 {
1688 struct rfc822 *msg;
1689 struct task *task;
1690
1691 MAILESTD_ASSERT(_thread_self() == _this->dbworker.thread);
1692 for (;;) {
1693 msg = TAILQ_FIRST_ITEM(&_this->rfc822_pendings);
1694 task = TAILQ_FIRST_ITEM(&_this->rfc822_tasks);
1695 if (msg == NULL || task == NULL)
1696 break;
1697 TAILQ_REMOVE(&_this->rfc822_tasks, task, queue);
1698 TAILQ_REMOVE(&_this->rfc822_pendings, msg, queue);
1699 ((struct task_rfc822 *)task)->msg = msg;
1700 task->type = MAILESTD_TASK_RFC822_DRAFT;
1701 _this->rfc822_ntask++;
1702
1703 return (task_worker_add_task(&_this->mainworker, task));
1704 }
1705
1706 return (0);
1707 }
1708
1709 static uint64_t
mailestd_schedule_putdb(struct mailestd * _this,struct task * task,struct rfc822 * msg)1710 mailestd_schedule_putdb(struct mailestd *_this, struct task *task,
1711 struct rfc822 *msg)
1712 {
1713 /* given task is a member of mailestd.rfc822_tasks */
1714 task->type = MAILESTD_TASK_RFC822_PUTDB;
1715 ((struct task_rfc822 *)task)->msg = msg;
1716
1717 return (task_worker_add_task(&_this->dbworker, task));
1718 }
1719
1720 static uint64_t
mailestd_schedule_deldb(struct mailestd * _this,struct gather * ctx,struct rfc822 * msg)1721 mailestd_schedule_deldb(struct mailestd *_this, struct gather *ctx,
1722 struct rfc822 *msg)
1723 {
1724 struct task *task;
1725
1726 MAILESTD_ASSERT(!msg->ontask);
1727
1728 msg->ontask = true;
1729 task = xcalloc(1, sizeof(struct task_rfc822));
1730 task->type = MAILESTD_TASK_RFC822_DELDB;
1731 ((struct task_rfc822 *)task)->msg = msg;
1732
1733 if (ctx != NULL) {
1734 msg->gather_id = ctx->id;
1735 ctx->dels++;
1736 }
1737
1738 return (task_worker_add_task(&_this->dbworker, task));
1739 }
1740
1741 static uint64_t
mailestd_schedule_search(struct mailestd * _this,const char * searchstr,ESTCOND * cond,enum MAILESTCTL_OUTFORM outform)1742 mailestd_schedule_search(struct mailestd *_this, const char *searchstr,
1743 ESTCOND *cond, enum MAILESTCTL_OUTFORM outform)
1744 {
1745 struct task_search *task;
1746
1747 mailestd_log(LOG_INFO, "Searching(%s)", searchstr);
1748
1749 task = xcalloc(1, sizeof(struct task_search));
1750 task->type = MAILESTD_TASK_SEARCH;
1751 task->cond = cond;
1752 task->highprio = true;
1753 task->outform = outform;
1754 strlcpy(task->str, searchstr, sizeof(task->str));
1755
1756 return (task_worker_add_task(&_this->dbworker, (struct task *)task));
1757 }
1758
1759 static uint64_t
mailestd_schedule_smew(struct mailestd * _this,const char * msgid,const char * folder)1760 mailestd_schedule_smew(struct mailestd *_this, const char *msgid,
1761 const char *folder)
1762 {
1763 struct task_smew *task;
1764
1765 if (!isnull(folder))
1766 mailestd_log(LOG_INFO, "Starting smew (%s, +%s)",
1767 msgid, folder);
1768 else
1769 mailestd_log(LOG_INFO, "Starting smew (%s)", msgid);
1770
1771 task = xcalloc(1, sizeof(struct task_smew));
1772 task->type = MAILESTD_TASK_SMEW;
1773 strlcpy(task->msgid, msgid, sizeof(task->msgid));
1774 strlcpy(task->folder, folder, sizeof(task->folder));
1775 task->highprio = true;
1776
1777 return (task_worker_add_task(&_this->dbworker, (struct task *)task));
1778 }
1779
1780 static uint64_t
mailestd_schedule_inform(struct mailestd * _this,uint64_t task_id,u_char * inform,size_t informsiz)1781 mailestd_schedule_inform(struct mailestd *_this, uint64_t task_id,
1782 u_char *inform, size_t informsiz)
1783 {
1784 struct task_inform *task;
1785
1786 task = xcalloc(1, sizeof(struct task_inform) + informsiz);
1787 task->type = MAILESTD_TASK_INFORM;
1788 task->highprio = true;
1789 task->informsiz = informsiz;
1790 task->src_id = task_id;
1791 if (informsiz > 0)
1792 memcpy(&task->inform[0], inform, informsiz);
1793
1794 return (task_worker_add_task(&_this->mainworker, (struct task *)task));
1795 }
1796
1797 static void
mailestd_schedule_message_all(struct mailestd * _this,enum MAILESTD_TASK tsk_type)1798 mailestd_schedule_message_all(struct mailestd *_this,
1799 enum MAILESTD_TASK tsk_type)
1800 {
1801 int i;
1802 struct task *task;
1803
1804 for (i = 0; _this->workers[i] != NULL; i++) {
1805 task = xcalloc(1, sizeof(struct task));
1806 task->type = tsk_type;
1807 task->highprio = true;
1808 task_worker_add_task(_this->workers[i], task);
1809 }
1810 }
1811
1812 static uint64_t
mailestd_schedule_monitor(struct mailestd * _this,const char * path)1813 mailestd_schedule_monitor(struct mailestd *_this, const char *path)
1814 {
1815 struct task_monitor *task;
1816
1817 if (!_this->monitor)
1818 return (0);
1819 task = xcalloc(1, sizeof(struct task_monitor));
1820 task->type = MAILESTD_TASK_MONITOR_FOLDER;
1821 task->highprio = true;
1822 if (path[0] == '/')
1823 strlcpy(task->path, path, sizeof(task->path));
1824 else {
1825 strlcpy(task->path, _this->maildir, sizeof(task->path));
1826 strlcat(task->path, "/", sizeof(task->path));
1827 strlcat(task->path, path, sizeof(task->path));
1828 }
1829
1830 return (task_worker_add_task(&_this->monitorworker,
1831 (struct task *)task));
1832 }
1833
1834 static uint64_t
mailestd_schedule_guess_parid(struct mailestd * _this,struct rfc822 * msg)1835 mailestd_schedule_guess_parid(struct mailestd *_this, struct rfc822 *msg)
1836 {
1837 struct task_rfc822 *task;
1838
1839 MAILESTD_ASSERT(!msg->ontask);
1840 task = xcalloc(1, sizeof(struct task_rfc822));
1841 /* given task is NOT a member of mailestd.rfc822_tasks */
1842 task->type = MAILESTD_TASK_RFC822_GUESS;
1843 task->msg = msg;
1844 msg->ontask = true;
1845
1846 return (task_worker_add_task(&_this->dbworker, (struct task *)task));
1847 }
1848
1849 /***********************************************************************
1850 * Tasks
1851 ***********************************************************************/
1852 static void
task_worker_init(struct task_worker * _this,struct mailestd * mailestd)1853 task_worker_init(struct task_worker *_this, struct mailestd *mailestd)
1854 {
1855 int pairsock[2];
1856
1857 memset(_this, 0, sizeof(struct task_worker));
1858 TAILQ_INIT(&_this->head);
1859 _thread_mutex_init(&_this->lock, NULL);
1860 _this->mailestd_this = mailestd;
1861 if (socketpair(PF_UNIX, SOCK_SEQPACKET, 0, pairsock) == -1)
1862 err(EX_OSERR, "socketpair()");
1863 if (setnonblock(pairsock[0]) == -1)
1864 err(EX_OSERR, "setnonblock()");
1865 if (setnonblock(pairsock[1]) == -1)
1866 err(EX_OSERR, "setnonblock()");
1867 _this->sock = pairsock[1];
1868 _this->sock_itc = pairsock[0];
1869 }
1870
1871 static void
task_worker_start(struct task_worker * _this)1872 task_worker_start(struct task_worker *_this)
1873 {
1874 _this->thread = _thread_self();
1875
1876 EVENT_SET(&_this->evsock, _this->sock, EV_READ | EV_PERSIST,
1877 task_worker_on_itc_event, _this);
1878 event_add(&_this->evsock, NULL);
1879 }
1880
1881 #ifdef MAILESTD_MT
1882 static void *
task_worker_start0(void * ctx)1883 task_worker_start0(void *ctx)
1884 {
1885 struct task_worker *_this = ctx;
1886
1887 EVENT_INIT();
1888 task_worker_start(_this);
1889 EVENT_LOOP(0);
1890 EVENT_BASE_FREE();
1891 return (NULL);
1892 }
1893 #endif
1894
1895 static void
task_worker_run(struct task_worker * _this)1896 task_worker_run(struct task_worker *_this)
1897 {
1898 #ifdef MAILESTD_MT
1899 _thread_create(&_this->thread, NULL, task_worker_start0, _this);
1900 #endif
1901 }
1902
1903 static void
task_worker_stop(struct task_worker * _this)1904 task_worker_stop(struct task_worker *_this)
1905 {
1906 struct task *tske, *tskt;
1907
1908 MAILESTD_ASSERT(_thread_self() == _this->thread);
1909 if (_this->sock >= 0) {
1910 if (event_initialized(&_this->evsock))
1911 event_del(&_this->evsock);
1912 close(_this->sock);
1913 close(_this->sock_itc);
1914 _this->sock = _this->sock_itc = -1;
1915 }
1916 _thread_mutex_destroy(&_this->lock);
1917 TAILQ_FOREACH_SAFE(tske, &_this->head, queue, tskt) {
1918 free(tske);
1919 }
1920 }
1921
1922 static uint64_t
task_worker_add_task(struct task_worker * _this,struct task * task)1923 task_worker_add_task(struct task_worker *_this, struct task *task)
1924 {
1925 struct task *tske;
1926 uint64_t id;
1927
1928 task->id = mailestd_new_id(_this->mailestd_this);
1929 id = task->id;
1930
1931 _thread_mutex_lock(&_this->lock);
1932 if (task->highprio) {
1933 TAILQ_FOREACH(tske, &_this->head, queue) {
1934 if (!tske->highprio)
1935 break;
1936 }
1937 if (tske != NULL)
1938 TAILQ_INSERT_BEFORE(tske, task, queue);
1939 else
1940 TAILQ_INSERT_HEAD(&_this->head, task, queue);
1941 } else
1942 TAILQ_INSERT_TAIL(&_this->head, task, queue);
1943 _thread_mutex_unlock(&_this->lock);
1944
1945 if (write(_this->sock_itc, "A", 1) < 0) {
1946 if (errno != EAGAIN)
1947 mailestd_log(LOG_WARNING, "%s: write(): %m", __func__);
1948 }
1949
1950 return (id);
1951 }
1952
1953 static void
task_worker_on_itc_event(int fd,short evmask,void * ctx)1954 task_worker_on_itc_event(int fd, short evmask, void *ctx)
1955 {
1956 ssize_t siz;
1957 u_char buf[BUFSIZ];
1958 struct task_worker *_this = ctx;
1959
1960 time(&_this->mailestd_this->curr_time);
1961 if (evmask & EV_READ) {
1962 for (;;) {
1963 siz = read(_this->sock, buf, sizeof(buf));
1964 if (siz <= 0) {
1965 if (siz < 0) {
1966 if (errno == EINTR || errno == EAGAIN)
1967 break;
1968 }
1969 task_worker_stop(_this);
1970 return;
1971 }
1972 }
1973 task_worker_on_proc(_this);
1974 }
1975 }
1976
1977 static void
task_worker_on_proc(struct task_worker * _this)1978 task_worker_on_proc(struct task_worker *_this)
1979 {
1980 struct task *task;
1981 struct task_inform *inf;
1982 struct rfc822 *msg;
1983 bool stop = false;
1984 struct mailestd *mailestd = _this->mailestd_this;
1985 _thread_t thread_this = _thread_self();
1986 struct task_dbworker_context dbctx;
1987 struct mailestc *ce, *ct;
1988 enum MAILESTD_TASK task_type;
1989
1990 memset(&dbctx, 0, sizeof(dbctx));
1991 while (!stop) {
1992 _thread_mutex_lock(&_this->lock);
1993 task = TAILQ_FIRST_ITEM(&_this->head);
1994 if (task != NULL) {
1995 if (_this->suspend &&
1996 !mailestd_is_db_sync_done(mailestd) &&
1997 task->type == MAILESTD_TASK_GATHER)
1998 /*
1999 * gathering before the first db_sync
2000 * requires the database is working.
2001 */
2002 task = NULL;
2003 else if (!_this->suspend || task->highprio)
2004 TAILQ_REMOVE(&_this->head, task, queue);
2005 else
2006 task = NULL;
2007 }
2008 _thread_mutex_unlock(&_this->lock);
2009 if (task == NULL) {
2010 if (!stop && thread_this == mailestd->dbworker.thread &&
2011 !task_worker_on_proc_db(_this, &dbctx, NULL))
2012 continue;
2013 break;
2014 }
2015
2016 task_type = task->type;
2017 switch (task_type) {
2018 case MAILESTD_TASK_RFC822_DRAFT:
2019 msg = ((struct task_rfc822 *)task)->msg;
2020 MAILESTD_ASSERT(msg->draft == NULL);
2021 mailestd_draft(mailestd, msg);
2022 if (msg->draft == NULL)
2023 /* No draft, no parid */
2024 msg->pariddone = true;
2025 else {
2026 msg->pariddone = estdoc_add_parid(msg->draft);
2027 if (mailestd->paridguess && !msg->pariddone) {
2028 if (skip_subject(
2029 est_doc_attr(msg->draft, "@title"))
2030 == NULL)
2031 msg->pariddone = true;
2032 /*
2033 * Otherwise, let msg->pariddone
2034 * keep false to indicate it's a
2035 * target in mailestd_guess_parid().
2036 */
2037 }
2038 }
2039 if (mailestd->paridguess && !msg->pariddone)
2040 mailestd->paridnotdone++;
2041 /*
2042 * This thread can't return the task even if creating
2043 * a draft failed. (since it's not dbworker)
2044 */
2045 mailestd_schedule_putdb(mailestd, task, msg);
2046 task = NULL; /* reused */
2047 break;
2048
2049 case MAILESTD_TASK_RFC822_GUESS:
2050 case MAILESTD_TASK_RFC822_PUTDB:
2051 case MAILESTD_TASK_RFC822_DELDB:
2052 case MAILESTD_TASK_SEARCH:
2053 case MAILESTD_TASK_SMEW:
2054 MAILESTD_ASSERT(thread_this ==
2055 mailestd->dbworker.thread);
2056 task_worker_on_proc_db(_this, &dbctx, task);
2057 if (task_type == MAILESTD_TASK_RFC822_PUTDB)
2058 task = NULL; /* recycled */
2059 break;
2060
2061 case MAILESTD_TASK_SYNCDB:
2062 mailestd_db_sync(mailestd);
2063 break;
2064
2065 case MAILESTD_TASK_GATHER_START:
2066 mailestd_gather_start(mailestd,
2067 (struct task_gather *)task);
2068 break;
2069
2070 case MAILESTD_TASK_GATHER:
2071 mailestd_gather(mailestd, (struct task_gather *)task);
2072 if (!mailestd_is_db_sync_done(mailestd)) {
2073 TAILQ_INSERT_TAIL(&mailestd->gather_pendings,
2074 task, queue);
2075 task = NULL;
2076 }
2077 break;
2078
2079 case MAILESTD_TASK_SUSPEND:
2080 _this->suspend = true;
2081 break;
2082
2083 case MAILESTD_TASK_STOP:
2084 stop = true;
2085 if (thread_this == mailestd->dbworker.thread)
2086 task_worker_on_proc_db(_this, &dbctx, task);
2087 task_worker_stop(_this);
2088 break;
2089
2090 case MAILESTD_TASK_RESUME:
2091 _this->suspend = false;
2092 break;
2093
2094 case MAILESTD_TASK_INFORM:
2095 MAILESTD_ASSERT(thread_this ==
2096 mailestd->mainworker.thread);
2097 inf = (struct task_inform *)task;
2098 TAILQ_FOREACH_SAFE(ce, &mailestd->ctls, queue, ct) {
2099 mailestc_task_inform(ce, inf->src_id,
2100 inf->inform, inf->informsiz);
2101 }
2102 break;
2103
2104 case MAILESTD_TASK_MONITOR_FOLDER:
2105 mailestd_monitor_folder(mailestd,
2106 ((struct task_monitor *)task)->path);
2107 break;
2108
2109 case MAILESTD_TASK_NONE:
2110 break;
2111
2112 default:
2113 abort();
2114 }
2115 if (task)
2116 free(task);
2117 }
2118 }
2119
2120 static bool
task_worker_on_proc_db(struct task_worker * _this,struct task_dbworker_context * ctx,struct task * task)2121 task_worker_on_proc_db(struct task_worker *_this,
2122 struct task_dbworker_context *ctx, struct task *task)
2123 {
2124 struct rfc822 *msg;
2125 enum MAILESTD_TASK task_type;
2126 struct mailestd *mailestd = _this->mailestd_this;
2127 struct task_search *search;
2128
2129 if (task == NULL)
2130 task_type = MAILESTD_TASK_NONE;
2131 else
2132 task_type = task->type;
2133
2134 switch (task_type) {
2135 default:
2136 break;
2137
2138 case MAILESTD_TASK_RFC822_PUTDB:
2139 msg = ((struct task_rfc822 *)task)->msg;
2140 if (mailestd_db_open_wr(mailestd) == NULL)
2141 break;
2142 ctx->puts++;
2143 ctx->resche++;
2144 if (msg->draft != NULL)
2145 mailestd_putdb(mailestd, msg);
2146 mailestd_gather_inform(mailestd, task, NULL);
2147 msg->ontask = false;
2148 TAILQ_INSERT_TAIL(&mailestd->rfc822_tasks, task, queue);
2149 mailestd->rfc822_ntask--;
2150 if (msg->db_id == 0) {
2151 RB_REMOVE(rfc822_tree, &mailestd->root, msg);
2152 rfc822_free(msg);
2153 }
2154 break;
2155
2156 case MAILESTD_TASK_RFC822_DELDB:
2157 msg = ((struct task_rfc822 *)task)->msg;
2158 if (mailestd_db_open_wr(mailestd) == NULL)
2159 break;
2160 ctx->dels++;
2161 mailestd_gather_inform(mailestd, task, NULL);
2162 mailestd_deldb(mailestd, msg);
2163 RB_REMOVE(rfc822_tree, &mailestd->root, msg);
2164 rfc822_free(msg);
2165 break;
2166
2167 case MAILESTD_TASK_RFC822_GUESS:
2168 msg = ((struct task_rfc822 *)task)->msg;
2169 if (mailestd_db_open_wr(mailestd) == NULL)
2170 break;
2171 mailestd_guess(mailestd, msg);
2172 msg->pariddone = true;
2173 msg->ontask = false;
2174 break;
2175
2176 case MAILESTD_TASK_SEARCH:
2177 search = (struct task_search *)task;
2178 if (mailestd_db_open_rd(mailestd) == NULL) {
2179 mailestd_schedule_inform(mailestd, task->id, NULL, 0);
2180 break;
2181 }
2182 mailestd_search(mailestd, task->id, search->str, search->cond,
2183 search->outform);
2184 break;
2185
2186 case MAILESTD_TASK_SMEW:
2187 mailestd_db_smew(mailestd, (struct task_smew *)task);
2188 break;
2189
2190 case MAILESTD_TASK_NONE:
2191 if (ctx->resche)
2192 mailestd_reschedule_draft(mailestd);
2193 if (mailestd->db == NULL || !mailestd->db_wr)
2194 /* Keep the read only db connection */
2195 break;
2196 if (mailestd->paridguess && mailestd->paridnotdone > 0) {
2197 mailestd_guess_parid(mailestd);
2198 mailestd->paridnotdone = 0;
2199 }
2200 if (ctx->puts + ctx->dels > 0 &&
2201 est_db_used_cache_size(mailestd->db) > MAILESTD_DBFLUSHSIZ){
2202 /*
2203 * When we wrote something, flush the DB first. Since
2204 * closing DB takes long time. Flush before close
2205 * to make the other tasks can interrupt.
2206 */
2207 if (debug > 1) {
2208 mailestd_log(LOG_DEBUG, "Flusing DB %d",
2209 est_db_used_cache_size(mailestd->db));
2210 est_db_set_informer(mailestd->db,
2211 mailestd_db_informer, NULL);
2212 }
2213 est_db_flush(mailestd->db, MAILESTD_DBFLUSHSIZ);
2214 if (debug > 1) {
2215 mailestd_log(LOG_DEBUG, "Flusinged DB %d",
2216 est_db_used_cache_size(mailestd->db));
2217 est_db_set_informer(mailestd->db, NULL, NULL);
2218 }
2219 return (false); /* check the other tasks
2220 then call me again */
2221 }
2222 if (!ctx->optimized && ctx->puts + ctx->dels > 800) {
2223 mailestd_log(LOG_DEBUG, "Optimizing DB");
2224 est_db_optimize(mailestd->db,
2225 ESTOPTNOPURGE | ESTOPTNODBOPT);
2226 mailestd_log(LOG_DEBUG, "Optimized DB");
2227 ctx->optimized = true;
2228 return (false);
2229 }
2230 /* Then close the DB */
2231 /* FALLTHROUGH */
2232
2233 case MAILESTD_TASK_STOP:
2234 if (mailestd->db != NULL) {
2235 mailestd_log(LOG_INFO, "Closing DB");
2236 if (debug > 1)
2237 est_db_set_informer(mailestd->db,
2238 mailestd_db_informer, NULL);
2239 mailestd_db_close(mailestd);
2240 mailestd_log(LOG_INFO, "Closed DB (put=%d delete=%d)",
2241 ctx->puts, ctx->dels);
2242 }
2243 break;
2244 }
2245
2246 if (ctx->resche && mailestd->rfc822_ntask <
2247 mailestd->rfc822_task_max / 2) {
2248 mailestd_reschedule_draft(mailestd);
2249 ctx->resche = 0;
2250 }
2251
2252 return (true);
2253 }
2254
2255 /***********************************************************************
2256 * mailestc
2257 ***********************************************************************/
2258 static struct timeval mailestc_timeout = { MAILESTCTL_IDLE_TIMEOUT, 0 };
2259
2260 static void
mailestc_start(struct mailestc * _this,struct mailestd * mailestd,int sock)2261 mailestc_start(struct mailestc *_this, struct mailestd *mailestd, int sock)
2262 {
2263 memset(_this, 0, sizeof(struct mailestc));
2264 _this->sock = sock;
2265 _this->mailestd_this = mailestd;
2266 _this->wbuf = bytebuffer_create(256);
2267 bytebuffer_flip(_this->wbuf);
2268 if (_this->wbuf == NULL)
2269 abort();
2270 EVENT_SET(&_this->evsock, _this->sock, EV_READ | EV_WRITE | EV_TIMEOUT,
2271 mailestc_on_event, _this);
2272 event_add(&_this->evsock, &mailestc_timeout);
2273 TAILQ_INSERT_TAIL(&_this->mailestd_this->ctls, _this, queue);
2274 }
2275
2276 static void
mailestc_stop(struct mailestc * _this)2277 mailestc_stop(struct mailestc *_this)
2278 {
2279 MAILESTD_ASSERT(_this->sock >= 0);
2280 bytebuffer_destroy(_this->wbuf);
2281 event_del(&_this->evsock);
2282 close(_this->sock);
2283 TAILQ_REMOVE(&_this->mailestd_this->ctls, _this, queue);
2284 free(_this);
2285 }
2286
2287 static void
mailestc_on_event(int fd,short evmask,void * ctx)2288 mailestc_on_event(int fd, short evmask, void *ctx)
2289 {
2290 struct mailestc *_this = ctx;
2291 struct mailestd *mailestd = _this->mailestd_this;
2292 short nev;
2293 ssize_t siz;
2294 char msgbuf[MAILESTD_SOCK_MSGSIZ];
2295 struct {
2296 enum MAILESTCTL_CMD command;
2297 char space[8192];
2298 } cmd;
2299 struct mailestctl_smew *smew = (struct mailestctl_smew *)&cmd;
2300 struct mailestctl_update
2301 *update = (struct mailestctl_update *)&cmd;
2302
2303 nev = EV_READ | EV_TIMEOUT; /* next event */
2304 if (evmask & EV_READ) {
2305 if ((siz = read(_this->sock, &cmd, sizeof(cmd))) <= 0) {
2306 if (siz != 0) {
2307 if (errno == EINTR || errno == EAGAIN)
2308 return;
2309 mailestd_log(LOG_ERR, "%s(): read: %m",
2310 __func__);
2311 }
2312 goto on_error;
2313 }
2314 if (siz < sizeof(struct mailestctl)) {
2315 mailestd_log(LOG_ERR, "%s(): received message size "
2316 "is too small", __func__);
2317 goto on_error;
2318 }
2319 switch (cmd.command) {
2320 case MAILESTCTL_CMD_NONE:
2321 break;
2322
2323 case MAILESTCTL_CMD_DEBUGI:
2324 debug++;
2325 mailestd_log(LOG_INFO,
2326 "debug++ requested. debug=%d", debug);
2327 break;
2328
2329 case MAILESTCTL_CMD_DEBUGD:
2330 debug--;
2331 mailestd_log(LOG_INFO,
2332 "--debug requested. debug=%d", debug);
2333 break;
2334
2335 case MAILESTCTL_CMD_SUSPEND:
2336 mailestd_log(LOG_INFO, "suspend requested");
2337 mailestd_schedule_message_all(mailestd,
2338 MAILESTD_TASK_SUSPEND);
2339 break;
2340
2341 case MAILESTCTL_CMD_RESUME:
2342 mailestd_log(LOG_INFO, "resume requested");
2343 mailestd_schedule_message_all(mailestd,
2344 MAILESTD_TASK_RESUME);
2345 break;
2346
2347 case MAILESTCTL_CMD_STOP:
2348 mailestd_log(LOG_INFO, "Stop requested");
2349 mailestd_stop(mailestd);
2350 /* freed _this */
2351 return;
2352
2353 case MAILESTCTL_CMD_SEARCH:
2354 if (siz < sizeof(struct mailestctl_search)) {
2355 mailestd_log(LOG_ERR, "%s(): received message "
2356 "size is too small", __func__);
2357 goto on_error;
2358 }
2359 if (mailestc_cmd_search(_this,
2360 (struct mailestctl_search *)&cmd) != 0)
2361 goto on_error;
2362 break;
2363
2364 case MAILESTCTL_CMD_SMEW:
2365 if (siz < sizeof(struct mailestctl_smew)) {
2366 mailestd_log(LOG_ERR, "%s(): received message "
2367 "size is too small", __func__);
2368 goto on_error;
2369 }
2370 _this->monitoring_cmd = MAILESTCTL_CMD_SMEW;
2371 _this->monitoring_id =
2372 mailestd_schedule_smew(mailestd,
2373 smew->msgid, smew->folder);
2374 if (_this->monitoring_id == 0)
2375 goto on_error;
2376 break;
2377
2378 case MAILESTCTL_CMD_UPDATE:
2379 if (siz < sizeof(struct mailestctl_update)) {
2380 mailestd_log(LOG_ERR, "%s(): received message "
2381 "size is too small", __func__);
2382 goto on_error;
2383 }
2384 _this->monitoring_cmd = MAILESTCTL_CMD_UPDATE;
2385 _this->monitoring_id = mailestd_schedule_gather_start(
2386 mailestd, update->folder);
2387 if (_this->monitoring_id == 0)
2388 goto on_error;
2389 break;
2390 }
2391 }
2392
2393 if (evmask & EV_WRITE || _this->wready) {
2394 _this->wready = true;
2395 if (bytebuffer_remaining(_this->wbuf) > 0) {
2396 siz = MINIMUM(sizeof(msgbuf),
2397 bytebuffer_remaining(_this->wbuf));
2398 bytebuffer_get(_this->wbuf, msgbuf, siz);
2399 siz = write(_this->sock, msgbuf, siz);
2400 if (siz <= 0)
2401 goto on_error;
2402 _this->wready = false;
2403 if (_this->monitoring_stop &&
2404 !bytebuffer_has_remaining(_this->wbuf)) {
2405 mailestc_stop(_this);
2406 return;
2407 }
2408 }
2409 } else if (evmask & EV_TIMEOUT) {
2410 /*
2411 * Remark "else". Skip run the timeout handler since the
2412 * timer is also used to firing an event for writing. See
2413 * mailestc_send_message().
2414 */
2415 if (_this->monitoring_cmd == MAILESTCTL_CMD_NONE)
2416 goto on_error;
2417 }
2418
2419 if (!_this->wready)
2420 nev |= EV_WRITE;
2421 EVENT_SET(&_this->evsock, _this->sock, nev, mailestc_on_event, _this);
2422 event_add(&_this->evsock, &mailestc_timeout);
2423
2424 return;
2425 on_error:
2426 mailestc_stop(_this);
2427 }
2428
2429 static int
mailestc_cmd_search(struct mailestc * _this,struct mailestctl_search * search)2430 mailestc_cmd_search(struct mailestc *_this, struct mailestctl_search *search)
2431 {
2432 struct mailestd *mailestd = _this->mailestd_this;
2433 ESTCOND *cond;
2434 const char *phrase = search->phrase;
2435 int i;
2436 char buf[80];
2437
2438 cond = est_cond_new();
2439 if (phrase[0] != '\0') {
2440 while (*phrase == '\t' || *phrase == ' ')
2441 phrase++;
2442 est_cond_set_phrase(cond, phrase);
2443 strlcpy(buf, phrase, sizeof(buf));
2444 } else
2445 strlcpy(buf, "*", sizeof(buf));
2446 for (i = 0; i < (int)nitems(search->attrs) &&
2447 search->attrs[i][0] != '\0'; i++) {
2448 est_cond_add_attr(cond, search->attrs[i]);
2449 strlcat(buf, ", ", sizeof(buf));
2450 strlcat(buf, search->attrs[i], sizeof(buf));
2451 }
2452 est_cond_set_order(cond, search->order);
2453 if (search->max > 0)
2454 est_cond_set_max(cond, search->max);
2455 _this->monitoring_id = mailestd_schedule_search(mailestd, buf, cond,
2456 search->outform);
2457 if (_this->monitoring_id == 0)
2458 return (-1);
2459 _this->monitoring_cmd = MAILESTCTL_CMD_SEARCH;
2460
2461 return (0);
2462 }
2463
2464 static void
mailestc_send_message(struct mailestc * _this,u_char * msg,size_t msgsiz)2465 mailestc_send_message(struct mailestc *_this, u_char *msg, size_t msgsiz)
2466 {
2467 struct timeval soon = { 0, 0 };
2468
2469 bytebuffer_compact(_this->wbuf);
2470 if (msgsiz > bytebuffer_remaining(_this->wbuf)) {
2471 if (bytebuffer_realloc(_this->wbuf,
2472 bytebuffer_position(_this->wbuf) + msgsiz) != 0)
2473 abort();
2474 }
2475 bytebuffer_put(_this->wbuf, msg, msgsiz);
2476 bytebuffer_flip(_this->wbuf);
2477 event_add(&_this->evsock, &soon); /* fire an I/O event */
2478 }
2479
2480 static void
mailestc_task_inform(struct mailestc * _this,uint64_t task_id,u_char * inform,size_t informsiz)2481 mailestc_task_inform(struct mailestc *_this, uint64_t task_id, u_char *inform,
2482 size_t informsiz)
2483 {
2484 if (_this->monitoring_id != task_id)
2485 return;
2486 switch (_this->monitoring_cmd) {
2487 default:
2488 break;
2489 case MAILESTCTL_CMD_SMEW:
2490 case MAILESTCTL_CMD_SEARCH:
2491 if (informsiz == 0)
2492 mailestc_stop(_this);
2493 else
2494 mailestc_send_message(_this, inform, informsiz);
2495 _this->monitoring_stop = true;
2496 break;
2497 case MAILESTCTL_CMD_UPDATE:
2498 {
2499 struct gather *result = (struct gather *)inform;
2500 bool del_compl, put_compl;
2501 char *msg = NULL, msg0[80];
2502
2503 del_compl = (result->dels_done == result->dels)? true : false;
2504 put_compl = (result->puts_done == result->puts)? true : false;
2505 if (result->errmsg[0] != '\0') {
2506 strlcpy(msg0, result->errmsg, sizeof(msg0));
2507 strlcat(msg0, "...failed\n", sizeof(msg0));
2508 msg = msg0;
2509 } else if (put_compl && del_compl)
2510 msg = "new messages...done\n";
2511 else if (del_compl && result->dels_done > 0)
2512 msg = "old messages...done\n";
2513 if (msg != NULL) {
2514 mailestc_send_message(_this, (u_char *)msg,
2515 strlen(msg));
2516 }
2517 if (result->errmsg[0] != '\0' || (
2518 del_compl && put_compl &&
2519 result->folders == result->folders_done))
2520 _this->monitoring_stop = true;
2521 }
2522 break;
2523 }
2524 }
2525
2526 /***********************************************************************
2527 * Monitoring
2528 ***********************************************************************/
2529 static void
mailestd_monitor_init(struct mailestd * _this)2530 mailestd_monitor_init(struct mailestd *_this)
2531 {
2532 #ifdef MONITOR_KQUEUE
2533 if ((_this->monitor_kq = kqueue()) == -1)
2534 err(EX_OSERR, "kqueue");
2535 _this->monitor_kev = xcalloc(1, sizeof(struct kevent));
2536 _this->monitor_kev_siz = 1;
2537 #endif
2538 #ifdef MONITOR_INOTIFY
2539 _this->monitor_in = inotify_init();
2540 #endif
2541 RB_INIT(&_this->monitors);
2542 }
2543
2544 #ifdef MAILESTD_MT
2545 static void *
mailestd_monitor_start0(void * ctx)2546 mailestd_monitor_start0(void *ctx)
2547 {
2548 mailestd_monitor_start(ctx);
2549 return (NULL);
2550 }
2551 #endif
2552
2553 static void
mailestd_monitor_run(struct mailestd * _this)2554 mailestd_monitor_run(struct mailestd *_this)
2555 {
2556 #ifdef MAILESTD_MT
2557 _thread_create(&_this->monitorworker.thread, NULL,
2558 mailestd_monitor_start0, _this);
2559 #endif
2560 }
2561
2562 static void mailestd_monitor_stop(struct mailestd *);
2563 static void
mailestd_monitor_start(struct mailestd * _this)2564 mailestd_monitor_start(struct mailestd *_this)
2565 {
2566 MAILESTD_ASSERT(_this->monitor);
2567
2568 #ifdef MONITOR_INOTIFY
2569 EVENT_INIT();
2570 task_worker_start(&_this->monitorworker);
2571 EVENT_SET(&_this->monitor_inev, _this->monitor_in, EV_READ | EV_PERSIST,
2572 mailestd_monitor_on_inotify, _this);
2573 event_add(&_this->monitor_inev, NULL);
2574 EVENT_SET(&_this->monitor_intimerev, -1, EV_TIMEOUT,
2575 mailestd_monitor_on_inotify, _this);
2576 for (;;) {
2577 if (EVENT_LOOP(EVLOOP_ONCE) != 0)
2578 break;
2579 if (_this->monitorworker.sock < 0)
2580 mailestd_monitor_stop(_this);
2581 }
2582 EVENT_BASE_FREE();
2583 #endif
2584 #ifdef MONITOR_KQUEUE
2585 /* libevent can't handle kquque inode events, so treat them directly */
2586 {
2587 int i, ret, nkev, sock;
2588 struct kevent kev[64];
2589 struct folder *flde;
2590 struct timespec *ts, ts0;
2591
2592 _this->monitorworker.thread = _thread_self();
2593 for (;;) {
2594 ts = NULL;
2595 nkev = 0;
2596 if ((sock = _this->monitorworker.sock) < 0)
2597 mailestd_monitor_stop(_this);
2598 else {
2599 EV_SET(&_this->monitor_kev[nkev], sock,
2600 EVFILT_READ, EV_ADD, 0, 0, NULL);
2601 nkev++;
2602 if (mailestd_monitor_schedule(_this, &ts0) > 0)
2603 ts = &ts0;
2604 }
2605
2606 RB_FOREACH(flde, folder_tree, &_this->monitors) {
2607 if (flde->fd >= 0) {
2608 if (_this->monitor_kev_siz <= nkev) {
2609 _this->monitor_kev_siz++;
2610 _this->monitor_kev = xreallocarray(
2611 _this->monitor_kev,
2612 _this->monitor_kev_siz,
2613 sizeof(struct kevent));
2614 }
2615 EV_SET(&_this->monitor_kev[nkev], flde->fd,
2616 EVFILT_VNODE, EV_ADD | EV_ONESHOT,
2617 NOTE_WRITE | NOTE_DELETE |
2618 NOTE_RENAME | NOTE_REVOKE, 0, flde);
2619 nkev++;
2620 }
2621 }
2622 if (nkev == 0)
2623 break;
2624 memset(kev, 0, sizeof(kev));
2625 if ((ret = kevent(_this->monitor_kq, _this->monitor_kev, nkev,
2626 kev, nitems(kev), ts)) == -1) {
2627 if (errno == EINTR || errno == EAGAIN)
2628 continue;
2629 mailestd_log(LOG_ERR, "%s: kevent: %m", __func__);
2630 break;
2631 }
2632 for (i = 0; i < ret; i++) {
2633 if (kev[i].ident == (unsigned)sock)
2634 task_worker_on_itc_event(kev[i].ident,
2635 EV_READ, &_this->monitorworker);
2636 else if (kev[i].udata != NULL) {
2637 flde = kev[i].udata;
2638 if ((kev[i].fflags & (NOTE_DELETE |
2639 NOTE_RENAME | NOTE_REVOKE)) != 0) {
2640 close(flde->fd);
2641 flde->fd = -1;
2642 }
2643 clock_gettime(CLOCK_MONOTONIC, &flde->mtime);
2644 }
2645 }
2646 }
2647 return;
2648 }
2649 #endif
2650 }
2651
2652 #ifdef MONITOR_INOTIFY
2653 static void
mailestd_monitor_on_inotify(int fd,short evmask,void * ctx)2654 mailestd_monitor_on_inotify(int fd, short evmask, void *ctx)
2655 {
2656 struct inotify_event *inev;
2657 u_char buf[1024];
2658 ssize_t siz;
2659 struct mailestd *_this = ctx;
2660 struct folder *flde;
2661 struct timespec *ts = NULL, ts0;
2662 struct timeval tv;
2663
2664 if (evmask & EV_READ) {
2665 if ((siz = read(_this->monitor_in, buf, sizeof(buf))) <= 0){
2666 if (siz != 0) {
2667 if (errno == EAGAIN || errno == EINTR)
2668 return;
2669 mailestd_log(LOG_ERR, "%s: read: %m", __func__);
2670 }
2671 mailestd_monitor_stop(_this);
2672 }
2673 inev = (struct inotify_event *)buf;
2674 RB_FOREACH(flde, folder_tree, &_this->monitors) {
2675 if (flde->fd == inev->wd)
2676 break;
2677 }
2678 MAILESTD_ASSERT(flde != NULL);
2679 if ((inev->mask & (IN_DELETE_SELF | IN_MOVE_SELF)) != 0) {
2680 inotify_rm_watch(_this->monitor_in, flde->fd);
2681 close(flde->fd);
2682 flde->fd = -1;
2683 }
2684 clock_gettime(CLOCK_MONOTONIC, &flde->mtime);
2685 }
2686 if (mailestd_monitor_schedule(_this, &ts0) > 0)
2687 ts = &ts0;
2688 if (ts != NULL) {
2689 TIMESPEC_TO_TIMEVAL(&tv, ts);
2690 event_add(&_this->monitor_intimerev, &tv);
2691 }
2692 }
2693 #endif
2694
2695 static void
mailestd_monitor_stop(struct mailestd * _this)2696 mailestd_monitor_stop(struct mailestd *_this)
2697 {
2698 struct folder *flde, *fldt;
2699
2700 RB_FOREACH_SAFE(flde, folder_tree, &_this->monitors, fldt) {
2701 RB_REMOVE(folder_tree, &_this->monitors, flde);
2702 #ifdef MONITOR_INOTIFY
2703 inotify_rm_watch(_this->monitor_in, flde->fd);
2704 #endif
2705 if (flde->fd >= 0)
2706 close(flde->fd);
2707 flde->fd = -1;
2708 }
2709 #ifdef MONITOR_INOTIFY
2710 if (event_pending(&_this->monitor_intimerev, EV_TIMEOUT, NULL))
2711 event_del(&_this->monitor_intimerev);
2712 close(_this->monitor_in);
2713 event_del(&_this->monitor_inev);
2714 #endif
2715 }
2716
2717 static void
mailestd_monitor_fini(struct mailestd * _this)2718 mailestd_monitor_fini(struct mailestd *_this)
2719 {
2720 #ifdef MONITOR_KQUEUE
2721 free(_this->monitor_kev);
2722 #endif
2723 }
2724
2725 static void
mailestd_monitor_folder(struct mailestd * _this,const char * dirpath)2726 mailestd_monitor_folder(struct mailestd *_this, const char *dirpath)
2727 {
2728 int fd = -1;
2729 char buf[PATH_MAX];
2730 struct folder *fld, fld0;
2731
2732 MAILESTD_ASSERT(_thread_self() == _this->monitorworker.thread);
2733 fld0.path = (char *)dirpath;
2734 if ((fld = RB_FIND(folder_tree, &_this->monitors, &fld0)) != NULL)
2735 return;
2736 #ifdef MONITOR_KQUEUE
2737 if ((fd = open(dirpath, O_RDONLY)) < 0)
2738 return;
2739 #endif
2740 #ifdef MONITOR_INOTIFY
2741 if ((fd = inotify_add_watch(_this->monitor_in,
2742 dirpath, IN_CREATE | IN_DELETE | IN_DELETE_SELF |
2743 IN_MOVED_FROM | IN_MOVED_TO | IN_MOVE_SELF)) == -1) {
2744 mailestd_log(LOG_ERR, "%s() inotify_add_watch: %m", __func__);
2745 }
2746 #endif
2747 fld = xcalloc(1, sizeof(struct folder));
2748 fld->fd = fd;
2749 fld->path = xstrdup(dirpath);
2750 RB_INSERT(folder_tree, &_this->monitors, fld);
2751 mailestd_log(LOG_DEBUG, "Start monitoring %s",
2752 mailestd_folder_name(_this, dirpath, buf, sizeof(buf)));
2753 }
2754
2755 static void
mailestd_monitor_maildir_changed(struct mailestd * _this)2756 mailestd_monitor_maildir_changed(struct mailestd *_this)
2757 {
2758 DIR *dp;
2759 struct dirent *de;
2760 char path[PATH_MAX];
2761 struct folder *fld, fld0;
2762
2763 strlcpy(path, _this->maildir, sizeof(path));
2764 path[_this->lmaildir] = '/';
2765 path[_this->lmaildir] = '\0';
2766
2767 if ((dp = opendir(_this->maildir)) == NULL)
2768 return;
2769 while ((de = readdir(dp)) != NULL) {
2770 if (de->d_type != DT_DIR || strcmp(de->d_name, ".") == 0 ||
2771 strcmp(de->d_name, "..") == 0)
2772 continue;
2773 if (!mailestd_folder_match(_this, de->d_name))
2774 continue;
2775 strlcpy(path + _this->lmaildir + 1, de->d_name,
2776 sizeof(path) - _this->lmaildir - 1);
2777 fld0.path = path;
2778 if ((fld = RB_FIND(folder_tree, &_this->monitors, &fld0))
2779 != NULL)
2780 continue;
2781 /* new folder */
2782 mailestd_monitor_folder(_this, path);
2783 fld = RB_FIND(folder_tree, &_this->monitors, &fld0);
2784 MAILESTD_ASSERT(fld != NULL);
2785 clock_gettime(CLOCK_MONOTONIC, &fld->mtime);
2786 }
2787 closedir(dp);
2788 }
2789
2790 static int
mailestd_monitor_schedule(struct mailestd * _this,struct timespec * wait)2791 mailestd_monitor_schedule(struct mailestd *_this, struct timespec *wait)
2792 {
2793 int pends;
2794 struct timespec currtime, diffts, maxts;
2795 struct folder *dir0, *dir1, *maildir = NULL;
2796 struct dirpend {
2797 struct folder *dir;
2798 TAILQ_ENTRY(dirpend) queue;
2799 } *pnde, *pndt;
2800 TAILQ_HEAD(, dirpend) pend;
2801 char buf[PATH_MAX];
2802
2803 TAILQ_INIT(&pend);
2804 RB_FOREACH(dir0, folder_tree, &_this->monitors) {
2805 if (dir0->mtime.tv_sec == 0 && dir0->mtime.tv_nsec == 0)
2806 continue;
2807 if (strcmp(dir0->path, _this->maildir) == 0) {
2808 maildir = dir0;
2809 continue;
2810 }
2811 /*
2812 * pending entry
2813 */
2814 TAILQ_FOREACH_SAFE(pnde, &pend, queue, pndt) {
2815 dir1 = pnde->dir;
2816 if (is_parent_dir(dir0->path, dir1->path)) {
2817 if (timespeccmp(&dir0->mtime, &dir1->mtime, <))
2818 dir0->mtime = dir1->mtime;
2819 timespecclear(&dir1->mtime);
2820 TAILQ_REMOVE(&pend, pnde, queue);
2821 }
2822 if (is_parent_dir(dir1->path, dir0->path)) {
2823 if (timespeccmp(&dir1->mtime, &dir0->mtime, <))
2824 dir1->mtime = dir0->mtime;
2825 timespecclear(&dir0->mtime);
2826 }
2827 }
2828 if (dir0->mtime.tv_sec != 0) {
2829 pnde = xcalloc(1, sizeof(struct dirpend));
2830 pnde->dir = dir0;
2831 TAILQ_INSERT_TAIL(&pend, pnde, queue);
2832 }
2833 }
2834 if (maildir != NULL) {
2835 pnde = xcalloc(1, sizeof(struct dirpend));
2836 pnde->dir = maildir;
2837 TAILQ_INSERT_TAIL(&pend, pnde, queue);
2838 }
2839
2840 if (TAILQ_EMPTY(&pend))
2841 return (0);
2842
2843 pends = 0;
2844 timespecclear(&maxts);
2845 clock_gettime(CLOCK_MONOTONIC, &currtime);
2846 TAILQ_FOREACH_SAFE(pnde, &pend, queue, pndt) {
2847 dir0 = pnde->dir;
2848 free(pnde);
2849 timespecsub(&currtime, &dir0->mtime, &diffts);
2850 if (timespeccmp(&diffts, &_this->monitor_delay, >=)) {
2851 MAILESTD_DBG((LOG_DEBUG, "FIRE %lld.%09lld %s",
2852 (long long)diffts.tv_sec, (long long)
2853 diffts.tv_nsec, dir0->path));
2854 timespecclear(&dir0->mtime);
2855 if (strcmp(dir0->path, _this->maildir) == 0) {
2856 mailestd_monitor_maildir_changed(_this);
2857 /* may scheduled again */
2858 pends++;
2859 } else {
2860 mailestd_log(LOG_INFO, "Gathering %s by "
2861 "monitor", mailestd_folder_name(_this,
2862 dir0->path, buf, sizeof(buf)));
2863 mailestd_schedule_gather_start(_this,
2864 dir0->path);
2865 }
2866 if (dir0->fd <= 0) {
2867 mailestd_log(LOG_DEBUG,
2868 "Stop monitoring %s", dir0->path);
2869 RB_REMOVE(folder_tree, &_this->monitors, dir0);
2870 folder_free(dir0);
2871 }
2872 } else if (timespeccmp(&diffts, &maxts, >)) {
2873 MAILESTD_DBG((LOG_DEBUG, "PEND %lld.%09lld %s",
2874 (long long)diffts.tv_sec,
2875 (long long)diffts.tv_nsec, dir0->path));
2876 maxts = diffts;
2877 pends++;
2878 }
2879 }
2880 if (pends > 0)
2881 timespecsub(&_this->monitor_delay, &maxts, wait);
2882
2883 return (pends);
2884 }
2885
2886 /***********************************************************************
2887 * log
2888 ***********************************************************************/
2889 static _thread_spinlock_t mailestd_log_spin;
2890 static FILE * volatile mailestd_log_fp = NULL;
2891 static int volatile mailestd_log_initialized = 0;
2892
2893 static void
mailestd_log_init(void)2894 mailestd_log_init(void)
2895 {
2896 _thread_spin_init(&mailestd_log_spin, 0);
2897 mailestd_log_initialized = 1;
2898 }
2899
2900 static void
mailestd_log_fini(void)2901 mailestd_log_fini(void)
2902 {
2903 if (!foreground)
2904 fclose(mailestd_log_fp);
2905 _thread_spin_destroy(&mailestd_log_spin);
2906 }
2907
2908 void
mailestd_log(int priority,const char * message,...)2909 mailestd_log(int priority, const char *message, ...)
2910 {
2911 va_list ap;
2912
2913 if (debug < 1 && LOG_PRI(priority) >= LOG_DEBUG)
2914 return;
2915 va_start(ap, message);
2916 mailestd_vlog(priority, message, ap);
2917 va_end(ap);
2918 }
2919
2920 void
mailestd_vlog(int priority,const char * message,va_list args)2921 mailestd_vlog(int priority, const char *message, va_list args)
2922 {
2923 FILE *fp = stderr;
2924 u_int i;
2925 int fmtoff = 0, state = 0, msglen, saved_errno;
2926 char fmt[1024];
2927 struct tm *lt;
2928 time_t now;
2929 static const char *prio_name_idx[64];
2930 static struct {
2931 int prio;
2932 const char *name;
2933 } prio_names[] = {
2934 #define VAL_NAME(x) { (x), #x }
2935 VAL_NAME(LOG_EMERG),
2936 VAL_NAME(LOG_ALERT),
2937 VAL_NAME(LOG_CRIT),
2938 VAL_NAME(LOG_ERR),
2939 VAL_NAME(LOG_WARNING),
2940 VAL_NAME(LOG_NOTICE),
2941 VAL_NAME(LOG_INFO),
2942 VAL_NAME(LOG_DEBUG)
2943 #undef VAL_NAME
2944 };
2945
2946 if (!foreground && mailestd_log_fp != NULL)
2947 fp = mailestd_log_fp;
2948
2949 if (prio_name_idx[LOG_EMERG] == NULL) {
2950 for (i = 0; i < nitems(prio_names); i++)
2951 prio_name_idx[prio_names[i].prio] =
2952 &prio_names[i].name[4];
2953 }
2954
2955 time(&now);
2956 lt = localtime(&now);
2957
2958 msglen = strlen(message);
2959 for (i = 0; i < msglen; i++) {
2960 /* 2 chars in this block and 2 chars after this block */
2961 if (sizeof(fmt) - fmtoff < 4)
2962 break;
2963 switch (state) {
2964 case 0:
2965 switch (message[i]) {
2966 case '%':
2967 state = 1;
2968 goto copy_loop;
2969 }
2970 break;
2971 case 1:
2972 switch (message[i]) {
2973 default:
2974 case '%':
2975 fmt[fmtoff++] = '%';
2976 state = 0;
2977 break;
2978 case 'm':
2979 saved_errno = errno;
2980 /* -1 is to reserve '\n' */
2981 if (strerror_r(saved_errno, fmt + fmtoff,
2982 sizeof(fmt) - fmtoff - 1) == 0)
2983 fmtoff = strlen(fmt);
2984 errno = saved_errno;
2985 state = 0;
2986 goto copy_loop;
2987 }
2988 }
2989 fmt[fmtoff++] = message[i];
2990 copy_loop:
2991 continue;
2992 }
2993 fmt[fmtoff++] = '\n';
2994 fmt[fmtoff] = '\0';
2995
2996 if (!mailestd_log_initialized) {
2997 extern char *__progname;
2998 fputs(__progname, fp);
2999 fputs(": ", fp);
3000 vfprintf(fp, fmt, args);
3001 fflush(fp);
3002 return;
3003 }
3004 _thread_spin_lock(&mailestd_log_spin);
3005 fprintf(fp, "%04d-%02d-%02d %02d:%02d:%02d:%s: ",
3006 lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
3007 lt->tm_hour, lt->tm_min, lt->tm_sec,
3008 prio_name_idx[LOG_PRI(priority)]);
3009 vfprintf(fp, fmt, args);
3010 fflush(fp);
3011 _thread_spin_unlock(&mailestd_log_spin);
3012 fsync(fileno(fp));
3013 }
3014
3015 static void
mailestd_log_rotation(const char * logfn,int siz,int max)3016 mailestd_log_rotation(const char *logfn, int siz, int max)
3017 {
3018 int i;
3019 struct stat st;
3020 char *fn = NULL, *ofn = NULL, fn0[PATH_MAX], fn1[PATH_MAX];
3021 bool turnover = false;
3022 mode_t oumask;
3023
3024 if (foreground)
3025 return;
3026 if (siz != 0 && stat(logfn, &st) != -1 && st.st_size >= (ssize_t)siz) {
3027 for (i = max - 1; i >= 0; i--) {
3028 fn = (ofn != fn0)? fn0 : fn1;
3029 if (i == 0)
3030 strlcpy(fn, logfn, PATH_MAX);
3031 else
3032 snprintf(fn, PATH_MAX, "%s.%d", logfn, i - 1);
3033 if (i == max - 1)
3034 unlink(fn);
3035 else
3036 rename(fn, ofn);
3037 ofn = fn;
3038 }
3039 turnover = true;
3040 }
3041 _thread_spin_lock(&mailestd_log_spin);
3042 if (mailestd_log_fp != NULL)
3043 fclose(mailestd_log_fp);
3044 oumask = umask(077);
3045 mailestd_log_fp = fopen(logfn, "a");
3046 umask(oumask);
3047 _thread_spin_unlock(&mailestd_log_spin);
3048 if (turnover)
3049 mailestd_log(LOG_INFO, "logfile turned over");
3050 }
3051
3052 /***********************************************************************
3053 * Miscellaneous functions
3054 ***********************************************************************/
3055 static int
rfc822_compar(struct rfc822 * a,struct rfc822 * b)3056 rfc822_compar(struct rfc822 *a, struct rfc822 *b)
3057 {
3058 return strcmp(a->path, b->path);
3059 }
3060
3061 static void
rfc822_free(struct rfc822 * msg)3062 rfc822_free(struct rfc822 *msg)
3063 {
3064 free(msg->path);
3065 if (msg->draft != NULL)
3066 est_doc_delete(msg->draft);
3067 free(msg);
3068 }
3069
3070 static int
setnonblock(int sock)3071 setnonblock(int sock)
3072 {
3073 int flags;
3074
3075 if ((flags = fcntl(sock, F_GETFL)) == -1 ||
3076 (flags = fcntl(sock, F_SETFL, flags | O_NONBLOCK)) == -1)
3077 return (-1);
3078
3079 return (0);
3080 }
3081
3082 static void *
xcalloc(size_t nmemb,size_t size)3083 xcalloc(size_t nmemb, size_t size)
3084 {
3085 void *ptr;
3086
3087 ptr = calloc(nmemb, size);
3088 if (ptr == NULL) {
3089 mailestd_log(LOG_CRIT, "calloc: %m");
3090 abort();
3091 }
3092 return (ptr);
3093 }
3094
3095 static char *
xstrdup(const char * str)3096 xstrdup(const char *str)
3097 {
3098 char *ret;
3099
3100 ret = strdup(str);
3101 if (ret == NULL) {
3102 mailestd_log(LOG_CRIT, "strdup: %m");
3103 abort();
3104 }
3105
3106 return (ret);
3107 }
3108
3109 static void *
xreallocarray(void * ptr,size_t nmemb,size_t size)3110 xreallocarray(void *ptr, size_t nmemb, size_t size)
3111 {
3112 void *nptr;
3113
3114 nptr = reallocarray(ptr, nmemb, size);
3115 if (nptr == NULL) {
3116 mailestd_log(LOG_CRIT, "calloc: %m");
3117 abort();
3118 }
3119 return (nptr);
3120 }
3121
3122 static int
unlimit_data(void)3123 unlimit_data(void)
3124 {
3125 rlim_t olim;
3126 struct rlimit rl;
3127
3128 if (getrlimit(RLIMIT_DATA, &rl) == -1)
3129 return (-1);
3130
3131 olim = rl.rlim_cur;
3132 rl.rlim_cur = rl.rlim_max;
3133 if (setrlimit(RLIMIT_DATA, &rl) == -1)
3134 return (-1);
3135 if (getrlimit(RLIMIT_DATA, &rl) == -1)
3136 return (-1);
3137
3138 mailestd_log(LOG_DEBUG, "Unlimited datasize %dMB -> %dMB",
3139 (int)(olim / 1024 / 1024), (int)(rl.rlim_cur / 1024 / 1024));
3140
3141 return (0);
3142 }
3143
3144 static int
unlimit_nofile(void)3145 unlimit_nofile(void)
3146 {
3147 rlim_t olim;
3148 struct rlimit rl;
3149
3150 if (getrlimit(RLIMIT_NOFILE, &rl) == -1)
3151 return (-1);
3152
3153 olim = rl.rlim_cur;
3154 rl.rlim_cur = rl.rlim_max;
3155 if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
3156 return (-1);
3157 if (getrlimit(RLIMIT_NOFILE, &rl) == -1)
3158 return (-1);
3159
3160 mailestd_log(LOG_DEBUG, "Unlimited nofile %d -> %d",
3161 (int)olim, (int)rl.rlim_cur);
3162
3163 return (0);
3164 }
3165
3166 static int
folder_compar(struct folder * a,struct folder * b)3167 folder_compar(struct folder *a, struct folder *b)
3168 {
3169 return strcmp(a->path, b->path);
3170 }
3171
3172 static void
folder_free(struct folder * dir)3173 folder_free(struct folder *dir)
3174 {
3175 free(dir->path);
3176 free(dir);
3177 }
3178
3179 static bool
estdoc_add_parid(ESTDOC * doc)3180 estdoc_add_parid(ESTDOC *doc)
3181 {
3182 int cinrp = 0;
3183 const char *inrp0, *refs0;
3184 char *t, *sp, *inrp = NULL, *refs = NULL, *parid = NULL;
3185
3186 /*
3187 * In cmew:
3188 * > (1) The In-Reply-To contains one ID, use it.
3189 * > (2) The References contains one or more IDs, use the last one.
3190 * > (3) The In-Reply-To contains two or more IDs, use the first one.
3191 */
3192 if ((inrp0 = est_doc_attr(doc, "in-reply-to")) != NULL) {
3193 inrp = xstrdup(inrp0);
3194 sp = inrp;
3195 while ((t = strsep(&sp, " \t")) != NULL) {
3196 if (valid_msgid(t)) {
3197 if (cinrp == 0)
3198 parid = t;
3199 cinrp++;
3200 }
3201 }
3202 }
3203 if (cinrp != 1) {
3204 if ((refs0 = est_doc_attr(doc, "references")) != NULL) {
3205 refs = xstrdup(refs0);
3206 sp = refs;
3207 while ((t = strsep(&sp, " \t")) != NULL) {
3208 if (valid_msgid(t))
3209 parid = t;
3210 }
3211 }
3212 }
3213 if (parid != NULL)
3214 est_doc_add_attr(doc, ATTR_PARID, parid);
3215
3216 free(inrp);
3217 free(refs);
3218
3219 return ((parid != NULL)? true : false);
3220 }
3221
3222 /* -a-zA-Z0-9!#$%&\'*+/=?^_`{}|~.@ is valid chars for Message-Id */
3223 static int msgid_chars[] = {
3224 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3225 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3226 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, /* !"#$%&'()*+,-./ */
3227 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, /* 0123456789:;<=>? */
3228 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* @ABCDEFGHIJKLMNO */
3229 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, /* PQRSTUVWXYZ[\]^_ */
3230 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* `abcdefghijklmno */
3231 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 /* pqrstuvwxyz{|}~ */
3232 };
3233
3234 static bool
valid_msgid(const char * str)3235 valid_msgid(const char *str)
3236 {
3237 if (*(str++) != '<')
3238 return (false);
3239
3240 for (;*str != '\0' && *str != '>'; str++) {
3241 if ((unsigned)(*str) >= 128 || msgid_chars[(int)*str] == 0)
3242 break;
3243 }
3244
3245 return ((*str == '>')? true : false);
3246 }
3247
3248 static bool
is_parent_dir(const char * dirp,const char * dir)3249 is_parent_dir(const char *dirp, const char *dir)
3250 {
3251 int ldirp;
3252
3253 ldirp = strlen(dirp);
3254 return ((strncmp(dirp, dir, ldirp) == 0 && dir[ldirp] == '/')
3255 ? true : false);
3256 }
3257
3258 static const char *
skip_subject(const char * subj)3259 skip_subject(const char *subj)
3260 {
3261 if (subj == NULL)
3262 return (NULL);
3263 if ((subj[0] != 'r' && subj[0] != 'R') ||
3264 (subj[1] != 'e' && subj[1] != 'E'))
3265 return NULL;
3266 subj += 2;
3267 /* Re^2: Re*2: */
3268 while (*subj == '*' || *subj == '^')
3269 subj++;
3270 while (isdigit(*subj))
3271 subj++;
3272 if (*subj != ':')
3273 return (NULL);
3274 subj++;
3275 while (isspace(*subj))
3276 subj++;
3277 return (subj);
3278 }
3279
3280 static const char *
uri2normalpath(struct mailestd * _this,const char * uri)3281 uri2normalpath(struct mailestd *_this, const char *uri)
3282 {
3283 if (uri == NULL)
3284 return (NULL);
3285 if (is_parent_dir(_this->maildir, URI2PATH(uri)))
3286 return (uri + _this->lmaildir + 8);
3287 else
3288 return (URI2PATH(uri));
3289 }
3290
3291 RB_GENERATE_STATIC(rfc822_tree, rfc822, tree, rfc822_compar);
3292 RB_GENERATE_STATIC(folder_tree, folder, tree, folder_compar);
3293