1 /* $OpenBSD: collect.c,v 1.34 2014/01/17 18:42:30 okan Exp $ */
2 /* $NetBSD: collect.c,v 1.9 1997/07/09 05:25:45 mikel Exp $ */
3
4 /*
5 * Copyright (c) 1980, 1993
6 * The Regents of the University of California. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33 /*
34 * Mail -- a mail program
35 *
36 * Collect input from standard input, handling
37 * ~ escapes.
38 */
39
40 #include "rcv.h"
41 #include "extern.h"
42
43 /*
44 * Read a message from standard output and return a read file to it
45 * or NULL on error.
46 */
47
48 /*
49 * The following hokiness with global variables is so that on
50 * receipt of an interrupt signal, the partial message can be salted
51 * away on dead.letter.
52 */
53 static FILE *collf; /* File for saving away */
54 static int hadintr; /* Have seen one SIGINT so far */
55
56 FILE *
collect(struct header * hp,int printheaders)57 collect(struct header *hp, int printheaders)
58 {
59 FILE *fbuf;
60 int lc, cc, fd, c, t, lastlong, rc, sig;
61 int escape, eofcount, longline;
62 char getsub;
63 char linebuf[LINESIZE], tempname[PATHSIZE], *cp;
64
65 collf = NULL;
66 eofcount = 0;
67 hadintr = 0;
68 lastlong = 0;
69 longline = 0;
70 if ((cp = value("escape")) != NULL)
71 escape = *cp;
72 else
73 escape = ESCAPE;
74 noreset++;
75
76 (void)snprintf(tempname, sizeof(tempname),
77 "%s/mail.RsXXXXXXXXXX", tmpdir);
78 if ((fd = mkstemp(tempname)) == -1 ||
79 (collf = Fdopen(fd, "w+")) == NULL) {
80 warn("%s", tempname);
81 goto err;
82 }
83 (void)rm(tempname);
84
85 /*
86 * If we are going to prompt for a subject,
87 * refrain from printing a newline after
88 * the headers (since some people mind).
89 */
90 t = GTO|GSUBJECT|GCC|GNL;
91 getsub = 0;
92 if (hp->h_subject == NULL && value("interactive") != NULL &&
93 (value("ask") != NULL || value("asksub") != NULL))
94 t &= ~GNL, getsub++;
95 if (printheaders) {
96 puthead(hp, stdout, t);
97 fflush(stdout);
98 }
99 if (getsub && gethfromtty(hp, GSUBJECT) == -1)
100 goto err;
101
102 if (0) {
103 cont:
104 /* Come here for printing the after-suspend message. */
105 if (isatty(0)) {
106 puts("(continue)");
107 fflush(stdout);
108 }
109 }
110 for (;;) {
111 c = readline(stdin, linebuf, LINESIZE, &sig);
112
113 /* Act on any signal caught during readline() ignoring 'c' */
114 switch (sig) {
115 case 0:
116 break;
117 case SIGINT:
118 if (collabort())
119 goto err;
120 continue;
121 case SIGHUP:
122 rewind(collf);
123 savedeadletter(collf);
124 /*
125 * Let's pretend nobody else wants to clean up,
126 * a true statement at this time.
127 */
128 exit(1);
129 default:
130 /* Stopped due to job control */
131 (void)kill(0, sig);
132 goto cont;
133 }
134
135 /* No signal, check for error */
136 if (c < 0) {
137 if (value("interactive") != NULL &&
138 value("ignoreeof") != NULL && ++eofcount < 25) {
139 puts("Use \".\" to terminate letter");
140 continue;
141 }
142 break;
143 }
144 lastlong = longline;
145 longline = (c == LINESIZE - 1);
146 eofcount = 0;
147 hadintr = 0;
148 if (linebuf[0] == '.' && linebuf[1] == '\0' &&
149 value("interactive") != NULL && !lastlong &&
150 (value("dot") != NULL || value("ignoreeof") != NULL))
151 break;
152 if (linebuf[0] != escape || value("interactive") == NULL ||
153 lastlong) {
154 if (putline(collf, linebuf, !longline) < 0)
155 goto err;
156 continue;
157 }
158 c = (unsigned char)linebuf[1];
159 switch (c) {
160 default:
161 /*
162 * On double escape, just send the single one.
163 * Otherwise, it's an error.
164 */
165 if (c == escape) {
166 if (putline(collf, &linebuf[1], !longline) < 0)
167 goto err;
168 else
169 break;
170 }
171 puts("Unknown tilde escape.");
172 break;
173 case '!':
174 /*
175 * Shell escape, send the balance of the
176 * line to sh -c.
177 */
178 shell(&linebuf[2]);
179 break;
180 case ':':
181 case '_':
182 /*
183 * Escape to command mode, but be nice!
184 */
185 execute(&linebuf[2], 1);
186 goto cont;
187 case '.':
188 /*
189 * Simulate end of file on input.
190 */
191 goto out;
192 case 'q':
193 /*
194 * Force a quit of sending mail.
195 * Act like an interrupt happened.
196 */
197 hadintr++;
198 collabort();
199 fputs("Interrupt\n", stderr);
200 goto err;
201 case 'x':
202 /*
203 * Force a quit of sending mail.
204 * Do not save the message.
205 */
206 goto err;
207 case 'h':
208 /*
209 * Grab a bunch of headers.
210 */
211 grabh(hp, GTO|GSUBJECT|GCC|GBCC);
212 goto cont;
213 case 't':
214 /*
215 * Add to the To list.
216 */
217 hp->h_to = cat(hp->h_to, extract(&linebuf[2], GTO));
218 break;
219 case 's':
220 /*
221 * Set the Subject list.
222 */
223 cp = &linebuf[2];
224 while (isspace((unsigned char)*cp))
225 cp++;
226 hp->h_subject = savestr(cp);
227 break;
228 case 'c':
229 /*
230 * Add to the CC list.
231 */
232 hp->h_cc = cat(hp->h_cc, extract(&linebuf[2], GCC));
233 break;
234 case 'b':
235 /*
236 * Add stuff to blind carbon copies list.
237 */
238 hp->h_bcc = cat(hp->h_bcc, extract(&linebuf[2], GBCC));
239 break;
240 case 'd':
241 linebuf[2] = '\0';
242 strlcat(linebuf, getdeadletter(), sizeof(linebuf));
243 /* fall into . . . */
244 case 'r':
245 case '<':
246 /*
247 * Invoke a file:
248 * Search for the file name,
249 * then open it and copy the contents to collf.
250 */
251 cp = &linebuf[2];
252 while (isspace((unsigned char)*cp))
253 cp++;
254 if (*cp == '\0') {
255 puts("Interpolate what file?");
256 break;
257 }
258 cp = expand(cp);
259 if (cp == NULL)
260 break;
261 if (isdir(cp)) {
262 printf("%s: Directory\n", cp);
263 break;
264 }
265 if ((fbuf = Fopen(cp, "r")) == NULL) {
266 warn("%s", cp);
267 break;
268 }
269 printf("\"%s\" ", cp);
270 fflush(stdout);
271 lc = 0;
272 cc = 0;
273 while ((rc = readline(fbuf, linebuf, LINESIZE, NULL)) >= 0) {
274 if (rc != LINESIZE - 1)
275 lc++;
276 if ((t = putline(collf, linebuf,
277 rc != LINESIZE-1)) < 0) {
278 (void)Fclose(fbuf);
279 goto err;
280 }
281 cc += t;
282 }
283 (void)Fclose(fbuf);
284 printf("%d/%d\n", lc, cc);
285 break;
286 case 'w':
287 /*
288 * Write the message on a file.
289 */
290 cp = &linebuf[2];
291 while (*cp == ' ' || *cp == '\t')
292 cp++;
293 if (*cp == '\0') {
294 fputs("Write what file!?\n", stderr);
295 break;
296 }
297 if ((cp = expand(cp)) == NULL)
298 break;
299 rewind(collf);
300 exwrite(cp, collf, 1);
301 break;
302 case 'm':
303 case 'M':
304 case 'f':
305 case 'F':
306 /*
307 * Interpolate the named messages, if we
308 * are in receiving mail mode. Does the
309 * standard list processing garbage.
310 * If ~f is given, we don't shift over.
311 */
312 if (forward(linebuf + 2, collf, tempname, c) < 0)
313 goto err;
314 goto cont;
315 case '?':
316 if ((fbuf = Fopen(_PATH_TILDE, "r")) == NULL) {
317 warn(_PATH_TILDE);
318 break;
319 }
320 while ((t = getc(fbuf)) != EOF)
321 (void)putchar(t);
322 (void)Fclose(fbuf);
323 break;
324 case 'p':
325 /*
326 * Print out the current state of the
327 * message without altering anything.
328 */
329 rewind(collf);
330 puts("-------\nMessage contains:");
331 puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL);
332 while ((t = getc(collf)) != EOF)
333 (void)putchar(t);
334 goto cont;
335 case '|':
336 /*
337 * Pipe message through command.
338 * Collect output as new message.
339 */
340 rewind(collf);
341 mespipe(collf, &linebuf[2]);
342 goto cont;
343 case 'v':
344 case 'e':
345 /*
346 * Edit the current message.
347 * 'e' means to use EDITOR
348 * 'v' means to use VISUAL
349 */
350 rewind(collf);
351 mesedit(collf, c);
352 goto cont;
353 }
354 }
355
356 if (value("interactive") != NULL) {
357 if (value("askcc") != NULL || value("askbcc") != NULL) {
358 if (value("askcc") != NULL) {
359 if (gethfromtty(hp, GCC) == -1)
360 goto err;
361 }
362 if (value("askbcc") != NULL) {
363 if (gethfromtty(hp, GBCC) == -1)
364 goto err;
365 }
366 } else {
367 puts("EOT");
368 (void)fflush(stdout);
369 }
370 }
371 goto out;
372 err:
373 if (collf != NULL) {
374 (void)Fclose(collf);
375 collf = NULL;
376 }
377 out:
378 if (collf != NULL)
379 rewind(collf);
380 noreset--;
381 return(collf);
382 }
383
384 /*
385 * Write a file, ex-like if f set.
386 */
387 int
exwrite(char * name,FILE * fp,int f)388 exwrite(char *name, FILE *fp, int f)
389 {
390 FILE *of;
391 int c;
392 ssize_t cc, lc;
393 struct stat junk;
394
395 if (f) {
396 printf("\"%s\" ", name);
397 fflush(stdout);
398 }
399 if (stat(name, &junk) >= 0 && S_ISREG(junk.st_mode)) {
400 if (!f)
401 fprintf(stderr, "%s: ", name);
402 fputs("File exists\n", stderr);
403 return(-1);
404 }
405 if ((of = Fopen(name, "w")) == NULL) {
406 warn(NULL);
407 return(-1);
408 }
409 lc = 0;
410 cc = 0;
411 while ((c = getc(fp)) != EOF) {
412 cc++;
413 if (c == '\n')
414 lc++;
415 (void)putc(c, of);
416 if (ferror(of)) {
417 warn("%s", name);
418 (void)Fclose(of);
419 return(-1);
420 }
421 }
422 (void)Fclose(of);
423 printf("%lld/%lld\n", (long long)lc, (long long)cc);
424 fflush(stdout);
425 return(0);
426 }
427
428 /*
429 * Edit the message being collected on fp.
430 * On return, make the edit file the new temp file.
431 */
432 void
mesedit(FILE * fp,int c)433 mesedit(FILE *fp, int c)
434 {
435 FILE *nf;
436 struct sigaction oact;
437 sigset_t oset;
438
439 (void)ignoresig(SIGINT, &oact, &oset);
440 nf = run_editor(fp, (off_t)-1, c, 0);
441 if (nf != NULL) {
442 fseek(nf, 0L, SEEK_END);
443 collf = nf;
444 (void)Fclose(fp);
445 }
446 (void)sigprocmask(SIG_SETMASK, &oset, NULL);
447 (void)sigaction(SIGINT, &oact, NULL);
448 }
449
450 /*
451 * Pipe the message through the command.
452 * Old message is on stdin of command;
453 * New message collected from stdout.
454 * Sh -c must return 0 to accept the new message.
455 */
456 void
mespipe(FILE * fp,char * cmd)457 mespipe(FILE *fp, char *cmd)
458 {
459 FILE *nf;
460 int fd;
461 char *shell, tempname[PATHSIZE];
462 struct sigaction oact;
463 sigset_t oset;
464
465 (void)ignoresig(SIGINT, &oact, &oset);
466 (void)snprintf(tempname, sizeof(tempname),
467 "%s/mail.ReXXXXXXXXXX", tmpdir);
468 if ((fd = mkstemp(tempname)) == -1 ||
469 (nf = Fdopen(fd, "w+")) == NULL) {
470 warn("%s", tempname);
471 goto out;
472 }
473 (void)rm(tempname);
474 /*
475 * stdin = current message.
476 * stdout = new message.
477 */
478 shell = value("SHELL");
479 if (run_command(shell,
480 0, fileno(fp), fileno(nf), "-c", cmd, NULL) < 0) {
481 (void)Fclose(nf);
482 goto out;
483 }
484 if (fsize(nf) == 0) {
485 fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
486 (void)Fclose(nf);
487 goto out;
488 }
489 /*
490 * Take new files.
491 */
492 (void)fseek(nf, 0L, SEEK_END);
493 collf = nf;
494 (void)Fclose(fp);
495 out:
496 (void)sigprocmask(SIG_SETMASK, &oset, NULL);
497 (void)sigaction(SIGINT, &oact, NULL);
498 }
499
500 /*
501 * Interpolate the named messages into the current
502 * message, preceding each line with a tab.
503 * Return a count of the number of characters now in
504 * the message, or -1 if an error is encountered writing
505 * the message temporary. The flag argument is 'm' if we
506 * should shift over and 'f' if not.
507 */
508 int
forward(char * ms,FILE * fp,char * fn,int f)509 forward(char *ms, FILE *fp, char *fn, int f)
510 {
511 int *msgvec;
512 struct ignoretab *ig;
513 char *tabst;
514
515 msgvec = (int *)salloc((msgCount+1) * sizeof(*msgvec));
516 if (msgvec == NULL)
517 return(0);
518 if (getmsglist(ms, msgvec, 0) < 0)
519 return(0);
520 if (*msgvec == 0) {
521 *msgvec = first(0, MMNORM);
522 if (*msgvec == 0) {
523 puts("No appropriate messages");
524 return(0);
525 }
526 msgvec[1] = 0;
527 }
528 if (tolower(f) == 'f')
529 tabst = NULL;
530 else if ((tabst = value("indentprefix")) == NULL)
531 tabst = "\t";
532 ig = isupper(f) ? NULL : ignore;
533 fputs("Interpolating:", stdout);
534 for (; *msgvec != 0; msgvec++) {
535 struct message *mp = message + *msgvec - 1;
536
537 touch(mp);
538 printf(" %d", *msgvec);
539 if (sendmessage(mp, fp, ig, tabst) < 0) {
540 warn("%s", fn);
541 return(-1);
542 }
543 }
544 putchar('\n');
545 return(0);
546 }
547
548 /*
549 * User aborted during message composition.
550 * Save the partial message in ~/dead.letter.
551 */
552 int
collabort(void)553 collabort(void)
554 {
555 /*
556 * the control flow is subtle, because we can be called from ~q.
557 */
558 if (hadintr == 0 && isatty(0)) {
559 if (value("ignore") != NULL) {
560 puts("@");
561 fflush(stdout);
562 clearerr(stdin);
563 } else {
564 fflush(stdout);
565 fputs("\n(Interrupt -- one more to kill letter)\n",
566 stderr);
567 hadintr++;
568 }
569 return(0);
570 }
571 fflush(stdout);
572 rewind(collf);
573 if (value("nosave") == NULL)
574 savedeadletter(collf);
575 return(1);
576 }
577
578 void
savedeadletter(FILE * fp)579 savedeadletter(FILE *fp)
580 {
581 FILE *dbuf;
582 int c;
583 char *cp;
584
585 if (fsize(fp) == 0)
586 return;
587 cp = getdeadletter();
588 c = umask(077);
589 dbuf = Fopen(cp, "a");
590 (void)umask(c);
591 if (dbuf == NULL)
592 return;
593 while ((c = getc(fp)) != EOF)
594 (void)putc(c, dbuf);
595 (void)Fclose(dbuf);
596 rewind(fp);
597 }
598
599 int
gethfromtty(struct header * hp,int gflags)600 gethfromtty(struct header *hp, int gflags)
601 {
602
603 hadintr = 0;
604 while (grabh(hp, gflags) != 0) {
605 if (collabort())
606 return(-1);
607 }
608 return(0);
609 }
610