pw(8) -- a backend utility to manage the user and group databases.

sysinstall's new User&group menu will use it, hence it's a 2.2
candidate despite of providing new functionality.

Submitted by:	David L. Nugent, <davidn@blaze.net.au>
This commit is contained in:
Joerg Wunsch 1996-12-09 14:05:35 +00:00
parent 581f6ca842
commit d6f907dc7a
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/cvs2svn/branches/DAVIDN/; revision=20253
21 changed files with 4523 additions and 0 deletions

18
usr.sbin/pw/Makefile Normal file
View File

@ -0,0 +1,18 @@
# $Id$
PROG= pw
SRCS= pw.c pw_conf.c pw_user.c pw_group.c pw_log.c \
grupd.c pwupd.c fileupd.c edgroup.c psdate.c \
bitmap.c cpdir.c rm_r.c
MAN5= pw.conf.5
MAN8= pw.8
CFLAGS+= -Wall
LDADD= -lcrypt
DPADD= ${LIBCRYPT}
BINOWN= root
BINMODE=0555
.include <bsd.prog.mk>

22
usr.sbin/pw/README Normal file
View File

@ -0,0 +1,22 @@
pw is a command-line driven passwd/group editor utility that provides
an easy and safe means of modifying of any/all fields in the system
password files, and has an add, modify and delete mode for user and
group records. Command line options have been fashioned to be similar
to those used by the Sun/shadow commands: useradd, usermod, userdel,
groupadd, groupmod, groupdel, but combines all operations within the
single command `pw'.
User add mode also provides a means of easily setting system useradd
defaults (see pw.conf.5), so that adding a user is as easy as issuing
the command "pw useradd <loginid>". Creation of a unique primary
group for each user and automatic memberhip in secondary groups
is fully supported.
This program may be FreBSD specific, but should be trivial to port to
other bsd4.4 variants.
Author and maintainer: David L. Nugent, <davidn@blaze.net.au>
$Id$

135
usr.sbin/pw/bitmap.c Normal file
View File

@ -0,0 +1,135 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <stdlib.h>
#include <string.h>
#include "bitmap.h"
struct bitmap
bm_alloc(int size)
{
struct bitmap bm;
int szmap = (size / 8) + !!(size % 8);
bm.size = size;
bm.map = malloc(szmap);
if (bm.map)
memset(bm.map, 0, szmap);
return bm;
}
void
bm_dealloc(struct bitmap * bm)
{
if (bm->map)
free(bm->map);
}
static void
bm_getmask(int *pos, unsigned char *bmask)
{
*bmask = (unsigned char) (1 << (*pos % 8));
*pos /= 8;
}
void
bm_setbit(struct bitmap * bm, int pos)
{
unsigned char bmask;
bm_getmask(&pos, &bmask);
bm->map[pos] |= bmask;
}
void
bm_clrbit(struct bitmap * bm, int pos)
{
unsigned char bmask;
bm_getmask(&pos, &bmask);
bm->map[pos] &= ~bmask;
}
int
bm_isset(struct bitmap * bm, int pos)
{
unsigned char bmask;
bm_getmask(&pos, &bmask);
return !!(bm->map[pos] & bmask);
}
int
bm_firstunset(struct bitmap * bm)
{
int szmap = (bm->size / 8) + !!(bm->size % 8);
int at = 0;
int pos = 0;
while (pos < szmap) {
unsigned char bmv = bm->map[pos++];
unsigned char bmask = 1;
while (bmask & 0xff) {
if ((bmv & bmask) == 0)
return at;
bmask <<= 1;
++at;
}
}
return at;
}
int
bm_lastset(struct bitmap * bm)
{
int szmap = (bm->size / 8) + !!(bm->size % 8);
int at = 0;
int pos = 0;
int ofs = 0;
while (pos < szmap) {
unsigned char bmv = bm->map[pos++];
unsigned char bmask = 1;
while (bmask & 0xff) {
if ((bmv & bmask) != 0)
ofs = at;
bmask <<= 1;
++at;
}
}
return ofs;
}

56
usr.sbin/pw/bitmap.h Normal file
View File

@ -0,0 +1,56 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#ifndef _BITMAP_H_
#define _BITMAP_H_
#include <sys/cdefs.h>
struct bitmap
{
int size;
unsigned char *map;
};
__BEGIN_DECLS
struct bitmap bm_alloc __P((int size));
void bm_dealloc __P((struct bitmap * bm));
void bm_setbit __P((struct bitmap * bm, int pos));
void bm_clrbit __P((struct bitmap * bm, int pos));
int bm_isset __P((struct bitmap * bm, int pos));
int bm_firstunset __P((struct bitmap * bm));
int bm_lastset __P((struct bitmap * bm));
__END_DECLS
#endif /* !_BITMAP_H */

118
usr.sbin/pw/cpdir.c Normal file
View File

@ -0,0 +1,118 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/param.h>
#include <errno.h>
#include "pwupd.h"
void
copymkdir(char const * dir, char const * skel, mode_t mode, uid_t uid, gid_t gid)
{
int rc = 0;
char src[MAXPATHLEN];
char dst[MAXPATHLEN];
if (mkdir(dir, mode) != 0 && errno != EEXIST) {
sprintf(src, "mkdir(%s)", dir);
perror(src);
} else {
int infd, outfd;
struct stat st;
static char counter = 0;
static char *copybuf = NULL;
++counter;
chown(dir, uid, gid);
if (skel == NULL || *skel == '\0')
rc = 1;
else {
DIR *d = opendir(skel);
if (d != NULL) {
struct dirent *e;
while ((e = readdir(d)) != NULL) {
char *p = e->d_name;
sprintf(src, "%s/%s", skel, p);
if (stat(src, &st) == 0) {
if (strncmp(p, "dot.", 4) == 0) /* Conversion */
p += 3;
sprintf(dst, "%s/%s", dir, p);
if (S_ISDIR(st.st_mode)) { /* Recurse for this */
if (strcmp(e->d_name, ".") != 0 && strcmp(e->d_name, "..") != 0)
copymkdir(dst, src, (st.st_mode & 0777), uid, gid);
/*
* Note: don't propogate 'special' attributes
*/
} else if (S_ISREG(st.st_mode) && (outfd = open(dst, O_RDWR | O_CREAT | O_EXCL, st.st_mode)) != -1) {
if ((infd = open(src, O_RDONLY)) == -1) {
close(outfd);
remove(dst);
} else {
int b;
/*
* Allocate our copy buffer if we need to
*/
if (copybuf == NULL)
copybuf = malloc(4096);
while ((b = read(infd, copybuf, 4096)) > 0)
write(outfd, copybuf, b);
close(infd);
close(outfd);
chown(dst, uid, gid);
}
}
}
}
closedir(d);
}
}
if (--counter == 0 && copybuf != NULL) {
free(copybuf);
copybuf = NULL;
}
}
}

206
usr.sbin/pw/edgroup.c Normal file
View File

@ -0,0 +1,206 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <fcntl.h>
#include <sys/param.h>
#include <ctype.h>
#include "pwupd.h"
static int
isingroup(char const * name, char **mem)
{
int i;
for (i = 0; i < MAXGROUPS && mem[i] != NULL; i++)
if (strcmp(name, mem[i]) == 0)
return i;
return -1;
}
static char groupfile[] = _PATH_GROUP;
static char grouptmp[] = _PATH_GROUP ".new";
int
editgroups(char *name, char **groups)
{
int rc = 0;
int infd;
if ((infd = open(groupfile, O_RDWR | O_CREAT | O_EXLOCK, 0644)) != -1) {
FILE *infp;
if ((infp = fdopen(infd, "r+")) == NULL)
close(infd);
else {
int outfd;
if ((outfd = open(grouptmp, O_RDWR | O_CREAT | O_TRUNC | O_EXLOCK, 0644)) != -1) {
FILE *outfp;
if ((outfp = fdopen(outfd, "w+")) == NULL)
close(outfd);
else {
char line[MAXPWLINE];
char outl[MAXPWLINE];
while (fgets(line, sizeof(line), infp) != NULL) {
char *p = strchr(line, '\n');
if (p == NULL) { /* Line too long */
int ch;
fputs(line, outfp);
while ((ch = fgetc(infp)) != EOF) {
fputc(ch, outfp);
if (ch == '\n')
break;
}
continue;
}
if (*line == '#')
strcpy(outl, line);
else if (*line == '\n')
*outl = '\0';
else {
int i,
mno = 0;
char *cp = line;
char const *sep = ":\n";
struct group grp;
char *mems[MAXGROUPS];
memset(&grp, 0, sizeof grp);
grp.gr_mem = mems;
for (i = 0; (p = strsep(&cp, sep)) != NULL; i++) {
switch (i) {
case 0: /* Group name */
grp.gr_name = p;
break;
case 1: /* Group password */
grp.gr_passwd = p;
break;
case 2: /* Group id */
grp.gr_gid = atoi(p);
break;
case 3: /* Member list */
cp = p;
sep = ",\n";
break;
default: /* Individual members */
if (mno < MAXGROUPS && *p)
mems[mno++] = p;
break;
}
}
if (i < 2) /* Bail out -
* insufficient fields */
continue;
for (i = mno; i < MAXGROUPS; i++)
mems[i] = NULL;
/*
* Delete from group, or add to group?
*/
if (groups == NULL || isingroup(grp.gr_name, groups) == -1) { /* Delete */
int idx;
while ((idx = isingroup(name, mems)) != -1) {
for (i = idx; i < (MAXGROUPS - 1); i++)
mems[i] = mems[i + 1];
mems[i] = NULL;
--mno;
}
/*
* Special case - deleting user and group may be user's own
*/
if (groups == NULL && mems[0] == NULL && strcmp(name, grp.gr_name) == 0) { /* First, make _sure_ we
* don't have other
* members */
struct passwd *pwd;
setpwent();
while ((pwd = getpwent()) != NULL && pwd->pw_gid != grp.gr_gid);
endpwent();
if (pwd == NULL) /* No members at all */
continue; /* Drop the group */
}
} else if (isingroup(name, mems) == -1)
mems[mno++] = name;
fmtgrentry(outl, &grp, PWF_GROUP);
}
fputs(outl, outfp);
}
if (fflush(outfp) != EOF) {
rc = 1;
/*
* Copy data back into the original file and truncate
*/
rewind(infp);
rewind(outfp);
while (fgets(line, sizeof(line), outfp) != NULL)
fputs(line, infp);
/*
* This is a gross hack, but we may have corrupted the
* original file. Unfortunately, it will lose preservation
* of the inode.
*/
if (fflush(infp) == EOF || ferror(infp))
rc = rename(grouptmp, groupfile) == 0;
else
ftruncate(infd, ftell(infp));
}
fclose(outfp);
}
remove(grouptmp);
}
fclose(infp);
}
}
return rc;
}

159
usr.sbin/pw/fileupd.c Normal file
View File

@ -0,0 +1,159 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <errno.h>
#include <unistd.h>
#include "pwupd.h"
int
fileupdate(char const * filename, mode_t fmode, char const * newline, char const * prefix, int pfxlen, int updmode)
{
int rc = 0;
if (pfxlen <= 1)
errno = EINVAL;
else {
int infd = open(filename, O_RDWR | O_CREAT | O_EXLOCK, fmode);
if (infd != -1) {
FILE *infp = fdopen(infd, "r+");
if (infp == NULL)
close(infd);
else {
int outfd;
char file[MAXPATHLEN];
strcpy(file, filename);
strcat(file, ".new");
outfd = open(file, O_RDWR | O_CREAT | O_TRUNC | O_EXLOCK, fmode);
if (outfd != -1) {
FILE *outfp = fdopen(outfd, "w+");
if (outfp == NULL)
close(outfd);
else {
int updated = UPD_CREATE;
char line[2048];
while (fgets(line, sizeof(line), infp) != NULL) {
char *p = strchr(line, '\n');
if (p == NULL) { /* Line too long */
int ch;
fputs(line, outfp);
while ((ch = fgetc(infp)) != EOF) {
fputc(ch, outfp);
if (ch == '\n')
break;
}
continue;
}
if (*line != '#' && *line != '\n') {
if (!updated && strncmp(line, prefix, pfxlen) == 0) {
updated = updmode == UPD_REPLACE ? UPD_REPLACE : UPD_DELETE;
/*
* Only actually write changes if updating
*/
if (updmode == UPD_REPLACE)
strcpy(line, newline);
else if (updmode == UPD_DELETE)
continue;
}
}
fputs(line, outfp);
}
/*
* Now, we need to decide what to do: If we are in
* update mode, and no record was updated, then error If
* we are in insert mode, and record already exists,
* then error
*/
if (updmode != updated)
errno = (updmode == UPD_CREATE) ? EEXIST : ENOENT;
else {
/*
* If adding a new record, append it to the end
*/
if (updmode == UPD_CREATE)
fputs(newline, outfp);
/*
* Flush the file and check for the result
*/
rc = fflush(outfp) != EOF;
if (rc) {
/*
* Copy data back into the
* original file and truncate
*/
rewind(infp);
rewind(outfp);
while (fgets(line, sizeof(line), outfp) != NULL)
fputs(line, infp);
/*
* This is a gross hack, but we may have
* corrupted the original file
* Unfortunately, it will lose the inode.
*/
if (fflush(infp) == EOF || ferror(infp))
rc = rename(file, filename) == 0;
else
ftruncate(infd, ftell(infp));
}
}
fclose(outfp);
}
remove(file);
}
fclose(infp);
}
}
}
return rc;
}

111
usr.sbin/pw/grupd.c Normal file
View File

@ -0,0 +1,111 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "pwupd.h"
int
fmtgrentry(char *buf, struct group * grp, int type)
{
int i, l;
if (type == PWF_STANDARD)
l = sprintf(buf, "%s:*:%ld:", grp->gr_name, (long) grp->gr_gid);
else
l = sprintf(buf, "%s:%s:%ld:", grp->gr_name, grp->gr_passwd, (long) grp->gr_gid);
/*
* Now, list members
*/
for (i = 0; i < 200 && grp->gr_mem[i]; i++)
l += sprintf(buf + l, "%s%s", i ? "," : "", grp->gr_mem[i]);
buf[l++] = '\n';
buf[l] = '\0';
return l;
}
int
fmtgrent(char *buf, struct group * grp)
{
return fmtgrentry(buf, grp, PWF_STANDARD);
}
static int
gr_update(struct group * grp, char const * group, int mode)
{
int l;
char pfx[32];
char grbuf[MAXPWLINE];
endgrent();
l = sprintf(pfx, "%s:", group);
/*
* Update the group file
*/
if (grp == NULL)
*grbuf = '\0';
else
fmtgrentry(grbuf, grp, PWF_PASSWD);
return fileupdate(_PATH_GROUP, 0644, grbuf, pfx, l, mode);
}
int
addgrent(struct group * grp)
{
return gr_update(grp, grp->gr_name, UPD_CREATE);
}
int
chggrent(char const * login, struct group * grp)
{
return gr_update(grp, login, UPD_REPLACE);
}
int
delgrent(struct group * grp)
{
return gr_update(NULL, grp->gr_name, UPD_DELETE);
}

303
usr.sbin/pw/psdate.c Normal file
View File

@ -0,0 +1,303 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "psdate.h"
static int
a2i(char const ** str)
{
int i = 0;
char const *s = *str;
if (isdigit(*s)) {
i = atoi(s);
while (isdigit(*s))
++s;
*str = s;
}
return i;
}
static int
numerics(char const * str)
{
int rc = isdigit(*str);
if (rc)
while (isdigit(*str) || *str == 'x')
++str;
return rc && !*str;
}
static int
aindex(char const * arr[], char const ** str, int len)
{
int l, i;
char mystr[32];
mystr[len] = '\0';
l = strlen(strncpy(mystr, *str, len));
for (i = 0; i < l; i++)
mystr[i] = (char) tolower(mystr[i]);
for (i = 0; arr[i] && strcmp(mystr, arr[i]) != 0; i++);
if (arr[i] == NULL)
i = -1;
else { /* Skip past it */
while (**str && isalpha(**str))
++(*str);
/* And any following whitespace */
while (**str && (**str == ',' || isspace(**str)))
++(*str);
} /* Return index */
return i;
}
static int
weekday(char const ** str)
{
static char const *days[] =
{"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL};
return aindex(days, str, 3);
}
static int
month(char const ** str)
{
static char const *months[] =
{"jan", "feb", "mar", "apr", "may", "jun", "jul",
"aug", "sep", "oct", "nov", "dec", NULL};
return aindex(months, str, 3);
}
static void
parse_time(char const * str, int *hour, int *min, int *sec)
{
*hour = a2i(&str);
if ((str = strchr(str, ':')) == NULL)
*min = *sec = 0;
else {
++str;
*min = a2i(&str);
*sec = ((str = strchr(str, ':')) == NULL) ? 0 : atoi(++str);
}
}
static void
parse_datesub(char const * str, int *day, int *mon, int *year)
{
int i;
static char const nchrs[] = "0123456789 \t,/-.";
if ((i = month(&str)) != -1) {
*mon = i;
if ((i = a2i(&str)) != 0)
*day = i;
} else if ((i = a2i(&str)) != 0) {
*day = i;
while (*str && strchr(nchrs + 10, *str) != NULL)
++str;
if ((i = month(&str)) != -1)
*mon = i;
else if ((i = a2i(&str)) != 0)
*mon = i - 1;
} else
return;
while (*str && strchr(nchrs + 10, *str) != NULL)
++str;
if (isdigit(*str)) {
*year = atoi(str);
if (*year > 1900)
*year -= 1900;
else if (*year < 32)
*year += 100;
}
}
/*-
* Parse time must be flexible, it handles the following formats:
* nnnnnnnnnnn UNIX timestamp (all numeric), 0 = now
* 0xnnnnnnnn UNIX timestamp in hexadecimal
* 0nnnnnnnnn UNIX timestamp in octal
* 0 Given time
* +nnnn[smhdwoy] Given time + nnnn hours, mins, days, weeks, months or years
* -nnnn[smhdwoy] Given time - nnnn hours, mins, days, weeks, months or years
* dd[ ./-]mmm[ ./-]yy Date }
* hh:mm:ss Time } May be combined
*/
time_t
parse_date(time_t dt, char const * str)
{
char *p;
int i;
long val;
struct tm *T;
if (dt == 0)
dt = time(NULL);
while (*str && isspace(*str))
++str;
if (numerics(str)) {
val = strtol(str, &p, 0);
dt = val ? val : dt;
} else if (*str == '+' || *str == '-') {
val = strtol(str, &p, 0);
switch (*p) {
case 'h':
case 'H': /* hours */
dt += (val * 3600L);
break;
case '\0':
case 'm':
case 'M': /* minutes */
dt += (val * 60L);
break;
case 's':
case 'S': /* seconds */
dt += val;
break;
case 'd':
case 'D': /* days */
dt += (val * 86400L);
break;
case 'w':
case 'W': /* weeks */
dt += (val * 604800L);
break;
case 'o':
case 'O': /* months */
T = localtime(&dt);
T->tm_mon += (int) val;
i = T->tm_mday;
goto fixday;
case 'y':
case 'Y': /* years */
T = localtime(&dt);
T->tm_year += (int) val;
i = T->tm_mday;
fixday:
dt = mktime(T);
T = localtime(&dt);
if (T->tm_mday != i) {
T->tm_mday = 1;
dt = mktime(T);
dt -= (time_t) 86400L;
}
default: /* unknown */
break; /* leave untouched */
}
} else {
char *q, tmp[64];
/*
* Skip past any weekday prefix
*/
weekday(&str);
str = strncpy(tmp, str, sizeof tmp - 1);
tmp[sizeof tmp - 1] = '\0';
T = localtime(&dt);
/*
* See if we can break off any timezone
*/
while ((q = strrchr(tmp, ' ')) != NULL) {
if (strchr("(+-", q[1]) != NULL)
*q = '\0';
else {
int j = 1;
while (q[j] && isupper(q[j]))
++j;
if (q[j] == '\0')
*q = '\0';
else
break;
}
}
/*
* See if there is a time hh:mm[:ss]
*/
if ((p = strchr(tmp, ':')) == NULL) {
/*
* No time string involved
*/
T->tm_hour = T->tm_min = T->tm_sec = 0;
parse_datesub(tmp, &T->tm_mday, &T->tm_mon, &T->tm_year);
} else {
char datestr[64], timestr[64];
/*
* Let's chip off the time string
*/
if ((q = strpbrk(p, " \t")) != NULL) { /* Time first? */
int l = q - str;
strncpy(timestr, str, l);
timestr[l] = '\0';
strncpy(datestr, q + 1, sizeof datestr);
datestr[sizeof datestr - 1] = '\0';
parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec);
parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year);
} else if ((q = strrchr(tmp, ' ')) != NULL) { /* Time last */
int l = q - tmp;
strncpy(timestr, q + 1, sizeof timestr);
timestr[sizeof timestr - 1] = '\0';
strncpy(datestr, tmp, l);
datestr[l] = '\0';
} else /* Bail out */
return dt;
parse_time(timestr, &T->tm_hour, &T->tm_min, &T->tm_sec);
parse_datesub(datestr, &T->tm_mday, &T->tm_mon, &T->tm_year);
}
dt = mktime(T);
}
return dt;
}

46
usr.sbin/pw/psdate.h Normal file
View File

@ -0,0 +1,46 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#ifndef _PSDATE_H_
#define _PSDATE_H_
#include <time.h>
#include <sys/cdefs.h>
__BEGIN_DECLS
time_t parse_date __P((time_t dt, char const * str));
void print_date __P((char *buf, time_t t, int dotime));
__END_DECLS
#endif /* !_PSDATE_H_ */

648
usr.sbin/pw/pw.8 Normal file
View File

@ -0,0 +1,648 @@
.\" Copyright (c) 1996
.\" David L. Nugent.
.\" Password Maintenance
.\"
.\" $Id: pw.8,v 1.3 1996/11/18 03:09:01 davidn Exp $
.\"
.Dd November 13, 1996
.Dt PW 8
.Os
.Sh NAME
.Nm pw
.Nd create, remove and modify system users and groups
.Sh SYNOPSIS
.Nm pw
.Ar useradd
.Op name|uid
.Op Fl C Ar config
.Op Fl q
.Op Fl n Ar name
.Op Fl u Ar uid
.Op Fl c Ar comment
.Op Fl d Ar dir
.Op Fl e Ar date
.Op Fl p Ar date
.Op Fl g Ar group
.Op Fl G Ar grouplist
.Op Fl m
.Op Fl k Ar dir
.Op Fl s Ar shell
.Op Fl o
.Op Fl L Ar class
.Op Fl h Ar fd
.Nm pw
.Ar useradd
.Op name|uid
.Op Fl D
.Op Fl C Ar config
.Op Fl q
.Op Fl b Ar dir
.Op Fl e Ar days
.Op Fl p Ar days
.Op Fl g Ar group
.Op Fl G Ar grouplist
.Op Fl k Ar dir
.Op Fl u Ar min,max
.Op Fl i Ar min,max
.Op Fl w Ar method
.Op Fl s Ar shell
.Nm pw
.Ar userdel
.Op name|uid
.Op Fl n Ar name
.Op Fl u Ar uid
.Op Fl r
.Nm pw
.Ar usermod
.Op name|uid
.Op Fl C Ar config
.Op Fl q
.Op Fl n Ar name
.Op Fl u Ar uid
.Op Fl c Ar comment
.Op Fl d Ar dir
.Op Fl e Ar date
.Op Fl p Ar date
.Op Fl g Ar group
.Op Fl G Ar grouplist
.Op Fl l Ar name
.Op Fl m
.Op Fl k Ar dir
.Op Fl s Ar shell
.Op Fl L Ar class
.Op Fl h Ar fd
.Nm pw
.Ar usershow
.Op name|uid
.Op Fl n Ar name
.Op Fl u Ar uid
.Op Fl F
.Op Fl p
.Op Fl a
.Nm pw
.Ar groupadd
.Op group|gid
.Op Fl C Ar config
.Op Fl q
.Op Fl n Ar group
.Op Fl g Ar gid
.Op Fl o
.Op Fl h Ar fd
.Nm pw
.Ar groupdel
.Op Fl n Ar name
.Op Fl g Ar gid
.Nm pw
.Ar groupmod
.Op Fl C Ar config
.Op Fl q
.Op Fl F
.Op Fl n Ar name
.Op Fl g Ar gid
.Op Fl l Ar name
.Op Fl h Ar fd
.Nm pw
.Ar groupshow
.Op Fl n Ar name
.Op Fl g Ar gid
.Op Fl F
.Op Fl p
.Op Fl a
.Sh DESCRIPTION
.Nm pw
is a command-line based editor for the system
.Em user
and
.Em group
files, allowing the superuser and easy to use and standardised way of adding,
modifying and removing users and groups.
Note that
.Nm pw
only operates on the local user and group files; NIS users and groups must be
maintained on the NIS server.
.Nm pw
handles updating the passwd, master.passwd, group and the secure and insecure
password database files, and must be run as root.
.Pp
The first one or two keywords provided on
.Xr pw 8 's
command line provide the context for the remainder of the arguments.
One of the keywords
.Ar user
and
.Ar group
may be combined or provided separately with
.Ar add ,
.Ar del ,
.Ar mod
or
.Ar show ,
and may be specified in either order (ie. showuser, usershow, show user and user show
are all considered to be the same thing).
This flexiblity is useful for interactive scripts which call
.Nm pw
for the actual user and group database manipulation.
Following these keywords, you may optionally specify the user or group name or numeric
id as an alternative to using the
.Fl n Ar name ,
.Fl u Ar uid ,
.Fl g Ar gid
switches.
.Pp
The following flags are common to most modes of operation:
.Pp
.Bl -tag -width "-C config"
.It Fl C Ar config
By default,
.Nm pw
reads the file
.Pa /etc/pw.conf
to obtain policy information on how new user accounts and groups are to be created,
and the
.Fl c
option overrides this to read a different file.
Most of the contents in the configuration file may be overridden via command line
options, but it may be more useful to set up standard information for addition of
new accounts in the configuration
file.
.It Fl q
Use of this option causes
.Nm pw
to suppress error messages, which may be useful in interactive environments where it
is preferable to interpret status codes returned by
.Nm pw
rather than messing up a carefully formatted display.
.El
.Pp
.Sh USER OPTIONS
The following options apply to the
.Ar useradd ,
and
.Ar usermod ,
commands:
.Pp
.Bl -tag -width "-C config"
.It Fl n Ar name
Specifies the user/account name.
.It Fl u Ar uid
Specifies the user/account numeric id.
.Pp
Usually, you need only to provide one or the other of these options, as the account
name will imply the uid, and vice verca.
Also, you may provide either the account or userid immediately after the
.Ar useradd ,
.Ar userdel ,
.Ar usermod
or
.Ar usershow
keyword on the command line without the need to use
.Ql Fl n
or
.Ql Fl u .
There are times, however, were you need to provide both.
For example, when changing the uid of an existing user with
.Ar usermod ,
or overriding the default uid when creating a new account.
If you wish
.Nm pw
to automatically allocate the uid to a new user on
.Ar useradd ,
then you should
.Em not
use the
.Ql Fl u
switch.
.El
.Pp
Options available with both
.Ar useradd
and
.Ar usermod
are:
.Bl -tag -width "-G grouplist"
.It Fl c Ar comment
This field sets the contents of the passwd GECOS field, which normally contains up
to four comma-separated fields containing the user's full name, office or location,
work and home phone numbers.
These sub-fields are used by convention only, however, and are optional.
If this field is to contain spaces, you need to quote the comment itself with double
quotes
.Ql \&" .
Avoid using commas in this field as these are used as sub-field separators, and the
colon
.Ql \&:
character also cannot be used as this is the field separator in the passwd file.
.It Fl d Ar dir
This option sets the account's home directory.
Normally, you will only use this if the home directory is to be different from the
default (which is determined from pw.conf, which specifies the base home directory
- normally /home - with the account name as a subdirectory).
.It Fl e Ar date
Sets the account's expiration date.
Format of the date is either a UNIX time in decimal, or a date in
.Ql \& dd-mmm-yy[yy]
format, where dd is the day, mmm is the month, either in numeric or alphabetic format
('Jan', 'Feb' etc) and year is either a two or four digit year.
This option also accepts a relative date in the form
.Ql \&+n[mhdwoy]
where
.Ql \&n
is a decimal, octal (leading 0) or hexadecimal (leading 0x) digit followed by the
number of Minutes, Hours, Days, Weeks, mOnths or Years from the current date at
which the expiry date is to be set.
.It Fl p Ar date
Sets the account's password expiration date.
This field is identical to the account expiration date option, except that it
applies to forced password changes.
The same formats are accepted as with the account expiratino option.
.It Fl g Ar group
Sets the account's primary group to the given group.
.Ar group
may be either the group name or its corresponding group id number.
.It Fl G Ar grouplist
Sets the additional groups to which an account belongs.
.Ar grouplist
is a comma-separated list or group names or group ids.
When adding a user, the user's name is added to the group lists in
.Pa /etc/group ,
and when editing a user, the user's name is also added to the group lists, and
removed from any groups not specified in
.Ar grouplist .
Note: a user should not be added to their primary group in
.Pa /etc/group .
Also, group membership changes do not take effect immediately for current logins,
only logins subsequent to the change.
.It Fl m
This option instructs
.Nm pw
to attempt to create the user's home directory.
While primarily useful when adding a new account with
.Ar useradd ,
this may also be of use when moving an existing user's home directory elsewhere on
the filesystem.
The new home directory is populated with the contents of the
.Ar skeleton
directory, which typically contains a set of shell configuration files that the
user may personalise to taste.
When
.Ql Fl m
is used on an account with
.Ar usermod ,
any existing configuration files in the user's home directory are
.Em not
overwritten with the prototype files.
.Pp
When a user's home directory is created, it will be default be as a subdirectory of the
.Ar basehome
directory specified with the
.Ql Fl b Ar dir
option (see below), and will be named the same as the account.
This may be overridden with the
.Ql Fl d Ar dir
option on the command line, if desired.
.It Fl k Ar dir
Sets the
.Ar skeleton
subdirectory, from which the basic startup and configuration files are copied when
the user's home directory is created.
This option only has meaning when used with
.Ql Fl D
(see below) or
.Ql Fl m .
.It Fl s Ar shell
Sets or changes the user's login shell to
.Ar shell .
If the path to the shell program is omitted,
.Nm pw
searches the
.Ar shellpath
specified in
.Pa /etc/pw.conf
and fills it in as appropriate.
Note that unless you have a specific reason to do so, you should avoid
specifying the path - this will allow
.Nm pw
to validate that the program exists and is executable.
Specifying a full path (or supplying a blank "" shell) avoids this check
and allows for such entries as
.Ql \& /nonexistent
that should be set for accounts not intended for interactive login.
.It Fl L Ar class
Sets the
.Em class
field in the user's passwd record.
This field is not currently used, but will be in the future used to specify a
.Em termcap
entry like tag (see
.Xr passwd 5
for details).
.It Fl h Ar fd
This option provides a special interface by which interactive scripts can
set an account password using
.Nm pw .
Because the command line and environment are fundamental insecure mechanisms
by which programs can accept information,
.Nm pw
will only allow setting of account and group passwords via a file descriptor
(usually a pipe between an interactive script and the program).
.Ar sh ,
.Ar bash ,
.Ar ksh
and
.Ar perl
all posses mechanisms by which this can be done.
Alternatively,
.Nm pw
will prompt for the user's password if
.Ql Fl h Ar 0
is given, nominating
.Em stdin
as the file descriptor on which to read the password.
Note that this password will be read once and once only and is intended
for use by a script or similar rather than interactive use.
If you wish to have new password confirmation along the lines of
.Xr passwd 1 ,
this must be implemented as part of the interactive script that calls
.Nm pw .
.Pp
If a value of
.Ql \&-
is given as the argument
.Ar fd ,
then the password will be set to
.Ql \&* ,
rendering the account inaccessible via passworded login.
.El
.Pp
It is possible to use
.Ar useradd
to create a new account that duplicates an existing user id.
While this is normally considered an error and will be rejected, the
.Ql Fl o
switch overrides the check for duplicates and allows the duplication of the user id.
This may be useful if you allow the same user to login under different contexts
(different group allocations, different home directory, different shell) while
providing basically the same permissions for access to the user's files in each
account.
.Pp
The
.Ar useradd
command also has the ability to set new user and group defaults by using the
.Ql Fl D
switch.
Instead of adding a new user,
.Nm pw
writes a new set of defaults to its configuration file,
.Pa /etc/pw.conf .
When using the
.Ql Fl D
switch, you must not use either
.Ql Fl n Ar name
or
.Ql Fl u Ar uid
or an error will result.
Use of
.Ql Fl D
adds switches and changes the meaning of several command line switches in the
.Ar useradd
command.
These are:
.Bl -tag -width "-G grouplist"
.It Fl D
Set default values in
.Pa /etc/pw.conf
configuration file, or a different named configuration file if the
.Ql Fl C Ar config
switch is used.
.It Fl b Ar dir
Sets the root directory in which user home directories are created.
The default value for this is
.Ql \&/home ,
but it may be set elsewhere as desired.
.It Fl e Ar days
Sets the default account expiration period in days.
Unlike use without
.Ql Fl D ,
the argument must be numeric, which specifies the number of days after creation when
the account is to expire.
A value of 0 suppresses automatic calculation of the expiry date.
.It Fl p Ar days
Sets the default password expiration period in days.
.It Fl g Ar group
Sets the default group for new users.
If a blank group is specified using
.Ql Fl g Ar \&"" ,
then new users will be allocated their own private primary group (a new group created
with the same name as their login name).
If a group is supplied, either its name or uid may be given as an argument.
.It Fl G Ar grouplist
Sets the default groups in which new users are made members.
This is a separate set of groups from the primary group, and you should avoid
nominating the same group as both the primary and in extra groups.
In other words, these extra groups determine membership in groups
.Em other than
the primary group.
.Ar grouplist
is a comma-separated list of group names or ids, or a mixture of both, and are always
stored in
.Pa /etc/pw.conf
by their symbolic names.
.It Fl k Ar dir
Sets the default
.Em skeleton
directory, from which prototype shell and other initialisation files are copied when
.Nm pw
creates a user's home directory.
.It Fl u Ar min,max
.It Fl i Ar min,max
These switches set the minimum and maximum user and group ids allocated for new accounts
and groups created by
.Nm pw .
The default values for each is 1000 minimum and 32000 maximum.
.Ar min
and
.Ar max
are both numbers, where max must be greater than min, and both must be between 0
and 32767.
In general, user and group ids less than 100 are reserved for use by the system,
and numbers greater than 32000 may also be reserved for special purposes (used by
some system daemons).
.It Fl w Ar method
The
.Ql Fl w
switch sets the default method used to set passwords for newly created user accounts.
.Ar method
is one of:
.Pp
.Bl -tag -width random -offset indent -compact
.It no
disables login on newly created accounts
.It yes
forces the password to be the account name
.It none
forces a blank password
.It random
Generates a random password
.El
.Pp
The
.Ql \&random
or
.Ql \&no
methods are the most secure; in the former case,
.Nm pw
generates a password and prints it to stdout, which is suitable where you issue
users with passwords to access their accounts rather than having the user nominate
their own (possibly poorly chosen) password.
The
.Ql \&no
method requires that the superuser use
.Xr passwd 1
to render the account accessible with a password.
.El
.Pp
The
.Ar userdel
command has only three valid switches. The
.Ql Fl n Ar name
and
.Ql Fl u Ar uid
switches have already been covered above.
The additional switch is:
.Bl -tag -width flag
.It Fl r
This tells
.Nm pw
to remove the user's home directory and all of its contents.
.Nm pw
errs on the side of caution when removing files from the system.
Firstly, it will not do so if the uid of the account being removed is also used by
another account on the system, and the 'home' directory in the password file is
a valid path that commences with the character
.Ql \&/ .
Secondly, it will only remove files and directories that are actually owned by
the user, or symbolic links owned by anyone under the user's home directory.
Finally, after deleting all contents owned by the user only empty directories
will be removed.
If any additional cleanup work is required, this is left to the adminstrator.
.El
.Pp
Mail spool files and crontabs are always removed when an account is deleted as these
are unconditionally attached to the user name.
Jobs queued for processing by
.Ar at
are also removed if the user's uid is unique (not also used by another account on the
system).
.Pp
The
.Ar usershow
command allows viewing of an account in one of two formats.
By default, the format is identical to the format used in
.Pa /etc/master.passwd
with the password field replaced with a
.Ql \&* .
Class, account and password expiration fields will be blank or zero zero unless the user
running
.Nm pw
has root priviledges, as the secure password file where these reside is not accessible
to non-root users.
If the
.Ql Fl p
switch is used, then
.Nm pw
outputs the account details in a more human readable form.
The
.Ql Fl a
switch lists all users currently on file.
.Pp
.Sh GROUP OPTIONS
The
.Ql Fl C Ar config
and
.Ql Fl q
options (explained at the start of the previous section) are available with the
.Ar groupadd
and
.Ar groupmod
commands.
Other common options to all group-related commands are:
.Bl -tag -width "-n name"
.It Fl n Ar name
Specifies the group name.
.It Fl g Ar gid
Specifies the group numeric id.
.Pp
As with the account name and id fields, yo uwill usually only need to supply one of
these, as the group name implies the uid and vice versa.
You will only need to use both when setting a specific group id against a new group
or when changing the uid of an existing group.
.El
.Pp
.Ar groupadd
also has a
.Ql Fl o
option that allows allocation of an existing group id to new group.
The default action is to reject an attempt to add a group, and this option overrides
the check for duplicate group ids.
There is rarely any need to duplicate a group id.
.Pp
The
.Ar groupmod
command adds one additonal switch:
.Pp
.Bl -tag -width "-l name"
.It Fl l Ar name
This option allows changing of an existing group name to
.Ql \&name .
The new name must not already exist, and any attempt to duplicate an existing group
name will be rejected.
.El
.Pp
Options for
.Ar groupshow
are the same as for
.Ar usershow ,
with the
.Ql Fl g Ar gid
replacing
.Ql Fl u Ar uid
to specify the group id.
.Pp
.Sh NOTES
For a summary of options available with each command, you can use
.Dl pw [command] help
For example,
.Dl pw useradd help
lists all available options for the useradd operation.
.Sh FILES
.Bl -tag -width /etc/master.passwd.new -compact
.It Pa /etc/master.passwd
The user database
.It Pa /etc/passwd
A Version 7 format password file
.It Pa /etc/group
The group database
.It Pa /etc/master.passwd.new
Temporary copy of the master password file
.It Pa /etc/passwd.new
Temporary copy of the Version 7 password file
.It Pa /etc/group.new
Temporary copy of the group file
.It Pa /etc/pw.conf
Pw default options file
.El
.Sh SEE ALSO
.Xr pw.conf 5 ,
.Xr passwd 1 ,
.Xr chpass 1 ,
.Xr passwd 5 ,
.Xr group 5 ,
.Xr pwd_mkdb 8 ,
.Xr vipw 5
.Sh HISTORY
.Nm pw
was written to mimick many of the options used in the Linux
.Em shadow
suite, but is modified for passwd and group fields specific to
the BSD 4.4 operating system.

321
usr.sbin/pw/pw.c Normal file
View File

@ -0,0 +1,321 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include "pw.h"
static char *progname = "pw";
const char *Modes[] = {"add", "del", "mod", "show", NULL};
const char *Which[] = {"user", "group", NULL};
static const char *Combo1[] = {"useradd", "userdel", "usermod", "usershow",
"groupadd", "groupdel", "groupmod", "groupshow",
NULL};
static const char *Combo2[] = {"adduser", "deluser", "moduser", "showuser",
"addgroup", "delgroup", "modgroup", "showgroup",
NULL};
static struct cargs arglist;
static int getindex(const char *words[], const char *word);
static void cmdhelp(int mode, int which);
int
main(int argc, char *argv[])
{
int ch;
int mode = -1;
int which = -1;
struct userconf *cnf;
static const char *opts[W_NUM][M_NUM] =
{
/* user */ {"C:qn:u:c:d:e:p:g:G:mk:s:oL:i:w:h:Db", "C:qn:u:r", "C:qn:u:c:d:e:p:g:G:mk:s:L:h:F", "C:qn:u:Fpa"},
/* grp */ {"C:qn:g:h:p", "C:qn:g:", "C:qn:g:l:h:F", "C:qn:g:Fpa"}
};
static int (*funcs[W_NUM]) (struct userconf * _cnf, int _mode, struct cargs * _args) =
{ /* Request handlers */
pw_user,
pw_group
};
umask(0); /* We wish to handle this manually */
progname = strrchr(argv[0], '/');
if (progname != NULL)
++progname;
else
progname = argv[0];
LIST_INIT(&arglist);
/*
* Break off the first couple of words to determine what exactly
* we're being asked to do
*/
while (argc > 1 && *argv[1] != '-') {
int tmp;
if ((tmp = getindex(Modes, argv[1])) != -1)
mode = tmp;
else if ((tmp = getindex(Which, argv[1])) != -1)
which = tmp;
else if ((tmp = getindex(Combo1, argv[1])) != -1 || (tmp = getindex(Combo2, argv[1])) != -1) {
which = tmp / M_NUM;
mode = tmp % M_NUM;
} else if (strcmp(argv[1], "help") == 0)
cmdhelp(mode, which);
else if (which != -1 && mode != -1 && arglist.lh_first == NULL)
addarg(&arglist, 'n', argv[1]);
else
cmderr(X_CMDERR, "Unknown keyword `%s'\n", argv[1]);
++argv;
--argc;
}
/*
* Bail out unless the user is specific!
*/
if (mode == -1 || which == -1)
cmdhelp(mode, which);
/*
* Must be root to attempt an update
*/
if (getuid() != 0 && mode != M_PRINT)
cmderr(X_PERMERR, "you must be root to run this program\n");
/*
* We know which mode we're in and what we're about to do, so now
* let's dispatch the remaining command line args in a genric way.
*/
argv[0] = progname; /* Preserve this */
optarg = NULL;
while ((ch = getopt(argc, argv, opts[which][mode])) != -1) {
if (ch == '?')
cmderr(X_CMDERR, NULL);
else
addarg(&arglist, ch, optarg);
optarg = NULL;
}
/*
* We should immediately look for the -q 'quiet' switch so that we
* don't bother with extraneous errors
*/
if (getarg(&arglist, 'q') != NULL)
freopen("/dev/null", "w", stderr);
ch = X_CMDERR;
/*
* Now, let's do the common initialisation
*/
cnf = read_userconfig(getarg(&arglist, 'C') ? getarg(&arglist, 'C')->val : NULL);
if (funcs[which])
ch = funcs[which] (cnf, mode, &arglist);
else
fprintf(stderr, "%s: %s[%s] not yet implemented.\n", progname, Which[which], Modes[mode]);
return ch;
}
static int
getindex(const char *words[], const char *word)
{
int i = 0;
while (words[i]) {
if (strcmp(words[i], word) == 0)
return i;
i++;
}
return -1;
}
/*
* This is probably an overkill for a cmdline help system, but it reflects
* the complexity of the command line.
*/
static void
banner(void)
{
fprintf(stderr, "%s: ", progname);
}
void
cmderr(int ec, char const * fmt,...)
{
if (fmt != NULL) {
va_list argp;
banner();
va_start(argp, fmt);
vfprintf(stderr, fmt, argp);
va_end(argp);
}
exit(ec);
}
static void
cmdhelp(int mode, int which)
{
banner();
if (which == -1)
fprintf(stderr, "usage: %s [user|group] [add|del|mod|show] [ help | switches/values ]\n", progname);
else if (mode == -1)
fprintf(stderr, "usage: %s %s [add|del|mod] [ help | switches/values ]\n", progname, Which[which]);
else {
/*
* We need to give mode specific help
*/
static const char *help[W_NUM][M_NUM] =
{
{
"usage: %s useradd [name] [switches]\n"
"\t-C config configuration file\n"
"\t-q quiet operation\n"
" Adding users:\n"
"\t-n name login name\n"
"\t-u uid user id\n"
"\t-c comment user name/comment\n"
"\t-d directory home directory\n"
"\t-e date account expiry date\n"
"\t-p date password expiry date\n"
"\t-g grp initial group\n"
"\t-G grp1,grp2 additional groups\n"
"\t-m [ -k dir ] create and set up home\n"
"\t-s shell name of login shell\n"
"\t-o duplicate uid ok\n"
"\t-L class user class\n"
"\t-h fd read password on fd\n"
" Setting defaults:\n"
"\t-D set user defaults\n"
"\t-b dir default home root dir\n"
"\t-e period default expiry period\n"
"\t-p period default password change period\n"
"\t-g group default group\n"
"\t-G grp1,grp2 additional groups\n"
"\t-L class default user class\n"
"\t-k dir default home skeleton\n"
"\t-u min,max set min,max uids\n"
"\t-i min,max set min,max gids\n"
"\t-w method set default password method\n"
"\t-s shell default shell\n",
"usage: %s userdel [uid|name] [switches]\n"
"\t-n name login name\n"
"\t-u uid user id\n"
"\t-r remove home & contents\n",
"usage: %s usermod [uid|name] [switches]\n"
"\t-C config configuration file\n"
"\t-q quiet operation\n"
"\t-F force add if no user\n"
"\t-n name login name\n"
"\t-u uid user id\n"
"\t-c comment user name/comment\n"
"\t-d directory home directory\n"
"\t-e date account expiry date\n"
"\t-p date password expiry date\n"
"\t-g grp initial group\n"
"\t-G grp1,grp2 additional groups\n"
"\t-l name new login name\n"
"\t-L class user class\n"
"\t-m [ -k dir ] create and set up home\n"
"\t-s shell name of login shell\n"
"\t-h fd read password on fd\n",
"usage: %s usershow [uid|name] [switches]\n"
"\t-n name login name\n"
"\t-u uid user id\n"
"\t-F force print\n"
"\t-p prettier format\n"
"\t-a print all users\n"
},
{
"usage: %s groupadd [group|gid] [switches]\n"
"\t-C config configuration file\n"
"\t-q quiet operation\n"
"\t-n group group name\n"
"\t-g gid group id\n"
"\t-o duplicate gid ok\n",
"usage: %s groupdel [group|gid] [switches]\n"
"\t-n name group name\n"
"\t-g gid group id\n",
"usage: %s groupmod [group|gid] [switches]\n"
"\t-C config configuration file\n"
"\t-q quiet operation\n"
"\t-F force add if not exists\n"
"\t-n name group name\n"
"\t-g gid group id\n"
"\t-l name new group name\n",
"usage: %s groupshow [group|gid] [switches]\n"
"\t-n name group name\n"
"\t-g gid group id\n"
"\t-F force print\n"
"\t-p prettier format\n"
"\t-a print all accounting groups\n"
}
};
fprintf(stderr, help[which][mode], progname);
}
exit(X_CMDERR);
}
struct carg *
getarg(struct cargs * _args, int ch)
{
struct carg *c = _args->lh_first;
while (c != NULL && c->ch != ch)
c = c->list.le_next;
return c;
}
struct carg *
addarg(struct cargs * _args, int ch, char *argstr)
{
struct carg *ca = malloc(sizeof(struct carg));
if (ca == NULL)
cmderr(X_MEMERR, "Abort - out of memory\n");
ca->ch = ch;
ca->val = argstr;
LIST_INSERT_HEAD(_args, ca, list);
return ca;
}

266
usr.sbin/pw/pw.conf.5 Normal file
View File

@ -0,0 +1,266 @@
.\" Copyright (c) 1996
.\" David L. Nugent.
.\" Password/Group file maintenance suite
.\"
.\" $Id: pw.conf.5,v 1.2 1996/11/18 03:09:02 davidn Exp $
.\"
.Dd November 13, 1996
.Dt PW.CONF 5
.Os
.Sh NAME
.Nm pw.conf
.Nd format of the pw.conf configuration file
.Sh DESCRIPTION
The file
.Aq Pa /etc/pw.conf
contains configuration data for the
.Xr pw 8
program.
The
.Xr pw 8
program is used for maintenance of the system password and group
files, allowing users and groups to be added, deleted and changed.
This file may be modified via the
.Xr pw 8
command using the
.Ql \&useradd
command and the
.Ql \&-D
option, or by editing it directly with a text editor.
.Pp
Each line in
.Aq Pa /etc/pw.conf
is treated either a comment or as configuration data;
blank lines and lines commencing with a
.Ql \&#
character are considered comments, and any remaining lines are
examined for a leading keyword, followed by corresponding data.
.Pp
Keywords recognised by
.Xr pw 8
are:
.Bl -tag -width password_days -offset indent -compact
.It defaultpasswd
affects passwords generated for new users
.It reuseuids
reuse gaps in uid sequences
.It reusegids
reuse gaps in gid sequences
.It skeleton
where to obtain default home contents
.It newmail
mail to send to new users
.It logfile
log user/group modifications to this file
.It home
root directory for home directories
.It shellpath
paths in which to locate shell programs
.It shells
list of valid shells (without path)
.It defaultshell
default shell (without path)
.It defaultgroup
default group
.It extragroups
add new users to this groups
.It loginclass
place new users in this login class
.It minuid
.It maxuid
range of valid default user ids
.It mingid
.It maxgid
range of valid default group ids
.It expire_days
days after which account expires
.It password_days
days after which password expires
.El
.Pp
Valid values for
.Ar defaultpasswd
are
.Bl -tag -width password_days -offset indent -compact
.It no
disables login on newly created accounts
.It yes
forces the password to be the account name
.It none
forces a blank password
.It random
Generates a random password
.El
.Pp
The second and third options are insecure and should be avoided if
possible on a publicly accessible system.
The first option requires that the superuser run
.Xr passwd 1
to set a password before the account may be used.
This may also be useful for creating administrative accounts.
The final option causes
.Xr pw 8
to respond by printing a randomly generated password on stdout.
This is the preferred and most secure option.
.Xr pw 8
also provides a method of setting a specific password for the new
user via a filehandle (command lines are not secure).
.Pp
Both
.Ar reuseuids
and
.Ar reusegids
determine the method by which new user and group id numbers are
generated.
A
.Ql \&yes
in this field will cause
.Xr pw 8
to search for the first unused user or group id within the allowed
range, whereas a
.Ql \&no
will ensure that no other existing user or group id within the range
is numerically lower than the new one generated, and therefore avoids
reusing gaps in the user or group id sequence that are caused by
previous user or group deletions.
Note that if the default group is not specified using the
.Ar defaultgroup
keyword,
.Xr pw 8
will create a new group for the user and attempt to keep the new
user's uid and gid the same.
If the new user's uid is currently in use as a group id, then the next
available group id is chosen instead.
.Pp
The
.Ar skeleton
keyword nominates a directory from which the contents of a user's
new home directory is constructed.
This is
.Pa /usr/share/skel
by default.
.Xr pw 8 's
.Ql \&-m
option causes the user's home directory to be created and populated
using the files contained in the
.Ar skeleton
directory.
.Pp
To send an initial email to new users, the
.Ar newmail
keyword may be used to specify a path name to a file containing
the message body of the message to be sent.
To avoid sending mail when accounts are created, leave this entry
blank or specify
.Ql \&no .
.Pp
The
.Ar logfile
option allows logging of password file modifications into the
nominated log file.
To avoid creating or adding to such a logfile, then leave this
field blank or specify
.Ql \&no .
.Pp
The
.Ar home
keyword is mandatory.
This specifies the location of the directory in which all new user
home directories are created.
.Pp
.Ar shellpath
specifies a list of directories - separated by colons
.Ql \&:
- which contain the programs used by the login shells.
.Pp
The
.Ar shells
keyword specifies a list of programs available for use as login
shells.
This list is a comma-separated list of shell names which should
not contain a path.
These shells must exist in one of the directories nominated by
.Ar shellpath .
.Pp
The
.Ar defaultshell
keyword nominates which shell program to use for new users when
none is specified on the
.Xr pw 8
command line.
.Pp
The
.Ar defaultgroup
keyword defines the primary group (the group id number in the
password file) used for new accounts.
If left blank, or the word
.Ql \&no
is used, then each new user will have a corresponding group of
their own created automatically.
This is the recommended procedure for new users as it best secures each
user's files against interference by other users of the system
irrespective of the
.Em umask .
normally used by the user.
.Pp
.Ar extragroups
provides an automatic means of placing new users into groups within
the
.Pa /etc/groups
file.
This is useful where all users share some resources, and is preferable
to placing users into the same primary group.
The effect of this keyword can be overridden using the
.Ql \&-G
option on
.Xr pw 8 's
command line.
.Pp
The
.Ar minuid ,
.Ar maxuid ,
.Ar mingid ,
.Ar maxgid
keywords determines the allowed ranges of automatically allocated user
and group id numbers.
The default values for both user and group ids are 1000 and 32000 as
minimum and maximum respectively.
The user and group id's actually used when creating an account with
.Xr pw 8
may be overridden using the
.Ql \&-u
and
.Ql \&-g
command line options.
.Pp
The
.Ar expire_days
and
.Ar password_days
are used to automatically calculate the number of days from the date
on which an account is created when the account will expire or the
user will be forced to change the account's password.
A value of
.Ql \&0
in either field will disable the corresponding (account or password)
expiration date.
.Pp
.Sh LIMITS
The maximum line length of
.Pa /etc/acct/pw.conf
is 1024 characters. Longer lines will be skipped and treated
as comments.
.Sh FILES
.Bl -tag -width /etc/master.passwd -compact
.It Pa /etc/pw.conf
.It Pa /etc/passwd
.It Pa /etc/master.passwd
.It Pa /etc/group
.El
.Sh SEE ALSO
.Xr pw 8 ,
.Xr passwd 1 ,
.Xr passwd 5 ,
.Xr group 5

142
usr.sbin/pw/pw.h Normal file
View File

@ -0,0 +1,142 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <sys/queue.h>
#include "psdate.h"
enum _mode
{
M_ADD,
M_DELETE,
M_UPDATE,
M_PRINT,
M_NUM
};
enum _which
{
W_USER,
W_GROUP,
W_NUM
};
enum _excode
{
X_ALLOK,
X_CMDERR,
X_PERMERR,
X_MEMERR,
X_NOUPDATE,
X_NOTFOUND,
X_UPDERROR,
X_TOOMANY,
X_EXISTS,
X_DBERROR,
X_CONFIG
};
struct carg
{
int ch;
char *val;
LIST_ENTRY(carg) list;
};
extern LIST_HEAD(cargs, carg) arglist;
struct userconf
{
int default_password; /* Default password for new users? */
int reuse_uids; /* Reuse uids? */
int reuse_gids; /* Reuse gids? */
char *dotdir; /* Where to obtain skeleton files */
char *newmail; /* Mail to send to new accounts */
char *logfile; /* Where to log changes */
char *home; /* Where to create home directory */
char *shelldir; /* Where shells are located */
char **shells; /* List of shells */
char *shell_default; /* Default shell */
char *default_group; /* Default group number */
char **groups; /* Default (additional) groups */
char *default_class; /* Default user class */
uid_t min_uid, max_uid; /* Allowed range of uids */
gid_t min_gid, max_gid; /* Allowed range of gids */
int expire_days; /* Days to expiry */
int password_days; /* Days to password expiry */
};
#define _PATH_PW_CONF "/etc/pw.conf"
#define _UC_MAXLINE 1024
#define _UC_MAXSHELLS 32
#define _UC_MAXGROUPS 200
struct userconf *read_userconfig(char const * file);
int write_userconfig(char const * file);
struct carg *addarg(struct cargs * _args, int ch, char *argstr);
struct carg *getarg(struct cargs * _args, int ch);
void cmderr(int ec, char const * fmt,...);
int pw_user(struct userconf * cnf, int mode, struct cargs * _args);
int pw_group(struct userconf * cnf, int mode, struct cargs * _args);
int addpwent(struct passwd * pwd);
int delpwent(struct passwd * pwd);
int chgpwent(char const * login, struct passwd * pwd);
int fmtpwent(char *buf, struct passwd * pwd);
int addgrent(struct group * grp);
int delgrent(struct group * grp);
int chggrent(char const * login, struct group * grp);
int fmtgrent(char *buf, struct group * grp);
int boolean_val(char const * str, int dflt);
char const *boolean_str(int val);
char *newstr(char const * p);
void pw_log(struct userconf * cnf, int mode, int which, char const * fmt,...);
char *pw_pwcrypt(char *password);
extern const char *Modes[];
extern const char *Which[];

458
usr.sbin/pw/pw_conf.c Normal file
View File

@ -0,0 +1,458 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include "pw.h"
#define debugging 0
enum {
_UC_NONE,
_UC_DEFAULTPWD,
_UC_REUSEUID,
_UC_REUSEGID,
_UC_DOTDIR,
_UC_NEWMAIL,
_UC_LOGFILE,
_UC_HOMEROOT,
_UC_SHELLPATH,
_UC_SHELLS,
_UC_DEFAULTSHELL,
_UC_DEFAULTGROUP,
_UC_EXTRAGROUPS,
_UC_DEFAULTCLASS,
_UC_MINUID,
_UC_MAXUID,
_UC_MINGID,
_UC_MAXGID,
_UC_EXPIRE,
_UC_PASSWORD,
_UC_FIELDS
};
static char bourne_shell[] = "sh";
static char *system_shells[_UC_MAXSHELLS] =
{
bourne_shell,
"csh"
};
static char *default_groups[_UC_MAXGROUPS] =
{
NULL
};
static char const *booltrue[] =
{
"yes", "true", "1", "on", NULL
};
static char const *boolfalse[] =
{
"no", "false", "0", "off", NULL
};
static struct userconf config =
{
0, /* Default password for new users? (nologin) */
0, /* Reuse uids? */
0, /* Reuse gids? */
"/usr/share/skel", /* Where to obtain skeleton files */
NULL, /* Mail to send to new accounts */
"/var/log/userlog", /* Where to log changes */
"/home", /* Where to create home directory */
"/bin", /* Where shells are located */
system_shells, /* List of shells (first is default) */
bourne_shell, /* Default shell */
NULL, /* Default group name */
default_groups, /* Default (additional) groups */
NULL, /* Default login class */
1000, 32000, /* Allowed range of uids */
1000, 32000, /* Allowed range of gids */
0, /* Days until account expires */
0 /* Days until password expires */
};
static char const *comments[_UC_FIELDS] =
{
"#\n# pw.conf - user/group configuration defaults\n#\n",
"\n# Password for new users? no=nologin yes=loginid none=blank random=random\n",
"\n# Reuse gaps in uid sequence? (yes or no)\n",
"\n# Reuse gaps in gid sequence? (yes or no)\n",
"\n# Obtain default dotfiles from this directory\n",
"\n# Mail this file to new user (/etc/newuser.msg or no)\n",
"\n# Log add/change/remove information in this file\n",
"\n# Root directory in which $HOME directory is created\n",
"\n# Colon separated list of directories containing valid shells\n",
"\n# Space separated list of available shells (without paths)\n",
"\n# Default shell (without path)\n",
"\n# Default group (leave blank for new group per user)\n",
"\n# Extra groups for new users\n",
"\n# Default login class for new users\n",
"\n# Range of valid default user ids\n",
NULL,
"\n# Range of valid default group ids\n",
NULL,
"\n# Days after which account expires (0=disabled)\n",
"\n# Days after which password expires (0=disabled)\n"
};
static char const *kwds[] =
{
"",
"defaultpasswd",
"reuseuids",
"reusegids",
"skeleton",
"newmail",
"logfile",
"home",
"shellpath",
"shells",
"defaultshell",
"defaultgroup",
"extragroups",
"defaultclass",
"minuid",
"maxuid",
"mingid",
"maxgid",
"expire_days",
"password_days",
NULL
};
static char *
unquote(char const * str)
{
if (str && (*str == '"' || *str == '\'')) {
char *p = strchr(str + 1, *str);
if (p != NULL)
*p = '\0';
return (char *) (*++str ? str : NULL);
}
return (char *) str;
}
int
boolean_val(char const * str, int dflt)
{
if ((str = unquote(str)) != NULL) {
int i;
for (i = 0; booltrue[i]; i++)
if (strcmp(str, booltrue[i]) == 0)
return 1;
for (i = 0; boolfalse[i]; i++)
if (strcmp(str, boolfalse[i]) == 0)
return 0;
/*
* Special cases for defaultpassword
*/
if (strcmp(str, "random") == 0)
return -1;
if (strcmp(str, "none") == 0)
return -2;
}
return dflt;
}
char const *
boolean_str(int val)
{
if (val == -1)
return "random";
else if (val == -2)
return "none";
else
return val ? booltrue[0] : boolfalse[0];
}
char *
newstr(char const * p)
{
char *q = NULL;
if ((p = unquote(p)) != NULL) {
int l = strlen(p) + 1;
if ((q = malloc(l)) != NULL)
memcpy(q, p, l);
}
return q;
}
struct userconf *
read_userconfig(char const * file)
{
FILE *fp;
if (file == NULL)
file = _PATH_PW_CONF;
if ((fp = fopen(file, "r")) != NULL) {
char buf[_UC_MAXLINE];
while (fgets(buf, sizeof buf, fp) != NULL) {
char *p = strchr(buf, '\n');
if (p == NULL) { /* Line too long */
int ch;
while ((ch = fgetc(fp)) != '\n' && ch != EOF);
} else {
*p = '\0';
if (*buf && *buf != '\n' && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
static char const toks[] = " \t\r\n,=";
char *q = strtok(NULL, toks);
int i = 0;
while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
++i;
#if debugging
if (i == _UC_FIELDS)
printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
else
printf("Got kwd[%s]=%s\n", p, q);
#endif
switch (i) {
case _UC_DEFAULTPWD:
config.default_password = boolean_val(q, 1);
break;
case _UC_REUSEUID:
config.reuse_uids = boolean_val(q, 0);
break;
case _UC_REUSEGID:
config.reuse_gids = boolean_val(q, 0);
break;
case _UC_DOTDIR:
config.dotdir = (q == NULL || !boolean_val(q, 1))
? NULL : newstr(q);
break;
case _UC_NEWMAIL:
config.newmail = (q == NULL || !boolean_val(q, 1))
? NULL : newstr(q);
break;
case _UC_LOGFILE:
config.logfile = (q == NULL || !boolean_val(q, 1))
? NULL : newstr(q);
break;
case _UC_HOMEROOT:
config.home = (q == NULL || !boolean_val(q, 1))
? "/home" : newstr(q);
break;
case _UC_SHELLPATH:
config.shelldir = (q == NULL || !boolean_val(q, 1))
? "/bin" : newstr(q);
break;
case _UC_SHELLS:
for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
system_shells[i] = newstr(q);
if (i > 0)
while (i < _UC_MAXSHELLS)
system_shells[i++] = NULL;
break;
case _UC_DEFAULTSHELL:
config.shell_default = (q == NULL || !boolean_val(q, 1))
? (char *) bourne_shell : newstr(q);
break;
case _UC_DEFAULTGROUP:
config.default_group = (q == NULL || !boolean_val(q, 1) || getgrnam(q) == NULL)
? NULL : newstr(q);
break;
case _UC_EXTRAGROUPS:
for (i = 0; i < _UC_MAXGROUPS && q != NULL; i++, q = strtok(NULL, toks))
default_groups[i] = newstr(q);
if (i > 0)
while (i < _UC_MAXGROUPS)
default_groups[i++] = NULL;
break;
case _UC_DEFAULTCLASS:
config.default_class = (q == NULL || !boolean_val(q, 1))
? NULL : newstr(q);
break;
case _UC_MINUID:
if ((q = unquote(q)) != NULL && isdigit(*q))
config.min_uid = (uid_t) atol(q);
break;
case _UC_MAXUID:
if ((q = unquote(q)) != NULL && isdigit(*q))
config.max_uid = (uid_t) atol(q);
break;
case _UC_MINGID:
if ((q = unquote(q)) != NULL && isdigit(*q))
config.min_gid = (gid_t) atol(q);
break;
case _UC_MAXGID:
if ((q = unquote(q)) != NULL && isdigit(*q))
config.max_gid = (gid_t) atol(q);
break;
case _UC_EXPIRE:
if ((q = unquote(q)) != NULL && isdigit(*q))
config.expire_days = atoi(q);
break;
case _UC_PASSWORD:
if ((q = unquote(q)) != NULL && isdigit(*q))
config.password_days = atoi(q);
break;
case _UC_FIELDS:
case _UC_NONE:
break;
}
}
}
}
fclose(fp);
}
return &config;
}
int
write_userconfig(char const * file)
{
int fd;
if (file == NULL)
file = _PATH_PW_CONF;
if ((fd = open(file, O_CREAT | O_RDWR | O_TRUNC | O_EXLOCK, 0644)) != -1) {
FILE *fp;
if ((fp = fdopen(fd, "w")) == NULL)
close(fd);
else {
int i, j, k;
char buf[_UC_MAXLINE];
for (i = _UC_NONE; i < _UC_FIELDS; i++) {
int quote = 1;
char const *val = buf;
*buf = '\0';
switch (i) {
case _UC_DEFAULTPWD:
val = boolean_str(config.default_password);
break;
case _UC_REUSEUID:
val = boolean_str(config.reuse_uids);
break;
case _UC_REUSEGID:
val = boolean_str(config.reuse_gids);
break;
case _UC_DOTDIR:
val = config.dotdir ? config.dotdir : boolean_str(0);
break;
case _UC_NEWMAIL:
val = config.newmail ? config.newmail : boolean_str(0);
break;
case _UC_LOGFILE:
val = config.logfile ? config.logfile : boolean_str(0);
break;
case _UC_HOMEROOT:
val = config.home;
break;
case _UC_SHELLPATH:
val = config.shelldir;
break;
case _UC_SHELLS:
for (j = k = 0; j < _UC_MAXSHELLS && system_shells[j] != NULL; j++)
k += sprintf(buf + k, "%s\"%s\"", k ? "," : "", system_shells[j]);
quote = 0;
break;
case _UC_DEFAULTSHELL:
val = config.shell_default ? config.shell_default : bourne_shell;
break;
case _UC_DEFAULTGROUP:
val = config.default_group ? config.default_group : "";
break;
case _UC_EXTRAGROUPS:
for (j = k = 0; j < _UC_MAXGROUPS && default_groups[j] != NULL; j++)
k += sprintf(buf + k, "%s\"%s\"", k ? "," : "", default_groups[j]);
quote = 0;
break;
case _UC_DEFAULTCLASS:
val = config.default_class ? config.default_class : "";
break;
case _UC_MINUID:
sprintf(buf, "%lu", (unsigned long) config.min_uid);
quote = 0;
break;
case _UC_MAXUID:
sprintf(buf, "%lu", (unsigned long) config.max_uid);
quote = 0;
break;
case _UC_MINGID:
sprintf(buf, "%lu", (unsigned long) config.min_gid);
quote = 0;
break;
case _UC_MAXGID:
sprintf(buf, "%lu", (unsigned long) config.max_gid);
quote = 0;
break;
case _UC_EXPIRE:
sprintf(buf, "%d", config.expire_days);
quote = 0;
break;
case _UC_PASSWORD:
sprintf(buf, "%d", config.password_days);
quote = 0;
break;
case _UC_NONE:
break;
}
if (comments[i])
fputs(comments[i], fp);
if (*kwds[i]) {
if (quote)
fprintf(fp, "%s = \"%s\"\n", kwds[i], val);
else
fprintf(fp, "%s = %s\n", kwds[i], val);
#if debugging
printf("WROTE: %s = %s\n", kwds[i], val);
#endif
}
}
return fclose(fp) != EOF;
}
}
return 0;
}

273
usr.sbin/pw/pw_group.c Normal file
View File

@ -0,0 +1,273 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <unistd.h>
#include <ctype.h>
#include <termios.h>
#include "pw.h"
#include "bitmap.h"
static int print_group(struct group * grp, int pretty);
static gid_t gr_gidpolicy(struct userconf * cnf, struct cargs * args);
int
pw_group(struct userconf * cnf, int mode, struct cargs * args)
{
struct carg *a_name = getarg(args, 'n');
struct carg *a_gid = getarg(args, 'g');
struct carg *arg;
struct group *grp = NULL;
char *members[_UC_MAXGROUPS];
static struct group fakegroup =
{
"nogroup",
"*",
-1,
NULL
};
if (mode == M_PRINT && getarg(args, 'a')) {
int pretty = getarg(args, 'p') != NULL;
setgrent();
while ((grp = getgrent()) != NULL)
print_group(grp, pretty);
endgrent();
return X_ALLOK;
}
if (a_gid == NULL) {
if (a_name == NULL)
cmderr(X_CMDERR, "group name or id required\n");
if (mode != M_ADD && grp == NULL && isdigit(*a_name->val)) {
(a_gid = a_name)->ch = 'g';
a_name = NULL;
}
}
grp = (a_name != NULL) ? getgrnam(a_name->val) : getgrgid((gid_t) atoi(a_gid->val));
if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) {
if (a_name == NULL && grp == NULL) /* Try harder */
grp = getgrgid(atoi(a_gid->val));
if (grp == NULL) {
if (mode == M_PRINT && getarg(args, 'F')) {
fakegroup.gr_name = a_name ? a_name->val : "nogroup";
fakegroup.gr_gid = a_gid ? (gid_t) atol(a_gid->val) : -1;
return print_group(&fakegroup, getarg(args, 'p') != NULL);
}
cmderr(X_CMDERR, "unknown group `%s'\n", a_name ? a_name->val : a_gid->val);
}
if (a_name == NULL) /* Needed later */
a_name = addarg(args, 'n', grp->gr_name);
/*
* Handle deletions now
*/
if (mode == M_DELETE) {
gid_t gid = grp->gr_gid;
if (delgrent(grp) == -1)
cmderr(X_NOUPDATE, "Error updating group file: %s\n", strerror(errno));
pw_log(cnf, mode, W_GROUP, "%s(%ld) removed", a_name->val, (long) gid);
return X_ALLOK;
} else if (mode == M_PRINT)
return print_group(grp, getarg(args, 'p') != NULL);
if (a_gid)
grp->gr_gid = (gid_t) atoi(a_gid->val);
if ((arg = getarg(args, 'l')) != NULL)
grp->gr_name = arg->val;
} else {
if (a_name == NULL) /* Required */
cmderr(X_CMDERR, "group name required\n");
else if (grp != NULL) /* Exists */
cmderr(X_EXISTS, "group name `%s' already exists\n", a_name->val);
memset(members, 0, sizeof members);
grp = &fakegroup;
grp->gr_name = a_name->val;
grp->gr_passwd = "*";
grp->gr_gid = gr_gidpolicy(cnf, args);
grp->gr_mem = members;
}
/*
* This allows us to set a group password Group passwords is an
* antique idea, rarely used and insecure (no secure database) Should
* be discouraged, but it is apparently still supported by some
* software.
*/
if ((arg = getarg(args, 'h')) != NULL) {
if (strcmp(arg->val, "-") == 0)
grp->gr_passwd = "*"; /* No access */
else {
int fd = atoi(arg->val);
int b;
int istty = isatty(fd);
struct termios t;
char *p, line[256];
if (istty) {
if (tcgetattr(fd, &t) == -1)
istty = 0;
else {
struct termios n = t;
/* Disable echo */
n.c_lflag &= ~(ECHO);
tcsetattr(fd, TCSANOW, &n);
printf("%sassword for group %s:", (mode == M_UPDATE) ? "New p" : "P", grp->gr_name);
fflush(stdout);
}
}
b = read(fd, line, sizeof(line) - 1);
if (istty) { /* Restore state */
tcsetattr(fd, TCSANOW, &t);
fputc('\n', stdout);
fflush(stdout);
}
if (b < 0) {
perror("-h file descriptor");
return X_CMDERR;
}
line[b] = '\0';
if ((p = strpbrk(line, " \t\r\n")) != NULL)
*p = '\0';
if (!*line)
cmderr(X_CMDERR, "empty password read on file descriptor %d\n", fd);
grp->gr_passwd = pw_pwcrypt(line);
}
}
if ((mode == M_ADD && !addgrent(grp)) || (mode == M_UPDATE && !chggrent(a_name->val, grp))) {
perror("group update");
return X_NOUPDATE;
}
/* grp may have been invalidated */
if ((grp = getgrnam(a_name->val)) == NULL)
cmderr(X_NOTFOUND, "group disappeared during update\n");
pw_log(cnf, mode, W_GROUP, "%s(%ld)", grp->gr_name, (long) grp->gr_gid);
return X_ALLOK;
}
static gid_t
gr_gidpolicy(struct userconf * cnf, struct cargs * args)
{
struct group *grp;
gid_t gid = (gid_t) - 1;
struct carg *a_gid = getarg(args, 'g');
/*
* Check the given gid, if any
*/
if (a_gid != NULL) {
gid = (gid_t) atol(a_gid->val);
if ((grp = getgrgid(gid)) != NULL && getarg(args, 'o') == NULL)
cmderr(X_EXISTS, "gid `%ld' has already been allocated\n", (long) grp->gr_gid);
} else {
struct bitmap bm;
/*
* We need to allocate the next available gid under one of
* two policies a) Grab the first unused gid b) Grab the
* highest possible unused gid
*/
if (cnf->min_gid >= cnf->max_gid) { /* Sanity claus^H^H^H^Hheck */
cnf->min_gid = 1000;
cnf->max_gid = 32000;
}
bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
/*
* Now, let's fill the bitmap from the password file
*/
setgrent();
while ((grp = getgrent()) != NULL)
if (grp->gr_gid >= (int) cnf->min_gid && grp->gr_gid <= (int) cnf->max_gid)
bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
endgrent();
/*
* Then apply the policy, with fallback to reuse if necessary
*/
if (cnf->reuse_gids)
gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
else {
gid = (gid_t) (bm_lastset(&bm) + 1);
if (!bm_isset(&bm, gid))
gid += cnf->min_gid;
else
gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
}
/*
* Another sanity check
*/
if (gid < cnf->min_gid || gid > cnf->max_gid)
cmderr(X_EXISTS, "unable to allocate a new gid - range fully used\n");
bm_dealloc(&bm);
}
return gid;
}
static int
print_group(struct group * grp, int pretty)
{
if (!pretty) {
char buf[_UC_MAXLINE];
fmtgrent(buf, grp);
fputs(buf, stdout);
} else {
int i;
printf("Group Name : %-10s #%lu\n"
" Members : ",
grp->gr_name, (long) grp->gr_gid);
for (i = 0; i < _UC_MAXGROUPS && grp->gr_mem[i]; i++)
printf("%s%s", i ? "," : "", grp->gr_mem[i]);
fputs("\n\n", stdout);
}
return X_ALLOK;
}

70
usr.sbin/pw/pw_log.c Normal file
View File

@ -0,0 +1,70 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <fcntl.h>
#include "pw.h"
static FILE *logfile = NULL;
void
pw_log(struct userconf * cnf, int mode, int which, char const * fmt,...)
{
if (cnf->logfile && *cnf->logfile) {
if (logfile == NULL) { /* With umask==0 we need to control file access modes on create */
int fd = open(cnf->logfile, O_WRONLY | O_CREAT | O_APPEND, 0600);
if (fd != -1)
logfile = fdopen(fd, "a");
}
if (logfile != NULL) {
va_list argp;
int l;
time_t now = time(NULL);
struct tm *t = localtime(&now);
char nfmt[256];
char *name;
if ((name = getenv("LOGNAME")) == NULL && (name = getenv("USER")) == NULL)
name = "unknown";
strftime(nfmt, sizeof nfmt, "%d-%b-%Y %R ", t);
l = strlen(nfmt);
sprintf(nfmt + strlen(nfmt), "[%s:%s%s] %s\n", name, Which[which], Modes[mode], fmt);
va_start(argp, fmt);
vfprintf(logfile, nfmt, argp);
va_end(argp);
fflush(logfile);
}
}
}

849
usr.sbin/pw/pw_user.c Normal file
View File

@ -0,0 +1,849 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <paths.h>
#include <sys/param.h>
#include <dirent.h>
#include <termios.h>
#include "pw.h"
#include "bitmap.h"
#include "pwupd.h"
static int print_user(struct passwd * pwd, int pretty);
static uid_t pw_uidpolicy(struct userconf * cnf, struct cargs * args);
static uid_t pw_gidpolicy(struct userconf * cnf, struct cargs * args, char *nam, gid_t prefer);
static time_t pw_pwdpolicy(struct userconf * cnf, struct cargs * args);
static time_t pw_exppolicy(struct userconf * cnf, struct cargs * args);
static char *pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user);
static char *pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell);
static char *pw_password(struct userconf * cnf, struct cargs * args, char const * user);
static char *pw_checkname(char *name, int gecos);
static char *shell_path(char const * path, char *shells[], char *sh);
static void rmat(uid_t uid);
/*-
* -C config configuration file
* -q quiet operation
* -n name login name
* -u uid user id
* -c comment user name/comment
* -d directory home directory
* -e date account expiry date
* -p date password expiry date
* -g grp primary group
* -G grp1,grp2 additional groups
* -m [ -k dir ] create and set up home
* -s shell name of login shell
* -o duplicate uid ok
* -L class user class
* -l name new login name
* -h fd password filehandle
* -F force print or add
* Setting defaults:
* -D set user defaults
* -b dir default home root dir
* -e period default expiry period
* -p period default password change period
* -g group default group
* -G grp1,grp2.. default additional groups
* -L class default login class
* -k dir default home skeleton
* -s shell default shell
* -w method default password method
*/
int
pw_user(struct userconf * cnf, int mode, struct cargs * args)
{
char *p = NULL;
struct carg *a_name;
struct carg *a_uid;
struct carg *arg;
struct passwd *pwd = NULL;
struct group *grp;
struct stat st;
char line[MAXPWLINE];
static struct passwd fakeuser =
{
NULL,
"*",
-1,
-1,
0,
"",
"User &",
"/bin/sh",
0,
0
};
/*
* We can do all of the common legwork here
*/
if ((arg = getarg(args, 'b')) != NULL) {
if (stat(cnf->home = arg->val, &st) == -1 || S_ISDIR(st.st_mode))
cmderr(X_CMDERR, "root home `%s' is not a directory or does not exist\n", cnf->home);
}
if ((arg = getarg(args, 'e')) != NULL)
cnf->expire_days = atoi(arg->val);
if ((arg = getarg(args, 'p')) != NULL && arg->val)
cnf->password_days = atoi(arg->val);
if ((arg = getarg(args, 'g')) != NULL) {
p = arg->val;
if ((grp = getgrnam(p)) == NULL) {
if (!isdigit(*p) || (grp = getgrgid((gid_t) atoi(p))) == NULL)
cmderr(X_NOTFOUND, "group `%s' does not exist\n", p);
}
cnf->default_group = newstr(grp->gr_name);
}
if ((arg = getarg(args, 'L')) != NULL)
cnf->default_class = pw_checkname(arg->val, 0);
if ((arg = getarg(args, 'G')) != NULL && arg->val) {
int i = 0;
for (p = strtok(arg->val, ", \t"); i < _UC_MAXGROUPS && p != NULL; p = strtok(NULL, ", \t")) {
if ((grp = getgrnam(p)) == NULL) {
if (!isdigit(*p) || (grp = getgrgid((gid_t) atoi(p))) == NULL)
cmderr(X_NOTFOUND, "group `%s' does not exist\n", p);
}
cnf->groups[i++] = newstr(grp->gr_name);
}
while (i < _UC_MAXGROUPS)
cnf->groups[i++] = NULL;
}
if ((arg = getarg(args, 'k')) != NULL) {
if (stat(cnf->dotdir = arg->val, &st) == -1 || S_ISDIR(st.st_mode))
cmderr(X_CMDERR, "skeleton `%s' is not a directory or does not exist\n", cnf->dotdir);
}
if ((arg = getarg(args, 's')) != NULL)
cnf->shell_default = arg->val;
if (mode == M_ADD && getarg(args, 'D')) {
if (getarg(args, 'n') != NULL)
cmderr(X_CMDERR, "can't combine `-D' with `-n name'\n");
if ((arg = getarg(args, 'u')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) {
if ((cnf->min_uid = (uid_t) atoi(p)) == 0)
cnf->min_uid = 1000;
if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_uid = (uid_t) atoi(p)) < cnf->min_uid)
cnf->max_uid = 32000;
}
if ((arg = getarg(args, 'i')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) {
if ((cnf->min_gid = (gid_t) atoi(p)) == 0)
cnf->min_gid = 1000;
if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_gid = (gid_t) atoi(p)) < cnf->min_gid)
cnf->max_gid = 32000;
}
if ((arg = getarg(args, 'w')) != NULL)
cnf->default_password = boolean_val(arg->val, cnf->default_password);
arg = getarg(args, 'C');
if (write_userconfig(arg ? arg->val : NULL))
return X_ALLOK;
perror("config update");
return X_UPDERROR;
}
if (mode == M_PRINT && getarg(args, 'a')) {
int pretty = getarg(args, 'p') != NULL;
setpwent();
while ((pwd = getpwent()) != NULL)
print_user(pwd, pretty);
endpwent();
return X_ALLOK;
}
if ((a_name = getarg(args, 'n')) != NULL)
pwd = getpwnam(pw_checkname(a_name->val, 0));
a_uid = getarg(args, 'u');
if (a_uid == NULL) {
if (a_name == NULL)
cmderr(X_CMDERR, "user name or id required\n");
/*
* Determine whether 'n' switch is name or uid - we don't
* really don't really care which we have, but we need to
* know.
*/
if (mode != M_ADD && pwd == NULL && isdigit(*a_name->val) && atoi(a_name->val) > 0) { /* Assume uid */
(a_uid = a_name)->ch = 'u';
a_name = NULL;
}
}
/*
* Update, delete & print require that the user exists
*/
if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) {
if (a_name == NULL && pwd == NULL) /* Try harder */
pwd = getpwuid(atoi(a_uid->val));
if (pwd == NULL) {
if (mode == M_PRINT && getarg(args, 'F')) {
fakeuser.pw_name = a_name ? a_name->val : "nouser";
fakeuser.pw_uid = a_uid ? (uid_t) atol(a_uid->val) : -1;
return print_user(&fakeuser, getarg(args, 'p') != NULL);
}
if (a_name == NULL)
cmderr(X_NOTFOUND, "no such uid `%s'\n", a_uid->val);
cmderr(X_NOTFOUND, "no such user `%s'\n", a_name->val);
}
if (a_name == NULL) /* May be needed later */
a_name = addarg(args, 'n', newstr(pwd->pw_name));
/*
* Handle deletions now
*/
if (mode == M_DELETE) {
char file[MAXPATHLEN];
char home[MAXPATHLEN];
uid_t uid = pwd->pw_uid;
if (strcmp(pwd->pw_name, "root") == 0)
cmderr(X_CMDERR, "cannot remove user 'root'\n");
/*
* Remove crontabs
*/
sprintf(file, "/var/cron/tabs/%s", pwd->pw_name);
if (access(file, F_OK) == 0) {
sprintf(file, "crontab -u %s -r", pwd->pw_name);
system(file);
}
/*
* Save these for later, since contents of pwd may be
* invalidated by deletion
*/
sprintf(file, "%s/%s", _PATH_MAILDIR, pwd->pw_name);
strncpy(home, pwd->pw_dir, sizeof home);
home[sizeof home - 1] = '\0';
if (!delpwent(pwd))
cmderr(X_NOUPDATE, "Error updating passwd file: %s\n", strerror(errno));
editgroups(a_name->val, NULL);
pw_log(cnf, mode, W_USER, "%s(%ld) account removed", a_name->val, (long) uid);
/*
* Remove mail file
*/
remove(file);
/*
* Remove at jobs
*/
if (getpwuid(uid) == NULL)
rmat(uid);
/*
* Remove home directory and contents
*/
if (getarg(args, 'r') != NULL && *home == '/' && getpwuid(uid) == NULL) {
if (stat(home, &st) != -1) {
rm_r(home, uid);
pw_log(cnf, mode, W_USER, "%s(%ld) home '%s' %sremoved",
a_name->val, (long) uid, home,
stat(home, &st) == -1 ? "" : "not completely ");
}
}
return X_ALLOK;
} else if (mode == M_PRINT)
return print_user(pwd, getarg(args, 'p') != NULL);
/*
* The rest is edit code
*/
if ((arg = getarg(args, 'l')) != NULL) {
if (strcmp(pwd->pw_name, "root") == 0)
cmderr(X_CMDERR, "can't rename `root' account\n");
pwd->pw_name = pw_checkname(arg->val, 0);
}
if ((arg = getarg(args, 'u')) != NULL && isdigit(*arg->val)) {
pwd->pw_uid = (uid_t) atol(arg->val);
if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0)
cmderr(X_CMDERR, "can't change uid of `root' account\n");
if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0)
fprintf(stderr, "WARNING: account `%s' will have a uid of 0 (superuser access!)\n", pwd->pw_name);
}
if ((arg = getarg(args, 'g')) != NULL && pwd->pw_uid != 0) /* Already checked this */
pwd->pw_gid = (gid_t) getgrnam(cnf->default_group)->gr_gid;
if ((arg = getarg(args, 'p')) != NULL) {
if (*arg->val == '\0' || strcmp(arg->val, "0") == 0)
pwd->pw_change = 0;
else {
time_t now = time(NULL);
time_t expire = parse_date(now, arg->val);
if (now == expire)
cmderr(X_CMDERR, "Invalid password change date `%s'\n", arg->val);
pwd->pw_change = expire;
}
}
if ((arg = getarg(args, 'p')) != NULL) {
if (*arg->val == '\0' || strcmp(arg->val, "0") == 0)
pwd->pw_expire = 0;
else {
time_t now = time(NULL);
time_t expire = parse_date(now, arg->val);
if (now == expire)
cmderr(X_CMDERR, "Invalid password change date `%s'\n", arg->val);
pwd->pw_expire = expire;
}
}
if ((arg = getarg(args, 's')) != NULL)
pwd->pw_shell = shell_path(cnf->shelldir, cnf->shells, arg->val);
if (getarg(args, 'L'))
pwd->pw_class = cnf->default_class;
} else {
if (a_name == NULL) /* Required */
cmderr(X_CMDERR, "login name required\n");
else if ((pwd = getpwnam(a_name->val)) != NULL) /* Exists */
cmderr(X_EXISTS, "login name `%s' already exists\n", a_name->val);
/*
* Now, set up defaults for a new user
*/
pwd = &fakeuser;
pwd->pw_name = a_name->val;
pwd->pw_class = cnf->default_class ? cnf->default_class : "";
pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name);
pwd->pw_uid = pw_uidpolicy(cnf, args);
pwd->pw_gid = pw_gidpolicy(cnf, args, pwd->pw_name, (gid_t) pwd->pw_uid);
pwd->pw_change = pw_pwdpolicy(cnf, args);
pwd->pw_expire = pw_exppolicy(cnf, args);
pwd->pw_dir = pw_homepolicy(cnf, args, pwd->pw_name);
pwd->pw_shell = pw_shellpolicy(cnf, args, NULL);
if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0)
fprintf(stderr, "WARNING: new account `%s' has a uid of 0 (superuser access!)\n", pwd->pw_name);
}
/*
* Shared add/edit code
*/
if ((arg = getarg(args, 'c')) != NULL)
pwd->pw_gecos = pw_checkname(arg->val, 1);
if ((arg = getarg(args, 'h')) != NULL) {
if (strcmp(arg->val, "-") == 0)
pwd->pw_passwd = "*"; /* No access */
else {
int fd = atoi(arg->val);
int b;
int istty = isatty(fd);
struct termios t;
if (istty) {
if (tcgetattr(fd, &t) == -1)
istty = 0;
else {
struct termios n = t;
/* Disable echo */
n.c_lflag &= ~(ECHO);
tcsetattr(fd, TCSANOW, &n);
printf("%sassword for user %s:", (mode == M_UPDATE) ? "New p" : "P", pwd->pw_name);
fflush(stdout);
}
}
b = read(fd, line, sizeof(line) - 1);
if (istty) { /* Restore state */
tcsetattr(fd, TCSANOW, &t);
fputc('\n', stdout);
fflush(stdout);
}
if (b < 0) {
perror("-h file descriptor");
return X_CMDERR;
}
line[b] = '\0';
if ((p = strpbrk(line, " \t\r\n")) != NULL)
*p = '\0';
if (!*line)
cmderr(X_CMDERR, "empty password read on file descriptor %d\n", fd);
pwd->pw_passwd = pw_pwcrypt(line);
}
}
if ((mode == M_ADD && !addpwent(pwd)) ||
(mode == M_UPDATE && !chgpwent(a_name->val, pwd))) {
perror("password update");
return X_NOUPDATE;
}
/*
* Ok, user is created or changed - now edit group file
*/
if (mode == M_ADD || getarg(args, 'G') != NULL)
editgroups(pwd->pw_name, cnf->groups);
/* pwd may have been invalidated */
if ((pwd = getpwnam(a_name->val)) == NULL)
cmderr(X_NOTFOUND, "user '%s' disappeared during update\n", a_name->val);
grp = getgrgid(pwd->pw_gid);
pw_log(cnf, mode, W_USER, "%s(%ld):%s(%d):%s:%s:%s",
pwd->pw_name, (long) pwd->pw_uid,
grp ? grp->gr_name : "unknown", (long) (grp ? grp->gr_gid : -1),
pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);
/*
* If adding, let's touch and chown the user's mail file. This is not
* strictly necessary under BSD with a 0755 maildir but it also
* doesn't hurt anything to create the empty mailfile
*/
if (mode == M_ADD) {
FILE *fp;
sprintf(line, "%s/%s", _PATH_MAILDIR, pwd->pw_name);
close(open(line, O_RDWR | O_CREAT, 0600)); /* Preserve contents &
* mtime */
chown(line, pwd->pw_uid, pwd->pw_gid);
/*
* Send mail to the new user as well, if we are asked to
*/
if (cnf->newmail && *cnf->newmail && (fp = fopen(cnf->newmail, "r")) != NULL) {
FILE *pfp = popen(_PATH_SENDMAIL " -t", "w");
if (pfp == NULL)
perror("sendmail");
else {
fprintf(pfp, "From: root\n" "To: %s\n" "Subject: Welcome!\n\n", pwd->pw_name);
while (fgets(line, sizeof(line), fp) != NULL) {
/* Do substitutions? */
fputs(line, pfp);
}
pclose(pfp);
pw_log(cnf, mode, W_USER, "%s(%ld) new user mail sent",
pwd->pw_name, (long) pwd->pw_uid);
}
fclose(fp);
}
}
/*
* Finally, let's create and populate the user's home directory. Note
* that this also `works' for editing users if -m is used, but
* existing files will *not* be overwritten.
*/
if (getarg(args, 'm') != NULL && pwd->pw_dir && *pwd->pw_dir == '/' && pwd->pw_dir[1]) {
copymkdir(pwd->pw_dir, cnf->dotdir, 0755, pwd->pw_uid, pwd->pw_gid);
pw_log(cnf, mode, W_USER, "%s(%ld) home %s made",
pwd->pw_name, (long) pwd->pw_uid, pwd->pw_dir);
}
return X_ALLOK;
}
static uid_t
pw_uidpolicy(struct userconf * cnf, struct cargs * args)
{
struct passwd *pwd;
uid_t uid = (uid_t) - 1;
struct carg *a_uid = getarg(args, 'u');
/*
* Check the given uid, if any
*/
if (a_uid != NULL) {
uid = (uid_t) atol(a_uid->val);
if ((pwd = getpwuid(uid)) != NULL && getarg(args, 'o') == NULL)
cmderr(X_EXISTS, "uid `%ld' has already been allocated\n", (long) pwd->pw_uid);
} else {
struct bitmap bm;
/*
* We need to allocate the next available uid under one of
* two policies a) Grab the first unused uid b) Grab the
* highest possible unused uid
*/
if (cnf->min_uid >= cnf->max_uid) { /* Sanity
* claus^H^H^H^Hheck */
cnf->min_uid = 1000;
cnf->max_uid = 32000;
}
bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1);
/*
* Now, let's fill the bitmap from the password file
*/
setpwent();
while ((pwd = getpwent()) != NULL)
if (pwd->pw_uid >= (int) cnf->min_uid && pwd->pw_uid <= (int) cnf->max_uid)
bm_setbit(&bm, pwd->pw_uid - cnf->min_uid);
endpwent();
/*
* Then apply the policy, with fallback to reuse if necessary
*/
if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid)
uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid);
/*
* Another sanity check
*/
if (uid < cnf->min_uid || uid > cnf->max_uid)
cmderr(X_EXISTS, "unable to allocate a new uid - range fully used\n");
bm_dealloc(&bm);
}
return uid;
}
static uid_t
pw_gidpolicy(struct userconf * cnf, struct cargs * args, char *nam, gid_t prefer)
{
struct group *grp;
gid_t gid = (uid_t) - 1;
struct carg *a_gid = getarg(args, 'g');
/*
* If no arg given, see if default can help out
*/
if (a_gid == NULL && cnf->default_group && *cnf->default_group)
a_gid = addarg(args, 'g', cnf->default_group);
/*
* Check the given gid, if any
*/
if (a_gid != NULL) {
setgrent();
if ((grp = getgrnam(a_gid->val)) == NULL) {
gid = (gid_t) atol(a_gid->val);
if ((gid == 0 && !isdigit(*a_gid->val)) || (grp = getgrgid(gid)) == NULL)
cmderr(X_NOTFOUND, "group `%s' is not defined\n", a_gid->val);
}
endgrent();
gid = grp->gr_gid;
} else {
struct cargs grpargs;
char tmp[32];
LIST_INIT(&grpargs);
addarg(&grpargs, 'n', nam);
/*
* We need to auto-create a group with the user's name. We
* can send all the appropriate output to our sister routine
* bit first see if we can create a group with gid==uid so we
* can keep the user and group ids in sync. We purposely do
* NOT check the gid range if we can force the sync. If the
* user's name dups an existing group, then the group add
* function will happily handle that case for us and exit.
*/
if (getgrgid(prefer) == NULL) {
sprintf(tmp, "%lu", (unsigned long) prefer);
addarg(&grpargs, 'g', tmp);
}
endgrent();
pw_group(cnf, M_ADD, &grpargs);
a_gid = grpargs.lh_first;
while (a_gid != NULL) {
struct carg *t = a_gid->list.le_next;
LIST_REMOVE(a_gid, list);
a_gid = t;
}
if ((grp = getgrnam(nam)) != NULL)
gid = grp->gr_gid;
}
return gid;
}
static time_t
pw_pwdpolicy(struct userconf * cnf, struct cargs * args)
{
time_t result = 0;
time_t now = time(NULL);
struct carg *arg = getarg(args, 'e');
if (arg != NULL) {
if ((result = parse_date(now, arg->val)) == now)
cmderr(X_NOTFOUND, "invalid date/time `%s'\n", arg->val);
} else if (cnf->password_days > 0)
result = now + ((long) cnf->password_days * 86400L);
return result;
}
static time_t
pw_exppolicy(struct userconf * cnf, struct cargs * args)
{
time_t result = 0;
time_t now = time(NULL);
struct carg *arg = getarg(args, 'e');
if (arg != NULL) {
if ((result = parse_date(now, arg->val)) == now)
cmderr(X_NOTFOUND, "invalid date/time `%s'\n", arg->val);
} else if (cnf->expire_days > 0)
result = now + ((long) cnf->expire_days * 86400L);
return result;
}
static char *
pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user)
{
struct carg *arg = getarg(args, 'd');
if (arg)
return arg->val;
else {
static char home[128];
if (cnf->home == NULL || *cnf->home == '\0')
cmderr(X_NOTFOUND, "no base home directory set\n");
sprintf(home, "%s/%s", cnf->home, user);
return home;
}
}
static char *
shell_path(char const * path, char *shells[], char *sh)
{
if (sh != NULL && (*sh == '/' || *sh == '\0'))
return sh; /* specified full path or forced none */
else {
char *p;
char paths[_UC_MAXLINE];
/*
* We need to search paths
*/
strncpy(paths, path, sizeof paths);
paths[sizeof paths - 1] = '\0';
for (p = strtok(paths, ": \t\r\n"); p != NULL; p = strtok(NULL, ": \t\r\n")) {
int i;
static char shellpath[256];
if (sh != NULL) {
sprintf(shellpath, "%s/%s", p, sh);
if (access(shellpath, X_OK) == 0)
return shellpath;
} else
for (i = 0; i < _UC_MAXSHELLS && shells[i] != NULL; i++) {
sprintf(shellpath, "%s/%s", p, shells[i]);
if (access(shellpath, X_OK) == 0)
return shellpath;
}
}
if (sh == NULL)
cmderr(X_CMDERR, "can't find shell `%s' in shell paths\n", sh);
cmderr(X_CMDERR, "no default shell available or defined\n");
return NULL;
}
}
static char *
pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell)
{
char *sh = newshell;
struct carg *arg = getarg(args, 's');
if (newshell == NULL && arg != NULL)
sh = arg->val;
return shell_path(cnf->shelldir, cnf->shells, sh ? sh : cnf->shell_default);
}
static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
char *
pw_pwcrypt(char *password)
{
int i;
char salt[12];
static char buf[256];
/*
* Calculate a salt value
*/
srandom((unsigned) (time(NULL) | getpid()));
for (i = 0; i < 8; i++)
salt[i] = chars[random() % 63];
salt[i] = '\0';
return strcpy(buf, crypt(password, salt));
}
static char *
pw_password(struct userconf * cnf, struct cargs * args, char const * user)
{
int i, l;
char pwbuf[32];
switch (cnf->default_password) {
case -1: /* Random password */
srandom((unsigned) (time(NULL) | getpid()));
l = (random() % 8 + 8); /* 8 - 16 chars */
for (i = 0; i < l; i++)
pwbuf[i] = chars[random() % sizeof(chars)];
pwbuf[i] = '\0';
/*
* We give this information back to the user
*/
if (getarg(args, 'h') == NULL) {
if (isatty(0))
printf("Password is: ");
printf("%s\n", pwbuf);
fflush(stdout);
}
break;
case -2: /* No password at all! */
return "";
case 0: /* No login - default */
default:
return "*";
case 1: /* user's name */
strncpy(pwbuf, user, sizeof pwbuf);
pwbuf[sizeof pwbuf - 1] = '\0';
break;
}
return pw_pwcrypt(pwbuf);
}
static int
print_user(struct passwd * pwd, int pretty)
{
if (!pretty) {
char buf[_UC_MAXLINE];
fmtpwent(buf, pwd);
fputs(buf, stdout);
} else {
char *p;
struct group *grp = getgrgid(pwd->pw_gid);
char uname[60] = "User &", office[60] = "[None]",
wphone[60] = "[None]", hphone[60] = "[None]";
if ((p = strtok(pwd->pw_gecos, ",")) != NULL) {
strncpy(uname, p, sizeof uname);
uname[sizeof uname - 1] = '\0';
if ((p = strtok(NULL, ",")) != NULL) {
strncpy(office, p, sizeof office);
office[sizeof office - 1] = '\0';
if ((p = strtok(NULL, ",")) != NULL) {
strncpy(wphone, p, sizeof wphone);
wphone[sizeof wphone - 1] = '\0';
if ((p = strtok(NULL, "")) != NULL) {
strncpy(hphone, p, sizeof hphone);
hphone[sizeof hphone - 1] = '\0';
}
}
}
}
/*
* Handle '&' in gecos field
*/
if ((p = strchr(uname, '&')) != NULL) {
int l = strlen(pwd->pw_name);
int m = strlen(p);
memmove(p + l, p + 1, m);
memmove(p, pwd->pw_name, l);
*p = (char) toupper(*p);
}
printf("Login Name : %-10s #%-22ld Group : %-10s #%ld\n"
" Full Name : %s\n"
" Home : %-32.32s Class : %s\n"
" Shell : %-32.32s Office : %s\n"
"Work Phone : %-32.32s Home Phone : %s\n\n",
pwd->pw_name, (long) pwd->pw_uid,
grp ? grp->gr_name : "(invalid)", (long) pwd->pw_gid,
uname, pwd->pw_dir, pwd->pw_class,
pwd->pw_shell, office, wphone, hphone);
}
return X_ALLOK;
}
static char *
pw_checkname(char *name, int gecos)
{
int l = 0;
char const *notch = gecos ? ":" : " :+-&#%$^()!@~*?<>=|\\/\"";
while (name[l]) {
if (strchr(notch, name[l]) != NULL || name[l] < ' ')
cmderr(X_CMDERR, "invalid character `%c' in field\n", name[l]);
++l;
}
if (!gecos && l > 8)
cmderr(X_CMDERR, "name too long `%s'\n", name);
return name;
}
static void
rmat(uid_t uid)
{
DIR *d = opendir("/var/at/jobs");
if (d != NULL) {
struct dirent *e;
while ((e = readdir(d)) != NULL) {
struct stat st;
if (strncmp(e->d_name, ".lock", 5) != 0 &&
stat(e->d_name, &st) == 0 &&
!S_ISDIR(st.st_mode) &&
st.st_uid == uid) {
char tmp[MAXPATHLEN];
sprintf(tmp, "/usr/bin/atrm %s", e->d_name);
system(tmp);
}
}
closedir(d);
}
}

160
usr.sbin/pw/pwupd.c Normal file
View File

@ -0,0 +1,160 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdarg.h>
#include "pwupd.h"
static int
pwdb(char *arg,...)
{
int i = 0;
pid_t pid;
va_list ap;
char *args[8];
args[i++] = _PATH_PWD_MKDB;
va_start(ap, arg);
while (i < 6 && arg != NULL) {
args[i++] = arg;
arg = va_arg(ap, char *);
}
args[i++] = _PATH_MASTERPASSWD;
args[i] = NULL;
if ((pid = fork()) == -1) /* Error (errno set) */
i = -1;
else if (pid == 0) { /* Child */
execv(args[0], args);
_exit(1);
} else { /* Parent */
waitpid(pid, &i, 0);
if ((i = WEXITSTATUS(i)) != 0)
errno = EIO; /* set SOMETHING */
}
return i;
}
int
fmtpwentry(char *buf, struct passwd * pwd, int type)
{
int l;
char *pw;
pw = (pwd->pw_passwd == NULL || !*pwd->pw_passwd) ? "" : (type == PWF_MASTER) ? pwd->pw_passwd : "*";
if (type == PWF_PASSWD)
l = sprintf(buf, "%s:*:%ld:%ld:%s:%s:%s\n",
pwd->pw_name, (long) pwd->pw_uid, (long) pwd->pw_gid,
pwd->pw_gecos ? pwd->pw_gecos : "User &",
pwd->pw_dir, pwd->pw_shell);
else
l = sprintf(buf, "%s:%s:%ld:%ld:%s:%lu:%lu:%s:%s:%s\n",
pwd->pw_name, pw, (long) pwd->pw_uid, (long) pwd->pw_gid,
pwd->pw_class ? pwd->pw_class : "",
(unsigned long) pwd->pw_change,
(unsigned long) pwd->pw_expire,
pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);
return l;
}
int
fmtpwent(char *buf, struct passwd * pwd)
{
return fmtpwentry(buf, pwd, PWF_STANDARD);
}
static int
pw_update(struct passwd * pwd, char const * user, int mode)
{
int rc = 0;
endpwent();
/*
* First, let's check the see if the database is alright
*/
if (pwdb("-c", NULL) == 0) { /* Check only */
char pfx[32];
char pwbuf[MAXPWLINE];
int l = sprintf(pfx, "%s:", user);
/*
* Update the passwd file first
*/
if (pwd == NULL)
*pwbuf = '\0';
else
fmtpwentry(pwbuf, pwd, PWF_PASSWD);
if ((rc = fileupdate(_PATH_PASSWD, 0644, pwbuf, pfx, l, mode)) != 0) {
/*
* Then the master.passwd file
*/
if (pwd != NULL)
fmtpwentry(pwbuf, pwd, PWF_MASTER);
if ((rc = fileupdate(_PATH_MASTERPASSWD, 0644, pwbuf, pfx, l, mode)) != 0)
rc = pwdb(NULL) == 0;
}
}
return rc;
}
int
addpwent(struct passwd * pwd)
{
return pw_update(pwd, pwd->pw_name, UPD_CREATE);
}
int
chgpwent(char const * login, struct passwd * pwd)
{
return pw_update(pwd, login, UPD_REPLACE);
}
int
delpwent(struct passwd * pwd)
{
return pw_update(NULL, pwd->pw_name, UPD_DELETE);
}

85
usr.sbin/pw/pwupd.h Normal file
View File

@ -0,0 +1,85 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#ifndef _PWUPD_H_
#define _PWUPD_H_
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <sys/cdefs.h>
enum updtype
{
UPD_DELETE = -1,
UPD_CREATE = 0,
UPD_REPLACE = 1
};
__BEGIN_DECLS
int fileupdate __P((char const * fname, mode_t fm, char const * nline, char const * pfx, int pfxlen, int updmode));
__END_DECLS
enum pwdfmttype
{
PWF_STANDARD, /* MASTER format but with '*' as password */
PWF_PASSWD, /* V7 format */
PWF_GROUP = PWF_PASSWD,
PWF_MASTER /* MASTER format with password */
};
__BEGIN_DECLS
int addpwent __P((struct passwd * pwd));
int delpwent __P((struct passwd * pwd));
int chgpwent __P((char const * login, struct passwd * pwd));
int fmtpwent __P((char *buf, struct passwd * pwd));
int fmtpwentry __P((char *buf, struct passwd * pwd, int type));
int addgrent __P((struct group * grp));
int delgrent __P((struct group * grp));
int chggrent __P((char const * name, struct group * grp));
int fmtgrent __P((char *buf, struct group * grp));
int fmtgrentry __P((char *buf, struct group * grp, int type));
int editgroups __P((char *name, char **groups));
__END_DECLS
#define MAXGROUPS 200
#define MAXPWLINE 1024
__BEGIN_DECLS
void copymkdir __P((char const * dir, char const * skel, mode_t mode, uid_t uid, gid_t gid));
void rm_r __P((char const * dir, uid_t uid));
__END_DECLS
#endif /* !_PWUPD_H */

77
usr.sbin/pw/rm_r.c Normal file
View File

@ -0,0 +1,77 @@
/*-
* Copyright (c) 1996 by David L. Nugent <davidn@blaze.net.au>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer as
* the first lines of this file unmodified.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by David L. Nugent.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DAVID L. NUGENT ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id$
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <unistd.h>
#include <dirent.h>
#include "pwupd.h"
void
rm_r(char const * dir, uid_t uid)
{
DIR *d = opendir(dir);
if (d != NULL) {
struct dirent *e;
struct stat st;
char file[MAXPATHLEN];
while ((e = readdir(d)) != NULL) {
if (strcmp(e->d_name, ".") != 0 && strcmp(e->d_name, "..") != 0) {
sprintf(file, "%s/%s", dir, e->d_name);
if (lstat(file, &st) == 0) { /* Need symlinks, not
* linked file */
if (S_ISDIR(st.st_mode)) /* Directory - recurse */
rm_r(file, uid);
else {
if (S_ISLNK(st.st_mode) || st.st_uid == uid)
remove(file);
}
}
}
}
closedir(d);
if (lstat(dir, &st) == 0) {
if (S_ISLNK(st.st_mode))
remove(dir);
else if (st.st_uid == uid)
rmdir(dir);
}
}
}