reid@Glacier.ARPA (11/08/84)
Here is a little program that we use at Stanford to create, modify, delete, and purge Unix accounts. It is not the best-structured thing in the world, because it has undergone several years of evolution and modification, but we are now pretty happy with what it does and how it works, even if we aren't overly proud of the code. There is a fairly complete man page. You should not have much trouble adapting it for use at your own site. Brian Reid Stanford ..decwrl!glacier!reid Reid@SU-Glacier.ARPA #! /bin/sh : This is a shell archive. Extract with sh, not csh. echo x - Makefile cat > Makefile << BeginBombingInFiveMinutes nu: nu.c cc -O -o nu nu.c -ldbm install: nu -mkdir /etc/nulib -install -s nu /etc -cp nu*.sh /etc/nulib -cp nu.cf.debug /etc/nulib -cp nu.cf.real /etc/nulib -ln -s /etc/nulib/nu.cf.real /etc/nu.cf -cp nu.8 /usr/man/man8 -echo Make sure you edit /etc/nulib/nu.cf.\* for your site BeginBombingInFiveMinutes echo x - nu.8 cat > nu.8 << BeginBombingInFiveMinutes .TH NU 8 .SU .SH NAME nu \- manage user login accounts (create, modify, destroy Unix accounts) .SH SYNOPSIS .nf .B /etc/nu -a .B /etc/nu -m .B /etc/nu -d .B /etc/nu -k user1 user2 ... .fi .SH DESCRIPTION .I Nu is a program to help a Unix system manager create, modify, delete, and and destroy accounts on that machine. While everything accomplished by \fInu\fR can be done manually by editing files and issuing shell commands, \fInu\fR will steer you through getting all the details right, worrying about file locking, checking for typos, etc. .PP When \fInu\fR is run with the ``a'' option, it adds new accounts. The program prompts you for the login id, password, name, and other information about each new user, and then goes off and creates the account, creates its directories, initializes their contents, and makes an entry in a log file. .PP When \fInu\fR is run with the ``m'' option, it modifies existing accounts. It repeatedly asks for account names and instructions for the changes that you want to make to those accounts, until you tell it that you are done making changes. At that time it sorts the updated account records and merges them all at once into /etc/passwd. .PP When \fInu\fR is run with the ``d'' option, it deletes accounts while still leaving an /etc/passwd entry for the deleted account. This will prevent that uid from being reused, and will enable you to make sense out of accounting data after deleting an account. The program repeatedly asks you for the names of accounts to be deleted, and how much deleting you really want to do to them. .PP When \fInu\fR is run with the ``k'' option and a list of login id's, it deletes from the system almost all information pertaining to those login id's. Specifically, it removes the entry from /etc/passwd, deletes the login directory and all of its contents, and deletes the mailbox. It does not currently remove that user from any mailing lists in /usr/lib/aliases. The ``k'' option is not interactive: the complete list of accounts to be deleted is provided on the command line after the ``\-k''. .SH CONFIGURATION When .I nu is started up, it reads configuration commands from the file /etc/nu.cf. This file specifies the details of how new accounts are to be created on your machine. Typically you will need to change only the GroupHome declarations in that file, which declare the file systems that hold the login directories for members of different groups. However, you can change anything that you find there if your system management policies require it. .PP When \fInu\fR wants to create a new directory, it runs a shell script named in /etc/nu.cf. Similarly, when it wants to initialize the files in a newly-created directory, it runs another shell script whose name it determines from /etc/nu.cf. By way of configuration and customization, you can edit those shell scripts to conform to local practices. When you do that editing, please remember that \fInu\fR runs as root and that the shell scripts contain statements like ``rm \-rf *''; it goes without saying that you must be quite cautious. There is a debug mode available, in which \fInu\fR will try not to hurt anything, but whenever you are running as root you should be unusually careful. \fINu\fR can be run by non-root users if its debug mode is enabled by a ``Debug=1'' statement in /etc/nu.cf. .PP .SH CONFIGURATION FILE FORMAT The configuration file /etc/nu.cf is a text file containing a series of statements, one statement per line. A semicolon that is not inside a quoted string causes the rest of that line to be treated as a comment. Each line in the file that is nonblank after stripping comments is treated as an assignment statement. Each statement assigns a value to one variable. With the exception of the variable "GroupHome", which is special, all of the variables act like ordinary shell variables, which is to say that they can take either integer values or string values. All integers are decimal; all strings must be delimited with double-quotes ("). There is no quoting or doubling convention for putting a doublequote character inside a string. .PP Here are the configuration variables and what they mean. Case is significant. .HP 5 .B Backupfile .br This string variable gives the pathname that \fInu\fR will use to make a backup copy of /etc/passwd, to protect itself from disaster in case something happens while it is writing /etc/passwd. Typical value of Backupfile is "/usr/adm/nu.passwd". .HP 5 .B CreateDir .br This string variable identifies the shell script that is run whenever \fInu\fR needs to create a new directory. That shell script must be executable. It is called with 6 arguments: 1, the integer uid; 2, the integer groupid; 3, the name of the user's actual home directory; 4, the name of a symbolic link that should be set up to point to that home directory; 5, an integer that is nonzero iff it is ok to clobber an existing directory of the same name as argument 4; and 6, an integer that is nonzero iff nu is running in debug mode. The standard value for CreateDir is "/etc/nulib/nu1.sh". .HP 5 .B CreateFiles .br This string variable identifies the shell script that is run whenever \fInu\fR needs to initialize a directory (newly-created or otherwise) with some standard files. For example, /usr/skel/.[a-z]* are often copied into a new login directory. This shell script must be executable. It is called with 5 arguments: 1, the name of the login directory to be initialized; 2, the integer uid of the user; 3, the integer groupid of the user; 4, an integer that is nonzero iff an MH-format mailbox is to be set up with some initial contents; and 5, an integer that is nonzero iff \fInu\fR is running in debug mode. The standard value for CreateFiles is "/etc/nulib/nu2.sh". .HP 5 .B Debug .br This integer variable is set nonzero to cause \fInu\fR to run in debug mode. Debug mode is intended to help you get the bugs out of your shell scripts before you go foolishly running them as root. If Debug is nonzero, then you do not need to be logged on as root to run \fInu\fR. The standard value for Debug is 0. .HP 5 .B DefaultGroup .br This integer variable is set to the group number of the default user group. The default is used if the person running \fInu\fR types a carriage return in response to the question asking for a group id for the new user. \fInu\fR requires that a valid GroupHome assignment exist for the default group number. The standard value for DefaultGroup is any group number from /etc/group. .HP 5 .B DefaultHome .br This string variable is set to the file system or top-level directory that will be used to hold the login directory for accounts in groups not explicitly set up to have their login directories somewhere else. When you are creating a new account, \fInu\fR asks you what group number you would like the account in. If that group number is mentioned in a GroupHome declaration (see below), then the home directory for the group is the one named in that GroupHome declaration. If the group number is not mentioned in a GroupHome declaration, then login accounts created in that group will have their login directories put into DefaultHome. The standard value for DefaultGroup is "/mnt". .HP 5 .B DefaultShell .br This string variable is set to the name of the shell file to use by default. The standard value for DefaultShell is "/bin/csh". .HP 5 .B DeleteAccts .br This string variable identifies the shell script that is run whenever \fInu\fR needs to delete a user's account that was created in some earlier session with \fInu\fR. Deleting accounts involves changing the password so that the user cannot log in, deleting all of his files and directories, and deleting his mailbox. This shell script must be executable. It is called with 5 arguments: 1, the login id of the account to be deleted; 2, the login directory for that account; 3, the name given in /etc/passwd for the login directory (which might possibly be a symbolic link to item 2, above, and therefore needs to be named separately); 4, the name of the log file in which account changes are being logged, and 5, an integer that is nonzero iff \fInu\fR is running in debug mode. The standard value for DeleteAccts is "/etc/nulib/nu4.sh". .HP 5 .B DestroyAccts .br This string variable identifies the shell script that is run whenever \fInu\fR needs to destroy a user's account that was created in some earlier session with \fInu\fR. Destroying accounts involves removing the user from the password file, deleting all of his files and directories, and deleting his mailbox. For a (slightly) less drastic account removal action, see ``DeleteAccts'', above. This shell script must be executable. It is called with 5 arguments: 1, the login id of the account to be deleted; 2, the login directory for that account; 3, the name given in /etc/passwd for the login directory (which might possibly be a symbolic link to item 2, above, and therefore needs to be named separately); 4, the name of the log file in which account changes are being logged, and 5, an integer that is nonzero iff \fInu\fR is running in debug mode. The standard value for DestroyAccts is "/etc/nulib/nu3.sh". .HP 5 .B Dummyfile .br This string variable holds the name of the hard link that is created as part of the locking process on /etc/passwd; see \fIvipw(8)\fR. The correct value for Dummyfile is "/etc/vipw.lock". The only reason that it is specified in the configuration file and not hardwired into the code of \fInu\fR is that in debugging you do not want to muck with the real lock (and might in fact not even have permissions to lock it). .HP 5 .B GroupHome .br This pseudo-variable is the only name defined in the configuration file that has any trickery attached to it. GroupHome is not really a variable; rather, it is a name by which the configuration code can load entries into a directory location table. In particular, if you provide two GroupHome declarations, they are both processed, while if you provide two of any other declaration, only the latest one has any effect. A typical set of GroupHome declarations might look something like this: .nf GroupHome= 10 "/usr" GroupHome= 20 "/mnt" GroupHome= 25 "/usr/cis" GroupHome= 31 "/usr/guest" .fi The GroupHome declarations serve as default login directory location information for new accounts. You can put any account anywhere you want; the GroupHome information is used to make the defaults come out in the right places, so that the process of creating a new account consists mostly of hitting the return key to accept the defaults. The sample declarations above cause group 10 to default to /usr, i.e. /usr/smith or /usr/jones, and group 31 to default to /usr/guest, i.e. /usr/guest/smith or /usr/guest/jones. If the login group is not mentioned in a GroupHome declaration, then the DefaultHome variable is used. A GroupHome declaration is required for the default group (see variable DefaultGroup); all others are optional. .HP 5 .B Linkfile .br See also ``Dummyfile''. This string variable gives the name of the file to which links are made for the purpose of locking the password file. Any value besides "/etc/ptmp" is suspect. .HP 5 .B Logfile .br This string variable names the file in which all \fInu\fR transactions are logged. The standard value of Logfile is "/usr/adm/nu.log". .HP 5 .B MaxNameLength .br This integer variable gives the maximum number of characters permitted in a login name. For unmodified 4BSD systems it should be set to 8. .HP 5 .B PasswdFile .br This string variable gives the name of the file into which \fInu\fR will write its new account entries. Unless you are debugging, its value should be "/etc/passwd". .HP 5 .B SymbolicLinkDir .br This string variable gives the name of a directory that can be filled with symbolic links to real login directories. The value of SymbolicLinkDir is ignored unless the variable WantSymbolicLinks is nonzero. See its description, below, for more information. Standard values for SymbolicLinkDir are "/user" or "/udir". .HP 5 .B Tempfile .br This string variable names the file that \fInu\fR will use for building a scratch copy of /etc/passwd during the account modification process. The value doesn't really matter much; it is created at the beginning of an \fInu\fR execution and destroyed before exit. A typical value for Tempfile is "/usr/adm/nu.temp". .HP 5 .B WantMHsetup .br This integer variable should be set to 1 if you would like \fInu\fR to take care of initializing mailbox contents. Initializing an MH mailbox turns out to be a pleasant way to provide new users with information about the system, and to give them a tutorial on the use of MH. \fINu\fR just passes the value of WantMHsetup through to the shell script named in CreateFiles, which is responsible for doing the actual initialization. Standard value is 1. .HP 5 .B WantSymbolicLinks .br This integer variable controls whether login directory names or symbolic links to them are put in the actual /etc/passwd file. If WantSymbolicLinks is nonzero, then all created accounts are given uniform login directory names in some directory that exists only for the purpose of holding symbolic links, e.g. /user/smith and /user/jones; the file /user/smith or /user/jones is then made to be a symbolic link to the real login directory. This is preferable to the ~smith or ~jones scheme for finding login directories because the ~ notation is not handled by the kernel, but must be handled individually by all programs that open files. If the variable WantSymbolicLinks is 0, then accounts will be created such that the true directory name is stored in /etc/passwd. .SH SYSTEM ISSUES \fINu\fR obeys the standard locking protocol for /etc/passwd; see \fIvipw(8)\fR. It traps INTR characters (e.g. ^C) and refuses to die if you try to stop it in the middle of a critical section. Critical sections are primarily the updates of /etc/passwd. A list of all changes is recorded in a log file, usually /usr/adm/nu.log. .SH FILES .ta \w'/usr/adm/nu.passwd 'u /etc/passwd system password file .br /etc/group system group file .br /etc/ptmp lock file .br /etc/vipw.lock dummy file linked to by /etc/ptmp .br /etc/nu.cf Configuration file .br /etc/nulib/*.sh Shell scripts to perform the work .br others nu.cf and nulib/*.sh reference other .br files. .DT .SH "SEE ALSO" adduser(8), getgrent(3), getpwent(3), group(5), passwd(5), vipw(8) .SH AUTHOR Brian Reid, Erik Hedberg, Fred Yankowski .SH BUGS The extensive use of shell scripts for doing sensitive things like purging accounts means that somebody can make \fInu\fR fail in horrible ways without having access to the source code. With increased flexibility comes increased responsibility. BeginBombingInFiveMinutes echo x - nu.c cat > nu.c << BeginBombingInFiveMinutes /* New User Installation Program * * Brian Reid, Erik Hedberg, and Fred Yankowski * Stanford University * * This program helps system administrators manage login accounts. * it reflects the way we do things at Stanford, but can probably * be adapted to almost any situation. * It was originally written by Fred Yankowski as an MS project in 1980. * Several years of experience at using it showed us lots of things that * we would like it to do differently; Erik Hedberg added many changes. * In summer 1984 Brian Reid tore it all apart, adapted it to 4.2BSD, * modified it to use the C library as much as possible (this was Fred's * first attempt at a C program), added error checking and command-line * parsing, rewrote code that didn't amuse him, and changed the * structure of it so that it read in a configuration file instead of * having compile-time options. The man page is entirely by Brian Reid. * * This program does not contain any Unix source code, nor is it copied * from any. It is completely public domain, and you are free to use, * distribute, modify, or sell it, make T-shirts out of it, or do * whatever you want. * */ #define CONFIGFILE "/etc/nu.cf" /* configuration info from here */ #define ALIASES "/usr/lib/aliases" /* sendmail alias file */ #define MAXSYMBOLS 100 /* limit of configuration symbols */ #define MAXGROUPS 100 /* limit of GroupHome symbols */ #include <pwd.h> #include <stdio.h> #include <ctype.h> #include <grp.h> #include <signal.h> #include <sgtty.h> #include <sys/types.h> #include <sys/stat.h> #define CR '\n' #define BUF_LINE 250 /* line length buffer */ #define BUF_WORD 40 /* word length buffer */ #define TRUE 1 /* return(...) codes */ #define FALSE 0 #define ERROR 1 /* exit(...) codes */ #define OK 0 /* DoCommand codes ... */ #define FATAL 1 /* Errors during System calls exit */ #define NONFATAL 0 /* Errors just print message */ #define SAFE 1 /* Safe to execute during debugging */ #define UNSAFE 0 /* Must not execute while debugging */ #define NOT ! #define MAXUID 99999 /* assumed to be higher than any userid */ #define MAXMODS 50 /* max. modifications in session */ char *ctime (), *getlogin (), *crypt (), *StrV (); int IntV (); struct passwd *getpwent (), *getpwuid (), *getpwnam (); char *getpass (); struct group *getgrgid (), *getgrnam (); struct passwd *pwd; /* ptr to an entry of the passwd file */ /* This is a copy of the passwd structure, values are copied into this structure because getpw--- returns a pointer to a STATIC area that is overwritten on each call. */ struct cpasswd { char cpw_name[BUF_WORD]; char cpw_passwd[BUF_WORD]; char cpw_asciipw[BUF_WORD];/* Extra field: Ascii password */ int cpw_uid; int cpw_gid; char cpw_group[BUF_WORD];/* Extra field: Ascii group name */ char cpw_person[BUF_WORD]; char cpw_dir[BUF_WORD]; char cpw_linkdir[BUF_WORD];/* Extra field: top-level link dir */ char cpw_shell[BUF_WORD]; }; /* This structure is used to hold the configuration statements from nu.cf Note that no error checking of any kind is done! */ struct Symb { char *SymbName; /* Variable name goes here */ char *Svalue; /* String argument goes here OR */ long ivalue; /* Integer argument goes here */ }; struct Symb Symbols[MAXSYMBOLS]; long nsymbols = 0; /* Definitions for using dbm (to access alias database) */ typedef struct { char *dptr; int dsize; } datum; int dbminit (); datum fetch (), firstkey (), nextkey (); #define dbm_buf_size 1024 /* dbm(3) guarantees this limit */ /* End of dbm declarations */ char This_host[100]; /* Holds result of gethostname() */ struct topnode { int gid; /* login group number */ char *topnodename; /* what comes after /usr */ }; int numtopnodes = 0; struct topnode topnode[MAXGROUPS]; /* to be filled in from nu.cf */ int incritsect; /* Set to true when the program is in the section involved with updating the files and creating new files and, hence, should not be interruptable by ^C. */ int wasinterrupted; /* Set to true if interrupt occurs during a call to 'System' */ char cmd[BUF_LINE]; /* Buffer to construct commands in */ char editor[BUF_WORD]; /* Who is running this program */ FILE * logf; /* File to log actions in */ int whichmail; /* record which kind of mail the user wants */ struct stat statbuf; /* for stat and lstat calls */ ncpy (to, from, len) char *to, *from; int len; { register int i; for (i = 1; i <= len; i++) *to++ = *from++; } /* YesNo * Gets either a yes or a no answer. "Yy\n" for yes, "Nn" for no. */ int YesNo (defaultans) char defaultans; { char temp[BUF_WORD], ans; for (;;) { fgets (temp, BUF_WORD, stdin); MapLowerCase (temp); ans = temp[0]; if (ans == '\n') ans = defaultans; if (ans == 'y') return (TRUE); if (ans == 'n') return (FALSE); printf ("Answer y or n. [return means %c] ", defaultans); } } /* UseDefault * returns TRUE if NULL entered (meaning use default) or * FALSE after reading text to use in place of default */ UseDefault (str, def) char *str, *def; { char temp[BUF_WORD]; printf (" [%s] ", def); gets (temp); if (temp[0] == NULL) { if (str != def) strcpy (str, def); return (TRUE); } else { strcpy (str, temp); return (FALSE); } } /* GetUserID * returns the userid for the new user. The routine scans the * password file to find the highest userid so far assigned. * The new userid is then set to be the one more than this value. * This calculated default can be overridden, but the password * file is searched to insure that the chosen userid will be * unique. */ GetUserID () { int maxuid, newuid; char defaultID[BUF_WORD], buf[BUF_WORD]; /* scan the passwd file for the highest userid */ setpwent (); /* 'rewinds' passwd file search ptr */ maxuid = 0; while ((pwd = getpwent ()) != NULL) { if (pwd->pw_uid > maxuid) maxuid = pwd->pw_uid; } newuid = maxuid + 1; sprintf (defaultID, "%d", newuid); for (;;) { printf ("User id number? (small integer) "); UseDefault (buf, defaultID); newuid = atoi (buf); if (newuid <= 0) { printf ("Userid must be >0, and should be >10.\n"); continue; } setpwent (); /* rewind password file search */ if ((pwd = getpwuid (newuid)) == NULL) return (newuid); else printf ("User id %d is already assigned to %s (%s)\n", pwd->pw_uid, pwd->pw_name, pwd->pw_gecos); } } /* GetGroup * writes the group name in grpname and returns the numeric gid. * A groupid can be entered as a number or the name for a * group (as defined in /etc/group). The getgr-- functions are * used to peruse the group file. Legal symbolic names are * mapped into the corresponding groupid. Input numeric * groupid's are mapped into the corresponding symbolic name * (if such exists) for verification. */ GetGroupID (group) char *group; { struct group *agrp; int gid; char buf[BUF_WORD], def[BUF_WORD]; /* Get group name for IntV("DefaultGroup") */ setgrent (); if (agrp = getgrgid (IntV ("DefaultGroup"))) strcpy (def, agrp->gr_name); else strcpy (def, "unknown"); for (;;) { printf ("Which user group? (name or number; ? for list)"); UseDefault (buf, def); if ((strcmp (buf, "?") == 0 || (strcmp (buf, "help") == 0))) { char hbuf[BUF_LINE], *hptr; int gcount; setgrent (); hptr = hbuf; gcount = 0; sprintf (hptr, "Available groups are:"); while ((agrp = getgrent ()) != 0) { if ((gcount++) == 7) { strcat (hptr, "\n\t\t "); gcount = 0; } strcat (hptr, " "); strcat (hptr, agrp->gr_name); } endgrent (); puts (hptr); continue; } if (isdigit (*buf)) { /* presumably, a numeric groupid has been entered */ gid = atoi (buf); setgrent (); if (agrp = getgrgid (gid)) strcpy (buf, agrp->gr_name); else strcpy (buf, "unknown"); printf ("Selected groupid is %d (%s), OK? (y or n) [y]", gid, buf); if (YesNo ('y')) { strcpy (group, buf); return (gid); } } else { /* some symbolic group name has been entered */ setgrent (); if (agrp = getgrnam (buf)) { strcpy (group, agrp->gr_name); return (agrp->gr_gid); } else printf ("Sorry, %s is not a registered group name.\nIf you want a list of groups, type '?'\n", buf); } } } /* MapLowerCase * maps the given string into lower-case. */ MapLowerCase (b) char *b; { while (*b) {if (isupper (*b)) *b++ += 'a'-'A'; else b++;} } HasBadChars (b) char *b; { while (*b) { if ((NOT isalpha (*b)) && (NOT isdigit (*b)) && (*b != '_')) return (TRUE); b++; } return (FALSE); } /* GetLoginName * Prompts for a login name and checks to make sure it is legal. * The login name is returned, null-terminated, in "buf". */ GetLoginName (buf) char *buf; { int done = FALSE, i, j; char *aptr, aname[dbm_buf_size]; datum aliaskey, aliasname; while (NOT done) { printf ("Login name? (1-8 alphanumerics) [no default] "); gets (buf); if (buf[0] == NULL) continue; MapLowerCase (buf); if (HasBadChars (buf)) { printf ("Sorry, the login name can contain only alphanumerics or '_'\n"); continue; } if (strlen (buf) > IntV ("MaxNameLength")) { printf ("Sorry, login names must not contain more than "); printf ("%d characters\n", IntV ("MaxNameLength")); buf[IntV ("MaxNameLength")] = 0; printf ("Should it be truncated to '%s'? ", buf); if (NOT YesNo ('y')) continue; /* start over again */ } /* check to see that the login is unique */ setpwent (); if (pwd = getpwnam (buf)) { printf ("Sorry, the login '%s' is already in use (id %d, %s).\n", buf, pwd->pw_uid, pwd->pw_gecos); continue; } /* check to make sure that the login does not conflict with an alias. This whole section of code is ridiculous overkill, but I was in the mood (BKR). */ aliaskey.dptr = buf; aliaskey.dsize = strlen (buf) + 1; /* char count includes the null!! */ aliasname = fetch (aliaskey); if (aliasname.dptr != NULL) { printf ("Sorry, the name '%s' is already in use as a mail alias\n(aliased to '", buf); if (aliasname.dsize > dbm_buf_size-2) aliasname.dsize = dbm_buf_size-2; aptr = aliasname.dptr; for (i = 0; (aptr[i] & 0177) == ' ' || (aptr[i] & 0177) == '\t'; i++); if ((aptr[i] & 0177) == '"' && (aptr[aliasname.dsize-2] == '"')) { i++; /* Unquote quoted names */ aptr[aliasname.dsize-2] = 0; aliasname.dsize--; } for (j = 0; i < aliasname.dsize; i++, j++) { aname[j] = aptr[i] & 0177; if (i < 60 - strlen (buf)) putchar (aname[j]); } putchar ('\''); aname[j] = 0; if (aliasname.dsize >= 60 - strlen (buf)) printf ("..."); puts (")"); printf ("\nBecause some mail aliases are critical to system operation, you must\nresolve that conflict before you can create an account named '%s'.\n\n", buf); /* Try to figure out what the alias entry is. We do this because the person running nu is often an administrative assistant and not a programmer. */ if (aname[0] == '|') { /* It's a program */ printf ("The current alias is a program, automatically run whenever mail is sent\nto '%s'. It is almost certainly a bad idea to delete this alias,\nbut check with a systems programmer to be sure.\n", buf); } else if (aname[0] == '/') { /* A log file */ printf ("The current alias is a log file. All mail sent to '%s' is auto-\nmatically added to the end of %s. You can probably negotiate\nwith its owner to change from '%s' to a new name.\n", buf, aname, buf); } else { /* alias or list */ if (index (aname, ',')) {/* mailing list */ printf ("The current alias is a mailing list, enabling people to send mail\nto '%s@%s' and have it reach a group of people. To know whether\nit is safe to delete or rename that list, you need to learn who uses it (the\nusers are not necessarily