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