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