Home | History | Annotate | Line # | Download | only in mail
      1 /*	$NetBSD: main.c,v 1.32 2025/07/09 16:59:54 rillig Exp $	*/
      2 
      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 #include <sys/cdefs.h>
     33 #ifndef lint
     34 __COPYRIGHT("@(#) Copyright (c) 1980, 1993\
     35  The Regents of the University of California.  All rights reserved.");
     36 #endif /* not lint */
     37 
     38 #ifndef lint
     39 #if 0
     40 static char sccsid[] = "@(#)main.c	8.2 (Berkeley) 4/20/95";
     41 #else
     42 __RCSID("$NetBSD: main.c,v 1.32 2025/07/09 16:59:54 rillig Exp $");
     43 #endif
     44 #endif /* not lint */
     45 
     46 #define EXTERN
     47 #include "rcv.h"
     48 #undef EXTERN
     49 #include <assert.h>
     50 #include <util.h>
     51 
     52 #include "extern.h"
     53 #include "sig.h"
     54 
     55 #ifdef USE_EDITLINE
     56 #include "complete.h"
     57 #endif
     58 #include "format.h"
     59 #ifdef MIME_SUPPORT
     60 #include "mime.h"
     61 #endif
     62 #ifdef THREAD_SUPPORT
     63 #include "thread.h"
     64 #endif
     65 
     66 /*
     67  * Mail -- a mail program
     68  *
     69  * Startup -- interface with user.
     70  */
     71 
     72 __dead
     73 static void
     74 usage(void)
     75 {
     76 #ifdef MIME_SUPPORT
     77 	(void)fputs("\
     78 Usage: mail [-EiInv] [-r rcfile] [-s subject] [-a file] [-c cc-addr]\n\
     79             [-b bcc-addr] to-addr ... [- sendmail-options ...]\n\
     80        mail [-EiInNv] [-H[colon-modifier]] -f [name]\n\
     81        mail [-EiInNv] [-H[colon-modifier]] [-u user]\n",
     82 				stderr);
     83 #else /* MIME_SUPPORT */
     84 	(void)fputs("\
     85 Usage: mail [-EiInv] [-r rcfile] [-s subject] [-c cc-addr] [-b bcc-addr]\n\
     86             to-addr ... [- sendmail-options ...]\n\
     87        mail [-EiInNv] [-H[colon-modifier]] -f [name]\n\
     88        mail [-EiInNv] [-H[colon-modifier]] [-u user]\n",
     89 				stderr);
     90 #endif /* MIME_SUPPORT */
     91 	exit(1);
     92 }
     93 
     94 /*
     95  * Compute what the screen size for printing headers should be.
     96  * We use the following algorithm for the height:
     97  *	If baud rate < 1200, use  9
     98  *	If baud rate = 1200, use 14
     99  *	If baud rate > 1200, use 24 or ws_row
    100  * Width is either 80 or ws_col;
    101  */
    102 PUBLIC void
    103 setscreensize(void)
    104 {
    105 	struct termios tbuf;
    106 	struct winsize ws;
    107 	speed_t ospeed;
    108 	char *cp;
    109 
    110 	if (ioctl(1, TIOCGWINSZ, &ws) < 0)
    111 		ws.ws_col = ws.ws_row = 0;
    112 	if (tcgetattr(1, &tbuf) < 0)
    113 		ospeed = 9600;
    114 	else
    115 		ospeed = cfgetospeed(&tbuf);
    116 	if (ospeed < 1200)
    117 		screenheight = 9;
    118 	else if (ospeed == 1200)
    119 		screenheight = 14;
    120 	else if (ws.ws_row != 0)
    121 		screenheight = ws.ws_row;
    122 	else
    123 		screenheight = 24;
    124 	if ((realscreenheight = ws.ws_row) == 0)
    125 		realscreenheight = 24;
    126 	if ((screenwidth = ws.ws_col) == 0)
    127 		screenwidth = 80;
    128 	/*
    129 	 * Possible overrides from the rcfile.
    130 	 */
    131 	if ((cp = value(ENAME_SCREENWIDTH)) != NULL) {
    132 		int width;
    133 		width = *cp ? atoi(cp) : 0;
    134 		if (width >= 0)
    135 			screenwidth = width;
    136 	}
    137 	if ((cp = value(ENAME_SCREENHEIGHT)) != NULL) {
    138 		int height;
    139 		height = *cp ? atoi(cp) : 0;
    140 		if (height >= 0) {
    141 			realscreenheight = height;
    142 			screenheight = height;
    143 		}
    144 	}
    145 }
    146 
    147 /*
    148  * Break up a white-space or comma delimited name list so that aliases
    149  * can get expanded.  Without this, the CC: or BCC: list is broken too
    150  * late for alias expansion to occur.
    151  */
    152 PUBLIC struct name *
    153 lexpand(char *str, int ntype)
    154 {
    155 	char *list;
    156 	struct name *np = NULL;
    157 	char *word, *p;
    158 
    159 	list = estrdup(str);
    160 	word = list;
    161 	for (word = list; *word; word = p) {
    162 		word = skip_WSP(word);
    163 		for (p = word;
    164 		     *p && !is_WSP(*p) && *p != ',';
    165 		     p++)
    166 			continue;
    167 		if (*p)
    168 			*p++ = '\0';
    169 		np = cat(np, nalloc(word, ntype));
    170 	}
    171 
    172 	free(list);
    173 	return np;
    174 }
    175 
    176 PUBLIC int
    177 main(int argc, char *argv[])
    178 {
    179 	jmp_buf jmpbuf;
    180 	struct sigaction sa;
    181 	struct name *to, *cc, *bcc, *smopts;
    182 #ifdef MIME_SUPPORT
    183 	struct name *attach_optargs;
    184 	struct name *attach_end;
    185 #endif
    186 	char *subject;
    187 	const char *ef;
    188 	char nosrc = 0;
    189 	const char *rc;
    190 	int Hflag;
    191 	int i;
    192 
    193 	/*
    194 	 * For portability, call setprogname() early, before
    195 	 * getprogname() is called.
    196 	 */
    197 	(void)setprogname(argv[0]);
    198 
    199 	/*
    200 	 * Set up a reasonable environment.
    201 	 * Figure out whether we are being run interactively,
    202 	 * start the SIGCHLD catcher, and so forth.
    203 	 * (Other signals are setup later by sig_setup().)
    204 	 */
    205 	(void)sigemptyset(&sa.sa_mask);
    206 	sa.sa_flags = SA_RESTART;
    207 	sa.sa_handler = sigchild;
    208 	(void)sigaction(SIGCHLD, &sa, NULL);
    209 
    210 	if (isatty(0))
    211 		assign(ENAME_INTERACTIVE, "");
    212 	image = -1;
    213 
    214 	/*
    215 	 * Now, determine how we are being used.
    216 	 * We successively pick off - flags.
    217 	 * If there is anything left, it is the base of the list
    218 	 * of users to mail to.  Argp will be set to point to the
    219 	 * first of these users.
    220 	 */
    221 	rc = NULL;
    222 	ef = NULL;
    223 	to = NULL;
    224 	cc = NULL;
    225 	bcc = NULL;
    226 	smopts = NULL;
    227 	subject = NULL;
    228 	Hflag = 0;
    229 #ifdef MIME_SUPPORT
    230 	attach_optargs = NULL;
    231 	attach_end = NULL;
    232 	while ((i = getopt(argc, argv, ":~EH:INT:a:b:c:dfinr:s:u:v")) != -1)
    233 #else
    234 	while ((i = getopt(argc, argv, ":~EH:INT:b:c:dfinr:s:u:v")) != -1)
    235 #endif
    236 	{
    237 		switch (i) {
    238 		case 'T':
    239 			/*
    240 			 * Next argument is temp file to write which
    241 			 * articles have been read/deleted for netnews.
    242 			 */
    243 			Tflag = optarg;
    244 			if ((i = creat(Tflag, 0600)) < 0) {
    245 				warn("%s", Tflag);
    246 				exit(1);
    247 			}
    248 			(void)close(i);
    249 			break;
    250 #ifdef MIME_SUPPORT
    251 		case 'a': {
    252 			struct name *np;
    253 			np = nalloc(optarg, 0);
    254 			if (attach_end == NULL)
    255 				attach_optargs = np;
    256 			else {
    257 				np->n_blink = attach_end;
    258 				attach_end->n_flink = np;
    259 			}
    260 			attach_end = np;
    261 			break;
    262 		}
    263 #endif
    264 		case 'u':
    265 			/*
    266 			 * Next argument is person to pretend to be.
    267 			 */
    268 			myname = optarg;
    269 			(void)unsetenv("MAIL");
    270 			break;
    271 		case 'i':
    272 			/*
    273 			 * User wants to ignore interrupts.
    274 			 * Set the variable "ignore"
    275 			 */
    276 			assign(ENAME_IGNORE, "");
    277 			break;
    278 		case 'd':
    279 			debug++;
    280 			break;
    281 		case 'r':
    282 			rc = optarg;
    283 			break;
    284 		case 's':
    285 			/*
    286 			 * Give a subject field for sending from
    287 			 * non terminal
    288 			 */
    289 			subject = optarg;
    290 			break;
    291 		case 'f':
    292 			/*
    293 			 * User is specifying file to "edit" with Mail,
    294 			 * as opposed to reading system mailbox.
    295 			 * If no argument is given after -f, we read his
    296 			 * mbox file.
    297 			 *
    298 			 * getopt() can't handle optional arguments, so here
    299 			 * is an ugly hack to get around it.
    300 			 */
    301 			if ((argv[optind]) && (argv[optind][0] != '-'))
    302 				ef = argv[optind++];
    303 			else
    304 				ef = "&";
    305 			break;
    306 		case 'H':
    307 			/*
    308 			 * Print out the headers and quit.
    309 			 */
    310 			Hflag = get_Hflag(argv);
    311 			break;
    312 		case 'n':
    313 			/*
    314 			 * User doesn't want to source /usr/lib/Mail.rc
    315 			 */
    316 			nosrc++;
    317 			break;
    318 		case 'N':
    319 			/*
    320 			 * Avoid initial header printing.
    321 			 */
    322 			assign(ENAME_NOHEADER, "");
    323 			break;
    324 		case 'v':
    325 			/*
    326 			 * Send mailer verbose flag
    327 			 */
    328 			assign(ENAME_VERBOSE, "");
    329 			break;
    330 		case 'I':
    331 		case '~':
    332 			/*
    333 			 * We're interactive
    334 			 */
    335 			assign(ENAME_INTERACTIVE, "");
    336 			break;
    337 		case 'c':
    338 			/*
    339 			 * Get Carbon Copy Recipient list
    340 			 */
    341 			cc = cat(cc, lexpand(optarg, GCC));
    342 			break;
    343 		case 'b':
    344 			/*
    345 			 * Get Blind Carbon Copy Recipient list
    346 			 */
    347 			bcc = cat(bcc, lexpand(optarg, GBCC));
    348 
    349 			break;
    350 		case 'E':
    351 			/*
    352 			 * Don't send empty files.
    353 			 */
    354 			assign(ENAME_DONTSENDEMPTY, "");
    355 			break;
    356 		case ':':
    357 			/*
    358 			 * An optarg was expected but not found.
    359 			 */
    360 			if (optopt == 'H') {
    361 				Hflag = get_Hflag(NULL);
    362 				break;
    363 			}
    364 			(void)fprintf(stderr,
    365 			    "%s: option requires an argument -- %c\n",
    366 			    getprogname(), optopt);
    367 
    368 			/* FALLTHROUGH */
    369 		case '?':
    370 			/*
    371 			 * An unknown option flag.  We need to do the
    372 			 * error message.
    373 			 */
    374 			if (optopt != '?')
    375 				(void)fprintf(stderr,
    376 				    "%s: unknown option -- %c\n", getprogname(),
    377 				    optopt);
    378 			usage();	/* print usage message and die */
    379 			/*NOTREACHED*/
    380 		}
    381 	}
    382 	for (i = optind; (argv[i]) && (*argv[i] != '-'); i++)
    383 		to = cat(to, nalloc(argv[i], GTO));
    384 	for (/*EMPTY*/; argv[i]; i++)
    385 		smopts = cat(smopts, nalloc(argv[i], GSMOPTS));
    386 	/*
    387 	 * Check for inconsistent arguments.
    388 	 */
    389 	if (to == NULL && (subject != NULL || cc != NULL || bcc != NULL))
    390 		errx(EXIT_FAILURE, "You must specify direct recipients with -s, -c, or -b.");
    391 	if (ef != NULL && to != NULL) {
    392 		errx(EXIT_FAILURE, "Cannot give -f and people to send to.");
    393 	}
    394 	if (Hflag != 0 && to != NULL)
    395 		errx(EXIT_FAILURE, "Cannot give -H and people to send to.");
    396 #ifdef MIME_SUPPORT
    397 	if (attach_optargs != NULL && to == NULL)
    398 		errx(EXIT_FAILURE, "Cannot give -a without people to send to.");
    399 #endif
    400 	tinit();	/* must be done before loading the rcfile */
    401 	input = stdin;
    402 	mailmode = Hflag ? mm_hdrsonly :
    403 	    to ? mm_sending : mm_receiving;
    404 
    405 	spreserve();
    406 	if (!nosrc)
    407 		load(_PATH_MASTER_RC);
    408 	/*
    409 	 * Expand returns a savestr, but load only uses the file name
    410 	 * for fopen, so it's safe to do this.
    411 	 */
    412 	if (rc == NULL && (rc = getenv("MAILRC")) == NULL)
    413 		rc = "~/.mailrc";
    414 	load(expand(rc));
    415 	setscreensize();	/* do this after loading the rcfile */
    416 
    417 #ifdef USE_EDITLINE
    418 	/*
    419 	 * This is after loading the MAILRC so we can use value().
    420 	 * Avoid editline in mm_hdrsonly mode or pipelines will screw
    421 	 * up.  XXX - there must be a better way!
    422 	 */
    423 	if (mailmode != mm_hdrsonly)
    424 		init_editline();
    425 #endif
    426 
    427 	sig_setup();
    428 
    429 	switch (mailmode) {
    430 	case mm_sending:
    431 		(void)mail(to, cc, bcc, smopts, subject,
    432 		    mime_attach_optargs(attach_optargs));
    433 		/*
    434 		 * why wait?
    435 		 */
    436 		exit(senderr);
    437 
    438 	case mm_receiving:
    439 	case mm_hdrsonly:
    440 		/*
    441 		 * Ok, we are reading mail.
    442 		 * Decide whether we are editing a mailbox or reading
    443 		 * the system mailbox, and open up the right stuff.
    444 		 */
    445 		if (ef == NULL)
    446 			ef = "%";
    447 		if (setfile(ef) < 0)
    448 			exit(1);		/* error already reported */
    449 		if (value(ENAME_QUIET) == NULL)
    450 			(void)printf("Mail version %s.  Type ? for help.\n",
    451 			    version);
    452 		if (mailmode == mm_hdrsonly)
    453 			show_headers_and_exit(Hflag);	/* NORETURN */
    454 		announce();
    455 		(void)fflush(stdout);
    456 
    457 		if (setjmp(jmpbuf) != 0) {
    458 			/* Return here if quit() fails below. */
    459 			(void)printf("Use 'exit' to quit without saving changes.\n");
    460 		}
    461 		commands();
    462 
    463 		/* Ignore these signals from now on! */
    464 		(void)signal(SIGHUP, SIG_IGN);
    465 		(void)signal(SIGINT, SIG_IGN);
    466 		(void)signal(SIGQUIT, SIG_IGN);
    467 		quit(jmpbuf);
    468 		break;
    469 
    470 	default:
    471 		assert(/*CONSTCOND*/0);
    472 		break;
    473 	}
    474 
    475 	return 0;
    476 }
    477