Appendix I: Differences from other News software

Administrators will find that INN is fairly incompatible with B and C News. This section tries to mention the most important places where INN differs from the other news systems. Users will generally only notice is that INN is faster; it should be 100% compatible with the other systems at the user level. If you had particular problems that aren't mentioned here, please let me know. Note, however, that this is not a tutorial on how to set up a new INN system, or convert older software to it; no such document exists. Configuration Files

Below is a list of the data files used by B and C News, and the reference NNTP implementation, along with a short summary of how they map into INN configuration files. The syntax is always different: INN files are almost always a set of colon-separated fields where lines beginning with a poundsign are ignored.

explist
    This is replaced by the similar expire.ctl file. Archiving is done by a separate program.

mailpaths
    This is replaced by the moderators file. The ``default'' entry in mailpaths is replaced by either a full wildcard (``*'') entry in the moderators file, or by a ``moderatormailer'' entry in the inn.conf file.

nntp.access
    This is replaced by the hosts.nntp (for NNTP peers) and nnrp.access (for newsreading) files.

nntp.sys
    This is a password file used if NNTP is compiled with the ``AUTH'' option. It is replaced by the passwd.nntp file. Note that inews and rnews will also try to read passwd.nntp. Therefore, you will probably want to have one-line versions of it for your on-campus clients.

organization
    This is replaced by the ``organization'' entry in the inn.conf file.

rn/server
    This is replaced by the ``server'' entry in the inn.conf file.

whoami
    This is replaced by the ``pathhost'' and ``fromhost'' entries in the inn.conf file. Newsgroups, Active, Sys, and Newsfeeds

The biggest difference is how the newsfeeds file compares with the sys file. Newsgroup patterns like ``all.all.ctl'' are completely gone. All newsgroup patterns are shell-style wildcards, matched against the active file.

The active file is taken to be the definitive list of newsgroups that you want to receive. With B and C news, an article must match the subscription list of the local site as specified in the sys file. If it matches, each newsgroup is then looked up in the active file. If none of the newsgroups are found, then the article is filed into the newsgroup named ``junk''.

INN's behavior is much simpler. If a newsgroup does not appear in the active file, it is ignored. If none of the groups are mentioned, then the article is dropped. This is a deliberate design decision: if you do not want a particular newsgroup to take up your disk space, remove it from the active file; if your neighbors have not gotten around to updating your newsfeed, then the only thing that will happen is that some network bandwidth will have been wasted when they send you the article.

INN strictly believes in distributions, and the default is to reject articles that don't match the local list of desired distributions. Distributions are fixed strings — there are no patterns or special wildcards like ``all.'' Control Messages

Like C News, INN implements all control messages other than cancel as shell scripts. The number and type of parameters is different from that of C News. All control messages consult the file control.ctl before acting on the message. If the sender's address matches with the list of authorized addresses (e.g., ``tale@uunet.uu.net'', ``*'', etc.), the control message is either acted upon, mailed to the news administrator, or logged. For example, messages from ``tale@uunet.uu.net'' (the current moderator of news.announce.newgroups) are honored.

The ``control'', ``junk'', and ``to'' newsgroups can be explicitly sent or not sent. See doc/newsfeeds.5 and doc/innd.8.

The ctlinnd program is what really directs the server to create or remove newsgroups. This results in a semi-recursive process: the control message arrives, and a script is invoked to process the message. If approved, the script invokes ctlinnd to send a message back to the server telling it to create or remove the group. Locking

A running news system has many open files. These files can be divided into two groups. The first group includes the history database and active file. The second group includes the logfiles and batch files used to send articles to your feeds.

B news uses an internal protocol for the first group. For the second group, since inews does ``atomic appends,'' no locking is necessary. C news uses the locknews and newslock scripts for the first group, and provides no fine-grain mechanism for the second group.

With INN, the server is running all the time and all locking is done under the direction of ctlinnd. The first group is generally handled by using the ``throttle,'' ``pause,'' and ``go'' commands (sometimes ``reload'' will be necessary). The second group is handled by the ``flushlogs'' and ``flush'' commands. See the doc/ctlinnd.8 manpage; examples of their use can be found in various scripts in the samples directory.

Appendix II: Converting from other News software

INN is a complete news transport and expiration system. Since few people will be installing INN from scratch, this section should help you determine what you can ``throw out'' from your earlier news setups. It is also compatible with much of the existing news software, so you can create a mixed environment if you want to, and if you are careful. C News Expire

The expire program that is distributed with INN does not do any archiving. Since the history databases currently have the same format, it is possible to use the C News expire if you want to. (The INN history database may change, however, so you should only do this if you really have to — you really should use INN's expire.) There are three ways to do this.

The first way is to change your doexpire script so that it calls ctlinnd to ``throttle'' innd just before expire runs. It should then issue a ctlinnd ``go'' command after expire is done. The drawback to this method is that no incoming news is accepted until all expiration is finished.

The second way is to compile lib/lock.c and add it to your C News library libcnews.a, replacing the provided lock functions. You should then remove expire and relink it. This method has not been tested very thoroughly, but it is rather simple.

The third way is to teach the C News expire to talk to innd and tell it to cancel articles that it would remove. To do this, apply the patch file expire/expire.pch to your C News expire.c sources. You will also have to add lib/inndcomm.o to libcnews.a and then rebuild expire. Standard NNTP daemon

You can use the ``standard'' nntpd server. You should only have to do this if you have hosts that do both random newsreading and bulk transfer.

Make sure that you configure nntpd so that it is using DBZ, and have it feed each individual article to inews; don't use the ``batched input'' option. It should also be set up so that it acts as if it is running under inetd. You should also make sure that inetd does nothing with the NNTP port, number 119. Remote rn

The ``remote'' version of rn (also called rrn) uses a set of routines in the NNTP ``clientlib'' file. INN can emulate these routines; see doc/clientlib.3. If you need to build rn for client machines that don't have the entire INN distribution available, use the MakeLib script to build a distribution directory of the necessary routines. Use this script the same way you use the MakeInews script.

Rn, rrn, and trn are moving targets so these instructions may be out of date. The maintainers have agreed to official support INN, however, which is a good thing.

There are two ways to build rn so that it uses the INN library. If you don't have the NNTP distribution installed you will have to use the first way.

The first way is to apply a patch to the latest rn Configure script and then execute it and rebuild the program. To do this, type the following: cd rn_source patch <$inn/frontends/rn.pch ./Configure make At some point, Configure will ask you if you want to use the InterNetNews library; answer yes. You can then use either the full sources, or a special library that contains just the needed header and sources files. Tell Configure the appropriate pathnames, and then proceed with the rest of the rn installation.

The second way is to edit a couple of files after you have run Configure and set it up to build the remote rn. First, replace the rn file server.h with the INN file include/myserver.h. The next step is to edit the rn Makefile to remove the ``clientlib'' file from the source and object file lists. This can probably be done by commenting out the definitions of the c5 and obj5 variables. You must also edit the Makefile to add the INN library to the list of libraries that are linked in. This can probably be done by editing the line that defines the libs variable so that the full pathname to libinn.a is the first item after the equal sign. Removing the Other Stuff

The names below assume a ``standard'' news setup; things might be different on your machine. Also, many programs have alternate names and links; make sure you chase down and remove all of them.

You might find it easiest to rename your /usr/lib/news (and /usr/lib/newsbin) directories to something else and start with a clean slate, copying over the files as they are needed. Make sure that your news processing is completely stopped before you begin this process. That includes any cron jobs that may be running.

The /usr/lib/news directory can become cluttered — that's why C News split everything up into separate directories. The following files are compatible with C News and B2.11 News, and should be kept:
active    active.times
If you are running C News keep these files, otherwise delete them and use makehistory to rebuild them: history history.dir history.pag

Rn does not have to be modified so leave this directory alone (or copy it back if you moved your original): /usr/local/lib/rn If you set up rn to use the INN library, remove this file: /usr/local/lib/rn/server

The input system is completely replaced. Remove the following programs and their manpages: /bin/cunbatch /bin/inews, /usr/lib/news/inews, etc... /bin/rnews, /usr/bin/rnews, etc... /usr/lib/news/rnews.stall /etc/nntpd, /usr/etc/nntpd, etc... Also remove the following directories and everything within them: /usr/lib/news/bin/input /usr/lib/news/bin/relay /usr/lib/news/bin/ctl /usr/lib/news/bin/inject /usr/lib/news/nntp (mkgrdates, nntp_access, shlock, etc)

The transmission facility is completely replaced. You may keep your current feed subsystem if you want to, but it will require some changes to make sure that batchfiles are properly flushed; see the send-xxx scripts for examples. Remove these files and programs: /usr/lib/news/batchparms /usr/man/man8/newsbatch.8 Remove the following directory and everything within it: /usr/lib/news/bin/batch You can continue to use nntplink, newsxd, and the like, subject to the caveat just mentioned.

Article expiration and maintenance of the history and active files is completely replaced. Remove this file: /usr/lib/news/explist Remove the following directories and everything within them: /usr/lib/news/bin/expire /usr/lib/news/bin/maint

The following programs in /usr/lib/newsbin are not needed and can be deleted. Keeping them around is harmless, and if you find them useful don't delete them:
canonhdr    newshostname
ctime    newslock
dbz    queuelen
getabsdate    sizeof
getdate    spacefor
gngp Note that ctime, getabsdate, and getdate are replaced by convdate. More importantly, newslock does not lock innd; it is best to remove it.

The following files are replaced by INN configuration files. You should delete them, just to avoid confusion:
mailname    sys
mailpaths    whoami
organization If you have other software that uses them (except sys), you can keep them. The following will be rebuilt (or overwritten) by innd and scanlogs so you should remove them:
errlog    log

In addition to the manpages for the programs listed above, the following manual pages should be removed:
active.times.5    newsmail.8
expire.8    newsmaint.8
mkgrdates.8c    nntpd.8c
news.5    nntpxmit.1
newsaux.8

Any other files and directories can probably also be discarded.

Appendix III: Setting up different feeds

This section gives some notes and advice on how to set up different types of outgoing news feeds. It duplicates and expands upon the information in the manual pages. Ihave/sendme feed

For a standard UUCP newsfeed, a site batches up all the articles it receives and sends them to the downstream site, which unpacks the batch and processes each article. If the downstream site has multiple feeds, however, it might want to ``filter'' the articles that get sent. This is done by having the feeding site send a list of Message-ID's as an ``ihave'' control message. The receiving site examines the list to see which articles it does not currently have, and sends it back to the upstream site as a ``sendme'' message. The original site receives this message and prepares a batch in the standard way.

Note that this has nothing to do with NNTP. It is a specialized type of batched feed that is not used very often. To do ihave/sendme with a site named remote, the local site must either have a ``to.remote'' newsgroup or be compiled with MERGE_TO_GROUPS set to ``DO''

Accepting an ihave/sendme feed is easy. Suppose an ``ihave'' message is received from a site named remote. When innd processes the message it will invoke the appropriate control script, /usr/local/news/bin/control/ihave. The script will filter the body using grephistory, creating a list of Message-ID's not found in the history database. It uses this output to create a ``sendme'' control article which is posted to the ``to.remote'' newsgroup using inews. This article will then be queued and sent to remote in the normal way. The remote site will then send the desired articles back.

Providing an ihave/sendme feed is a bit more complicated. First, you must create two entries in your newsfeeds file. The first should be named remote.ihave. Make this a ``Tf,Wm'' feed that contains the remote's subscription list. This entry results in a a file that accumulates the Message-ID's of all articles that remote might want. The other entry should be named remote. This should be a ``Tf,Wn'' feed that only subscribes to the ``to.remote'' newsgroup. (Actually, if you feed some groups as a standard feed, you can put them on the remote entry, rather then the remote.ihave entry.)

The next step is to have the ``ihave'' control messages sent out. To do this, review the send-ihave script and make sure it is invoked as needed (usually out of cron). It splits the batchfile from the remote.ihave newsfeeds entry and posts ``ihave'' control messages into the ``to.remote'' newsgroup. These messages will get queued for the remote entry.

The next step is to send out any articles queued for the remote entry. Treat this as a standard UUCP feed, invoking send-uucp or sendbatch as appropriate, typically a few minutes after send-ihave runs.

When the remote site receives the ``ihave'' message it will filter it and send back a ``sendme'' message whose body is the list of desired Message-ID's. When innd processes this message it will invoke the appropriate control script, /usr/local/news/bin/control/sendme. This script will call grephistory to turn the list into a list of files appended to the batchfile for remote. Examine this script (the filename should probably match the filename in the remote newsfeeds entry) and send the batch to the remote site (usingbatcher, often called by send-uucp or sendbatch). Feeding a large number of sites

Innd tries to keep as many batchfiles open for as long as possible. It will normally open as many as it can, using all the available descriptors minus a fixed number for internal use (log files, etc.). You can explicitly set the number of files to open by using the ``-o'' flag.

If you have more outgoing feeds than available descriptors, innd will recycle the files on a a ``least recently used'' basis. If most of your feeds get most articles (or you have vastly more feeds then available descriptors), this will lead to ``file thrashing,'' closing and opening all the excess feeds on each article. To reduce this, you can have innd use an internal buffer for a site by using the ``I'' parameter in the newsfeeds Eric Allman
 * extensive changes by Ralph Campbell
 * more extensive changes by Eric Allman (again)
 */
#define    MAXLINE    1024    /* maximum line length */
#define    MAXSVLINE    120    /* maximum saved line length */
#define DEFUPRI    (LOG_USER|LOG_NOTICE)
#define DEFSPRI    (LOG_KERN|LOG_CRIT)
#define TIMERINTVL    30    /* interval for checking flush, mark */
#include <sys/param.h> #include <sys/errno.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/wait.h> #include <sys/socket.h> #include <sys/file.h> #include <sys/uio.h> #include <sys/un.h> #include <sys/time.h> #include <sys/resource.h> #include <sys/signal.h>
#ifdef    MSG_BSIZE
#undef    MSG_BSIZE
#endif
#define MSG_BSIZE    4096
#include <netinet/in.h> #include <netdb.h> #include <utmp.h> #include <stdio.h> #include <ctype.h> #include <string.h> #define SYSLOG_NAMES #include "syslog.h"
#define UT_NAMESIZE    8
#define    _PATH_UTMP    "/etc/utmp"
#define    _PATH_DEV    "/dev/"
#define    _PATH_CONSOLE    "/dev/console"
#ifndef sigmask #define sigmask(m) (1 << ((m)-1)) #endif
char    *LogName = _PATH_LOGFILE;
char    *ConfFile = _PATH_LOGCONF;
char    *PidFile = _PATH_LOGPID;
char    ctty[] = _PATH_CONSOLE;
#define FDMASK(fd)    (1 << (fd))
#define    dprintf    if (Debug) printf
#define MAXUNAMES    20    /* maximum number of user names */
#ifdef NEED_BZERO_ETC #include <fcntl.h> bzero(b, length)
    char *b;
    int length; {
    while (--length >= 0)
    *b++ = ' '; } bcopy(b1, b2, length)
    char *b1;
    char *b2;
    int length; {
    while (--length >= 0)
    *b2++ = *b1++; } setlinebuf(f)
    FILE *f; {
    setbuf(f, (char *)NULL); } wait3(status, flags, np)
    WAITVALUE *status;
    int flags;
    struct rusage *np; {
    return waitpid(-1, status, WNOHANG); } /* Not a general mask handler -- it know what we call, below. */ SIGHANDLER (*oldhup)(), (*oldalrm)(); int sigsetmask(omask)
    int omask; {
    if (omask & sigmask(SIGHUP))
    (void) signal(SIGHUP, oldhup);
    if (omask & sigmask(SIGALRM))
    (void) signal(SIGALRM, oldalrm); } int sigblock(mask)
    int mask; {
    if (mask & sigmask(SIGHUP))
    oldhup = signal(SIGHUP, SIG_IGN);
    if (mask & sigmask(SIGALRM))
    oldhup = signal(SIGALRM, SIG_IGN); } #endif /*
 * Flags to logmsg().
 */
#define IGN_CONS    0x001    /* don't print on console */
#define SYNC_FILE    0x002    /* do fsync on file after printing */
#define ADDDATE    0x004    /* add a date to the message */
#define MARK    0x008    /* this message is a mark */
/*
 * This structure represents the files that will have log
 * copies printed.
 */ struct filed {
    struct    filed *f_nextone;    /* next in linked list */
    short    f_type;    /* entry type, see below */
    short    f_file;    /* file descriptor */
    time_t    f_time;    /* time this was last written */
    u_char    f_pmask[LOG_NFACILITIES+1];    /* priority mask */
    union {
    char    f_uname[MAXUNAMES][UT_NAMESIZE+1];
        struct {
    char    f_hname[MAXHOSTNAMELEN+1];
    struct sockaddr_in    f_addr;
    } f_forw;    /* forwarding address */
    char    f_fname[MAXPATHLEN];
    } f_un;
    char    f_prevline[MAXSVLINE];    /* last message logged */
    char    f_lasttime[16];    /* time of last occurrence */
    char    f_prevhost[MAXHOSTNAMELEN+1];    /* host from which recd. */
    int    f_prevpri;    /* pri of f_prevline */
    int    f_prevlen;    /* length of f_prevline */
    int    f_prevcount;    /* repetition cnt of prevline */
    int    f_repeatcount;    /* number of "repeated" msgs */
}; /*
 * Intervals at which we flush out "message repeated" messages,
 * in seconds after previous message is logged. After each flush,
 * we move to the next interval until we reach the largest.
 */
int    repeatinterval[] = { 30, 120, 600 };    /* # of secs before flush */
#define    MAXREPEAT ((sizeof(repeatinterval) / sizeof(repeatinterval[0])) - 1)
#define    REPEATTIME(f)    ((f)->f_time + repeatinterval[(f)->f_repeatcount])
#define    BACKOFF(f)    { if (++(f)->f_repeatcount > MAXREPEAT)      (f)->f_repeatcount = MAXREPEAT;     }
/* values for f_type */
#define F_UNUSED    0    /* unused entry */
#define F_FILE    1    /* regular file */
#define F_TTY    2    /* terminal */
#define F_CONSOLE    3    /* console terminal */
#define F_FORW    4    /* remote machine */
#define F_USERS    5    /* list of users */
#define F_WALL    6    /* everyone logged on */
char    *TypeNames[7] = {
    "UNUSED",    "FILE",    "TTY",    "CONSOLE",
    "FORW",    "USERS",    "WALL"
};
struct    filed *Files;
struct    filed consfile;
int    Debug;    /* debug flag */
char    LocalHostName[MAXHOSTNAMELEN+1];    /* our hostname */
char    *LocalDomain;    /* our local domain name */
int    InetInuse = 0;    /* non-zero if INET sockets are being used */
int    finet;    /* Internet datagram socket */
int    LogPort;    /* port number for INET connections */
int    Initialized = 0;    /* set when we have initialized ourselves */
int    MarkInterval = 20 * 60;    /* interval between marks in seconds */
int    MarkSeq = 0;    /* mark sequence number */
extern    int errno;
extern    char *ctime(), *strchr(), *malloc();
SIGHANDLER die(), domark(), init(), reapchild(); int main(argc, argv)
    int argc;
    char **argv; {
    register int i;
    register char *p;
    int funix, inetm, fklog, klogm, len;
    struct sockaddr_un sunx, fromunix;
    struct sockaddr_in sin, frominet;
    FILE *fp;
    int ch;
    char line[MSG_BSIZE + 1];
    extern int optind;
    extern char *optarg;
    while ((ch = getopt(argc, argv, "df:m:p:")) != EOF)
        switch((char)ch) {
    case 'd':    /* debug */
            Debug++;
            break;
    case 'f':    /* configuration file */
            ConfFile = optarg;
            break;
    case 'm':    /* mark interval */
            MarkInterval = atoi(optarg) * 60;
            break;
    case 'p':    /* path */
            LogName = optarg;
            break;
        case '?':
        default:
            usage();
        }
    if (argc -= optind)
        usage();
    if (!Debug)
        daemon(0, 0);
    else
        setlinebuf(stdout);
    consfile.f_type = F_CONSOLE;
    (void) strcpy(consfile.f_un.f_fname, ctty);
    (void) gethostname(LocalHostName, sizeof LocalHostName);
    if (p = strchr(LocalHostName, '.')) {
        *p++ = ' ';
        LocalDomain = p;
    }
    else
        LocalDomain = "";
    (void) signal(SIGTERM, die);
    if (Debug) {
        (void) signal(SIGINT, die);
        (void) signal(SIGQUIT, die);
    }
    else {
        (void) signal(SIGINT, SIG_IGN);
        (void) signal(SIGQUIT, SIG_IGN);
    }
    (void) signal(SIGCHLD, reapchild);
    (void) signal(SIGALRM, domark);
    (void) alarm(TIMERINTVL);
    (void) unlink(LogName);
    bzero((char *)&sunx, sizeof(sunx));
    sunx.sun_family = AF_UNIX;
    (void) strncpy(sunx.sun_path, LogName, sizeof sunx.sun_path);
    funix = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (funix < 0 || bind(funix, (struct sockaddr *) &sunx,
        sizeof(sunx)) < 0 ||
        chmod(LogName, 0666) < 0) {
        (void) sprintf(line, "cannot create %s", LogName);
        logerror(line);
        dprintf("cannot create %s (%d), LogName, errno);
        die(0);
    }
#ifdef    DO_INET_SOCKET
    finet = socket(AF_INET, SOCK_DGRAM, 0);
    if (finet >= 0) {
        struct servent *sp;
        sp = getservbyname("syslog", "udp");
        if (sp == NULL) {
            errno = 0;
            logerror("syslog/udp: unknown service");
            die(0);
        }
        bzero((char *)&sin, sizeof(sin)); /* added uunet!rbj */
        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = INADDR_ANY;
        sin.sin_port = LogPort = htons(sp->s_port);
        if (bind(finet, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
            logerror("bind");
            if (!Debug)
                die(0);
        } else {
            inetm = FDMASK(finet);
            InetInuse = 1;
        }
    } #else
    finet = -1;
    inetm = 0;
#endif    /* DO_INET_SOCKET */
#ifdef    _PATH_KLOG
    if ((fklog = open(_PATH_KLOG, O_RDONLY, 0)) >= 0)
        klogm = FDMASK(fklog);
    else {
        dprintf("can't open %s (%d), _PATH_KLOG, errno);
        klogm = 0;
    } #else
    fklog = -1;
    klogm = 0;
#endif    /* _PATH_KLOG */
    /* tuck my process id away */
    fp = fopen(PidFile, "w");
    if (fp != NULL) {
        fprintf(fp, "%d, getpid());
        (void) fclose(fp);
    }
    dprintf("off & running....);
    init();
    (void) signal(SIGHUP, init);
    for (;;) {
        int nfds, readfds = FDMASK(funix) | inetm | klogm;
        errno = 0;
        dprintf("readfds = %#x, readfds);
        nfds = select(20, (fd_set *) &readfds, (fd_set *) NULL,
            (fd_set *) NULL, (struct timeval *) NULL);
        if (nfds == 0)
            continue;
        if (nfds < 0) {
            if (errno != EINTR)
                logerror("select");
            continue;
        }
        dprintf("got a message (%d, %#x), nfds, readfds);
        if (readfds & klogm) {
            i = read(fklog, line, sizeof(line) - 1);
            if (i > 0) {
                line[i] = ' ';
                printsys(line);
            } else if (i < 0 && errno != EINTR) {
                logerror("klog");
                fklog = -1;
                klogm = 0;
            }
        }
        if (readfds & FDMASK(funix)) {
            len = sizeof fromunix;
            i = recvfrom(funix, line, MAXLINE, 0,
                (struct sockaddr *) &fromunix, &len);
            if (i > 0) {
                line[i] = ' ';
                printline(LocalHostName, line);
            } else if (i < 0 && errno != EINTR)
                logerror("recvfrom unix");
        }
        if (readfds & inetm) {
            len = sizeof frominet;
            i = recvfrom(finet, line, MAXLINE, 0,
                (struct sockaddr *) &frominet, &len);
            if (i > 0) {
                extern char *cvthname();
                line[i] = ' ';
                printline(cvthname(&frominet), line);
            } else if (i < 0 && errno != EINTR)
                logerror("recvfrom inet");
        }
    } } usage() {
    (void) fprintf(stderr,
        "usage: syslogd [-d] [-f conffile] [-m markinterval] [-p path]);
    exit(1); } /*
 * Take a raw input line, decode the message, and print the message
 * on the appropriate log files.
 */ printline(hname, msg)
    char *hname;
    char *msg; {
    register char *p, *q;
    register int c;
    char line[MAXLINE + 1];
    int pri;
    /* test for special codes */
    pri = DEFUPRI;
    p = msg;
    if (*p == '<') {
        pri = 0;
        while (isdigit(*++p))
            pri = 10 * pri + (*p - '0');
        if (*p == '>')
            ++p;
    }
    if (pri &~ (LOG_FACMASK|LOG_PRIMASK))
        pri = DEFUPRI;
    /* don't allow users to log kernel messages */
    if (LOG_FAC(pri) == LOG_KERN)
        pri = LOG_MAKEPRI(LOG_USER, LOG_PRI(pri));
    q = line;
    while ((c = *p++ & 0177) != ' ' &&
        q < &line[sizeof(line) - 1])
        if (iscntrl(c))
            if (c == ')
                *q++ = ' ';
            else if (c == ' ')
                *q++ = ' ';
            else {
                *q++ = '^';
                *q++ = c ^ 0100;
            }
        else
            *q++ = c;
    *q = ' ';
    logmsg(pri, line, hname, 0); } /*
 * Take a raw input line from /dev/klog, split and format similar to syslog().
 */ printsys(msg)
    char *msg; {
    register char *p, *q;
    register int c;
    char line[MAXLINE + 1];
    int pri, flags;
    char *lp;
    (void) strcpy(line, "vmunix: ");
    lp = line + strlen(line);
    for (p = msg; *p != ' '; ) {
    flags = SYNC_FILE | ADDDATE;    /* fsync file after write */
        pri = DEFSPRI;
        if (*p == '<') {
            pri = 0;
            while (isdigit(*++p))
                pri = 10 * pri + (*p - '0');
            if (*p == '>')
                ++p;
        } else {
            /* kernel printf's come out on console */
            flags |= IGN_CONS;
        }
        if (pri &~ (LOG_FACMASK|LOG_PRIMASK))
            pri = DEFSPRI;
        q = lp;
        while (*p != ' ' && (c = *p++) != ' &&
            q < &line[MAXLINE])
            *q++ = c;
        *q = ' ';
        logmsg(pri, line, LocalHostName, flags);
    } }
#ifdef    VERBOSE_DEBUG
/*
 * Decode a numeric value to a symbolic name
 */ char * edoced(val, codetab)
    int val;
    CODE *codetab; {
    register CODE *c;
    if (val >= 0)
        for (c = codetab; c->c_val != -1; c++)
            if (c->c_val == val)
                return (c->c_name);
    return ("???"); } #endif
time_t    now;
/*
 * Log a message to the appropriate log files, users, etc. based on
 * the priority.
 */ logmsg(pri, msg, from, flags)
    int pri;
    char *msg, *from;
    int flags; {
    register struct filed *f;
    int fac, prilev;
    int omask, msglen;
    char *timestamp;
    time_t time();
#ifdef    VERBOSE_DEBUG
    if (Debug) {
        int pfac = (flags & MARK) ? INTERNAL_MARK : (pri & LOG_FACMASK);
        printf("logmsg: {%s.%s} pri %o, flags %x, from %s, msg %s,
            edoced(pfac, facilitynames),
            edoced(LOG_PRI(pri), prioritynames),
            pri, flags, from, msg);
     } #else
    dprintf("logmsg: pri %o, flags %x, from %s, msg %s,
        pri, flags, from, msg); #endif
    omask = sigblock(sigmask(SIGHUP)|sigmask(SIGALRM));
    /*
     * Check to see if msg looks non-standard.
     */
    msglen = strlen(msg);
    if (msglen < 16 || msg[3] != ' ' || msg[6] != ' ' ||
        msg[9] != ':' || msg[12] != ':' || msg[15] != ' ')
        flags |= ADDDATE;
    (void) time(&now);
    if (flags & ADDDATE)
        timestamp = ctime(&now) + 4;
    else {
        timestamp = msg;
        msg += 16;
        msglen -= 16;
    }
    /* extract facility and priority level */
    if (flags & MARK)
        fac = LOG_NFACILITIES;
    else
        fac = LOG_FAC(pri);
    prilev = LOG_PRI(pri);
    /* log the message to the particular outputs */
    if (!Initialized) {
        f = &consfile;
        f->f_file = open(ctty, O_WRONLY, 0);
        if (f->f_file >= 0) {
            fprintlog(f, flags, msg);
            (void) close(f->f_file);
        }
        (void) sigsetmask(omask);
        return;
    }
    for (f = Files; f; f = f->f_nextone) {
        /* skip messages that are incorrect priority */
        if ((int)f->f_pmask[fac] < prilev ||
            f->f_pmask[fac] == INTERNAL_NOPRI)
            continue;
        if (f->f_type == F_CONSOLE && (flags & IGN_CONS))
            continue;
        /* don't output marks to recently written files */
        if ((flags & MARK) && (now - f->f_time) < MarkInterval / 2)
            continue;
        /*
         * suppress duplicate lines to this file
         */
        if ((flags & MARK) == 0 && msglen == f->f_prevlen &&
            !strcmp(msg, f->f_prevline) &&
            !strcmp(from, f->f_prevhost)) {
            (void) strncpy(f->f_lasttime, timestamp, 15);
            f->f_prevcount++;
            dprintf("msg repeated %d times, %ld sec of %d,
                f->f_prevcount, now - f->f_time,
                repeatinterval[f->f_repeatcount]);
            /*
             * If domark would have logged this by now,
             * flush it now (so we don't hold isolated messages),
             * but back off so we'll flush less often
             * in the future.
             */
            if (now > REPEATTIME(f)) {
                fprintlog(f, flags, (char *)NULL);
                BACKOFF(f);
            }
        } else {
            /* new line, save it */
            if (f->f_prevcount)
                fprintlog(f, 0, (char *)NULL);
            f->f_repeatcount = 0;
            (void) strncpy(f->f_lasttime, timestamp, 15);
            (void) strncpy(f->f_prevhost, from,
                    sizeof(f->f_prevhost));
            if (msglen < MAXSVLINE) {
                f->f_prevlen = msglen;
                f->f_prevpri = pri;
                (void) strcpy(f->f_prevline, msg);
                fprintlog(f, flags, (char *)NULL);
            } else {
                f->f_prevline[0] = 0;
                f->f_prevlen = 0;
                fprintlog(f, flags, msg);
            }
        }
    }
    (void) sigsetmask(omask); } fprintlog(f, flags, msg)
    register struct filed *f;
    int flags;
    char *msg; {
    struct iovec iov[6];
    register struct iovec *v;
    register int l;
    char line[MAXLINE + 1], repbuf[80], greetings[200];
    v = iov;
    if (f->f_type == F_WALL) {
        v->iov_base = greetings;
        sprintf(greetings,
            "7Message from syslogd@%s at %.24s ...,
            f->f_prevhost, ctime(&now));
        v->iov_len = strlen(greetings);
        v++;
        v->iov_base = "";
        v->iov_len = 0;
        v++;
    } else {
        v->iov_base = f->f_lasttime;
        v->iov_len = 15;
        v++;
        v->iov_base = " ";
        v->iov_len = 1;
        v++;
    }
    v->iov_base = f->f_prevhost;
    v->iov_len = strlen(v->iov_base);
    v++;
    v->iov_base = " ";
    v->iov_len = 1;
    v++;
    if (msg) {
        v->iov_base = msg;
        v->iov_len = strlen(msg);
    } else if (f->f_prevcount > 1) {
        v->iov_base = repbuf;
        sprintf(repbuf, "last message repeated %d times",
            f->f_prevcount);
        v->iov_len = strlen(repbuf);
    } else {
        v->iov_base = f->f_prevline;
        v->iov_len = f->f_prevlen;
    }
    v++;
    dprintf("Logging to %s", TypeNames[f->f_type]);
    f->f_time = now;
    switch (f->f_type) {
    case F_UNUSED:
        dprintf(");
        break;
    case F_FORW:
        dprintf(" %s, f->f_un.f_forw.f_hname);
        sprintf(line, "<%d>%.15s %s", f->f_prevpri,
            iov[0].iov_base, iov[4].iov_base);
        l = strlen(line);
        if (l > MAXLINE)
            l = MAXLINE;
        if (sendto(finet, line, l, 0, &f->f_un.f_forw.f_addr,
            sizeof f->f_un.f_forw.f_addr) != l) {
            int e = errno;
            (void) close(f->f_file);
            f->f_type = F_UNUSED;
            errno = e;
            logerror("sendto");
        }
        break;
    case F_CONSOLE:
        if (flags & IGN_CONS) {
            dprintf(" (ignored));
            break;
        }
        /* FALLTHROUGH */
    case F_TTY:
    case F_FILE:
        dprintf(" %s, f->f_un.f_fname);
        if (f->f_type != F_FILE) {
            v->iov_base = ";
            v->iov_len = 2;
        } else {
            v->iov_base = ";
            v->iov_len = 1;
        }
    again:
        if (writev(f->f_file, iov, 6) < 0) {
            int e = errno;
            (void) close(f->f_file);
            /*
             * Check for errors on TTY's due to loss of tty
             */
            if ((e == EIO || e == EBADF) && f->f_type != F_FILE) {
                f->f_file = open(f->f_un.f_fname,
                    O_WRONLY|O_APPEND, 0);
                if (f->f_file < 0) {
                    f->f_type = F_UNUSED;
                    logerror(f->f_un.f_fname);
                } else
                    goto again;
            } else {
                f->f_type = F_UNUSED;
                errno = e;
                logerror(f->f_un.f_fname);
            }
        } else if (flags & SYNC_FILE)
            (void) fsync(f->f_file);
        break;
    case F_USERS:
    case F_WALL:
        dprintf(");
        v->iov_base = ";
        v->iov_len = 2;
        wallmsg(f, iov);
        break;
    }
    f->f_prevcount = 0; } /*
 * WALLMSG -- Write a message to the world at large
 *
 *    Write the specified message to either the entire
 *    world, or a list of approved users.
 */ wallmsg(f, iov)
    register struct filed *f;
    struct iovec *iov; {
    static int reenter;    /* avoid calling ourselves */
    register FILE *uf;
    register int i;
    struct utmp ut;
    char *p, *ttymsg();
    if (reenter++)
        return;
    if ((uf = fopen(_PATH_UTMP, "r")) == NULL) {
        logerror(_PATH_UTMP);
        reenter = 0;
        return;
    }
    /* NOSTRICT */
    while (fread((char *) &ut, sizeof ut, 1, uf) == 1) {
        if (ut.ut_name[0] == ' ')
            continue;
        if (f->f_type == F_WALL) {
            if (p = ttymsg(iov, 6, ut.ut_line, 1)) {
    errno = 0;    /* already in msg */
                logerror(p);
            }
            continue;
        }
        /* should we send the message to this user? */
        for (i = 0; i < MAXUNAMES; i++) {
            if (!f->f_un.f_uname[i][0])
                break;
            if (!strncmp(f->f_un.f_uname[i], ut.ut_name,
                UT_NAMESIZE)) {
                if (p = ttymsg(iov, 6, ut.ut_line, 1)) {
    errno = 0;    /* already in msg */
                    logerror(p);
                }
                break;
            }
        }
    }
    (void) fclose(uf);
    reenter = 0; } SIGHANDLER reapchild() {
    WAITVALUE status;
    while (wait3(&status, WNOHANG, (struct rusage *) NULL) > 0)
        continue; } /*
 * Return a printable representation of a host address.
 */ char * cvthname(f)
    struct sockaddr_in *f; {
    struct hostent *hp;
    register char *p;
    extern char *inet_ntoa();
    dprintf("cvthname(%s), inet_ntoa(f->sin_addr));
    if (f->sin_family != AF_INET) {
        dprintf("Malformed from address);
        return ("???");
    }
    hp = gethostbyaddr(&f->sin_addr, sizeof(struct in_addr), f->sin_family);
    if (hp == 0) {
        dprintf("Host name for your address (%s) unknown,
            inet_ntoa(f->sin_addr));
        return (inet_ntoa(f->sin_addr));
    }
    if ((p = strchr(hp->h_name, '.')) && strcmp(p + 1, LocalDomain) == 0)
        *p = ' ';
    return (hp->h_name); } SIGHANDLER domark() {
    register struct filed *f;
    time_t time();
    now = time((time_t *)NULL);
    MarkSeq += TIMERINTVL;
    if (MarkSeq >= MarkInterval) {
        logmsg(LOG_INFO, "-- MARK --", LocalHostName, ADDDATE|MARK);
        MarkSeq = 0;
    }
    for (f = Files; f; f = f->f_nextone) {
        if (f->f_prevcount && now >= REPEATTIME(f)) {
            dprintf("flush %s: repeated %d times, %d sec.,
                TypeNames[f->f_type], f->f_prevcount,
                repeatinterval[f->f_repeatcount]);
            fprintlog(f, 0, (char *)NULL);
            BACKOFF(f);
        }
    }
    (void) alarm(TIMERINTVL); } static char * xstrerror() {
    extern int    sys_nerr;
    extern char    *sys_errlist[];
    extern int    errno;
    static char    buff[30];
    if (errno >= 0 && errno < sys_nerr)
    return sys_errlist[errno];
    (void)sprintf(buff, "Error code %d, errno);
    return buff; } /*
 * Print syslogd errors some place.
 */ logerror(type)
    char *type; {
    char buf[100];
    if (errno)
        (void) sprintf(buf, "syslogd: %s: %s", type, xstrerror(errno));
    else
        (void) sprintf(buf, "syslogd: %s", type);
    errno = 0;
    dprintf("%s, buf);
    logmsg(LOG_SYSLOG|LOG_ERR, buf, LocalHostName, ADDDATE); } SIGHANDLER die(sig) {
    register struct filed *f;
    char buf[100];
    for (f = Files; f != NULL; f = f->f_nextone) {
        /* flush any pending output */
        if (f->f_prevcount)
            fprintlog(f, 0, (char *)NULL);
    }
    if (sig) {
        dprintf("syslogd: exiting on signal %d, sig);
        (void) sprintf(buf, "exiting on signal %d", sig);
        errno = 0;
        logerror(buf);
    }
    (void) unlink(LogName);
    exit(0); } /*
 * INIT -- Initialize syslogd from configuration table
 */ SIGHANDLER init() {
    register int i;
    register FILE *cf;
    register struct filed *f, *next, **nextp;
    register char *p;
    char cline[BUFSIZ];
    dprintf("init);
    /*
     * Close all open log files.
     */
    Initialized = 0;
    for (f = Files; f != NULL; f = next) {
        /* flush any pending output */
        if (f->f_prevcount)
            fprintlog(f, 0, (char *)NULL);
        switch (f->f_type) {
          case F_FILE:
          case F_TTY:
          case F_CONSOLE:
          case F_FORW:
            (void) close(f->f_file);
            break;
        }
        next = f->f_nextone;
        free((char *) f);
    }
    Files = NULL;
    nextp = &Files;
    /* open the configuration file */
    if ((cf = fopen(ConfFile, "r")) == NULL) {
        dprintf("cannot open %s, ConfFile);
        *nextp = (struct filed *)malloc(sizeof(*f));
        cfline("*.ERR /dev/console", *nextp);
        (*nextp)->f_nextone = (struct filed *)malloc(sizeof(*f));
        cfline("*.PANIC *", (*nextp)->f_nextone);
        Initialized = 1;
        return;
    }
    /*
     * Foreach line in the conf table, open that file.
     */
    f = NULL;
    while (fgets(cline, sizeof cline, cf) != NULL) {
        /*
         * check for end-of-section, comments, strip off trailing
         * spaces and newline character.
         */
        for (p = cline; isspace(*p); ++p);
        if (*p == NULL || *p == '#')
            continue;
        for (p = strchr(cline, ' '); isspace(*--p););
        *++p = ' ';
        f = (struct filed *)malloc(sizeof(*f));
        *nextp = f;
        nextp = &f->f_nextone;
        cfline(cline, f);
    }
    /* close the configuration file */
    (void) fclose(cf);
    Initialized = 1;
    if (Debug) {
        for (f = Files; f; f = f->f_nextone) {
            for (i = 0; i <= LOG_NFACILITIES; i++)
                if (f->f_pmask[i] == INTERNAL_NOPRI)
                    printf("X ");
                else
                    printf("%d ", f->f_pmask[i]);
            printf("%s: ", TypeNames[f->f_type]);
            switch (f->f_type) {
            case F_FILE:
            case F_TTY:
            case F_CONSOLE:
                printf("%s", f->f_un.f_fname);
                break;
            case F_FORW:
                printf("%s", f->f_un.f_forw.f_hname);
                break;
            case F_USERS:
                for (i = 0; i < MAXUNAMES && *f->f_un.f_uname[i]; i++)
                    printf("%s, ", f->f_un.f_uname[i]);
                break;
            }
            printf(");
        }
    }
    logmsg(LOG_SYSLOG|LOG_INFO, "syslogd: restart", LocalHostName, ADDDATE);
    dprintf("syslogd: restarted); } /*
 * Crack a configuration file line
 */ cfline(line, f)
    char *line;
    register struct filed *f; {
    register char *p;
    register char *q;
    register int i;
    char *bp;
    int pri;
    struct hostent *hp;
    char buf[MAXLINE];
    dprintf("cfline(%s), line);
    errno = 0;    /* keep strerror() stuff out of logerror messages */
    /* clear out file entry */
    bzero((char *) f, sizeof *f);
    for (i = 0; i <= LOG_NFACILITIES; i++)
        f->f_pmask[i] = INTERNAL_NOPRI;
    /* scan through the list of selectors */
    for (p = line; *p && *p != ' ';) {
        /* find the end of this facility name list */
        for (q = p; *q && *q != ' ' && *q++ != '.'; )
            continue;
        /* collect priority name */
        for (bp = buf; *q && !strchr(" ,;", *q); )
            *bp++ = *q++;
        *bp = ' ';
        /* skip cruft */
        while (strchr(", ;", *q))
            q++;
        /* decode priority name */
        pri = decode(buf, prioritynames);
        if (pri < 0) {
            char xbuf[200];
            (void) sprintf(xbuf, "unknown priority name
                buf);
            logerror(xbuf);
            return;
        }
        /* scan facilities */
        while (*p && !strchr(" .;", *p)) {
            for (bp = buf; *p && !strchr(" ,;.", *p); )
                *bp++ = *p++;
            *bp = ' ';
            if (*buf == '*')
                for (i = 0; i < LOG_NFACILITIES; i++)
                    f->f_pmask[i] = pri;
            else {
                i = decode(buf, facilitynames);
                if (i < 0) {
                    char xbuf[200];
                    (void) sprintf(xbuf,
                        "unknown facility name
                        buf);
                    logerror(xbuf);
                    return;
                }
                f->f_pmask[i >> 3] = pri;
            }
            while (*p == ',' || *p == ' ')
                p++;
        }
        p = q;
    }
    /* skip to action part */
    while (*p == ' ')
        p++;
    switch (*p)
    {
    case '@':
        if (!InetInuse)
            break;
        (void) strcpy(f->f_un.f_forw.f_hname, ++p);
        hp = gethostbyname(p);
        if (hp == NULL) {
            logerror("hostname lookup error");
            break;
        }
        bzero((char *) &f->f_un.f_forw.f_addr,
             sizeof f->f_un.f_forw.f_addr);
        f->f_un.f_forw.f_addr.sin_family = AF_INET;
        f->f_un.f_forw.f_addr.sin_port = LogPort;
        bcopy(hp->h_addr, (char *) &f->f_un.f_forw.f_addr.sin_addr, hp->h_length);
        f->f_type = F_FORW;
        break;
    case '/':
        (void) strcpy(f->f_un.f_fname, p);
        if ((f->f_file = open(p, O_WRONLY|O_APPEND, 0)) < 0) {
            f->f_file = F_UNUSED;
            logerror(p);
            break;
        }
        if (isatty(f->f_file))
            f->f_type = F_TTY;
        else
            f->f_type = F_FILE;
        if (strcmp(p, ctty) == 0)
            f->f_type = F_CONSOLE;
        break;
    case '*':
        f->f_type = F_WALL;
        break;
    default:
        for (i = 0; i < MAXUNAMES && *p; i++) {
            for (q = p; *q && *q != ','; )
                q++;
            (void) strncpy(f->f_un.f_uname[i], p, UT_NAMESIZE);
            if ((q - p) > UT_NAMESIZE)
                f->f_un.f_uname[i][UT_NAMESIZE] = ' ';
            else
                f->f_un.f_uname[i][q - p] = ' ';
            while (*q == ',' || *q == ' ')
                q++;
            p = q;
        }
        f->f_type = F_USERS;
        break;
    } } /*
 * Decode a symbolic name to a numeric value
 */ decode(name, codetab)
    char *name;
    CODE *codetab; {
    register CODE *c;
    register char *p;
    char buf[40];
    if (isdigit(*name))
        return (atoi(name));
    (void) strcpy(buf, name);
    for (p = buf; *p; p++)
        if (isupper(*p))
            *p = tolower(*p);
    for (c = codetab; c->c_name; c++)
        if (!strcmp(buf, c->c_name))
            return (c->c_val);
    return (-1); } daemon(nochdir, noclose)
        int nochdir, noclose; {
        int cpid;
        if ((cpid = fork()) == -1)
                return (-1);
        if (cpid)
                exit(0);
        (void) setsid();
        if (!nochdir)
                (void) chdir("/");
        if (!noclose) {
                int devnull = open("/dev/null", O_RDWR, 0);
                if (devnull != -1) {
                        (void) dup2(devnull, 0);
                        (void) dup2(devnull, 1);
                        (void) dup2(devnull, 2);
                        if (devnull > 2)
                        (void) close(devnull);
                }
        } } /*
 * Display the contents of a uio structure on a terminal. Used by wall(1)
 * and syslogd(8). Forks and finishes in child if write would block, waiting
 * at most five minutes. Returns pointer to error string on unexpected error;
 * string is not newline-terminated. Various "normal" errors are ignored
 * (exclusive-use, lack of permission, etc.).
 */ char * ttymsg(iov, iovcnt, line)
    struct iovec *iov;
    int iovcnt;
    char *line; {
    static char device[64] = _PATH_DEV;
    static char errbuf[1024];
    register int cnt, fd, left, wret;
    struct iovec localiov[6];
    int forked = 0;
    if (iovcnt > 6)
        return ("too many iov's");
    /*
     * open will fail on slip lines or exclusive-use lines
     * if not running as root; not an error.
     */
    (void) strcpy(device + sizeof(_PATH_DEV) - 1, line);
    if ((fd = open(device, O_WRONLY|O_NONBLOCK, 0)) < 0) {
        if (errno == EBUSY || errno == EACCES)
            return (NULL);
        (void) sprintf(errbuf, "%s: error %d", device, errno);
        return (errbuf);
    }
    for (cnt = left = 0; cnt < iovcnt; ++cnt)
        left += iov[cnt].iov_len;
    for (;;) {
        wret = writev(fd, iov, iovcnt);
        if (wret >= left)
            break;
        if (wret >= 0) {
            left -= wret;
            if (iov != localiov) {
                bcopy(iov, localiov,
                    iovcnt * sizeof(struct iovec));
                iov = localiov;
            }
            for (cnt = 0; wret >= iov->iov_len; ++cnt) {
                wret -= iov->iov_len;
                ++iov;
                --iovcnt;
            }
            if (wret) {
                iov->iov_base += wret;
                iov->iov_len -= wret;
            }
            continue;
        }
        if (errno == EWOULDBLOCK) {
            int cpid, off = 0;
            if (forked) {
                (void) close(fd);
                _exit(1);
            }
            cpid = fork();
            if (cpid < 0) {
                (void) sprintf(errbuf,
                    "fork: error %d", errno);
                (void) close(fd);
                return (errbuf);
            }
    if (cpid) {    /* parent */
                (void) close(fd);
                return (NULL);
            }
            forked++;
            /* wait at most 5 minutes */
            (void) signal(SIGALRM, SIG_DFL);
            (void) signal(SIGTERM, SIG_DFL); /* XXX */
            (void) sigsetmask(0);
            (void) alarm((u_int)(60 * 5));
            (void) fcntl(fd, FNDELAY, &off);
            continue;
        }
        /*
         * We get ENODEV on a slip line if we're running as root,
         * and EIO if the line just went away.
         */
        if (errno == ENODEV || errno == EIO)
            break;
        (void) close(fd);
        if (forked)
            _exit(1);
        (void) sprintf(errbuf, "%s: error %d", errno);
        return (errbuf);
    }
    (void) close(fd);
    if (forked)
        _exit(0);
    return (NULL); }