Home | History | Annotate | Download | only in usr.sbin
      1 /*
      2  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
      3  * Use is subject to license terms.
      4  */
      5 
      6 /*
      7  * Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T
      8  * All Rights Reserved.
      9  */
     10 
     11 /*
     12  * Copyright (c) 1980 Regents of the University of California.
     13  * All rights reserved.
     14  *
     15  * Redistribution and use in source and binary forms are permitted provided
     16  * that: (1) source distributions retain this entire copyright notice and
     17  * comment, and (2) distributions including binaries display the following
     18  * acknowledgement: ``This product includes software developed by the
     19  * University of California, Berkeley and its contributors'' in the
     20  * documentation or other materials provided with the distribution and in
     21  * all advertising materials mentioning features or use of this software.
     22  * Neither the name of the University nor the names of its contributors may
     23  * be used to endorse or promote products derived from this software without
     24  * specific prior written permission.
     25  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
     26  * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
     27  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
     28  */
     29 
     30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     31 
     32 
     33 #include <sys/types.h>
     34 #include <sys/socket.h>
     35 #include <sys/stat.h>
     36 #include <sys/wait.h>
     37 #include <sys/file.h>
     38 #include <fcntl.h>
     39 #include <ctype.h>
     40 #include <string.h>
     41 
     42 #include <netinet/in.h>
     43 
     44 #include <stdio.h>
     45 #include <sys/ttold.h>
     46 #include <utmpx.h>
     47 #include <signal.h>
     48 #include <errno.h>
     49 #include <sys/param.h>	/* for MAXHOSTNAMELEN */
     50 #include <netdb.h>
     51 #include <syslog.h>
     52 #include <sys/ioctl.h>
     53 #include <pwd.h>
     54 
     55 /*
     56  * comsat
     57  */
     58 
     59 
     60 #ifndef UTMPX_FILE
     61 #define	UTMPX_FILE "/etc/utmpx"
     62 #endif	/* UTMPX_FILE */
     63 
     64 int	debug = 0;
     65 #define	dsyslog	if (debug) syslog
     66 
     67 struct	sockaddr_in sin = { AF_INET };
     68 
     69 char	hostname[MAXHOSTNAMELEN];
     70 struct	utmpx *utmp = NULL;
     71 int	nutmp;
     72 int	uf;
     73 unsigned utmpmtime = 0;			/* last modification time for utmp */
     74 unsigned utmpsize = 0;			/* last malloced size for utmp */
     75 time_t	lastmsgtime;
     76 
     77 #ifndef SYSV
     78 int	reapchildren();
     79 #else
     80 
     81 #define	rindex strrchr
     82 #define	index strchr
     83 #define	signal(s, f)	sigset((s), (f))
     84 
     85 #ifndef sigmask
     86 #define	sigmask(m)	(1 << ((m)-1))
     87 #endif
     88 
     89 #define	set2mask(setp)	((setp)->__sigbits[0])
     90 #define	mask2set(mask, setp) \
     91 	((mask) == -1 ? sigfillset(setp) : (((setp)->__sigbits[0]) = (mask)))
     92 
     93 static int
     94 sigsetmask(mask)
     95 int mask;
     96 {
     97 	sigset_t oset;
     98 	sigset_t nset;
     99 
    100 	(void) sigprocmask(0, (sigset_t *)0, &nset);
    101 	mask2set(mask, &nset);
    102 	(void) sigprocmask(SIG_SETMASK, &nset, &oset);
    103 	return (set2mask(&oset));
    104 }
    105 
    106 static int
    107 sigblock(mask)
    108 int mask;
    109 {
    110 	sigset_t oset;
    111 	sigset_t nset;
    112 
    113 	(void) sigprocmask(0, (sigset_t *)0, &nset);
    114 	mask2set(mask, &nset);
    115 	(void) sigprocmask(SIG_BLOCK, &nset, &oset);
    116 	return (set2mask(&oset));
    117 }
    118 
    119 #endif /* SYSV */
    120 
    121 
    122 #define	MAXIDLE	120
    123 #define	NAMLEN (sizeof (uts[0].ut_name) + 1)
    124 
    125 void jkfprintf(FILE *tp, char *name, int mbox, int offset);
    126 void mailfor(char *name);
    127 void notify(struct utmpx *utp, int offset);
    128 void onalrm(int sig);
    129 
    130 int
    131 main(argc, argv)
    132 int argc;
    133 char *argv[];
    134 {
    135 	register int cc;
    136 	char buf[BUFSIZ];
    137 	char msgbuf[100];
    138 	struct sockaddr_in from;
    139 	socklen_t fromlen;
    140 	int c;
    141 	extern int optind;
    142 	extern int getopt();
    143 	extern char *optarg;
    144 
    145 	openlog("comsat", 0, LOG_DAEMON);
    146 
    147 	while ((c = getopt(argc, argv, "d")) != -1) {
    148 		switch ((char)c) {
    149 		case'd':
    150 			debug++;
    151 			break;
    152 		default:
    153 			syslog(LOG_ERR, "invalid argument %s", argv[optind]);
    154 			exit(1);
    155 		}
    156 	}
    157 
    158 	/* verify proper invocation */
    159 	fromlen = (socklen_t)sizeof (from);
    160 	if (getsockname(0, (struct sockaddr *)&from, &fromlen) < 0) {
    161 		fprintf(stderr, "%s: ", argv[0]);
    162 		perror("getsockname");
    163 		_exit(1);
    164 	}
    165 
    166 #ifdef SYSV
    167 	chdir("/var/mail");
    168 #else
    169 	chdir("/var/spool/mail");
    170 #endif /* SYSV */
    171 	if ((uf = open(UTMPX_FILE, 0)) < 0) {
    172 		syslog(LOG_ERR, "%s: %m", UTMPX_FILE);
    173 		(void) recv(0, msgbuf, sizeof (msgbuf) - 1, 0);
    174 		exit(1);
    175 	}
    176 	(void) time(&lastmsgtime);
    177 	(void) gethostname(hostname, sizeof (hostname));
    178 	onalrm(0);
    179 	(void) signal(SIGALRM, onalrm);
    180 	(void) signal(SIGTTOU, SIG_IGN);
    181 #ifndef SYSV
    182 	(void) signal(SIGCHLD, reapchildren);
    183 #else
    184 	(void) signal(SIGCHLD, SIG_IGN); /* no zombies */
    185 #endif /* SYSV */
    186 	for (;;) {
    187 		cc = recv(0, msgbuf, sizeof (msgbuf) - 1, 0);
    188 		if (cc <= 0) {
    189 			if (errno != EINTR)
    190 				sleep(1);
    191 			errno = 0;
    192 			continue;
    193 		}
    194 		if (nutmp == 0)			/* no users (yet) */
    195 			continue;
    196 		sigblock(sigmask(SIGALRM));
    197 		msgbuf[cc] = 0;
    198 		(void) time(&lastmsgtime);
    199 		mailfor(msgbuf);
    200 		sigsetmask(0);
    201 	}
    202 }
    203 
    204 #ifndef SYSV
    205 reapchildren()
    206 {
    207 
    208 	while (wait3((struct wait *)0, WNOHANG, (struct rusage *)0) > 0)
    209 		;
    210 }
    211 #endif /* SYSV */
    212 
    213 /* ARGSUSED */
    214 void
    215 onalrm(int sig)
    216 {
    217 	struct stat statbf;
    218 	time_t now;
    219 
    220 	(void) time(&now);
    221 	if ((ulong_t)now - (ulong_t)lastmsgtime >= MAXIDLE)
    222 		exit(0);
    223 	dsyslog(LOG_DEBUG, "alarm\n");
    224 	alarm(15);
    225 	fstat(uf, &statbf);
    226 	if (statbf.st_mtime > utmpmtime) {
    227 		dsyslog(LOG_DEBUG, " changed\n");
    228 		utmpmtime = statbf.st_mtime;
    229 		if (statbf.st_size > utmpsize) {
    230 			utmpsize = statbf.st_size + 10 * sizeof (struct utmpx);
    231 			if (utmp)
    232 				utmp = (struct utmpx *)realloc(utmp, utmpsize);
    233 			else
    234 				utmp = (struct utmpx *)malloc(utmpsize);
    235 			if (! utmp) {
    236 				dsyslog(LOG_DEBUG, "malloc failed\n");
    237 				exit(1);
    238 			}
    239 		}
    240 		lseek(uf, 0, 0);
    241 		nutmp = read(uf, utmp, statbf.st_size)/sizeof (struct utmpx);
    242 	} else
    243 		dsyslog(LOG_DEBUG, " ok\n");
    244 }
    245 
    246 void
    247 mailfor(name)
    248 char *name;
    249 {
    250 	struct utmpx *utp = &utmp[nutmp];
    251 	register char *cp;
    252 	char *rindex();
    253 	int offset;
    254 
    255 	/*
    256 	 * Don't bother doing anything if nobody is
    257 	 * logged into the system.
    258 	 */
    259 	if (utmp == NULL || nutmp == 0)
    260 		return;
    261 	dsyslog(LOG_DEBUG, "mailfor %s\n", name);
    262 	cp = name;
    263 	while (*cp && *cp != '@')
    264 		cp++;
    265 	if (*cp == 0) {
    266 		dsyslog(LOG_DEBUG, "bad format\n");
    267 		return;
    268 	}
    269 	*cp = 0;
    270 	offset = atoi(cp+1);
    271 	while (--utp >= utmp)
    272 		if ((utp->ut_type == USER_PROCESS) &&
    273 		    (!strncmp(utp->ut_name, name, sizeof (utmp[0].ut_name))))
    274 			notify(utp, offset);
    275 }
    276 
    277 char	*cr;
    278 
    279 void
    280 notify(utp, offset)
    281 struct utmpx *utp;
    282 {
    283 	FILE *tp;
    284 	struct sgttyb gttybuf;
    285 	char tty[sizeof (utmp[0].ut_line) + 5];
    286 	char name[sizeof (utmp[0].ut_name) + 1];
    287 	struct stat stb, stl;
    288 	time_t timep[2];
    289 	struct passwd *pwd;
    290 	int fd, mbox;
    291 
    292 
    293 	strcpy(tty, "/dev/");
    294 	strncat(tty, utp->ut_line, sizeof (utp->ut_line));
    295 	dsyslog(LOG_DEBUG, "notify %s on %s\n", utp->ut_name, tty);
    296 	if (stat(tty, &stb) == -1) {
    297 		dsyslog(LOG_DEBUG, "can't stat tty\n");
    298 		return;
    299 	}
    300 	if ((stb.st_mode & 0100) == 0) {
    301 		dsyslog(LOG_DEBUG, "wrong mode\n");
    302 		return;
    303 	}
    304 	if (fork())
    305 		return;
    306 	signal(SIGALRM, SIG_DFL);
    307 	alarm(30);
    308 
    309 	strncpy(name, utp->ut_name, sizeof (utp->ut_name));
    310 	name[sizeof (name) - 1] = '\0';
    311 
    312 	/*
    313 	 * Do all operations that check protections as the user who
    314 	 * will be getting the biff.
    315 	 */
    316 	if ((pwd = getpwnam(name)) == (struct passwd *)-1) {
    317 		dsyslog(LOG_DEBUG, "getpwnam failed\n");
    318 		exit(1);
    319 	}
    320 	if (setuid(pwd->pw_uid) == -1) {
    321 		dsyslog(LOG_DEBUG, "setuid failed\n");
    322 		exit(1);
    323 	}
    324 
    325 	/*
    326 	 * We need to make sure that the tty listed in the utmp
    327 	 * file really is a tty device so that a corrupted utmp
    328 	 * file doesn't cause us to over-write a real file.
    329 	 */
    330 	if ((fd = open(tty, O_RDWR)) == -1) {
    331 		dsyslog(LOG_DEBUG, "can't open tty");
    332 		exit(1);
    333 	}
    334 	if (isatty(fd) == 0) {
    335 		dsyslog(LOG_DEBUG, "line listed in utmp file is not a tty\n");
    336 		exit(1);
    337 	}
    338 
    339 	/*
    340 	 * For the case where the user getting the biff is root,
    341 	 * we need to make sure that the tty we will be sending
    342 	 * the biff to is also owned by root.
    343 	 *
    344 	 * Check after open, to prevent race on open.
    345 	 */
    346 
    347 	if (fstat(fd, &stb) != 0 || stb.st_uid != pwd->pw_uid) {
    348 		dsyslog(LOG_DEBUG,
    349 		    "tty is not owned by user getting the biff\n");
    350 		exit(1);
    351 	}
    352 
    353 	/*
    354 	 * Prevent race by doing fdopen on fd, not fopen
    355 	 * Fopen opens w/ O_CREAT, which is dangerous too
    356 	 */
    357 	if ((tp = fdopen(fd, "w")) == 0) {
    358 		dsyslog(LOG_DEBUG, "fdopen failed\n");
    359 		exit(-1);
    360 	}
    361 
    362 	if (ioctl(fd, TIOCGETP, &gttybuf) == -1) {
    363 		dsyslog(LOG_DEBUG, "ioctl TIOCGETP failed\n");
    364 		exit(1);
    365 	}
    366 	cr = (gttybuf.sg_flags&CRMOD) && !(gttybuf.sg_flags&RAW) ? "" : "\r";
    367 	fprintf(tp, "%s\n\007New mail for %s@%.*s\007 has arrived:%s\n",
    368 	    cr, name, sizeof (hostname), hostname, cr);
    369 	fprintf(tp, "----%s\n", cr);
    370 
    371 	if ((mbox = open(name, O_RDONLY)) == -1) {
    372 		dsyslog(LOG_DEBUG, "can't open mailbox for %s", name);
    373 		exit(1);
    374 	}
    375 	/*
    376 	 * In case of a worldwritable mail spool directory, we must take
    377 	 * care we don't open and read from the wrong file.
    378 	 */
    379 	if (fstat(mbox, &stb) == -1 || lstat(name, &stl) == -1) {
    380 		dsyslog(LOG_DEBUG, "stat() failed on mail file\n");
    381 		exit(1);
    382 	}
    383 
    384 	/*
    385 	 * Here we make sure that the file wasn't a hardlink or softlink
    386 	 * while we opened it and that it wasn't changed afterwards
    387 	 */
    388 	if (!S_ISREG(stl.st_mode) ||
    389 	    stl.st_dev != stb.st_dev ||
    390 	    stl.st_ino != stb.st_ino ||
    391 	    stl.st_uid != pwd->pw_uid ||
    392 	    stb.st_nlink != 1) {
    393 		dsyslog(LOG_DEBUG, "mail spool file must be plain file\n");
    394 		exit(1);
    395 	}
    396 
    397 	timep[0] = stb.st_atime;
    398 	timep[1] = stb.st_mtime;
    399 	jkfprintf(tp, name, mbox, offset);
    400 	utime(name, timep);
    401 	exit(0);
    402 }
    403 
    404 void
    405 jkfprintf(tp, name, mbox, offset)
    406 register FILE *tp;
    407 char *name;
    408 int mbox;
    409 {
    410 	register FILE *fi;
    411 	register int linecnt, charcnt;
    412 	char line[BUFSIZ];
    413 	int inheader;
    414 
    415 	dsyslog(LOG_DEBUG, "HERE %s's mail starting at %d\n",
    416 	    name, offset);
    417 	if ((fi = fdopen(mbox, "r")) == NULL) {
    418 		dsyslog(LOG_DEBUG, "Cant read the mail\n");
    419 		return;
    420 	}
    421 
    422 	fseek(fi, offset, L_SET);
    423 
    424 	/*
    425 	 * Print the first 7 lines or 560 characters of the new mail
    426 	 * (whichever comes first).  Skip header crap other than
    427 	 * From, Subject, To, and Date.
    428 	 */
    429 	linecnt = 7;
    430 	charcnt = 560;
    431 	inheader = 1;
    432 
    433 
    434 	while (fgets(line, sizeof (line), fi) != NULL) {
    435 		register char *cp;
    436 		char *index();
    437 		int cnt;
    438 		int i;
    439 
    440 		if (linecnt <= 0 || charcnt <= 0) {
    441 			fprintf(tp, "...more...%s\n", cr);
    442 			return;
    443 		}
    444 		if (strncmp(line, "From ", 5) == 0)
    445 			continue;
    446 		if (inheader && (line[0] == ' ' || line[0] == '\t'))
    447 			continue;
    448 		cp = index(line, ':');
    449 		if (cp == 0 || (index(line, ' ') && index(line, ' ') < cp))
    450 			inheader = 0;
    451 		else
    452 			cnt = cp - line;
    453 		if (inheader &&
    454 		    strncmp(line, "Date", cnt) &&
    455 		    strncmp(line, "From", cnt) &&
    456 		    strncmp(line, "Subject", cnt) &&
    457 		    strncmp(line, "To", cnt))
    458 			continue;
    459 		cp = index(line, '\n');
    460 		if (cp)
    461 			*cp = '\0';
    462 
    463 		for (i = strlen(line); i-- > 0; )
    464 			if (!isprint(line[i]))
    465 				line[i] = ' ';
    466 
    467 
    468 		fprintf(tp, "%s%s\n", line, cr);
    469 		linecnt--, charcnt -= strlen(line);
    470 	}
    471 	fprintf(tp, "----%s\n", cr);
    472 }
    473