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