1 /* $OpenBSD: send.c,v 1.26 2023/03/08 04:43:11 guenther Exp $ */
2 /* $NetBSD: send.c,v 1.6 1996/06/08 19:48:39 christos 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 #include "rcv.h"
34 #include "extern.h"
35
36 static volatile sig_atomic_t sendsignal; /* Interrupted by a signal? */
37
38 /*
39 * Mail -- a mail program
40 *
41 * Mail to others.
42 */
43
44 /*
45 * Send message described by the passed pointer to the
46 * passed output buffer. Return -1 on error.
47 * Adjust the status: field if need be.
48 * If doign is given, suppress ignored header fields.
49 * prefix is a string to prepend to each output line.
50 */
51 int
sendmessage(struct message * mp,FILE * obuf,struct ignoretab * doign,char * prefix)52 sendmessage(struct message *mp, FILE *obuf, struct ignoretab *doign,
53 char *prefix)
54 {
55 int count;
56 FILE *ibuf;
57 char line[LINESIZE];
58 char visline[4 * LINESIZE - 3];
59 int ishead, infld, ignoring = 0, dostat, firstline;
60 char *cp, *cp2;
61 int c = 0;
62 int length;
63 int prefixlen = 0;
64 int rval;
65 int dovis;
66 struct sigaction act, saveint;
67 sigset_t oset;
68
69 sendsignal = 0;
70 rval = -1;
71 dovis = isatty(fileno(obuf));
72 sigemptyset(&act.sa_mask);
73 act.sa_flags = SA_RESTART;
74 act.sa_handler = sendint;
75 (void)sigaction(SIGINT, &act, &saveint);
76 (void)sigprocmask(SIG_UNBLOCK, &intset, &oset);
77
78 /*
79 * Compute the prefix string, without trailing whitespace
80 */
81 if (prefix != NULL) {
82 cp2 = 0;
83 for (cp = prefix; *cp; cp++)
84 if (*cp != ' ' && *cp != '\t')
85 cp2 = cp;
86 prefixlen = cp2 == 0 ? 0 : cp2 - prefix + 1;
87 }
88 ibuf = setinput(mp);
89 count = mp->m_size;
90 ishead = 1;
91 dostat = doign == 0 || !isign("status", doign);
92 infld = 0;
93 firstline = 1;
94 /*
95 * Process headers first
96 */
97 while (count > 0 && ishead) {
98 if (fgets(line, sizeof(line), ibuf) == NULL)
99 break;
100 count -= length = strlen(line);
101 if (firstline) {
102 /*
103 * First line is the From line, so no headers
104 * there to worry about
105 */
106 firstline = 0;
107 ignoring = doign == ignoreall;
108 } else if (line[0] == '\n') {
109 /*
110 * If line is blank, we've reached end of
111 * headers, so force out status: field
112 * and note that we are no longer in header
113 * fields
114 */
115 if (dostat) {
116 if (statusput(mp, obuf, prefix) == -1)
117 goto out;
118 dostat = 0;
119 }
120 ishead = 0;
121 ignoring = doign == ignoreall;
122 } else if (infld && (line[0] == ' ' || line[0] == '\t')) {
123 /*
124 * If this line is a continuation (via space or tab)
125 * of a previous header field, just echo it
126 * (unless the field should be ignored).
127 * In other words, nothing to do.
128 */
129 } else {
130 /*
131 * Pick up the header field if we have one.
132 */
133 for (cp = line;
134 (c = (unsigned char)*cp++) && c != ':' && !isspace(c); )
135 ;
136 cp2 = --cp;
137 while (isspace((unsigned char)*cp++))
138 ;
139 if (cp[-1] != ':') {
140 /*
141 * Not a header line, force out status:
142 * This happens in uucp style mail where
143 * there are no headers at all.
144 */
145 if (dostat) {
146 if (statusput(mp, obuf, prefix) == -1)
147 goto out;
148 dostat = 0;
149 }
150 if (doign != ignoreall)
151 /* add blank line */
152 (void)putc('\n', obuf);
153 ishead = 0;
154 ignoring = 0;
155 } else {
156 /*
157 * If it is an ignored field and
158 * we care about such things, skip it.
159 */
160 *cp2 = 0; /* temporarily null terminate */
161 if (doign && isign(line, doign))
162 ignoring = 1;
163 else if (strcasecmp(line, "status") == 0) {
164 /*
165 * If the field is "status," go compute
166 * and print the real Status: field
167 */
168 if (dostat) {
169 if (statusput(mp, obuf, prefix) == -1)
170 goto out;
171 dostat = 0;
172 }
173 ignoring = 1;
174 } else {
175 ignoring = 0;
176 *cp2 = c; /* restore */
177 }
178 infld = 1;
179 }
180 }
181 if (!ignoring) {
182 /*
183 * Strip trailing whitespace from prefix
184 * if line is blank.
185 */
186 if (prefix != NULL) {
187 if (length > 1)
188 fputs(prefix, obuf);
189 else
190 (void)fwrite(prefix, sizeof(*prefix),
191 prefixlen, obuf);
192 }
193 if (dovis) {
194 length = strvis(visline, line, VIS_SAFE|VIS_NOSLASH);
195 (void)fwrite(visline, sizeof(*visline), length, obuf);
196 } else
197 (void)fwrite(line, sizeof(*line), length, obuf);
198 if (ferror(obuf))
199 goto out;
200 }
201 if (sendsignal == SIGINT)
202 goto out;
203 }
204 /*
205 * Copy out message body
206 */
207 if (doign == ignoreall)
208 count--; /* skip final blank line */
209 while (count > 0) {
210 if (fgets(line, sizeof(line), ibuf) == NULL) {
211 c = 0;
212 break;
213 }
214 count -= c = strlen(line);
215 if (prefix != NULL) {
216 /*
217 * Strip trailing whitespace from prefix
218 * if line is blank.
219 */
220 if (c > 1)
221 fputs(prefix, obuf);
222 else
223 (void)fwrite(prefix, sizeof(*prefix),
224 prefixlen, obuf);
225 }
226 /*
227 * We can't read the record file (or inbox for recipient)
228 * properly with 'From ' lines in the message body (from
229 * forwarded messages or sentences starting with "From "),
230 * so we will prepend those lines with a '>'.
231 */
232 if (strncmp(line, "From ", 5) == 0)
233 (void)fwrite(">", 1, 1, obuf); /* '>' before 'From ' */
234 if (dovis) {
235 length = strvis(visline, line, VIS_SAFE|VIS_NOSLASH);
236 (void)fwrite(visline, sizeof(*visline), length, obuf);
237 } else
238 (void)fwrite(line, sizeof(*line), c, obuf);
239 if (ferror(obuf) || sendsignal == SIGINT)
240 goto out;
241 }
242 if (doign == ignoreall && c > 0 && line[c - 1] != '\n')
243 /* no final blank line */
244 if ((c = getc(ibuf)) != EOF && putc(c, obuf) == EOF)
245 goto out;
246 rval = 0;
247 out:
248 sendsignal = 0;
249 (void)sigprocmask(SIG_SETMASK, &oset, NULL);
250 (void)sigaction(SIGINT, &saveint, NULL);
251 return(rval);
252 }
253
254 /*
255 * Output a reasonable looking status field.
256 */
257 int
statusput(struct message * mp,FILE * obuf,char * prefix)258 statusput(struct message *mp, FILE *obuf, char *prefix)
259 {
260 char statout[3];
261 char *cp = statout;
262
263 if (mp->m_flag & MREAD)
264 *cp++ = 'R';
265 if ((mp->m_flag & MNEW) == 0)
266 *cp++ = 'O';
267 *cp = 0;
268 if (statout[0]) {
269 fprintf(obuf, "%sStatus: %s\n",
270 prefix == NULL ? "" : prefix, statout);
271 return(ferror(obuf) ? -1 : 0);
272 }
273 return(0);
274 }
275
276 /*
277 * Interface between the argument list and the mail1 routine
278 * which does all the dirty work.
279 */
280 int
mail(struct name * to,struct name * cc,struct name * bcc,struct name * smopts,char * fromaddr,char * subject)281 mail(struct name *to, struct name *cc, struct name *bcc, struct name *smopts,
282 char *fromaddr, char *subject)
283 {
284 struct header head;
285
286 head.h_to = to;
287 head.h_from = fromaddr;
288 head.h_subject = subject;
289 head.h_cc = cc;
290 head.h_bcc = bcc;
291 head.h_smopts = smopts;
292 mail1(&head, 0);
293 return(0);
294 }
295
296 /*
297 * Send mail to a bunch of user names. The interface is through
298 * the mail routine below.
299 */
300 int
sendmail(void * v)301 sendmail(void *v)
302 {
303 char *str = v;
304 struct header head;
305
306 head.h_to = extract(str, GTO);
307 head.h_from = NULL;
308 head.h_subject = NULL;
309 head.h_cc = NULL;
310 head.h_bcc = NULL;
311 head.h_smopts = NULL;
312 mail1(&head, 0);
313 return(0);
314 }
315
316 /*
317 * Mail a message on standard input to the people indicated
318 * in the passed header. (Internal interface).
319 */
320 void
mail1(struct header * hp,int printheaders)321 mail1(struct header *hp, int printheaders)
322 {
323 char *cp, *envfrom = NULL;
324 char *argv[8];
325 char **ap = argv;
326 pid_t pid;
327 struct name *to;
328 FILE *mtf;
329
330 /*
331 * Collect user's mail from standard input.
332 * Get the result as mtf.
333 */
334 if ((mtf = collect(hp, printheaders)) == NULL)
335 return;
336 if (fsize(mtf) == 0) {
337 if (value("skipempty") != NULL)
338 goto out;
339 if (hp->h_subject == NULL || *hp->h_subject == '\0')
340 puts("No message, no subject; hope that's ok");
341 else
342 puts("Null message body; hope that's ok");
343 }
344 /*
345 * Now, take the user names from the combined
346 * to and cc lists and do all the alias
347 * processing.
348 */
349 senderr = 0;
350 to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc)));
351 if (to == NULL) {
352 puts("No recipients specified");
353 senderr++;
354 }
355 /*
356 * Look through the recipient list for names with /'s
357 * in them which we write to as files directly.
358 */
359 to = outof(to, mtf, hp);
360 if (senderr)
361 savedeadletter(mtf);
362 to = elide(to);
363 if (count(to) == 0)
364 goto out;
365 fixhead(hp, to);
366 if ((mtf = infix(hp, mtf)) == NULL) {
367 fputs(". . . message lost, sorry.\n", stderr);
368 return;
369 }
370 if ((cp = value("record")) != NULL)
371 (void)savemail(expand(cp), mtf);
372
373 /* Setup sendmail arguments. */
374 *ap++ = "sendmail";
375 *ap++ = "-i";
376 *ap++ = "-t";
377 cp = hp->h_from ? hp->h_from : value("from");
378 if (cp != NULL) {
379 envfrom = skin(cp);
380 *ap++ = "-f";
381 *ap++ = envfrom;
382 if (envfrom == cp)
383 envfrom = NULL;
384 }
385 if (value("metoo") != NULL)
386 *ap++ = "-m";
387 if (value("verbose") != NULL)
388 *ap++ = "-v";
389 *ap = NULL;
390 if (debug) {
391 fputs("Sendmail arguments:", stdout);
392 for (ap = argv; *ap != NULL; ap++)
393 printf(" \"%s\"", *ap);
394 putchar('\n');
395 goto out;
396 }
397 /*
398 * Fork, set up the temporary mail file as standard
399 * input for "mail", and exec with the user list we generated
400 * far above.
401 */
402 pid = fork();
403 if (pid == -1) {
404 warn("fork");
405 savedeadletter(mtf);
406 goto out;
407 }
408 if (pid == 0) {
409 sigset_t nset;
410
411 sigemptyset(&nset);
412 sigaddset(&nset, SIGHUP);
413 sigaddset(&nset, SIGINT);
414 sigaddset(&nset, SIGQUIT);
415 sigaddset(&nset, SIGTSTP);
416 sigaddset(&nset, SIGTTIN);
417 sigaddset(&nset, SIGTTOU);
418 prepare_child(&nset, fileno(mtf), -1);
419 if ((cp = value("sendmail")) != NULL)
420 cp = expand(cp);
421 else
422 cp = _PATH_SENDMAIL;
423 execv(cp, argv);
424 warn("%s", cp);
425 _exit(1);
426 }
427 free(envfrom);
428 if (value("verbose") != NULL)
429 (void)wait_child(pid);
430 else
431 free_child(pid);
432 out:
433 (void)Fclose(mtf);
434 }
435
436 /*
437 * Fix the header by glopping all of the expanded names from
438 * the distribution list into the appropriate fields.
439 */
440 void
fixhead(struct header * hp,struct name * tolist)441 fixhead(struct header *hp, struct name *tolist)
442 {
443 struct name *np;
444
445 hp->h_to = NULL;
446 hp->h_cc = NULL;
447 hp->h_bcc = NULL;
448 for (np = tolist; np != NULL; np = np->n_flink)
449 if ((np->n_type & GMASK) == GTO)
450 hp->h_to =
451 cat(hp->h_to, nalloc(np->n_name, np->n_type));
452 else if ((np->n_type & GMASK) == GCC)
453 hp->h_cc =
454 cat(hp->h_cc, nalloc(np->n_name, np->n_type));
455 else if ((np->n_type & GMASK) == GBCC)
456 hp->h_bcc =
457 cat(hp->h_bcc, nalloc(np->n_name, np->n_type));
458 }
459
460 /*
461 * Prepend a header in front of the collected stuff
462 * and return the new file.
463 */
464 FILE *
infix(struct header * hp,FILE * fi)465 infix(struct header *hp, FILE *fi)
466 {
467 FILE *nfo, *nfi;
468 int c, fd;
469 char tempname[PATHSIZE];
470
471 (void)snprintf(tempname, sizeof(tempname),
472 "%s/mail.RsXXXXXXXXXX", tmpdir);
473 if ((fd = mkstemp(tempname)) == -1 ||
474 (nfo = Fdopen(fd, "w")) == NULL) {
475 warn("%s", tempname);
476 return(fi);
477 }
478 if ((nfi = Fopen(tempname, "r")) == NULL) {
479 warn("%s", tempname);
480 (void)Fclose(nfo);
481 (void)rm(tempname);
482 return(fi);
483 }
484 (void)rm(tempname);
485 (void)puthead(hp, nfo, GTO|GSUBJECT|GCC|GBCC|GNL|GCOMMA);
486 c = getc(fi);
487 while (c != EOF) {
488 (void)putc(c, nfo);
489 c = getc(fi);
490 }
491 if (ferror(fi)) {
492 warn("read");
493 rewind(fi);
494 return(fi);
495 }
496 (void)fflush(nfo);
497 if (ferror(nfo)) {
498 warn("%s", tempname);
499 (void)Fclose(nfo);
500 (void)Fclose(nfi);
501 rewind(fi);
502 return(fi);
503 }
504 (void)Fclose(nfo);
505 (void)Fclose(fi);
506 rewind(nfi);
507 return(nfi);
508 }
509
510 /*
511 * Dump the to, subject, cc header on the
512 * passed file buffer.
513 */
514 int
puthead(struct header * hp,FILE * fo,int w)515 puthead(struct header *hp, FILE *fo, int w)
516 {
517 int gotcha;
518 char *from;
519
520 gotcha = 0;
521 from = hp->h_from ? hp->h_from : value("from");
522 if (from != NULL)
523 fprintf(fo, "From: %s\n", from), gotcha++;
524 if (hp->h_to != NULL && w & GTO)
525 fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++;
526 if (hp->h_subject != NULL && w & GSUBJECT)
527 fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
528 if (hp->h_cc != NULL && w & GCC)
529 fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++;
530 if (hp->h_bcc != NULL && w & GBCC)
531 fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++;
532 if (gotcha && w & GNL)
533 (void)putc('\n', fo);
534 return(0);
535 }
536
537 /*
538 * Format the given header line to not exceed 72 characters.
539 */
540 void
fmt(char * str,struct name * np,FILE * fo,int comma)541 fmt(char *str, struct name *np, FILE *fo, int comma)
542 {
543 int col, len;
544
545 comma = comma ? 1 : 0;
546 col = strlen(str);
547 if (col)
548 fputs(str, fo);
549 for (; np != NULL; np = np->n_flink) {
550 if (np->n_flink == NULL)
551 comma = 0;
552 len = strlen(np->n_name);
553 col++; /* for the space */
554 if (col + len + comma > 72 && col > 4) {
555 fputs("\n ", fo);
556 col = 4;
557 } else
558 putc(' ', fo);
559 fputs(np->n_name, fo);
560 if (comma)
561 putc(',', fo);
562 col += len + comma;
563 }
564 putc('\n', fo);
565 }
566
567 /*
568 * Save the outgoing mail on the passed file.
569 */
570 int
savemail(char * name,FILE * fi)571 savemail(char *name, FILE *fi)
572 {
573 FILE *fo;
574 char buf[BUFSIZ];
575 time_t now;
576 mode_t m;
577
578 m = umask(077);
579 fo = Fopen(name, "a");
580 (void)umask(m);
581 if (fo == NULL) {
582 warn("%s", name);
583 return(-1);
584 }
585 (void)time(&now);
586 fprintf(fo, "From %s %s", myname, ctime(&now));
587 while (fgets(buf, sizeof(buf), fi) == buf) {
588 /*
589 * We can't read the record file (or inbox for recipient)
590 * in the message body (from forwarded messages or sentences
591 * starting with "From "), so we will prepend those lines with
592 * a '>'.
593 */
594 if (strncmp(buf, "From ", 5) == 0)
595 (void)fwrite(">", 1, 1, fo); /* '>' before 'From ' */
596 (void)fwrite(buf, 1, strlen(buf), fo);
597 }
598 (void)putc('\n', fo);
599 (void)fflush(fo);
600 if (ferror(fo))
601 warn("%s", name);
602 (void)Fclose(fo);
603 rewind(fi);
604 return(0);
605 }
606
607 void
sendint(int s)608 sendint(int s)
609 {
610
611 sendsignal = s;
612 }
613