root/usr/src/cmd/nlsadmin/nlsadmin.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */

/*
 * nlsadmin.c -- control program for the network listener service
 *
 * This program replaces a previous version of nlsadmin.
 *
 * This version of nlsadmin works with the service access facility to
 * control the network listener.  The functionality of the SVR3.2 nlsadmin
 * command is supported through calls to the more general sacadm and pmadm
 * commands available through SAF.  Users should migrate away from nlsadmin
 * to sacadm and pmadm for these functions.
 *
 * The -m option of the SVR3.2 nlsadmin command is now ignored.
 *
 * The -t option associates an address with service code 1 (same as in SVR3.2).
 * The -l option associates an address with service code 0.
 *
 * nlsadmin also contains new functionality -- the ability to format a
 * "listener-specific" string to put in the _pmtab database.  This
 * functionality is required by SAF.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <sac.h>
#include "nlsadmin.h"

#define OPTIONS "a:c:d:e:ikl:mo:p:qr:st:vw:xy:z:A:N:VDR:"
#ifndef FALSE
#define TRUE    1
#define FALSE   0
#endif
/*
 * defines for -q exit codes: QZERO is used for conditions that the
 * man page documents as returning 0, QONE for those that return 1
 */
#define QZERO   0
#define QONE    1

/*
 * defines for simulated standard error format code
 */
#define MM_NOSEV        0
#define MM_HALT         1
#define MM_ERROR        2
#define MM_WARNING      3
#define MM_INFO         4

char    *Nlsname;               /* set to argv[0]                       */
char    Label[25];              /* label component for fmtmsg           */
int     Quietflag = FALSE;      /* set to TRUE when -q used             */

extern  int errno;
void    nlsmesg();
uid_t   geteuid();
char    *nexttok();
char    *pflags();
char    *gencmdstr();

struct  svcfields {
        char    *pmtag;
        char    *pmtype;
        char    *svc_code;
        char    *flags;
        char    *id;
        char    *res1;
        char    *res2;
        char    *res3;
        char    *addr;
        char    *rpc;
        char    *lflags;
        char    *modules;
        char    *command;
        char    *comment;
};

void no_permission(void) __NORETURN;
void usage(int flag);

int
main(int argc, char **argv)
{
        extern  char *optarg;
        extern  int optind;
        int     c;                      /* used for return from getopt  */
        char    *addrptr = NULL;        /* set when -A address is used  */
        char    *rpcptr = NULL;         /* set when -R rpcinfo is used  */
        char    *cmdptr = NULL;         /* set with -c command          */
        char    *comptr = NULL;         /* set with -y comment (old)    */
        char    *idptr = NULL;          /* set with -w id (old)         */
        char    *lptr = NULL;           /* set with -l addr (old)       */
        char    *moduleptr = NULL;      /* set with -m modules          */
        char    *pipeptr = NULL;        /* set with -o pipe             */
        char    *svcptr = NULL;         /* set when service code used (old) */
        char    *tptr = NULL;           /* set when -t addr used (old)  */
        char    *netspec = NULL;        /* set to the network specification */
        int     flag = 0;               /* bit flag of type of command  */
        int     exitcode = 0;           /* exit status of this command  */
        int     lflags = 0;             /* listener flags               */
        char    buf[BUFSIZ];            /* temp buffer #1               */
        char    mesg[BUFSIZ];           /* temp buffer #2               */
        FILE    *fp;                    /* used for checking netspec    */
        char    *ptr;                   /* temp pointer                 */
        char    *ptr2;                  /* temp pointer                 */
        int     sawsep = 0;             /* flag for RPC separator       */

        Nlsname = argv[0];
        sprintf(Label, "UX:%.14s", argv[0]);    /* for standard message fmt */

        while ((c = getopt(argc, argv, OPTIONS)) != -1) {
                switch (c) {
                case 'a':
                        if ( (flag && (flag != CMDFLAG)) || svcptr || Quietflag
                              || addrptr || rpcptr || lflags)
                                usage(INCONSISTENT);
                        svcptr = optarg;
                        break;
                case 'c':
                        if ( (flag && (flag != CMDFLAG)) || cmdptr || Quietflag )
                                usage(INCONSISTENT);
                        cmdptr = optarg;
                        flag |= CMDFLAG;
                        break;
                case 'd':
                        if ( flag || svcptr || Quietflag || comptr || addrptr
                             || rpcptr || cmdptr || idptr || lflags )
                                usage(INCONSISTENT);
                        svcptr = optarg;
                        flag |= DISFLAG;
                        break;
                case 'e':
                        if ( flag || svcptr || Quietflag || comptr || addrptr
                             || rpcptr || cmdptr || idptr || lflags )
                                usage(INCONSISTENT);
                        svcptr = optarg;
                        flag |= ENAFLAG;
                        break;
                case 'i':
                        if ( flag || svcptr || Quietflag || comptr || addrptr
                             || rpcptr || cmdptr || idptr || lflags )
                                usage(INCONSISTENT);
                        flag |= INIFLAG;
                        break;
                case 'k':
                        if ( flag || svcptr || Quietflag || comptr || addrptr
                             || rpcptr || cmdptr || idptr || lflags )
                                usage(INCONSISTENT);
                        flag |= KILFLAG;
                        break;
                case 'l':
                        if ( ( flag && (flag != ADRFLAG)) || svcptr || lptr
                              || Quietflag || comptr || addrptr || rpcptr
                              || cmdptr || idptr || lflags )
                                usage(INCONSISTENT);
                        lptr = optarg;
                        flag |= ADRFLAG;
                        break;
                case 'm':
                        if ( (flag && (flag != CMDFLAG)) || Quietflag || rpcptr || lflags )
                                usage(INCONSISTENT);
                        flag |= CMDFLAG;
                        break;
                case 'o':
                        if ( flag || svcptr || Quietflag || comptr || idptr || netspec )
                                usage(INCONSISTENT);
                        pipeptr = optarg;
                        flag |= PIPFLAG;
                        break;
                case 'p':
                        if ( (flag && (flag != CMDFLAG) && (flag != PIPFLAG)) || Quietflag )
                                usage(INCONSISTENT);
                        moduleptr = optarg;
                        break;
                case 'q':
                        if ( (flag && (flag != ZZZFLAG)) || Quietflag || comptr
                             || rpcptr || lflags || idptr )
                                usage(INCONSISTENT);
                        Quietflag = TRUE;
                        break;
                case 'r':
                        if ( flag || svcptr || Quietflag || comptr || addrptr
                             || rpcptr || cmdptr || idptr || lflags )
                                usage(INCONSISTENT);
                        flag |= REMFLAG;
                        svcptr = optarg;
                        break;
                case 's':
                        if ( flag || svcptr || Quietflag || comptr || addrptr
                             || rpcptr || cmdptr || idptr || lflags )
                                usage(INCONSISTENT);
                        flag |= STAFLAG;
                        break;
                case 't':
                        if ( (flag && (flag != ADRFLAG)) || svcptr || tptr
                              || Quietflag || comptr || addrptr || rpcptr
                              || cmdptr || idptr || lflags )
                                usage(INCONSISTENT);
                        tptr = optarg;
                        flag |= ADRFLAG;
                        break;
                case 'v':
                        if ( flag || svcptr || Quietflag || comptr || rpcptr
                             || addrptr || idptr || lflags )
                                usage(INCONSISTENT);
                        flag |= VBSFLAG;
                        break;
                case 'w':
                        if ( (flag && (flag != CMDFLAG)) || Quietflag || idptr
                             || rpcptr || addrptr || lflags )
                                usage(INCONSISTENT);
                        idptr = optarg;
                        break;
                case 'x':
                        if ( flag || svcptr || Quietflag || netspec || comptr
                             || rpcptr || addrptr || lflags || idptr )
                                usage(INCONSISTENT);
                        flag |= NETFLAG;
                        break;
                case 'y':
                        if ( (flag && (flag != CMDFLAG)) || Quietflag || comptr
                             || rpcptr || addrptr || lflags )
                                usage(INCONSISTENT);
                        comptr = optarg;
                        break;
                case 'z':
                        if ( flag || svcptr || comptr || addrptr || rpcptr
                             || idptr || lflags )
                                usage(INCONSISTENT);
                        flag |= ZZZFLAG;
                        svcptr = optarg;
                        break;
                case 'A':
                        if ( (flag && (flag != CMDFLAG) && (flag != PIPFLAG))
                             || netspec || svcptr || idptr || comptr )
                                usage(INCONSISTENT);
                        addrptr = optarg;
                        break;
                case 'D':
                        if ( (flag && (flag != CMDFLAG) && (flag != PIPFLAG))
                             || netspec || svcptr || idptr || comptr || addrptr
                             || lflags )
                                usage(INCONSISTENT);
                        lflags |= DFLAG;
                        break;
                case 'N':
                        if ( netspec )
                                usage(INCONSISTENT);
                        netspec = optarg;
                        break;
                case 'R':
                        if ( (flag && (flag != CMDFLAG) && (flag != PIPFLAG))
                             || netspec || svcptr || idptr || comptr )
                                usage(INCONSISTENT);
                        for (ptr = optarg; *ptr; ++ptr) {
                                if ((*ptr == ':') && !sawsep) {
                                        /*
                                         * skip separator - note that if
                                         * separator has been seen, it's not
                                         * a digit so it will generate a usage
                                         * message below like we want
                                         */
                                        sawsep++;
                                        continue;
                                }
                                if (!isdigit(*ptr))
                                        usage(USAGE);
                        }
                        ptr = strchr(optarg, ':');
                        if (ptr)
                                /* change the ':' to a ',' */
                                *ptr = ',';
                        else
                                usage(USAGE);
                        rpcptr = optarg;
                        break;
                case 'V':
                        if ( flag || svcptr || Quietflag || comptr || netspec
                             || rpcptr || addrptr || idptr || lflags )
                                usage(INCONSISTENT);
                        flag |= VERFLAG;
                        break;
                case '?':
                        usage(USAGE);
                }
                /* NOTREACHED */
        }

        if ((optind < argc) && ! netspec)
                netspec = argv[optind++];
        if (optind < argc)
                usage(USAGE);


        /* determine if this command requires a netspec */
        if (flag != CMDFLAG) {
                /* if flag is CMDFLAG, more complicated checking of netspec
                 * is done below in switch
                 */
                if ((flag == PIPFLAG || flag == VERFLAG || flag == NETFLAG)) {
                        if (netspec)
                                usage(USAGE);
                }
                else if (!netspec)
                        usage(USAGE);
        }

        if (netspec && (flag != INIFLAG)) {
                sprintf(buf, SAC_LSPM, netspec);

                if ((fp = popen(buf, "r")) == NULL) {
                        nlsmesg(MM_ERROR, "System error");
                        exit(NLS_SYSERR);
                }

                if (fgets(buf, BUFSIZ, fp) == NULL) {
                        nlsmesg(MM_ERROR, "Invalid network specification");
                        exit(NLS_BADPM);
                }
                else {
                        ptr = strchr(buf, ':');
                        ptr++;
                        ptr2 = strchr(ptr, ':');
                        *ptr2 = '\0';
                        if (strcmp(ptr, LISTENTYPE) != 0) {
                                sprintf(mesg, "Network specification \"%s\" is not of type %s", netspec, LISTENTYPE);
                                nlsmesg(MM_ERROR, mesg);
                                exit(NLS_BADPM);
                        }
                }

                pclose(fp);
        }

        if (svcptr) {
                /* check to see if service code is "correct" -- right range
                 * and format.  The -m flag is ignored, so no check for
                 * "administrative" service codes (0-100) is done.
                 */
                c = strlen(svcptr);
                if ((c == 0) || (c >= SVC_CODE_SZ)) {
                        sprintf(mesg, "Service code contains more than %d characters", SVC_CODE_SZ);
                        nlsmesg(MM_ERROR, mesg);
                        exit(NLS_SERV);
                }
        }

        switch (flag) {
        default:
                usage(USAGE);
                break;
        case NONE:
                if ( svcptr || comptr || rpcptr || lflags || idptr )
                        usage(INCONSISTENT);
                exitcode = prt_nets(netspec);
                break;
        case INIFLAG:
                if (geteuid() != ROOT)
                        no_permission();
                exitcode = add_pm(netspec);
                break;
        case CMDFLAG:
                if ( svcptr || comptr || idptr || netspec ) {
                        if (geteuid() != ROOT)
                                no_permission();
                        if ((exitcode = old_addsvc(svcptr, "", cmdptr, comptr, moduleptr, idptr, NULL, netspec)) != NLS_OK)
                                switch (exitcode) {
                                case NLS_SERV:
                                        nlsmesg(MM_ERROR, "Service code already exists");
                                        break;
                                default:
                                        nlsmesg(MM_ERROR, "Could not add service");
                                        break;
                                }
                }
                else {
                        if (netspec)
                                usage(INCONSISTENT);
                        exitcode = prt_cmd(cmdptr, CFLAG | lflags, moduleptr, addrptr, rpcptr);
                }
                break;
        case PIPFLAG:
                if (geteuid() != ROOT)
                        no_permission();
                exitcode = prt_cmd(pipeptr, PFLAG | lflags, moduleptr, addrptr, rpcptr);
                break;
        case VERFLAG:
                printf("%d\n", VERSION);
                exit(NLS_OK);
                break;
        case DISFLAG:
                if (geteuid() != ROOT)
                        no_permission();
                exitcode = disable_svc(svcptr, netspec);
                break;
        case ENAFLAG:
                if (geteuid() != ROOT)
                        no_permission();
                exitcode = enable_svc(svcptr, netspec);
                break;
        case KILFLAG:
                if (geteuid() != ROOT)
                        no_permission();
                exitcode = kill_listener(netspec);
                break;
        case ADRFLAG:
                /* check for root permissions in setup_addr */
                exitcode = setup_addr(lptr, tptr, netspec);
                break;
        case REMFLAG:
                if (geteuid() != ROOT)
                        no_permission();
                exitcode = remove_svc(svcptr, netspec, TRUE);
                break;
        case STAFLAG:
                if (geteuid() != ROOT)
                        no_permission();
                exitcode = start_listener(netspec);
                break;
        case VBSFLAG:
                exitcode = prt_svcs(NULL, netspec);
                break;
        case NETFLAG:
                exitcode = prt_nets(NULL);
                break;
        case ZZZFLAG:
                exitcode = prt_svcs(svcptr, netspec);
                break;
        }
        if (exitcode == NLS_SYSERR)
                nlsmesg(MM_ERROR, "System error in SAC command");
        return (exitcode);
}


static char umsg[] = "usage: %s -x\n\
       %s [ options ] netspec\n\
       %s [ options ] -N port_monitor_tag\n\
       %s -V\n\
       %s -c cmd | -o pipename [ -p modules ] [ -A addr | -D ] \\\n\
          [ -R prognum:versnum ]\n\
\n\
       [ options ] are:\n\
       [ -a svc_code -c \"cmd\" -y \"cmt\" [-p modules] [-w id] ]\n\
       [-q] | [-v] | [-s] | [-k] | [-i] |\n\
       [-e svc_code] | [-d svc_code] | [-r svc_code] | [[-q] -z svc_code]\n\
       [[-l addr | -] [-t addr | -]] |\n\
";

void
usage(int flag)
{
        switch (flag) {
        case INCONSISTENT:
                nlsmesg(MM_ERROR, "Inconsistent options");
                break;
        case MISSINGARG:
                nlsmesg(MM_ERROR, "Missing argument");
                break;
        case USAGE:
                break;
        }
        fprintf(stderr, umsg, Nlsname, Nlsname, Nlsname, Nlsname, Nlsname);
        exit(NLS_CMD);
}


/*
 * no_permission:  print out error message and exit when the user needs to
 *                 needs to be root and isn't.
 */

void
no_permission(void)
{
        nlsmesg(MM_ERROR, "Must be super user");
        exit(NLS_PERM);
}

/*
 * nlsmesg:  print out either an error or a warning message.  severity must
 *           be either MM_ERROR or MM_WARNING.  this routine will be converted
 *           to use the standard message format later.
 */

void
nlsmesg(int severity, char *text)
{
        int     class;

        if (severity == MM_ERROR)
                fprintf(stderr, "%s: error: %s\n", Nlsname, text);
        else
                fprintf(stderr, "%s: warning: %s\n", Nlsname, text);
        return;
}

/*
 * prt_cmd:  print out the listener-dependent string for sacadm.
 */

int
prt_cmd(char *path, long flags, char *modules, char *addr, char *rpcp)
        /* path: full path of command or pipe   */
        /* flags: listener flags                */
        /* PFLAG for pipe                       */
        /* CFLAG for command                    */
        /* DFLAG for dynamic addr               */
        /* modules: STREAMS modules to push     */
        /* addr: private address                */
        /* rpcp: RPC prog and ver #             */
{
        struct  stat    sbuf;
        char    mesgbuf[BUFSIZ];
        char    *tmp;

        if (*path != '/') {
                nlsmesg(MM_ERROR, "Must specify full path name");
                return(NLS_CMD);
        }

        if ((tmp = strchr(path, ' ')) != NULL)
                *tmp = '\0';

        if (stat(path, &sbuf) < 0) {
                if (errno != EFAULT) {
                        sprintf(mesgbuf, "%s does not exist", path);
                        nlsmesg(MM_WARNING, mesgbuf);
                }
                else
                        return(NLS_SYSERR);
        }

        if (tmp)
                *tmp = ' ';

        printf("%s:%s:%s:%s:%s\n", (addr ? addr : ""), (rpcp ? rpcp : ""),
                pflags(flags), (modules ? modules : ""), path);
        return(NLS_OK);
}

/*
 * old_addsvc:  use pmadm to add a service code to the listener.  this will
 *              not allow specification of a private address -- use pmadm!
 */

int
old_addsvc(char *svc, char *addr, char *cmd, char *com, char *module,
    char *id, char *flags, char *netspec)
{
        char    buf[BUFSIZ];
        char    mesgbuf[BUFSIZ];
        int     rtn;
        struct  stat    sbuf;
        char    *tmp;

        if (!svc || !cmd || !com || !netspec)
                usage(MISSINGARG);

        /* create "port-monitor specific" info in the same way as prt_cmd */

        if (*cmd != '/') {
                nlsmesg(MM_ERROR, "Must specify full path name");
                return(NLS_CMD);
        }

        if ((tmp = strchr(cmd, ' ')) != NULL)
                *tmp = '\0';

        if (stat(cmd, &sbuf) < 0) {
                if (errno != EFAULT) {
                        sprintf(mesgbuf, "%s does not exist", cmd);
                        nlsmesg(MM_WARNING, mesgbuf);
                }
                else
                        return(NLS_SYSERR);
        }

        if (tmp)
                *tmp = ' ';

        if (addr)
                sprintf(mesgbuf, "'%s::c:%s:%s'", addr, module ? module : "" , cmd);
        else
                sprintf(mesgbuf, "'::c:%s:%s'", module ? module : "" , cmd);

        if (flags && *flags)
                sprintf(buf, PM_ADDSVCF, netspec, svc, (id)?id:DEFAULTID, flags, mesgbuf, VERSION, com ? com : "");
        else
                sprintf(buf, PM_ADDSVC, netspec, svc, (id)?id:DEFAULTID, mesgbuf, VERSION, com ? com : "");

        if ((rtn = system(buf)) < 0) {
                return(NLS_SYSERR);
        }
        rtn = (rtn>>8) & 0xff;  /* get child return value out of exit word */

        switch (rtn) {
        case 0:
                return(NLS_OK);
                break;
        case E_BADARGS:
        case E_SAFERR:
        case E_SYSERR:
        case E_NOEXIST:
        case E_PMRUN:
        case E_PMNOTRUN:
        case E_RECOVER:
        case E_SACNOTRUN:
        default:
                return(NLS_SYSERR);
                break;
        case E_DUP:
                return(NLS_SERV);
                break;
        case E_NOPRIV:
                no_permission();
                break;
        }
        /* NOTREACHED */
}

/*
 * prt_nets:  print the status of one network, or all nets if netspec
 *            is NULL
 */
int
prt_nets(char *netspec)
{
        char    buf[BUFSIZ];
        FILE    *fp;
        char    *name;
        char    *state;
        char    *type;
        int     found = FALSE;
        int     rtn = NLS_OK;

        if (netspec == NULL)
                sprintf(buf, SAC_LSTY, LISTENTYPE);
        else
                sprintf(buf, SAC_LSPM, netspec);

        if ((fp = popen(buf, "r")) == NULL)
                return(NLS_SYSERR);

        while (fgets(buf, BUFSIZ, fp) != NULL) {
                if ((name = nexttok(buf, ":")) == NULL)
                        return(NLS_SYSERR);
                if ((type = nexttok(NULL, ":")) == NULL)
                        return(NLS_SYSERR);

                if (strcmp(type, LISTENTYPE) != 0)
                        continue;  /* ignore other types of port monitors */

                found = TRUE;
                if (nexttok(NULL, ":") == NULL)
                        return(NLS_SYSERR);
                if (nexttok(NULL, ":") == NULL)
                        return(NLS_SYSERR);
                if ((state = nexttok(NULL, ":")) == NULL)
                        return(NLS_SYSERR);
                if (strcmp(state, "ENABLED") == 0 ||
                    strcmp(state, "STARTING") == 0) {
                        rtn = QZERO;
                        if (!Quietflag)
                                printf("%s\t%s\n", name, "ACTIVE");
                }
                else {
                        rtn = QONE;
                        if (!Quietflag)
                                printf("%s\t%s\n", name, "INACTIVE");
                }
        }
        pclose(fp);

        if (netspec && !found) {
                nlsmesg(MM_ERROR, "Invalid network specification");
                return(NLS_BADPM);
        }

        if (netspec)
                return(rtn);
        else
                return(NLS_OK);

}


/*
 * print info about service on netspec, or all services on netspec
 * if svc is NULL
 */

int
prt_svcs(char *svc, char *netspec)
{
        char    buf[BUFSIZ];
        char    mesg[BUFSIZ];
        FILE    *fp;
        struct  svcfields entry;
        int     rtn;
        int     found = FALSE;
        char    *p;

        if (svc == NULL)
                sprintf(buf, PM_LSALL, netspec);
        else
                sprintf(buf, PM_LSONE, netspec, svc);

        if ((fp = popen(buf, "r")) == NULL)
                return(NLS_SYSERR);

        while (fgets(buf, BUFSIZ, fp) != NULL) {
                if ((rtn = svc_format(buf, &entry)) != 0) {
                        switch (rtn) {
                        case NOTLISTEN:
                                continue;
                                break;
                        case BADPMFMT:
                                return(NLS_SYSERR);
                                break;
                        case BADLISFMT:
                                sprintf(mesg, "Entry for code \"%s\" has incorrect format", entry.svc_code);
                                nlsmesg(MM_WARNING, mesg);
                                continue;
                                break;
                        }
                }
                found = TRUE;

                if (!Quietflag) {
                        printf("%s\t", entry.svc_code);
                        if (*entry.addr)
                                printf("%s\t", entry.addr);
                        else if (strchr(entry.lflags, 'd'))
                                printf("DYNAMIC\t");
                        else
                                printf("NOADDR\t");

                        if (strchr(entry.flags, 'x') == NULL)
                                printf("ENABLED \t");
                        else
                                printf("DISABLED\t");


                        printf("%s\t%s\t%s\t%s\t# %s",
                                (*entry.rpc)?entry.rpc:"NORPC", entry.id,
                                (*entry.modules)?entry.modules:"NOMODULES",
                                entry.command, (*entry.comment)?entry.comment:"");
                }
                else {
                        if (strchr(entry.flags, 'x') == NULL)
                                return(QZERO);
                        else
                                return(QONE);
                }
        }

        pclose(fp);

        if (rtn == NOTLISTEN) { /* check last return to see if error */
                sprintf(mesg, "Network specification \"%s\" is not of type %s", netspec, LISTENTYPE);
                nlsmesg(MM_ERROR, mesg);
                return(NLS_BADPM);
        }
        if (svc && !found) {
                if (!Quietflag) {
                        sprintf(mesg, "Service \"%s\" unknown", svc);
                        nlsmesg(MM_ERROR, mesg);
                }
                return(NLS_SERV);
        }

        return(NLS_OK);
}

/*
 * disable_svc:  use pmadm to disable a service
 */

int
disable_svc(char *svc, char *netspec)
{
        char    buf[BUFSIZ];
        int     rtn;

        sprintf(buf, PM_DISABLE, netspec, svc);

        if ((rtn = system(buf)) < 0) {
                return(NLS_SYSERR);
        }
        rtn = (rtn>>8) & 0xff;  /* get child return value out of exit word */

        switch (rtn) {
        case 0:
                return(NLS_OK);
                break;
        case E_BADARGS:
        case E_SAFERR:
        case E_SYSERR:
        case E_PMRUN:
        case E_PMNOTRUN:
        case E_RECOVER:
        case E_SACNOTRUN:
        default:
                return(NLS_SYSERR);
                break;
        case E_NOEXIST:
        case E_DUP:
                nlsmesg(MM_ERROR, "Non-existent service.");
                return(NLS_SERV);
                break;
        case E_NOPRIV:
                no_permission();
                break;
        }
        /* NOTREACHED */
}


int
enable_svc(char *svc, char *netspec)
{
        char    buf[BUFSIZ];
        int     rtn;

        sprintf(buf, PM_ENABLE, netspec, svc);

        if ((rtn = system(buf)) < 0) {
                return(NLS_SYSERR);
        }
        rtn = (rtn>>8) & 0xff;  /* get child return value out of exit word */

        switch (rtn) {
        case 0:
                return(NLS_OK);
                break;
        case E_BADARGS:
        case E_SAFERR:
        case E_SYSERR:
        case E_PMRUN:
        case E_PMNOTRUN:
        case E_RECOVER:
        case E_SACNOTRUN:
        default:
                return(NLS_SYSERR);
                break;
        case E_NOEXIST:
        case E_DUP:
                nlsmesg(MM_ERROR, "Non-existent service.");
                return(NLS_SERV);
                break;
        case E_NOPRIV:
                no_permission();
                break;
        }
        /* NOTREACHED */
}


int
remove_svc(char *svc, char *netspec, int printerrors)
{
        char    buf[BUFSIZ];
        int     rtn;

        sprintf(buf, PM_REMSVC, netspec, svc);

        if ((rtn = system(buf)) < 0) {
                return(NLS_SYSERR);
        }
        rtn = (rtn>>8) & 0xff;  /* get child return value out of exit word */

        switch (rtn) {
        case 0:
                return(NLS_OK);
                break;
        case E_BADARGS:
        case E_SAFERR:
        case E_SYSERR:
        case E_PMRUN:
        case E_PMNOTRUN:
        case E_RECOVER:
        case E_SACNOTRUN:
        default:
                return(NLS_SYSERR);
                break;
        case E_NOEXIST:
        case E_DUP:
                if (printerrors)
                        nlsmesg(MM_ERROR, "Non-existent service.");
                return(NLS_SERV);
                break;
        case E_NOPRIV:
                no_permission();
                break;
        }
        /* NOTREACHED */
}


int
kill_listener(char *netspec)
{
        char    buf[BUFSIZ];
        char    mesg[BUFSIZ];
        int     rtn;

        sprintf(buf, SAC_KILLPM, netspec);

        if ((rtn = system(buf)) < 0) {
                return(NLS_SYSERR);
        }
        rtn = (rtn>>8) & 0xff;  /* get child return value out of exit word */

        switch (rtn) {
        case 0:
                return(NLS_OK);
                break;
        case E_BADARGS:
        case E_DUP:
        case E_SAFERR:
        case E_SYSERR:
        case E_PMRUN:
        case E_RECOVER:
        case E_SACNOTRUN:
        default:
                return(NLS_SYSERR);
                break;
        case E_PMNOTRUN:
                sprintf(mesg, "No listener active on network \"%s\"", netspec);
                nlsmesg(MM_ERROR, mesg);
                return(NLS_FAILED);
        case E_NOEXIST:
                nlsmesg(MM_ERROR, "Non-existent port monitor.");
                return(NLS_SERV);
                break;
        case E_NOPRIV:
                no_permission();
                break;
        }
        /* NOTREACHED */
}


/*
 * add_pm:  add a port monitor (initialize directories) using sacadm
 */

int
add_pm(char *netspec)
{
        char    buf[BUFSIZ];
        char    mesg[BUFSIZ];
        int     rtn;

        sprintf(buf, SAC_ADDPM, netspec, LISTENTYPE, gencmdstr(netspec), VERSION);

        if ((rtn = system(buf)) < 0) {
                return(NLS_SYSERR);
        }
        rtn = (rtn>>8) & 0xff;  /* get child return value out of exit word */

        switch (rtn) {
        case 0:
                old_addsvc(NLPSSVCCODE, NULL, NLPSSRV, "NLPS server", "", "root", NULL, netspec);
                return(NLS_OK);
                break;
        case E_BADARGS:
        case E_SAFERR:
        case E_SYSERR:
        case E_RECOVER:
        case E_NOEXIST:
        case E_PMNOTRUN:
        case E_SACNOTRUN:
        default:
                return(NLS_SYSERR);
                break;
        case E_DUP:
        case E_PMRUN:
                nlsmesg(MM_ERROR, "Listener already initialized");
                return(NLS_FAILED);
                break;
        case E_NOPRIV:
                no_permission();
                break;
        }
        /* NOTREACHED */
}


/*
 * gencmdstr:  generate the correct string to invoke the listener (starlan
 *             requires special handling)
 */

char *
gencmdstr(char *netspec)
{
        static char buf[BUFSIZ];

        (void) strcpy(buf, LISTENCMD);
        if (!strcmp(netspec, "starlan"))
                (void) strcat(buf, " -m slan");
        (void) strcat(buf, " ");
        (void) strcat(buf, netspec);
        return(buf);
}


/*
 * start_listener: start the listener
 */

int
start_listener(char *netspec)
{
        char    buf[BUFSIZ];
        char    scratch[BUFSIZ];
        int     rtn;

        sprintf(buf, SAC_STARTPM, netspec);

        if ((rtn = system(buf)) < 0)
                return(NLS_SYSERR);
        rtn = (rtn>>8) & 0xff;
        switch (rtn) {
        case 0:
                break;
        case E_BADARGS:
        case E_SAFERR:
        case E_SYSERR:
        case E_RECOVER:
        case E_PMNOTRUN:
        case E_SACNOTRUN:
        default:
                return(NLS_SYSERR);
                break;
        case E_NOEXIST:
        case E_DUP:
                nlsmesg(MM_ERROR, "Non-existent port monitor.");
                return(NLS_BADPM);
                break;
        case E_PMRUN:
                nlsmesg(MM_ERROR, "Listener already running");
                return(NLS_FAILED);
        case E_NOPRIV:
                no_permission();
                break;
        }

        sprintf(buf, SAC_ENABLPM, netspec);

        if ((rtn = system(buf)) < 0) {
                return(NLS_SYSERR);
        }
        rtn = (rtn>>8) & 0xff;
        switch (rtn) {
        case 0:
                return(NLS_OK);
                break;
        case E_BADARGS:
        case E_SAFERR:
        case E_SYSERR:
        case E_RECOVER:
        case E_SACNOTRUN:
        default:
                return(NLS_SYSERR);
                break;
        case E_NOEXIST:
        case E_DUP:
                nlsmesg(MM_ERROR, "Non-existent port monitor.");
                return(NLS_BADPM);
                break;
        case E_PMRUN:
                nlsmesg(MM_ERROR, "Listener already running");
                return(NLS_FAILED);
        case E_PMNOTRUN:
                nlsmesg(MM_ERROR, "Listener start failed");
                return(NLS_FAILED);
        case E_NOPRIV:
                no_permission();
                break;
        }
        /* NOTREACHED */
}


/*
 * setup_addr:  setup the -l and -t addresses.
 */

int
setup_addr(char *laddr, char *taddr, char *netspec)
{
        char    buf[BUFSIZ];
        char    mesg[BUFSIZ];
        char    *p;
        int     rtn;
        int     qlisten = FALSE;
        int     qtty = FALSE;
        FILE    *fp;
        struct  svcfields entry;

        if (laddr && *laddr == '-')
                qlisten = TRUE;

        if (taddr && *taddr == '-')
                qtty = TRUE;

        if (laddr) {
                sprintf(buf, PM_LSONE, netspec, NLPSSVCCODE);

                if ((fp = popen(buf, "r")) == NULL) {
                        return(NLS_SYSERR);
                }

                if (fgets(buf, BUFSIZ, fp) != NULL) {
                        if ((rtn = svc_format(buf, &entry)) != 0) {
                                switch (rtn) {
                                case NOTLISTEN:
                                        nlsmesg(MM_ERROR, "Incorrect port monitor type.  Must be of type listen");
                                        return(NLS_FAILED);
                                        break;
                                case BADPMFMT:
                                        return(NLS_SYSERR);
                                        break;
                                case BADLISFMT:
                                        sprintf(mesg, "Entry for code \"%s\" has incorrect format", entry.svc_code);
                                        nlsmesg(MM_WARNING, mesg);
                                        break;
                                }
                        }
                        else {
                                if (qlisten)
                                        printf("%s\n", entry.addr);
                                else {
                                        if (geteuid() != ROOT)
                                                no_permission();
                                        /* add address */
                                        remove_svc(NLPSSVCCODE, netspec, FALSE);
                                        p = strchr(entry.comment, '\n');
                                        if (p)
                                                *p = '\0';
                                        old_addsvc(NLPSSVCCODE, laddr, entry.command, entry.comment, entry.modules, entry.id, entry.flags, netspec);
                                }
                        }
                        pclose(fp);
                }
                else if (!qlisten)
                        nlsmesg(MM_WARNING, "NLPS service not defined");
        }
        if (taddr) {
                sprintf(buf, PM_LSONE, netspec, TTYSVCCODE);

                if ((fp = popen(buf, "r")) == NULL) {
                        return(NLS_SYSERR);
                }

                if (fgets(buf, BUFSIZ, fp) != NULL) {
                        if ((rtn = svc_format(buf, &entry)) != 0) {
                                switch (rtn) {
                                case NOTLISTEN:
                                        nlsmesg(MM_ERROR, "Incorrect port monitor type.  Must be of type listen");
                                        return(NLS_FAILED);
                                        break;
                                case BADPMFMT:
                                        return(NLS_SYSERR);
                                        break;
                                case BADLISFMT:
                                        sprintf(mesg, "Entry for code \"%s\" has incorrect format", entry.svc_code);
                                        nlsmesg(MM_WARNING, mesg);
                                        break;
                                }
                        }
                        else {
                                if (qtty)
                                        printf("%s\n", entry.addr);
                                else {
                                        if (geteuid() != ROOT)
                                                no_permission();
                                        /* add address */
                                        remove_svc(TTYSVCCODE, netspec, FALSE);
                                        p = strchr(entry.comment, '\n');
                                        if (p)
                                                *p = '\0';
                                        old_addsvc(TTYSVCCODE, taddr, entry.command, entry.comment, entry.modules, entry.id, entry.flags, netspec);
                                }
                        }
                        pclose(fp);
                }
                else if (!qtty)
                        nlsmesg(MM_WARNING, "remote login service not defined");
        }
        return(NLS_OK);
}


/*
 * svc_format:  scan a line of output from pmadm to separate it into fields.
 *              returns BADPMFMT for missing fields or incorrect syntax.
 *                      NOTLISTEN is the port monitor type is not listen.
 *                      BADLISFMT if the listener-specific data is incorrect.
 *                      NLS_OK if everything checked out and data is broken
 *                             into the structure.
 */

int
svc_format(char *buf, struct svcfields *entry)
{
        char    *ptr;           /* temporary pointer into buffer        */
        char    *tmp;           /* temporary pointer into buffer        */

        entry->pmtag = buf;
        if ((ptr = strchr(buf, ':')) == NULL)
                return(BADPMFMT);
        *ptr++ = '\0';
        entry->pmtype = ptr;
        if ((ptr = strchr(entry->pmtype, ':')) == NULL)
                return(BADPMFMT);
        *ptr++ = '\0';
        entry->svc_code = ptr;

        if (strcmp(entry->pmtype, LISTENTYPE) != 0)
                return(NOTLISTEN);

        if ((ptr = strchr(entry->svc_code, ':')) == NULL)
                return(BADPMFMT);
        *ptr++ = '\0';
        entry->flags = ptr;
        if ((ptr = strchr(entry->flags, ':')) == NULL)
                return(BADPMFMT);
        *ptr++ = '\0';
        entry->id = ptr;
        if ((ptr = strchr(entry->id, ':')) == NULL)
                return(BADPMFMT);
        *ptr++ = '\0';
        entry->res1 = ptr;
        if ((ptr = strchr(entry->res1, ':')) == NULL)
                return(BADPMFMT);
        *ptr++ = '\0';
        entry->res2 = ptr;
        if ((ptr = strchr(entry->res2, ':')) == NULL)
                return(BADPMFMT);
        *ptr++ = '\0';
        entry->res3 = ptr;
        if ((ptr = strchr(entry->res3, ':')) == NULL)
                return(BADPMFMT);
        *ptr++ = '\0';
        entry->addr = ptr;
        if ((ptr = strchr(entry->addr, ':')) == NULL)
                return(BADLISFMT);
        *ptr++ = '\0';
        entry->rpc = ptr;
        if ((ptr = strchr(entry->rpc, ':')) == NULL)
                return(BADLISFMT);
        *ptr++ = '\0';
        if (*entry->rpc) {
                if ((tmp = strchr(entry->rpc, ',')) == NULL)
                        return(BADLISFMT);
                *tmp = ':';
        }
        entry->lflags = ptr;
        if ((ptr = strchr(entry->lflags, ':')) == NULL)
                return(BADLISFMT);
        *ptr++ = '\0';
        entry->modules = ptr;
        if ((ptr = strchr(entry->modules, ':')) == NULL)
                return(BADLISFMT);
        *ptr++ = '\0';
        entry->command = ptr;
        if ((ptr = strchr(entry->command, '#')) == NULL)
                return(BADLISFMT);
        *ptr++ = '\0';
        entry->comment = ptr;
        return(NLS_OK);
}


/*
 * nexttok - return next token, essentially a strtok, but it can
 *      deal with null fields and strtok can not
 *
 *      args:   str - the string to be examined, NULL if we should
 *                    examine the remembered string
 *              delim - the list of valid delimiters
 */


char *
nexttok(char *str, char *delim)
{
        static char *savep;     /* the remembered string */
        register char *p;       /* pointer to start of token */
        register char *ep;      /* pointer to end of token */

        p = (str == NULL) ? savep : str ;
        if (p == NULL)
                return(NULL);
        ep = strpbrk(p, delim);
        if (ep == NULL) {
                savep = NULL;
                return(p);
        }
        savep = ep + 1;
        *ep = '\0';
        return(p);
}


/*
 * pflags - put flags into intelligible form for output
 *
 *      args:   flags - binary representation of flags
 */

char *
pflags(long flags)
{
        register int i;                 /* scratch counter */
        static char buf[BUFSIZ];        /* formatted flags */

        if (flags == 0)
                return("");
        i = 0;
        if (flags & CFLAG) {
                buf[i++] = 'c';
                flags &= ~CFLAG;
        }
        if (flags & DFLAG) {
                buf[i++] = 'd';
                flags &= ~DFLAG;
        }
        if (flags & PFLAG) {
                buf[i++] = 'p';
                flags &= ~PFLAG;
        }
        if (flags) {
                nlsmesg(MM_ERROR, "Internal error in pflags");
                exit(NLS_FAILED);
        }
        buf[i] = '\0';
        return(buf);
}