/tttm.c
1 #include <errno.h>
2 #include <fcntl.h>
3 #include <libgen.h>
4 #include <signal.h>
5 #include <stdint.h>
6 #include <stdlib.h>
7 #include <unistd.h>
8 #include <sys/mman.h>
9 #include <sys/wait.h>
10 #include <sys/stat.h>
11
12 #include "errors.h"
13 #include "parser.h"
14 #include "imap.h"
15
16 #define MIN(a, b) ((a) <= (b) ? (a) : (b))
17
18 /* Run fetch, pipe and delete cycle on this many messages a pop. */
19 #define BATCHSZ 10
20
21 /* Retry fetching mail at most this many times after getting a nil fetch. */
22 #define MAXREFC 2
23
24 static int pipemsg(struct msgd *, char *, char **);
25 static int sink_init(char **, pid_t *, int *);
26 static int sink_free(pid_t, int);
27 static void fin(struct imapctx *, int, const char *);
28 static void terr(int, const char *);
29 static void twarn(int, const char *);
30 static void usage(void);
31
32 static char *userkey = "IMAP_USER";
33 static char *passkey = "IMAP_PASS";
34
35 char *prog;
36
37 int
38 main(int argc, char **argv)
39 {
40 int e;
41 struct imapctx con;
42 char *user, *pass;
43 int refetchc, batchsz, mpiped;
44 struct msgd bag[BATCHSZ], *m, *bend;
45 long sysvar;
46
47 prog = basename(argv[0]);
48
49 #ifdef __OpenBSD__
50 if(pledge("stdio proc exec", 0) == -1) {
51 err(1, "pledge");
52 }
53 #endif
54
55 if (argc < 2) {
56 usage();
57 }
58
59 if ((e = imap_init(&con, STDIN_FILENO, STDOUT_FILENO))) {
60 terr(e, "failed to initialize session");
61 }
62 if (con.state == IS_AUTH) {
63 goto select;
64 }
65
66 if (!(user = getenv(userkey))) {
67 warnx("cannot find user name within environment");
68 goto logout;
69 }
70 if (!(pass = getenv(passkey))) {
71 warnx("cannot find password within environment");
72 goto logout;
73 }
74
75 e = imap_login(&con, user, pass);
76 if (e == TE_NO) {
77 warnx("login credentials rejected");
78 goto logout;
79 } else if (e == TE_BAD) {
80 warnx("failed to log in: %s", errmsgs[e]);
81 goto logout;
82 } else if (e) {
83 terr(e, "failed to log in");
84 }
85
86 select:
87 if (unsetenv(userkey) == -1 || unsetenv(passkey) == -1) {
88 warn("failed to wipe credentials off of environment");
89 goto logout;
90 }
91
92 if ((e = imap_select(&con, "inbox"))) {
93 fin(&con, e, "failed to select mailbox");
94 }
95 refetchc = 0;
96 while (con.mb.exists > 0) {
97 batchsz = MIN(BATCHSZ, con.mb.exists);
98 bend = bag + batchsz;
99 e = imap_fetch(&con, 1, batchsz, bag);
100 if (e == TE_NIL && refetchc < MAXREFC) {
101 if ((e = imap_nop(&con))) {
102 fin(&con, e, "failed to receive updates");
103 }
104 refetchc++;
105 continue;
106 } else if (e) {
107 fin(&con, e, "failed to fetch mail");
108 }
109 refetchc = 0;
110 for (m = bag; m < bend; m++) {
111 if (pipemsg(m, con.resp, argv + 1) == -1)
112 break;
113 }
114 mpiped = m - bag;
115 if (mpiped == 0) {
116 break;
117 }
118 if ((e = imap_delete(&con, 1, mpiped))) {
119 fin(&con, e, "failed to delete mail");
120 }
121 if ((e = imap_expunge(&con))) {
122 fin(&con, e, "failed to expunge mail");
123 }
124 if (mpiped < batchsz) {
125 break;
126 }
127 }
128 if ((e = imap_close(&con))) {
129 fin(&con, e, "failed to close mailbox");
130 }
131
132 logout:
133 if ((e = imap_logout(&con))) {
134 fin(&con, e, "failed to log out");
135 }
136 imap_free(&con);
137 return 0;
138 }
139
140 int
141 pipemsg(struct msgd *m, char *stor, char **argv)
142 {
143 int e, sin;
144 void (*origpipe)(int);
145 char *cur;
146 size_t len;
147 pid_t sid;
148 ssize_t n;
149
150 e = -1;
151 cur = stor + m->off;
152 len = m->len;
153 n = -2;
154
155 if ((origpipe = signal(SIGPIPE, SIG_IGN)) == SIG_ERR) {
156 warn("failed to install SIGPIPE handler");
157 goto exit;
158 }
159 if (sink_init(argv, &sid, &sin) == -1)
160 goto csig;
161
162 while (len > 0) {
163 if ((n = write(sin, cur, len)) == 0) {
164 break;
165 } else if (n == -1) {
166 if (errno == EINTR || errno == EAGAIN)
167 continue;
168 else break;
169 }
170 cur += n;
171 len -= n;
172 }
173 if (n == 0) {
174 warn("sink shut down unexpectedly");
175 goto csink;
176 } else if (n == -1) {
177 warn("failed to write to sink");
178 goto csink;
179 }
180 e = 0;
181 csink:
182 if (sink_free(sid, sin) == -1)
183 e = -1;
184 csig:
185 if (signal(SIGPIPE, origpipe) == SIG_ERR) {
186 e = -1;
187 warn("failed to restore SIGPIPE handler");
188 }
189 exit:
190 return e;
191 }
192
193 int
194 sink_init(char **argv, pid_t *sid, int *sin)
195 {
196 int e;
197 int pd[2], r, w;
198
199 e = -1;
200 if (pipe(pd) == -1) {
201 warn("failed to create pipe");
202 goto exit;
203 }
204 r = pd[0];
205 *sin = w = pd[1];
206 *sid = fork();
207 if (*sid == -1) {
208 warn("failed to fork");
209 close(r);
210 close(w);
211 goto exit;
212 } else if (*sid == 0) {
213 if (dup2(2, 1) == -1 || dup2(r, 0) == -1 ||
214 close(r) == -1 || close(w) == -1) {
215 warn("failed to setup sink");
216 _exit(1);
217 }
218 execvp(argv[0], argv);
219 warn("failed to launch sink");
220 _exit(1);
221 }
222 if (close(r) == -1) {
223 warn("failed to let read end of pipe loose");
224 close(w);
225 goto exit;
226 }
227 e = 0;
228 exit:
229 return e;
230 }
231
232 int
233 sink_free(pid_t sid, int in)
234 {
235 int e, s;
236
237 e = -1;
238 if (close(in) == -1) {
239 warn("failed to close write end of pipe");
240 if (kill(sid, SIGINT) == -1) {
241 warn("failed to shut down sink");
242 goto exit;
243 }
244 }
245 if (waitpid(sid, &s, 0) == -1) {
246 warn("failed to randezvous with sink");
247 goto exit;
248 }
249 if (!WIFEXITED(s)) {
250 warnx("sink process terminated");
251 goto exit;
252 }
253 if (WEXITSTATUS(s)) {
254 warnx("sink process failed with exit satus %d",
255 (int)WEXITSTATUS(s));
256 goto exit;
257 }
258 e = 0;
259 exit:
260 return e;
261 }
262
263 void
264 fin(struct imapctx *con, int e, const char *msg)
265 {
266 int loerr;
267
268 if (e == TE_NO || e == TE_BAD || e == TE_NIL) {
269 if ((loerr = imap_logout(con)))
270 twarn(loerr, "failed to log out");
271 }
272 imap_free(con);
273 terr(e, msg);
274 }
275
276 void
277 terr(int e, const char *msg)
278 {
279 if (e >= TE_ERRNO) {
280 err(1, "%s: %s", msg, errmsgs[e]);
281 } else {
282 errx(1, "%s: %s", msg, errmsgs[e]);
283 }
284 }
285
286 void
287 twarn(int e, const char *msg)
288 {
289 if (e >= TE_ERRNO) {
290 warn("%s: %s", msg, errmsgs[e]);
291 } else {
292 warnx("%s: %s", msg, errmsgs[e]);
293 }
294 }
295
296 void
297 usage(void)
298 {
299 errx(1, "usage: tttm sink [argument ...]");
300 }