GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mail/collect.c Lines: 30 263 11.4 %
Date: 2017-11-07 Branches: 15 229 6.6 %

Line Branch Exec Source
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 *
57
collect(struct header *hp, int printheaders)
58
{
59
	FILE *fbuf;
60
4
	int lc, cc, fd, c, t, lastlong, rc, sig;
61
	int escape, eofcount, longline;
62
	char getsub;
63
2
	char linebuf[LINESIZE], tempname[PATHSIZE], *cp;
64
65
2
	collf = NULL;
66
	eofcount = 0;
67
2
	hadintr = 0;
68
	lastlong = 0;
69
	longline = 0;
70
2
	if ((cp = value("escape")) != NULL)
71
		escape = *cp;
72
	else
73
		escape = ESCAPE;
74
2
	noreset++;
75
76
4
	(void)snprintf(tempname, sizeof(tempname),
77
2
	    "%s/mail.RsXXXXXXXXXX", tmpdir);
78

4
	if ((fd = mkstemp(tempname)) == -1 ||
79
2
	    (collf = Fdopen(fd, "w+")) == NULL) {
80
		warn("%s", tempname);
81
		goto err;
82
	}
83
2
	(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

2
	if (hp->h_subject == NULL && value("interactive") != NULL &&
93
	    (value("ask") != NULL || value("asksub") != NULL))
94
		t &= ~GNL, getsub++;
95
2
	if (printheaders) {
96
		puthead(hp, stdout, t);
97
		fflush(stdout);
98
	}
99

2
	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
2
	for (;;) {
111
13049
		c = readline(stdin, linebuf, LINESIZE, &sig);
112
113
		/* Act on any signal caught during readline() ignoring 'c' */
114

13049
		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
13049
		if (c < 0) {
137

2
			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
13047
		longline = (c == LINESIZE - 1);
146
		eofcount = 0;
147
13047
		hadintr = 0;
148

13047
		if (linebuf[0] == '.' && linebuf[1] == '\0' &&
149
		    value("interactive") != NULL && !lastlong &&
150
		    (value("dot") != NULL || value("ignoreeof") != NULL))
151
			break;
152

13047
		if (linebuf[0] != escape || value("interactive") == NULL ||
153
		    lastlong) {
154
13047
			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
2
	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
2
	if (collf != NULL)
379
2
		rewind(collf);
380
2
	noreset--;
381
4
	return(collf);
382
2
}
383
384
/*
385
 * Write a file, ex-like if f set.
386
 */
387
int
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
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
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
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
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
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
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
}