root/libexec/comsat/comsat.c
/*-
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright (c) 1980, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/wait.h>

#include <netinet/in.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <paths.h>
#include <pwd.h>
#include <termios.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <utmpx.h>

static int      debug = 0;
#define dsyslog if (debug) syslog

#define MAXIDLE 120

static char     hostname[MAXHOSTNAMELEN];

static void     jkfprintf(FILE *, char[], off_t);
static void     mailfor(char *);
static void     notify(struct utmpx *, char[], off_t, int);
static void     reapchildren(int);

int
main(int argc __unused, char *argv[] __unused)
{
        struct sockaddr_in from;
        socklen_t fromlen;
        int cc;
        char msgbuf[256];

        /* verify proper invocation */
        fromlen = sizeof(from);
        if (getsockname(0, (struct sockaddr *)&from, &fromlen) < 0)
                err(1, "getsockname");
        openlog("comsat", LOG_PID, LOG_DAEMON);
        if (chdir(_PATH_MAILDIR)) {
                syslog(LOG_ERR, "chdir: %s: %m", _PATH_MAILDIR);
                (void) recv(0, msgbuf, sizeof(msgbuf) - 1, 0);
                exit(1);
        }
        (void)gethostname(hostname, sizeof(hostname));
        (void)signal(SIGTTOU, SIG_IGN);
        (void)signal(SIGCHLD, reapchildren);
        for (;;) {
                cc = recv(0, msgbuf, sizeof(msgbuf) - 1, 0);
                if (cc <= 0) {
                        if (errno != EINTR)
                                sleep(1);
                        errno = 0;
                        continue;
                }
                msgbuf[cc] = '\0';
                mailfor(msgbuf);
                sigsetmask(0L);
        }
}

static void
reapchildren(int signo __unused)
{
        while (wait3(NULL, WNOHANG, NULL) > 0);
}

static void
mailfor(char *name)
{
        struct utmpx *utp;
        char *cp;
        char *file;
        off_t offset;
        int folder;
        char buf[MAXPATHLEN];

        if ((cp = strchr(name, '@')) == NULL)
                return;
        *cp = '\0';
        offset = strtoll(cp + 1, NULL, 10);
        if ((cp = strchr(cp + 1, ':')) != NULL &&
            strchr((file = cp + 1), '/') == NULL) {
                snprintf(buf, sizeof(buf), "%s/%s", _PATH_MAILDIR, file);
                folder = 1;
        } else {
                snprintf(buf, sizeof(buf), "%s/%s", _PATH_MAILDIR, name);
                folder = 0;
        }
        setutxent();
        while ((utp = getutxent()) != NULL)
                if (utp->ut_type == USER_PROCESS && !strcmp(utp->ut_user, name))
                        notify(utp, buf, offset, folder);
        endutxent();
}

static const char *cr;

static void
notify(struct utmpx *utp, char file[], off_t offset, int folder)
{
        FILE *tp;
        struct stat stb;
        struct termios tio;
        struct passwd *p;
        char tty[20];
        const char *s = utp->ut_line;

        if (strncmp(s, "pts/", 4) == 0)
                s += 4;
        if (strchr(s, '/')) {
                /* A slash is an attempt to break security... */
                syslog(LOG_AUTH | LOG_NOTICE, "Unexpected `/' in `%s'",
                    utp->ut_line);
                return;
        }
        (void)snprintf(tty, sizeof(tty), "%s%s", _PATH_DEV, utp->ut_line);
        if (stat(tty, &stb) == -1 || !(stb.st_mode & (S_IXUSR | S_IXGRP))) {
                dsyslog(LOG_DEBUG, "%s: wrong mode on %s", utp->ut_user, tty);
                return;
        }
        dsyslog(LOG_DEBUG, "notify %s on %s", utp->ut_user, tty);
        switch (fork()) {
        case -1:
                syslog(LOG_NOTICE, "fork failed (%m)");
                return;
        case 0:
                break;
        default:
                return;
        }
        if ((tp = fopen(tty, "w")) == NULL) {
                dsyslog(LOG_ERR, "%s: %s", tty, strerror(errno));
                _exit(1);
        }
        (void)tcgetattr(fileno(tp), &tio);
        cr = ((tio.c_oflag & (OPOST|ONLCR)) == (OPOST|ONLCR)) ?  "\n" : "\n\r";

        /* Set uid/gid/groups to user's in case mail drop is on nfs */
        if ((p = getpwnam(utp->ut_user)) == NULL ||
            initgroups(p->pw_name, p->pw_gid) == -1 ||
            setgid(p->pw_gid) == -1 ||
            setuid(p->pw_uid) == -1)
                _exit(1);

        if (stb.st_mode & S_IXUSR) {
                (void)fprintf(tp, 
                    "%s\007New mail for %s@%.*s\007 has arrived%s%s%s:%s----%s",
                    cr, utp->ut_user, (int)sizeof(hostname), hostname,
                    folder ? cr : "", folder ? "to " : "", folder ? file : "",
                    cr, cr);
                jkfprintf(tp, file, offset);
        } else if (stb.st_mode & S_IXGRP) {
                (void)fprintf(tp, "\007");
                (void)fflush(tp);      
                (void)sleep(1);
                (void)fprintf(tp, "\007");
        }       
        (void)fclose(tp);
        _exit(0);
}

static void
jkfprintf(FILE *tp, char file[], off_t offset)
{
        unsigned char *cp, ch;
        FILE *fi;
        int linecnt, charcnt, inheader;
        unsigned char line[BUFSIZ];

        if ((fi = fopen(file, "r")) == NULL)
                return;

        (void)fseeko(fi, offset, SEEK_CUR);
        /*
         * Print the first 7 lines or 560 characters of the new mail
         * (whichever comes first).  Skip header crap other than
         * From, Subject, To, and Date.
         */
        linecnt = 7;
        charcnt = 560;
        inheader = 1;
        while (fgets(line, sizeof(line), fi) != NULL) {
                if (inheader) {
                        if (line[0] == '\n') {
                                inheader = 0;
                                continue;
                        }
                        if (line[0] == ' ' || line[0] == '\t' ||
                            (strncmp(line, "From:", 5) &&
                            strncmp(line, "Subject:", 8)))
                                continue;
                }
                if (linecnt <= 0 || charcnt <= 0) {
                        (void)fprintf(tp, "...more...%s", cr);
                        (void)fclose(fi);
                        return;
                }
                /* strip weird stuff so can't trojan horse stupid terminals */
                for (cp = line; (ch = *cp) && ch != '\n'; ++cp, --charcnt) {
                        /* disable upper controls and enable all other
                           8bit codes due to lack of locale knowledge
                         */
                        if (((ch & 0x80) && ch < 0xA0) ||
                            (!(ch & 0x80) && !isprint(ch) &&
                             !isspace(ch) && ch != '\a' && ch != '\b')
                           ) {
                                if (ch & 0x80) {
                                        ch &= ~0x80;
                                        (void)fputs("M-", tp);
                                }
                                if (iscntrl(ch)) {
                                        ch ^= 0x40;
                                        (void)fputc('^', tp);
                                }
                        }
                        (void)fputc(ch, tp);
                }
                (void)fputs(cr, tp);
                --linecnt;
        }
        (void)fprintf(tp, "----%s\n", cr);
        (void)fclose(fi);
}