1 /*
2  * Copyright (c) 2015 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 #include <sys/socket.h>
20 #include <sys/un.h>
21 
22 #include <err.h>
23 #include <errno.h>
24 #include <iconv.h>
25 #include <limits.h>
26 #include <signal.h>
27 #include <stdbool.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sysexits.h>
32 #include <unistd.h>
33 
34 #include "mailestd.h"
35 #include "parser.h"
36 
37 #ifndef	nitems
38 #define nitems(_n)	(sizeof((_n)) / sizeof((_n)[0]))
39 #endif
40 
41 static int		 mailestc_sock = -1;
42 static const char	*mailestc_path = NULL;
43 
44 static void		 mailestc_connect(void);
45 static bool		 mailestc_check_connection(void);
46 size_t			 ic_strlcpy(char *, const char *, size_t, const char *);
47 static void		 run_daemon(const char *, char *[]);
48 static void		 stop_daemon(void);
49 
50 static void
usage(void)51 usage(void)
52 {
53 	extern char *__progname;
54 
55 	fprintf(stderr,
56 	    "usage: %s [-h] [-s socket] [-S suffix] [-m maildir] command "
57 	    "[args...]\n", __progname);
58 }
59 
60 int
mailestctl_main(int argc,char * argv[])61 mailestctl_main(int argc, char *argv[])
62 {
63 	int			 i, ch, sz, cmdc = 0;
64 	struct mailestctl	 ctl;
65 	struct parse_result	*result;
66 	const char		*home, *cmd;
67 	char			*cmdv[64], msgbuf[MAILESTD_SOCK_MSGSIZ],
68 				 path0[PATH_MAX], *maildir = NULL;
69 	struct mailestctl_search search;
70 	struct mailestctl_smew	 smew;
71 	struct mailestctl_update update;
72 
73 	cmd = argv[0];
74 	cmdv[cmdc++] = "mailestd";
75 
76 	if ((home = getenv("HOME")) == NULL)
77 		errx(EX_OSERR,
78 		    "HOME environment variable is not set");
79 
80 	while ((ch = getopt(argc, argv, "+m:s:S:h")) != -1)
81 		switch (ch) {
82 		case 'm':
83 			maildir = optarg;
84 			break;
85 
86 		case 's':
87 			mailestc_path = optarg;
88 			break;
89 
90 		case 'S':
91 			cmdv[cmdc++] = "-S";
92 			cmdv[cmdc++] = optarg;
93 			break;
94 
95 		case 'h':
96 			usage();
97 			exit(EX_USAGE);
98 			break;
99 
100 		default:
101 			exit(EX_USAGE);
102 			break;
103 		}
104 
105 	if (maildir)
106 		cmdv[cmdc++] = maildir;
107 	cmdv[cmdc++] = NULL;
108 
109 	argc -= optind;
110 	argv += optind;
111 
112 	signal(SIGPIPE, SIG_IGN);
113 
114 	if (mailestc_path == NULL) {
115 		if (maildir == NULL) {
116 			strlcpy(path0, home, sizeof(path0));
117 			strlcat(path0, "/" MAILESTD_MAIL_DIR, sizeof(path0));
118 		} else
119 			strlcpy(path0, maildir, sizeof(path0));
120 		strlcat(path0, "/" MAILESTD_SOCK_PATH, sizeof(path0));
121 		mailestc_path = path0;
122 	}
123 
124 	result = parse(argc, argv);
125 	if (result == NULL)
126 		exit(EX_USAGE);
127 
128 	switch (result->action) {
129 	case RESTART:
130 		stop_daemon();
131 		run_daemon(cmd, cmdv);
132 		break;
133 
134 	case START:
135 		if (mailestc_check_connection())
136 			errx(1, "mailestd is already running?");
137 		run_daemon(cmd, cmdv);
138 		break;
139 
140 	case STOP:
141 		ctl.command = MAILESTCTL_CMD_STOP;
142 do_common:
143 		if (!mailestc_check_connection()) {
144 			warnx("could not connect to mailestd.  not running?");
145 			break;
146 		}
147 		if (write(mailestc_sock, &ctl, sizeof(ctl)) < 0)
148 			err(1, "write");
149 		break;
150 
151 	case DEBUGI:
152 		ctl.command = MAILESTCTL_CMD_DEBUGI;
153 		goto do_common;
154 
155 	case DEBUGD:
156 		ctl.command = MAILESTCTL_CMD_DEBUGD;
157 		goto do_common;
158 
159 	case SUSPEND:
160 		ctl.command = MAILESTCTL_CMD_SUSPEND;
161 		goto do_common;
162 
163 	case RESUME:
164 		ctl.command = MAILESTCTL_CMD_RESUME;
165 		goto do_common;
166 
167 	case UPDATE:
168 		run_daemon(cmd, cmdv);
169 		memset(&update, 0, sizeof(update));
170 		update.command = MAILESTCTL_CMD_UPDATE;
171 		if (result->folder != NULL)
172 			strlcpy(update.folder, result->folder,
173 			    sizeof(update.folder));
174 		if (write(mailestc_sock, &update, sizeof(update)) < 0)
175 			err(1, "write");
176 		goto wait_resp;
177 		break;
178 
179 	case CSEARCH:
180 		run_daemon(cmd, cmdv);
181 		memset(&search, 0, sizeof(search));
182 		search.command = MAILESTCTL_CMD_SEARCH;
183 		search.outform = MAILESTCTL_OUTFORM_COMPAT_VU;
184 		if (result->search.max)
185 			search.max = result->search.max;
186 		else
187 			search.max = 10;
188 		if ((result->search.flags & SEARCH_FLAG_VU) == 0)
189 			errx(EX_USAGE, "-vu must be always specified.  "
190 			    "Since any other is not implemented yet.");
191 		if (result->search.attrs != NULL) {
192 			for (i = 0; result->search.attrs[i] != NULL; i++) {
193 				if (i >= (int)nitems(search.attrs))
194 					errx(EX_USAGE, "Too many -attr.  "
195 					    "It's limited %d",
196 					    (int)nitems(search.attrs));
197 				if (ic_strlcpy(
198 				    search.attrs[i], result->search.attrs[i],
199 				    sizeof(search.attrs[i]),
200 				    result->search.ic) >=
201 				    sizeof(search.attrs[i]))
202 					err(EX_USAGE, "-attr %.9s...",
203 					    result->search.attrs[i]);
204 			}
205 		}
206 		if (result->search.phrase != NULL) {
207 			if (ic_strlcpy(search.phrase, result->search.phrase,
208 			    sizeof(search.phrase), result->search.ic)
209 			    >= sizeof(search.phrase))
210 				err(EX_USAGE, "<phrase>");
211 		}
212 		if (result->search.ord != NULL) {
213 			if (strlcpy(search.order, result->search.ord,
214 			    sizeof(search.order)) >= sizeof(search.order))
215 				err(EX_USAGE, "-ord");
216 		}
217 
218 		if (write(mailestc_sock, &search, sizeof(search)) < 0)
219 			err(1, "write");
220 wait_resp:
221 		while ((sz = read(mailestc_sock, msgbuf, sizeof(msgbuf))) > 0)
222 			fwrite(msgbuf, 1, sz, stdout);
223 		close(mailestc_sock);
224 		break;
225 
226 	case SEARCH_SMEW:
227 		run_daemon(cmd, cmdv);
228 		memset(&smew, 0, sizeof(smew));
229 		smew.command = MAILESTCTL_CMD_SMEW;
230 		strlcpy(smew.msgid, result->msgid, sizeof(smew.msgid));
231 		if (result->folder)
232 			strlcpy(smew.folder, result->folder,
233 			    sizeof(smew.folder));
234 		if (write(mailestc_sock, &smew, sizeof(smew)) < 0)
235 			err(1, "write");
236 		goto wait_resp;
237 
238 	case MESSAGE_ID:
239 	case PARENT_ID:
240 		run_daemon(cmd, cmdv);
241 		memset(&search, 0, sizeof(search));
242 		search.command = MAILESTCTL_CMD_SEARCH;
243 		search.outform = MAILESTCTL_OUTFORM_SMEW;
244 		if (result->search.max) {
245 			search.max = result->search.max;
246 			strlcpy(search.order, "@cdate NUMA",
247 			    sizeof(search.order));
248 		}
249 		if (result->action == MESSAGE_ID)
250 			strlcpy(search.attrs[0],
251 			    ATTR_MSGID " STREQ ", sizeof(search.attrs[0]));
252 		else
253 			strlcpy(search.attrs[0],
254 			    ATTR_PARID " STREQ ", sizeof(search.attrs[0]));
255 		strlcat(search.attrs[0], result->msgid,
256 		    sizeof(search.attrs[0]));
257 		if (write(mailestc_sock, &search, sizeof(search)) < 0)
258 			err(1, "write");
259 		goto wait_resp;
260 
261 	case NONE:
262 		break;
263 	}
264 
265 	if (mailestc_sock >= 0)
266 		close(mailestc_sock);
267 
268 	exit(EXIT_SUCCESS);
269 }
270 
271 static bool
mailestc_check_connection(void)272 mailestc_check_connection(void)
273 {
274 	if (mailestc_sock < 0)
275 		mailestc_connect();
276 
277 	return ((mailestc_sock >= 0)? true : false);
278 }
279 
280 static void
mailestc_connect(void)281 mailestc_connect(void)
282 {
283 	int			 sock;
284 	struct sockaddr_un	 sun;
285 
286 	memset(&sun, 0, sizeof(sun));
287 	sun.sun_family = AF_UNIX;
288 	strlcpy(sun.sun_path, mailestc_path, sizeof(sun.sun_path));
289 
290 	if ((sock = socket(PF_UNIX, SOCK_SEQPACKET, 0)) < 0)
291 		err(EX_OSERR, "socket");
292 	if (connect(sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
293 		if (errno == EEXIST || errno == ECONNREFUSED || errno == ENOENT)
294 			return;
295 		err(EX_OSERR, "connect(%s)", mailestc_path);
296 	}
297 
298 	mailestc_sock = sock;
299 }
300 
301 static void
run_daemon(const char * cmd,char * argv[])302 run_daemon(const char *cmd, char *argv[])
303 {
304 	int	 ntry;
305 
306 	if (!mailestc_check_connection()) {
307 		if (fork() == 0) {
308 			setsid();
309 			execvp(cmd, argv);
310 			/* NOTREACHED */
311 			abort();
312 		}
313 		for (ntry = 8; !mailestc_check_connection() && ntry-- > 0;)
314 			usleep(500000);
315 		if (ntry <= 0)
316 			errx(1, "cannot start mailestd");
317 	}
318 }
319 
320 static void
stop_daemon(void)321 stop_daemon(void)
322 {
323 	int			 ntry;
324 	struct mailestctl	 ctl;
325 
326 	for (ntry = 8; mailestc_check_connection() && ntry-- > 0;) {
327 		ctl.command = MAILESTCTL_CMD_STOP;
328 		if (write(mailestc_sock, &ctl, sizeof(ctl)) < 0) {
329 			if (errno != EPIPE)
330 				err(1, "write");
331 			close(mailestc_sock);
332 			mailestc_sock = -1;
333 			break;
334 		}
335 		usleep(500000);
336 	}
337 	if (ntry <= 0)
338 		warnx("cannot stop mailestd");
339 }
340 
341 size_t
ic_strlcpy(char * output,const char * input,size_t output_siz,const char * input_encoding)342 ic_strlcpy(char *output, const char *input, size_t output_siz,
343     const char *input_encoding)
344 {
345 	iconv_t		 cd;
346 	size_t		 isiz, osiz;
347 
348 	if (input_encoding == NULL)
349 		return (strlcpy(output, input, output_siz));
350 	if ((cd = iconv_open("UTF-8", input_encoding)) == (iconv_t)-1)
351 		err(1, "iconv_open(\"UTF-8\", \"%s\")", input_encoding);
352 	isiz = strlen(input) + 1;
353 	osiz = output_siz;
354 	iconv(cd, &input, &isiz, &output, &osiz);
355 	iconv_close(cd);
356 	if (isiz != 0)
357 		return ((size_t)-1);
358 
359 	return (osiz);
360 }
361