1 /*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1980, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #ifndef lint
33 #if 0
34 static char sccsid[] = "@(#)send.c 8.1 (Berkeley) 6/6/93";
35 #endif
36 #endif /* not lint */
37 #include <sys/cdefs.h>
38 #include "rcv.h"
39 #include "extern.h"
40
41 /*
42 * Mail -- a mail program
43 *
44 * Mail to others.
45 */
46
47 /*
48 * Send message described by the passed pointer to the
49 * passed output buffer. Return -1 on error.
50 * Adjust the status: field if need be.
51 * If doign is given, suppress ignored header fields.
52 * prefix is a string to prepend to each output line.
53 */
54 int
sendmessage(struct message * mp,FILE * obuf,struct ignoretab * doign,char * prefix)55 sendmessage(struct message *mp, FILE *obuf, struct ignoretab *doign,
56 char *prefix)
57 {
58 long count;
59 FILE *ibuf;
60 char *cp, *cp2, line[LINESIZE];
61 int ishead, infld, ignoring, dostat, firstline;
62 int c = 0, length, prefixlen;
63
64 /*
65 * Compute the prefix string, without trailing whitespace
66 */
67 if (prefix != NULL) {
68 cp2 = 0;
69 for (cp = prefix; *cp != '\0'; cp++)
70 if (*cp != ' ' && *cp != '\t')
71 cp2 = cp;
72 prefixlen = cp2 == NULL ? 0 : cp2 - prefix + 1;
73 }
74 ibuf = setinput(mp);
75 count = mp->m_size;
76 ishead = 1;
77 dostat = doign == 0 || !isign("status", doign);
78 infld = 0;
79 firstline = 1;
80 /*
81 * Process headers first
82 */
83 while (count > 0 && ishead) {
84 if (fgets(line, sizeof(line), ibuf) == NULL)
85 break;
86 count -= length = strlen(line);
87 if (firstline) {
88 /*
89 * First line is the From line, so no headers
90 * there to worry about
91 */
92 firstline = 0;
93 ignoring = doign == ignoreall;
94 } else if (line[0] == '\n') {
95 /*
96 * If line is blank, we've reached end of
97 * headers, so force out status: field
98 * and note that we are no longer in header
99 * fields
100 */
101 if (dostat) {
102 statusput(mp, obuf, prefix);
103 dostat = 0;
104 }
105 ishead = 0;
106 ignoring = doign == ignoreall;
107 } else if (infld && (line[0] == ' ' || line[0] == '\t')) {
108 /*
109 * If this line is a continuation (via space or tab)
110 * of a previous header field, just echo it
111 * (unless the field should be ignored).
112 * In other words, nothing to do.
113 */
114 } else {
115 /*
116 * Pick up the header field if we have one.
117 */
118 for (cp = line; (c = *cp++) != '\0' && c != ':' &&
119 !isspace((unsigned char)c);)
120 ;
121 cp2 = --cp;
122 while (isspace((unsigned char)*cp++))
123 ;
124 if (cp[-1] != ':') {
125 /*
126 * Not a header line, force out status:
127 * This happens in uucp style mail where
128 * there are no headers at all.
129 */
130 if (dostat) {
131 statusput(mp, obuf, prefix);
132 dostat = 0;
133 }
134 if (doign != ignoreall)
135 /* add blank line */
136 (void)putc('\n', obuf);
137 ishead = 0;
138 ignoring = 0;
139 } else {
140 /*
141 * If it is an ignored field and
142 * we care about such things, skip it.
143 */
144 *cp2 = '\0'; /* temporarily null terminate */
145 if (doign && isign(line, doign))
146 ignoring = 1;
147 else if ((line[0] == 's' || line[0] == 'S') &&
148 strcasecmp(line, "status") == 0) {
149 /*
150 * If the field is "status," go compute
151 * and print the real Status: field
152 */
153 if (dostat) {
154 statusput(mp, obuf, prefix);
155 dostat = 0;
156 }
157 ignoring = 1;
158 } else {
159 ignoring = 0;
160 *cp2 = c; /* restore */
161 }
162 infld = 1;
163 }
164 }
165 if (!ignoring) {
166 /*
167 * Strip trailing whitespace from prefix
168 * if line is blank.
169 */
170 if (prefix != NULL) {
171 if (length > 1)
172 fputs(prefix, obuf);
173 else
174 (void)fwrite(prefix, sizeof(*prefix),
175 prefixlen, obuf);
176 }
177 (void)fwrite(line, sizeof(*line), length, obuf);
178 if (ferror(obuf))
179 return (-1);
180 }
181 }
182 /*
183 * Copy out message body
184 */
185 if (doign == ignoreall)
186 count--; /* skip final blank line */
187 if (prefix != NULL)
188 while (count > 0) {
189 if (fgets(line, sizeof(line), ibuf) == NULL) {
190 c = 0;
191 break;
192 }
193 count -= c = strlen(line);
194 /*
195 * Strip trailing whitespace from prefix
196 * if line is blank.
197 */
198 if (c > 1)
199 fputs(prefix, obuf);
200 else
201 (void)fwrite(prefix, sizeof(*prefix),
202 prefixlen, obuf);
203 (void)fwrite(line, sizeof(*line), c, obuf);
204 if (ferror(obuf))
205 return (-1);
206 }
207 else
208 while (count > 0) {
209 c = count < LINESIZE ? count : LINESIZE;
210 if ((c = fread(line, sizeof(*line), c, ibuf)) <= 0)
211 break;
212 count -= c;
213 if (fwrite(line, sizeof(*line), c, obuf) != c)
214 return (-1);
215 }
216 if (doign == ignoreall && c > 0 && line[c - 1] != '\n')
217 /* no final blank line */
218 if ((c = getc(ibuf)) != EOF && putc(c, obuf) == EOF)
219 return (-1);
220 return (0);
221 }
222
223 /*
224 * Output a reasonable looking status field.
225 */
226 void
statusput(struct message * mp,FILE * obuf,char * prefix)227 statusput(struct message *mp, FILE *obuf, char *prefix)
228 {
229 char statout[3];
230 char *cp = statout;
231
232 if (mp->m_flag & MREAD)
233 *cp++ = 'R';
234 if ((mp->m_flag & MNEW) == 0)
235 *cp++ = 'O';
236 *cp = '\0';
237 if (statout[0] != '\0')
238 fprintf(obuf, "%sStatus: %s\n",
239 prefix == NULL ? "" : prefix, statout);
240 }
241
242 /*
243 * Interface between the argument list and the mail1 routine
244 * which does all the dirty work.
245 */
246 int
mail(struct name * to,struct name * cc,struct name * bcc,struct name * smopts,char * subject,char * replyto)247 mail(struct name *to, struct name *cc, struct name *bcc, struct name *smopts,
248 char *subject, char *replyto)
249 {
250 struct header head;
251
252 head.h_to = to;
253 head.h_subject = subject;
254 head.h_cc = cc;
255 head.h_bcc = bcc;
256 head.h_smopts = smopts;
257 head.h_replyto = replyto;
258 head.h_inreplyto = NULL;
259 mail1(&head, 0);
260 return (0);
261 }
262
263
264 /*
265 * Send mail to a bunch of user names. The interface is through
266 * the mail routine below.
267 */
268 int
sendmail(void * str)269 sendmail(void *str)
270 {
271 struct header head;
272
273 head.h_to = extract(str, GTO);
274 head.h_subject = NULL;
275 head.h_cc = NULL;
276 head.h_bcc = NULL;
277 head.h_smopts = NULL;
278 head.h_replyto = value("REPLYTO");
279 head.h_inreplyto = NULL;
280 mail1(&head, 0);
281 return (0);
282 }
283
284 /*
285 * Mail a message on standard input to the people indicated
286 * in the passed header. (Internal interface).
287 */
288 void
mail1(struct header * hp,int printheaders)289 mail1(struct header *hp, int printheaders)
290 {
291 char *cp;
292 char *nbuf;
293 int pid;
294 char **namelist;
295 struct name *to, *nsto;
296 FILE *mtf;
297
298 /*
299 * Collect user's mail from standard input.
300 * Get the result as mtf.
301 */
302 if ((mtf = collect(hp, printheaders)) == NULL)
303 return;
304 if (value("interactive") != NULL) {
305 if (value("askcc") != NULL || value("askbcc") != NULL) {
306 if (value("askcc") != NULL)
307 grabh(hp, GCC);
308 if (value("askbcc") != NULL)
309 grabh(hp, GBCC);
310 } else {
311 printf("EOT\n");
312 (void)fflush(stdout);
313 }
314 }
315 if (fsize(mtf) == 0) {
316 if (value("dontsendempty") != NULL)
317 goto out;
318 if (hp->h_subject == NULL)
319 printf("No message, no subject; hope that's ok\n");
320 else
321 printf("Null message body; hope that's ok\n");
322 }
323 /*
324 * Now, take the user names from the combined
325 * to and cc lists and do all the alias
326 * processing.
327 */
328 senderr = 0;
329 to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc)));
330 if (to == NULL) {
331 printf("No recipients specified\n");
332 senderr++;
333 }
334 /*
335 * Look through the recipient list for names with /'s
336 * in them which we write to as files directly.
337 */
338 to = outof(to, mtf, hp);
339 if (senderr)
340 savedeadletter(mtf);
341 to = elide(to);
342 if (count(to) == 0)
343 goto out;
344 if (value("recordrecip") != NULL) {
345 /*
346 * Before fixing the header, save old To:.
347 * We do this because elide above has sorted To: list, and
348 * we would like to save message in a file named by the first
349 * recipient the user has entered, not the one being the first
350 * after sorting happened.
351 */
352 if ((nsto = malloc(sizeof(struct name))) == NULL)
353 err(1, "Out of memory");
354 bcopy(hp->h_to, nsto, sizeof(struct name));
355 }
356 fixhead(hp, to);
357 if ((mtf = infix(hp, mtf)) == NULL) {
358 fprintf(stderr, ". . . message lost, sorry.\n");
359 return;
360 }
361 namelist = unpack(cat(hp->h_smopts, to));
362 if (debug) {
363 char **t;
364
365 printf("Sendmail arguments:");
366 for (t = namelist; *t != NULL; t++)
367 printf(" \"%s\"", *t);
368 printf("\n");
369 goto out;
370 }
371 if (value("recordrecip") != NULL) {
372 /*
373 * Extract first recipient username from saved To: and use it
374 * as a filename.
375 */
376 if ((nbuf = malloc(strlen(detract(nsto, 0)) + 1)) == NULL)
377 err(1, "Out of memory");
378 if ((cp = yanklogin(detract(nsto, 0), nbuf)) != NULL)
379 (void)savemail(expand(nbuf), mtf);
380 free(nbuf);
381 free(nsto);
382 } else if ((cp = value("record")) != NULL)
383 (void)savemail(expand(cp), mtf);
384 /*
385 * Fork, set up the temporary mail file as standard
386 * input for "mail", and exec with the user list we generated
387 * far above.
388 */
389 pid = fork();
390 if (pid == -1) {
391 warn("fork");
392 savedeadletter(mtf);
393 goto out;
394 }
395 if (pid == 0) {
396 sigset_t nset;
397 (void)sigemptyset(&nset);
398 (void)sigaddset(&nset, SIGHUP);
399 (void)sigaddset(&nset, SIGINT);
400 (void)sigaddset(&nset, SIGQUIT);
401 (void)sigaddset(&nset, SIGTSTP);
402 (void)sigaddset(&nset, SIGTTIN);
403 (void)sigaddset(&nset, SIGTTOU);
404 prepare_child(&nset, fileno(mtf), -1);
405 if ((cp = value("sendmail")) != NULL)
406 cp = expand(cp);
407 else
408 cp = _PATH_SENDMAIL;
409 execv(cp, namelist);
410 warn("%s", cp);
411 _exit(1);
412 }
413 if (value("verbose") != NULL)
414 (void)wait_child(pid);
415 else
416 free_child(pid);
417 out:
418 (void)Fclose(mtf);
419 }
420
421 /*
422 * Fix the header by glopping all of the expanded names from
423 * the distribution list into the appropriate fields.
424 */
425 void
fixhead(struct header * hp,struct name * tolist)426 fixhead(struct header *hp, struct name *tolist)
427 {
428 struct name *np;
429
430 hp->h_to = NULL;
431 hp->h_cc = NULL;
432 hp->h_bcc = NULL;
433 for (np = tolist; np != NULL; np = np->n_flink) {
434 /* Don't copy deleted addresses to the header */
435 if (np->n_type & GDEL)
436 continue;
437 if ((np->n_type & GMASK) == GTO)
438 hp->h_to =
439 cat(hp->h_to, nalloc(np->n_name, np->n_type));
440 else if ((np->n_type & GMASK) == GCC)
441 hp->h_cc =
442 cat(hp->h_cc, nalloc(np->n_name, np->n_type));
443 else if ((np->n_type & GMASK) == GBCC)
444 hp->h_bcc =
445 cat(hp->h_bcc, nalloc(np->n_name, np->n_type));
446 }
447 }
448
449 /*
450 * Prepend a header in front of the collected stuff
451 * and return the new file.
452 */
453 FILE *
infix(struct header * hp,FILE * fi)454 infix(struct header *hp, FILE *fi)
455 {
456 FILE *nfo, *nfi;
457 int c, fd;
458 char tempname[PATHSIZE];
459
460 (void)snprintf(tempname, sizeof(tempname),
461 "%s/mail.RsXXXXXXXXXX", tmpdir);
462 if ((fd = mkstemp(tempname)) == -1 ||
463 (nfo = Fdopen(fd, "w")) == NULL) {
464 warn("%s", tempname);
465 return (fi);
466 }
467 if ((nfi = Fopen(tempname, "r")) == NULL) {
468 warn("%s", tempname);
469 (void)Fclose(nfo);
470 (void)rm(tempname);
471 return (fi);
472 }
473 (void)rm(tempname);
474 (void)puthead(hp, nfo,
475 GTO|GSUBJECT|GCC|GBCC|GREPLYTO|GINREPLYTO|GNL|GCOMMA);
476 c = getc(fi);
477 while (c != EOF) {
478 (void)putc(c, nfo);
479 c = getc(fi);
480 }
481 if (ferror(fi)) {
482 warnx("read");
483 rewind(fi);
484 return (fi);
485 }
486 (void)fflush(nfo);
487 if (ferror(nfo)) {
488 warn("%s", tempname);
489 (void)Fclose(nfo);
490 (void)Fclose(nfi);
491 rewind(fi);
492 return (fi);
493 }
494 (void)Fclose(nfo);
495 (void)Fclose(fi);
496 rewind(nfi);
497 return (nfi);
498 }
499
500 /*
501 * Dump the to, subject, cc header on the
502 * passed file buffer.
503 */
504 int
puthead(struct header * hp,FILE * fo,int w)505 puthead(struct header *hp, FILE *fo, int w)
506 {
507 int gotcha;
508
509 gotcha = 0;
510 if (hp->h_to != NULL && w & GTO)
511 fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++;
512 if (hp->h_subject != NULL && w & GSUBJECT)
513 fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
514 if (hp->h_cc != NULL && w & GCC)
515 fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++;
516 if (hp->h_bcc != NULL && w & GBCC)
517 fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++;
518 if (hp->h_replyto != NULL && w & GREPLYTO)
519 fprintf(fo, "Reply-To: %s\n", hp->h_replyto), gotcha++;
520 if (hp->h_inreplyto != NULL && w & GINREPLYTO)
521 fprintf(fo, "In-Reply-To: <%s>\n", hp->h_inreplyto), gotcha++;
522 if (gotcha && w & GNL)
523 (void)putc('\n', fo);
524 return (0);
525 }
526
527 /*
528 * Format the given header line to not exceed 72 characters.
529 */
530 void
fmt(const char * str,struct name * np,FILE * fo,int comma)531 fmt(const char *str, struct name *np, FILE *fo, int comma)
532 {
533 int col, len;
534
535 comma = comma ? 1 : 0;
536 col = strlen(str);
537 if (col)
538 fputs(str, fo);
539 for (; np != NULL; np = np->n_flink) {
540 if (np->n_flink == NULL)
541 comma = 0;
542 len = strlen(np->n_name);
543 col++; /* for the space */
544 if (col + len + comma > 72 && col > 4) {
545 fprintf(fo, "\n ");
546 col = 4;
547 } else
548 fprintf(fo, " ");
549 fputs(np->n_name, fo);
550 if (comma)
551 fprintf(fo, ",");
552 col += len + comma;
553 }
554 fprintf(fo, "\n");
555 }
556
557 /*
558 * Save the outgoing mail on the passed file.
559 */
560
561 /*ARGSUSED*/
562 int
savemail(char name[],FILE * fi)563 savemail(char name[], FILE *fi)
564 {
565 FILE *fo;
566 char buf[BUFSIZ];
567 int i;
568 time_t now;
569 mode_t saved_umask;
570
571 saved_umask = umask(077);
572 fo = Fopen(name, "a");
573 umask(saved_umask);
574
575 if (fo == NULL) {
576 warn("%s", name);
577 return (-1);
578 }
579 (void)time(&now);
580 fprintf(fo, "From %s %s", myname, ctime(&now));
581 while ((i = fread(buf, 1, sizeof(buf), fi)) > 0)
582 (void)fwrite(buf, 1, i, fo);
583 fprintf(fo, "\n");
584 (void)fflush(fo);
585 if (ferror(fo))
586 warn("%s", name);
587 (void)Fclose(fo);
588 rewind(fi);
589 return (0);
590 }
591