home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Usenet 1994 October
/
usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso
/
misc
/
volume21
/
uoodoo
/
part01
/
uoodoo.c
< prev
Wrap
C/C++ Source or Header
|
1991-07-27
|
20KB
|
717 lines
/****************************************************************************
*
* uoodoo - create and update a directory of symbolic links which point
* to users' home directories.
*
* Usage:
* uoodoo [-w] [-d] [-a] [-c] [-u u-directory] [dir1 [dir2] ... ]
*
* Arguments:
* Directories which contain users' home directory entries.
* Different argument ordering may produce different results.
* See the man page for further details.
*
* Options:
* -a Report what actions, if any, are performed.
* -c Create output, but do not perform any actions.
* -d Debug mode. Creates lots of output.
* -p link-path
* Use the "link-path" as a path in the password file to
* indicate that the home directory is a symbolic link.
* -u u-directory
* Use the "u-directory" instead of the default (/u) directory.
* -w Print warnings: home directory conflicts, multiple password
* password file entries, and entries in the u-directory which
* are not symbolic links.
*
* Installation:
* Choose either the "BSD_LIKE" or "SYSV_LIKE" variable in
* the Makefile to include the proper header files for the
* directory libraries. Also add the proper library to
* link if your system requires a library to access a
* YP/NIS version of getpwent(3). Type make. Create /u
* before running.
*
* Copyright (c) 1991 by Shelley L. Shostak.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by Shelley L. Shostak.
*
* Any use of this software is at the user's own risk.
*
* Send comments and bugs to sls@cs.duke.edu.
*
****************************************************************************/
#ifndef lint
static char SccsId[] = "@(#)uoodoo.c 1.15\t7/25/91";
#endif
#include <stdio.h>
#include <sys/types.h>
#ifdef SYSV_LIKE /* SunOS, SGI, UNICOS */
#include <dirent.h>
#define DIRENTRY struct dirent
#endif
#ifdef BSD_LIKE /* SunOS, Convex */
#include <sys/dir.h>
#define DIRENTRY struct direct
#endif
#include <errno.h>
#include <sys/param.h>
#include <pwd.h>
#include <sys/stat.h>
#include <strings.h>
#define MAXIDLEN 16 /* Maximum length of login name */
#define MAXUSERS 2500 /* Maximum entries from the passwd file */
#define MAXHDLEN 128 /* Maximum length of home directory entry */
#define MAXLINKLEN 144 /* Maximum length of sym link name */
#define DEFAULTUDIR "/u" /* The default dir. containing sym. links */
#define SUCCEEDED 0 /* return code tokens */
#define FAILED 1
#define CONFLICT 1
#define FATAL 2
#define TRUE 1
#define FALSE 0
struct userinfo {
char name[MAXIDLEN]; /* login name for user */
char homedir[MAXHDLEN]; /* contains what we think the users real
home directory is at any time */
char conflictdir[MAXHDLEN]; /* store conflicts here */
int status; /* Unless we have found a link entry, the
* status flag indicates what type of home
* directory the user has at any given time and
* if we found a real directory for the user.
*/
int action; /* The action to be performed on this entry. */
} user[MAXUSERS];
/* Possible flags for status: */
#define HD_IS_LINK 001 /* home dir. is a symbolic link */
#define HD_IS_DIR 002 /* home dir. is a real directory */
#define DIR_FOUND 004 /* a directory for this user was found */
#define HD_CNFLCT 010 /* we discovered a conflict */
/* Possible flags for action: */
#define NONE 001 /* entry ok, don't do anything */
#define ADD 002 /* no u-dir entry, add one */
#define REMOVE 004 /* remove u-dir entry */
#define UPDATE 010 /* update u-dir, HD has changed */
unsigned int nusers = 0;
int errflg, aflg, cflg, pflg, uflg, wflg, dflg; /* option flags */
int action = FALSE; /* reports the actions which uoodoo takes */
int debug = FALSE; /* debug mode prints lots of output */
int warn = FALSE; /* warn prints other interesting info */
int doit = TRUE; /* if false, then the action will not be taken */
int endsort; /* Last sorted userinfo table entry (kludge) */
char usage[ ] = "[-w] [-d] [-a] [-c] [-u udir] [dir1 [dir2] ... ]";
char linkdir[MAXLINKLEN] = DEFAULTUDIR; /* directory where links can be found */
char linkpath[MAXLINKLEN] = DEFAULTUDIR; /* path which indicates a link */
char *program; /* Our name */
int doargs();
int readpw();
int lookdir();
int readu();
int actions();
int add_sl();
int rm_sl();
int update_sl();
int namecmp();
char *cstat();
void exit();
int checkpwent();
struct userinfo *addtabent();
struct userinfo *find();
struct userinfo *search();
main(argc, argv)
int argc;
char *argv[ ];
{
int dirlist;
int error = 0;
char dirname[MAXHDLEN];
program = *argv;
setlinebuf(stdout); /* use line buffering, intermix stderr stdio */
dirlist = doargs(argc, argv);
if (uflg) (void) printf("/u override %s\n", linkdir);
if (cflg) (void) printf("Just checking\n");
(void) strcat(linkdir, "/"); /* turn dir name into a path */
if (readpw() != SUCCEEDED) /* build user table from passwd file */
exit (FAILED); /* need to make MAXUSERS larger */
/* treat the remaining arguments as directory names */
while (argv[dirlist] != NULL ) {
(void) strcpy(dirname, argv[dirlist++]);
error += lookdir(dirname);
}
if (error) /* quit if ANY directory is empty */
exit (FATAL);
if (readu() == FATAL) /* read the directory of links */
exit (FAILED);
if (actions() != SUCCEEDED) /* run through table entries */
exit (FAILED);
exit (SUCCEEDED);
/* NOTREACHED */
}
/*
* Process the command line arguments. Returns one greater than the
* argument number of the last option flag, which is the first
* directory to process.
*/
doargs (argc, argv)
int argc;
char **argv;
{
int c;
extern char *optarg;
extern int optind;
int getopt();
errflg = aflg = cflg = dflg = pflg = uflg = wflg = 0;
while ((c = getopt(argc, argv, "acdp:u:w")) != -1 ) {
switch (c) {
case 'a': /* report actions */
if (aflg)
++errflg;
else {
++aflg;
action = TRUE;
}
break;
case 'c': /* don't actually do anything */
if (cflg)
++errflg;
else {
++cflg;
doit = FALSE;
}
break;
case 'd': /* print diagnostic messages */
if (dflg)
++errflg;
else {
++dflg;
debug = TRUE;
warn = TRUE;
action = TRUE;
}
break;
case 'p': /* override default link path */
if (pflg)
++errflg;
else {
++pflg;
(void) strcpy(linkpath, optarg);
}
break;
case 'u': /* override default link directory */
if (uflg)
++errflg;
else {
++uflg;
(void) strcpy(linkdir, optarg);
}
if (!pflg) /* by default, linkpath same as linkdir */
(void) strcpy(linkpath, optarg);
break;
case 'w': /* warn */
if (wflg)
++errflg;
else {
++wflg;
warn = TRUE;
}
break;
case '?': /* unknown option */
++errflg;
break;
}
if (errflg) {
(void) fprintf(stderr, "usage: %s %s\n", *argv, usage);
exit (FAILED);
}
}
return (optind);
}
/*
* Read the password file filling in all three members of the unserinfo
* structure for each user. Returns FAILED if the array isn't large
* enough to hold all of the entries from the password file.
*
* NOTE: This routine uses getpwent() which may behave differently
* on different systems.
*/
readpw()
{
struct passwd *pwentry;
struct userinfo *puser;
int qsort();
char linkhomedir[MAXHDLEN]; /* If HD is a link, this is its name */
debug && printf("\n+++++ FETCHING PASSWORD ENTRIES +++++\n\n");
puser = user;
while ((pwentry = getpwent()) != NULL) {
/* We only use the FIRST entry returned if getpwent() returns
more than one entry per login name */
if (find(pwentry->pw_name) != NULL) {
warn && printf("Multiple password entries for %s\n",
pwentry->pw_name);
continue;
}
/* YP password entries have probably been scrutinized by YP
* for errors, but local files can contain typing errors. */
if (checkpwent(pwentry) != 0)
continue;
if ((puser = addtabent(pwentry->pw_name)) == NULL)
return (FATAL);
(void) strncpy(linkhomedir, linkpath, sizeof(linkhomedir));
(void) strncat(linkhomedir, "/", sizeof(linkhomedir));
(void) strncat(linkhomedir, pwentry->pw_name, sizeof(linkhomedir));
if (strncmp(linkhomedir, pwentry->pw_dir, strlen(linkhomedir)) == 0 )
puser->status = HD_IS_LINK; /* HD is a sym. link */
else {
puser->status = HD_IS_DIR; /* HD is a directory */
(void) strcpy(puser->homedir, pwentry->pw_dir);
}
/* Until we find out differently, need to add a u-dir entry */
puser->action = ADD;
debug && printf("user=(%s) HD=(%s) status=(%s)\n",
puser->name, puser->homedir, cstat(puser->status));
}
debug && printf("FOUND %d USERS IN PASSWORD FILE\n", nusers);
endsort = nusers;
qsort((char *)user, endsort, sizeof(struct userinfo), namecmp);
return (SUCCEEDED);
}
/* Look at the entries in a directory. If a match is found with a user
* name, update the status flag, and possibly the homedir field. Warn
* if there is more than one directory for a single user. An empty
* directory indicates that there is a fatal error, such as a mount
* point with no file system mounted. A special error code (FATAL)
* indicates en empty directory.
*/
lookdir (direct)
char *direct;
{
int rc = SUCCEEDED;
int empty; /* flag indicating an empty dir. */
char compdirname[MAXHDLEN]; /* complete directory name */
struct userinfo *where;
DIR *dirp;
DIR *opendir();
DIRENTRY *readdir();
DIRENTRY *dp;
debug && printf("\n+++++ READING ARGUMENT DIRECTORIES +++++\n\n");
warn && printf("\n%s:\n", direct);
if ((dirp = opendir(direct)) == NULL) {
(void) fprintf(stderr, "Cannot open %s\n", direct);
return (FATAL);
}
(void) strcat(direct, "/");
empty = TRUE;
for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..") ||
!strcmp(dp->d_name, "quotas") ||
!strcmp(dp->d_name, "lost+found"))
continue;
empty = FALSE;
(void) strcpy(compdirname, direct);
(void) strcat(compdirname, dp->d_name);
/*
* There is no guarantee that the entry is a directory.
* Using stat to obtain the file type has some potential
* pitfalls so we choose to allow a users home directory
* to become a plain file. Hopefully the user already has a
* home directory and a conflict will be generated.
*
* Look for the directory name in the user table, so that the
* status flag can be upgraded to indicate an existing directory.
*/
if ((where = search(dp->d_name)) == NULL) {
warn && printf("No such user: %s\n", dp->d_name);
continue;
}
/* A dir was already found */
if (where->status & DIR_FOUND) {
where->status |= HD_CNFLCT;
(void) strcpy(where->conflictdir, (strcmp(where->conflictdir, where->homedir) == 0) ?
compdirname : where->conflictdir);
warn && printf("Conflict. %s: %s %s\n",
where->name, where->homedir,
where->conflictdir);
/* Conflict is an error if passwd HD is /u/user
* but complain about errors later */
} else {
/* do not update home directory if the password
* home directory is a real directory
*/
/* save only the FIRST directory found
* if the paswd entry is a link */
if (where->status == HD_IS_LINK)
(void) strcpy(where->homedir, compdirname);
(void) strcpy(where->conflictdir, compdirname);
where->status |= DIR_FOUND;
}
debug && printf("user=(%s) HD=(%s) status=(%s)\n",
where->name, where->homedir, cstat(where->status));
}
if (empty) {
(void) fprintf(stderr, "Quitting: empty directory encountered: %s\n", direct);
rc = FATAL;
}
(void) closedir(dirp);
return (rc);
}
/*
* Read symbolic link directory. During this process remove or update
* the symbolic link depending on the status flag. If a symbolic link
* is found for any user, the user's status flag is upgraded to DONE.
*/
readu ()
{
int nchars;
int rc = SUCCEEDED;
char upath[MAXLINKLEN], link[MAXLINKLEN];
DIR *opendir();
DIR *dirp;
DIRENTRY *readdir();
DIRENTRY *dp;
struct stat buf;
struct userinfo *where;
int lstat();
int readlink();
debug && printf("\n+++++ READING AND PROCESSING %s +++++\n\n", linkdir);
if ((dirp = opendir(linkdir)) == NULL) {
perror(program);
return (FATAL);
}
for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
continue;
(void) strcpy(upath, linkdir);
(void) strcat(upath, dp->d_name);
/* Do not stat the entry. If it is a directory then
* allow the calls to unlink and symlink to fail.
*
* Look in the user table for an entry.
*/
where = search(dp->d_name);
if (where == NULL) { /* No user exists, rm the link */
warn && printf("No such user: %s%s\n",
linkdir, dp->d_name);
if ((where = addtabent(dp->d_name)) == NULL)
return(FAILED);
else
where->action = REMOVE;
continue;
}
where->action = NONE; /* Found entry, assume it's OK */
/* Make sure entry is a link before we read its value */
if (lstat(upath, &buf) != 0) {
perror(program);
continue;
}
if ((buf.st_mode & S_IFMT) != S_IFLNK) {
warn && printf("%s is not a symbolic link\n", upath);
where->action = REMOVE;
continue;
}
if ((nchars = readlink(upath, link, (int)(sizeof(link)-1))) == 0) {
perror(program);
continue;
}
link[nchars] = '\0';
/* Update link if the link should point elsewhere AND...
* ... if the passwd entry is a link, there is no conflict */
if (strcmp(link, where->homedir) != 0 &&
(!(where->status & HD_IS_LINK) ||
(where->status & HD_IS_LINK && !(where->status & HD_CNFLCT)))) {
debug && printf("---> U-DIR ENTRY BEING UPDATED (%s) -> (%s)\n",
where->homedir, upath);
where->action = UPDATE;
}
}
(void) closedir(dirp);
return (rc);
}
/* Go through table and process entries which are not DONE. The only
* action left to take is to add entries to the sym link directory,
* if the home directory is not a reference to the symbolic link
* we are trying to create.
*/
actions()
{
struct userinfo *userp;
char upath[MAXLINKLEN];
int rc = SUCCEEDED;
debug && printf("\n+++++ CLEANING UP +++++\n\n", linkdir);
warn && printf("\n\n");
for (userp = user; (userp->name)[0] != '\0'; userp++) {
(void) strcpy(upath, linkdir);
(void) strcat(upath, userp->name);
if ((userp->status & (HD_IS_LINK | HD_CNFLCT)) == (HD_IS_LINK | HD_CNFLCT))
(void) fprintf(stderr, "Ambiguity: %s -> %s, %s\n",
upath, userp->homedir, userp->conflictdir);
switch (userp->action) {
case NONE:
break;
case REMOVE:
if (rm_sl(upath) == FAILED)
rc = FAILED;
break;
case UPDATE:
/* If this equality is true, then we didn't find a
* directory on any filesystem for this user */
if (userp->status == HD_IS_LINK) {
(void) fprintf(stderr, "Attempt to update nonexistent link for %s.\n",
userp->name);
rc = FAILED;
break;
}
if (update_sl(upath, userp->homedir) != SUCCEEDED)
rc = FAILED;
break;
case ADD:
/* If this equality is true, then we didn't find a
* directory on any filesystem for this user */
if (userp->status == HD_IS_LINK) {
(void) fprintf(stderr, "Attempt to add nonexistent link for %s.\n",
userp->name);
rc = FAILED;
} else {
/* Directory may be nonexistent, but DON'T stat it */
debug && printf("---> ADDING user=(%s) HD=(%s) status=(%s)\n",
userp->name, userp->homedir, cstat(userp->status));
if (add_sl(upath, userp->homedir) != SUCCEEDED)
rc = FAILED;
/* If there was more than one directory, we should tell
* someone about it. */
if ((userp->status & (HD_IS_LINK | HD_CNFLCT)) == (HD_IS_LINK | HD_CNFLCT))
(void) fprintf(stderr, "Arbitrary home directory for %s.\n", userp->name);
}
break;
default:
break;
}
debug && printf("user=(%s) HD=(%s) status=(%s)\n",
userp->name, userp->homedir, cstat(userp->status));
}
return (rc);
}
/*
* Given a user name and a home directory string, create a symbolic
* link in the link directory. Returns FAILED if the link could
* not be created.
*/
add_sl (link, homedir)
char *link;
char *homedir;
{
int rc = SUCCEEDED;
int symlink();
action && printf("Creating %s -> %s\n", link, homedir);
if (doit && symlink(homedir, link) != 0) {
(void) fprintf(stderr, "%s: add_sl: ", program);
perror((char *)NULL);
rc = FAILED;
}
return rc;
}
/*
* Given a user name, remove a symbolic link in the link directory.
* Returns FAILED if the link could not be removed.
*/
rm_sl (link)
char *link;
{
int rc = SUCCEEDED;
int unlink();
action && printf("Removing %s\n", link);
if (doit && unlink(link) != 0) {
(void) fprintf(stderr, "%s: rm_sl: ", program);
perror((char *)NULL);
rc = FAILED;
}
return rc;
}
/*
* Given a user name and a home directory string, update the symbolic
* link in the link directory. Returns FAILED if the link could
* not be updated.
*/
update_sl (link, homedir)
char *link;
char *homedir;
{
int rc = SUCCEEDED;
int symlink();
int unlink();
action && printf("Updating %s -> %s\n", link, homedir);
if (doit && (unlink(link) != 0 || symlink(homedir, link) != 0)) {
(void) fprintf(stderr, "%s: update_sl: ", program);
perror((char *)NULL);
rc = FAILED;
}
return rc;
}
/*
* Compare names in the userinfo structure.
*/
namecmp(struct1, struct2)
struct userinfo *struct1, *struct2;
{
return (strcmp (struct1->name, struct2->name));
}
/*
* Given a user name, reserve a table entry. Puts name in member field
* and zeros/nulls the remaining fields. Returns a pointer to the
* added structure entry, or NULL if there was no space available.
*/
struct userinfo *
addtabent(pw_name)
char *pw_name;
{
static struct userinfo *nextentry = user;
if (nextentry > &user[MAXUSERS]) {
(void) fprintf(stderr, "Not enough space allocated for more than %d users\n",
MAXUSERS);
return(NULL);
}
(void) strcpy(nextentry->name, pw_name);
nusers++;
return(nextentry++);
}
/*
* Linear search for "name" in the name member of the array users. Returns
* address in name string within the struct array if found, otherwise returns
* NULL if not found.
*/
struct userinfo *
find (name)
char *name;
{
int i;
for (i = 0; i < nusers; i++) {
if (strcmp(name, user[i].name) == 0)
return (&user[i]);
}
return (NULL);
}
/*
* Binary search for "name" in the name member of the array users. Returns
* address in name string within the struct array if found, otherwise returns
* NULL if not found.
*/
struct userinfo *
search (name)
char *name;
{
int low, mid, high;
int comp;
low = 0;
/* high = nusers - 1;
*/
high = endsort - 1;
while (low <= high) {
mid = (low + high) / 2;
comp = strcmp(name, user[mid].name);
if (comp < 0)
high = mid - 1;
else if (comp > 0)
low = mid + 1;
else
return (&user[mid]);
}
return (NULL);
}
/*
* Convert the status flag into a meaningful string. Returns pointer to
* a string.
*/
char *
cstat(status)
int status;
{
static char statstring[128];
statstring[0] = '\0';
if (status & HD_IS_LINK)
(void) strcat(statstring, "HD_IS_LINK ");
if (status & HD_IS_DIR)
(void) strcat(statstring, "HD_IS_DIR ");
if (status & DIR_FOUND)
(void) strcat(statstring, "DIR_FOUND ");
if (status & HD_CNFLCT)
(void) strcat(statstring, "HD_CNFLCT ");
return(statstring);
}
/*
* Check the password entry for a valid loginid and a
* non-null home directory.
*/
checkpwent(pwent)
struct passwd *pwent;
{
/* Sun's getpwent ignores a null login-id, but check anyway */
if (*pwent->pw_name == '\0' || *pwent->pw_dir == '\0') {
(void) fprintf(stderr, "Problems with password entry: user=(%s), HD=(%s)\n",
pwent->pw_name, pwent->pw_dir);
return(1);
}
return(0);
}