xref: /original-bsd/usr.bin/mail/send.c (revision e1ce3925)
1 /*
2  * Copyright (c) 1980 Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char sccsid[] = "@(#)send.c	5.21 (Berkeley) 06/25/90";
10 #endif /* not lint */
11 
12 #include "rcv.h"
13 
14 /*
15  * Mail -- a mail program
16  *
17  * Mail to others.
18  */
19 
20 /*
21  * Send message described by the passed pointer to the
22  * passed output buffer.  Return -1 on error.
23  * Adjust the status: field if need be.
24  * If doign is given, suppress ignored header fields.
25  * prefix is a string to prepend to each output line.
26  */
27 send(mp, obuf, doign, prefix)
28 	register struct message *mp;
29 	FILE *obuf;
30 	struct ignoretab *doign;
31 	char *prefix;
32 {
33 	long count;
34 	register FILE *ibuf;
35 	char line[LINESIZE];
36 	int ishead, infld, ignoring, dostat, firstline;
37 	register char *cp, *cp2;
38 	register int c;
39 	int length;
40 	int prefixlen;
41 
42 	/*
43 	 * Compute the prefix string, without trailing whitespace
44 	 */
45 	cp2 = 0;
46 	for (cp = prefix; *cp; cp++)
47 		if (*cp != ' ' && *cp != '\t')
48 			cp2 = cp;
49 	prefixlen = cp2 == 0 ? 0 : cp2 - prefix + 1;
50 	ibuf = setinput(mp);
51 	count = mp->m_size;
52 	ishead = 1;
53 	dostat = doign == 0 || !isign("status", doign);
54 	infld = 0;
55 	firstline = 1;
56 	/*
57 	 * Process headers first
58 	 */
59 	while (count > 0 && ishead) {
60 		if (fgets(line, LINESIZE, ibuf) == NULL)
61 			break;
62 		count -= length = strlen(line);
63 		if (firstline) {
64 			/*
65 			 * First line is the From line, so no headers
66 			 * there to worry about
67 			 */
68 			firstline = 0;
69 			ignoring = doign == ignoreall;
70 		} else if (line[0] == '\n') {
71 			/*
72 			 * If line is blank, we've reached end of
73 			 * headers, so force out status: field
74 			 * and note that we are no longer in header
75 			 * fields
76 			 */
77 			if (dostat) {
78 				statusput(mp, obuf, prefix);
79 				dostat = 0;
80 			}
81 			ishead = 0;
82 			ignoring = doign == ignoreall;
83 		} else if (infld && (line[0] == ' ' || line[0] == '\t')) {
84 			/*
85 			 * If this line is a continuation (via space or tab)
86 			 * of a previous header field, just echo it
87 			 * (unless the field should be ignored).
88 			 * In other words, nothing to do.
89 			 */
90 		} else {
91 			/*
92 			 * Pick up the header field if we have one.
93 			 */
94 			for (cp = line; (c = *cp++) && c != ':' && !isspace(c);)
95 				;
96 			cp2 = --cp;
97 			while (isspace(*cp++))
98 				;
99 			if (cp[-1] != ':') {
100 				/*
101 				 * Not a header line, force out status:
102 				 * This happens in uucp style mail where
103 				 * there are no headers at all.
104 				 */
105 				if (dostat) {
106 					statusput(mp, obuf, prefix);
107 					dostat = 0;
108 				}
109 				if (doign != ignoreall)
110 					/* add blank line */
111 					(void) putc('\n', obuf);
112 				ishead = 0;
113 				ignoring = 0;
114 			} else {
115 				/*
116 				 * If it is an ignored field and
117 				 * we care about such things, skip it.
118 				 */
119 				*cp2 = 0;	/* temporarily null terminate */
120 				if (doign && isign(line, doign))
121 					ignoring = 1;
122 				else if ((line[0] == 's' || line[0] == 'S') &&
123 					 strcasecmp(line, "status") == 0) {
124 					/*
125 					 * If the field is "status," go compute
126 					 * and print the real Status: field
127 					 */
128 					if (dostat) {
129 						statusput(mp, obuf, prefix);
130 						dostat = 0;
131 					}
132 					ignoring = 1;
133 				} else {
134 					ignoring = 0;
135 					*cp2 = c;	/* restore */
136 				}
137 				infld = 1;
138 			}
139 		}
140 		if (!ignoring) {
141 			/*
142 			 * Strip trailing whitespace from prefix
143 			 * if line is blank.
144 			 */
145 			if (prefix != NOSTR)
146 				if (length > 1)
147 					fputs(prefix, obuf);
148 				else
149 					(void) fwrite(prefix, sizeof *prefix,
150 							prefixlen, obuf);
151 			(void) fwrite(line, sizeof *line, length, obuf);
152 			if (ferror(obuf))
153 				return -1;
154 		}
155 	}
156 	/*
157 	 * Copy out message body
158 	 */
159 	if (doign == ignoreall)
160 		count--;		/* skip final blank line */
161 	if (prefix != NOSTR)
162 		while (count > 0) {
163 			if (fgets(line, LINESIZE, ibuf) == NULL) {
164 				c = 0;
165 				break;
166 			}
167 			count -= c = strlen(line);
168 			/*
169 			 * Strip trailing whitespace from prefix
170 			 * if line is blank.
171 			 */
172 			if (c > 1)
173 				fputs(prefix, obuf);
174 			else
175 				(void) fwrite(prefix, sizeof *prefix,
176 						prefixlen, obuf);
177 			(void) fwrite(line, sizeof *line, c, obuf);
178 			if (ferror(obuf))
179 				return -1;
180 		}
181 	else
182 		while (count > 0) {
183 			c = count < LINESIZE ? count : LINESIZE;
184 			if ((c = fread(line, sizeof *line, c, ibuf)) <= 0)
185 				break;
186 			count -= c;
187 			if (fwrite(line, sizeof *line, c, obuf) != c)
188 				return -1;
189 		}
190 	if (doign == ignoreall && c > 0 && line[c - 1] != '\n')
191 		/* no final blank line */
192 		if ((c = getc(ibuf)) != EOF && putc(c, obuf) == EOF)
193 			return -1;
194 	return 0;
195 }
196 
197 /*
198  * Output a reasonable looking status field.
199  */
200 statusput(mp, obuf, prefix)
201 	register struct message *mp;
202 	FILE *obuf;
203 	char *prefix;
204 {
205 	char statout[3];
206 	register char *cp = statout;
207 
208 	if (mp->m_flag & MREAD)
209 		*cp++ = 'R';
210 	if ((mp->m_flag & MNEW) == 0)
211 		*cp++ = 'O';
212 	*cp = 0;
213 	if (statout[0])
214 		fprintf(obuf, "%sStatus: %s\n",
215 			prefix == NOSTR ? "" : prefix, statout);
216 }
217 
218 /*
219  * Interface between the argument list and the mail1 routine
220  * which does all the dirty work.
221  */
222 mail(to, cc, bcc, smopts, subject)
223 	struct name *to, *cc, *bcc, *smopts;
224 	char *subject;
225 {
226 	struct header head;
227 
228 	head.h_to = to;
229 	head.h_subject = subject;
230 	head.h_cc = cc;
231 	head.h_bcc = bcc;
232 	head.h_smopts = smopts;
233 	mail1(&head, 0);
234 	return(0);
235 }
236 
237 
238 /*
239  * Send mail to a bunch of user names.  The interface is through
240  * the mail routine below.
241  */
242 sendmail(str)
243 	char *str;
244 {
245 	struct header head;
246 
247 	head.h_to = extract(str, GTO);
248 	head.h_subject = NOSTR;
249 	head.h_cc = NIL;
250 	head.h_bcc = NIL;
251 	head.h_smopts = NIL;
252 	mail1(&head, 0);
253 	return(0);
254 }
255 
256 /*
257  * Mail a message on standard input to the people indicated
258  * in the passed header.  (Internal interface).
259  */
260 mail1(hp, printheaders)
261 	struct header *hp;
262 {
263 	char *cp;
264 	int pid;
265 	char **namelist;
266 	struct name *to;
267 	FILE *mtf;
268 
269 	/*
270 	 * Collect user's mail from standard input.
271 	 * Get the result as mtf.
272 	 */
273 	if ((mtf = collect(hp, printheaders)) == NULL)
274 		return;
275 	if (value("interactive") != NOSTR)
276 		if (value("askcc") != NOSTR)
277 			grabh(hp, GCC);
278 		else {
279 			printf("EOT\n");
280 			(void) fflush(stdout);
281 		}
282 	if (fsize(mtf) == 0)
283 		if (hp->h_subject == NOSTR)
284 			printf("No message, no subject; hope that's ok\n");
285 		else
286 			printf("Null message body; hope that's ok\n");
287 	/*
288 	 * Now, take the user names from the combined
289 	 * to and cc lists and do all the alias
290 	 * processing.
291 	 */
292 	senderr = 0;
293 	to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc)));
294 	if (to == NIL) {
295 		printf("No recipients specified\n");
296 		senderr++;
297 	}
298 	/*
299 	 * Look through the recipient list for names with /'s
300 	 * in them which we write to as files directly.
301 	 */
302 	to = outof(to, mtf, hp);
303 	if (senderr)
304 		savedeadletter(mtf);
305 	to = elide(to);
306 	if (count(to) == 0)
307 		goto out;
308 	fixhead(hp, to);
309 	if ((mtf = infix(hp, mtf)) == NULL) {
310 		fprintf(stderr, ". . . message lost, sorry.\n");
311 		return;
312 	}
313 	namelist = unpack(cat(hp->h_smopts, to));
314 	if (debug) {
315 		char **t;
316 
317 		printf("Sendmail arguments:");
318 		for (t = namelist; *t != NOSTR; t++)
319 			printf(" \"%s\"", *t);
320 		printf("\n");
321 		goto out;
322 	}
323 	if ((cp = value("record")) != NOSTR)
324 		(void) savemail(expand(cp), mtf);
325 	/*
326 	 * Fork, set up the temporary mail file as standard
327 	 * input for "mail", and exec with the user list we generated
328 	 * far above.
329 	 */
330 	pid = fork();
331 	if (pid == -1) {
332 		perror("fork");
333 		savedeadletter(mtf);
334 		goto out;
335 	}
336 	if (pid == 0) {
337 		if (access(_PATH_MAIL_LOG, 0) == 0) {
338 			FILE *postage;
339 
340 			if ((postage = Fopen(_PATH_MAIL_LOG, "a")) != NULL) {
341 				fprintf(postage, "%s %d %ld\n", myname,
342 				    count(to), fsize(mtf));
343 				(void) Fclose(postage);
344 			}
345 		}
346 		prepare_child(sigmask(SIGHUP)|sigmask(SIGINT)|sigmask(SIGQUIT)|
347 			sigmask(SIGTSTP)|sigmask(SIGTTIN)|sigmask(SIGTTOU),
348 			fileno(mtf), -1);
349 		if ((cp = value("sendmail")) != NOSTR)
350 			cp = expand(cp);
351 		else
352 			cp = _PATH_SENDMAIL;
353 		execv(cp, namelist);
354 		perror(cp);
355 		_exit(1);
356 	}
357 	if (value("verbose") != NOSTR)
358 		(void) wait_child(pid);
359 	else
360 		free_child(pid);
361 out:
362 	(void) Fclose(mtf);
363 }
364 
365 /*
366  * Fix the header by glopping all of the expanded names from
367  * the distribution list into the appropriate fields.
368  */
369 fixhead(hp, tolist)
370 	struct header *hp;
371 	struct name *tolist;
372 {
373 	register struct name *np;
374 
375 	hp->h_to = NIL;
376 	hp->h_cc = NIL;
377 	hp->h_bcc = NIL;
378 	for (np = tolist; np != NIL; np = np->n_flink)
379 		if ((np->n_type & GMASK) == GTO)
380 			hp->h_to =
381 				cat(hp->h_to, nalloc(np->n_name, np->n_type));
382 		else if ((np->n_type & GMASK) == GCC)
383 			hp->h_cc =
384 				cat(hp->h_cc, nalloc(np->n_name, np->n_type));
385 		else if ((np->n_type & GMASK) == GBCC)
386 			hp->h_bcc =
387 				cat(hp->h_bcc, nalloc(np->n_name, np->n_type));
388 }
389 
390 /*
391  * Prepend a header in front of the collected stuff
392  * and return the new file.
393  */
394 FILE *
395 infix(hp, fi)
396 	struct header *hp;
397 	FILE *fi;
398 {
399 	extern char tempMail[];
400 	register FILE *nfo, *nfi;
401 	register int c;
402 
403 	if ((nfo = Fopen(tempMail, "w")) == NULL) {
404 		perror(tempMail);
405 		return(fi);
406 	}
407 	if ((nfi = Fopen(tempMail, "r")) == NULL) {
408 		perror(tempMail);
409 		(void) Fclose(nfo);
410 		return(fi);
411 	}
412 	(void) remove(tempMail);
413 	(void) puthead(hp, nfo, GTO|GSUBJECT|GCC|GBCC|GNL|GCOMMA);
414 	c = getc(fi);
415 	while (c != EOF) {
416 		(void) putc(c, nfo);
417 		c = getc(fi);
418 	}
419 	if (ferror(fi)) {
420 		perror("read");
421 		rewind(fi);
422 		return(fi);
423 	}
424 	(void) fflush(nfo);
425 	if (ferror(nfo)) {
426 		perror(tempMail);
427 		(void) Fclose(nfo);
428 		(void) Fclose(nfi);
429 		rewind(fi);
430 		return(fi);
431 	}
432 	(void) Fclose(nfo);
433 	(void) Fclose(fi);
434 	rewind(nfi);
435 	return(nfi);
436 }
437 
438 /*
439  * Dump the to, subject, cc header on the
440  * passed file buffer.
441  */
442 puthead(hp, fo, w)
443 	struct header *hp;
444 	FILE *fo;
445 {
446 	register int gotcha;
447 
448 	gotcha = 0;
449 	if (hp->h_to != NIL && w & GTO)
450 		fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++;
451 	if (hp->h_subject != NOSTR && w & GSUBJECT)
452 		fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
453 	if (hp->h_cc != NIL && w & GCC)
454 		fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++;
455 	if (hp->h_bcc != NIL && w & GBCC)
456 		fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++;
457 	if (gotcha && w & GNL)
458 		(void) putc('\n', fo);
459 	return(0);
460 }
461 
462 /*
463  * Format the given header line to not exceed 72 characters.
464  */
465 fmt(str, np, fo, comma)
466 	char *str;
467 	register struct name *np;
468 	FILE *fo;
469 	int comma;
470 {
471 	register col, len;
472 
473 	comma = comma ? 1 : 0;
474 	col = strlen(str);
475 	if (col)
476 		fputs(str, fo);
477 	for (; np != NIL; np = np->n_flink) {
478 		if (np->n_flink == NIL)
479 			comma = 0;
480 		len = strlen(np->n_name);
481 		col++;		/* for the space */
482 		if (col + len + comma > 72 && col > 4) {
483 			fputs("\n    ", fo);
484 			col = 4;
485 		} else
486 			putc(' ', fo);
487 		fputs(np->n_name, fo);
488 		if (comma)
489 			putc(',', fo);
490 		col += len + comma;
491 	}
492 	putc('\n', fo);
493 }
494 
495 /*
496  * Save the outgoing mail on the passed file.
497  */
498 
499 /*ARGSUSED*/
500 savemail(name, fi)
501 	char name[];
502 	register FILE *fi;
503 {
504 	register FILE *fo;
505 	char buf[BUFSIZ];
506 	register i;
507 	time_t now, time();
508 	char *ctime();
509 
510 	if ((fo = Fopen(name, "a")) == NULL) {
511 		perror(name);
512 		return (-1);
513 	}
514 	(void) time(&now);
515 	fprintf(fo, "From %s %s", myname, ctime(&now));
516 	while ((i = fread(buf, 1, sizeof buf, fi)) > 0)
517 		(void) fwrite(buf, 1, i, fo);
518 	(void) putc('\n', fo);
519 	(void) fflush(fo);
520 	if (ferror(fo))
521 		perror(name);
522 	(void) Fclose(fo);
523 	rewind(fi);
524 	return (0);
525 }
526