Subject: Re: PROPOSAL: making passwd pluggable (sort of)
To: None <tech-userlevel@netbsd.org>
From: Aidan Cully <aidan@kublai.com>
List: tech-security
Date: 01/30/2000 21:56:14
  by redmail.netbsd.org with SMTP; 31 Jan 2000 02:56:16 -0000
	id AA8D62537; Sun, 30 Jan 2000 21:56:14 -0500 (EST)
Date: Sun, 30 Jan 2000 21:56:14 -0500
From: Aidan Cully <aidan@kublai.com>
To: tech-userlevel@netbsd.org
Cc: current-users@netbsd.org, tech-security@netbsd.org
Subject: Re: PROPOSAL: making passwd pluggable (sort of)
Message-ID: <20000130215614.A9771@ozymandias.kublai.com>
References: <20000130122641.A8134@xanadu.kublai.com>
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary="MGYHOYXEY6WxJCY8"
User-Agent: Mutt/1.1.1i
In-Reply-To: <20000130122641.A8134@xanadu.kublai.com>; from aidan@kublai.com on Sun, Jan 30, 2000 at 12:26:41PM -0500


--MGYHOYXEY6WxJCY8
Content-Type: text/plain; charset=us-ascii

On Sun, Jan 30, 2000 at 12:26:41PM -0500, Aidan Cully wrote:
> I'll try to have something next weekend, if no one objects.

Next weekend was overly unambitious.  Note that I've barely tested this,
and I don't plan on committing it until there's been time for discussion,
but it implements everything I said it would in my proposal (except the
utility function which is getpass()), and a bag of crisps.

Notes:
	* I don't include patches to domestic source, for obvious reasons,
	and because I haven't done them yet. :-)
	* I can't test YP, but since it and local are the only pw_methods
	I've modified in this patch, that means that some features of the
	patch didn't get any kind of a shake at all.  The continuation
	logic, in particular (which is supposed to make up for the fact
	that yp_passwd can no longer call local_passwd, and that -k means
	both krb4 and krb5) is an important feature that hasn't been
	tested.
	* It's now possible (unlike in the proposal) for password modules
	to modify their acceptable argc/argv arguments and usage strings.
	(yppasswd apparently can't take a -y argument.)
	* I check against two modules using the same command line argument,
	one taking an optarg and one not.  It should never happen though,
	so I flag a violent warning and exit immediately.  Maybe I'm being
	too rash?

I also grew the source files by a fair amount, but I'd like to think that
for all that, the control flow is much more logical, now, and that the
bloat comes in places that we will (hopefully) not need to modify, later.

Comments?
--aidan

--MGYHOYXEY6WxJCY8
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="passwd.patch"

--- ../passwd.orig/extern.h	Tue Jan 25 20:18:48 2000
+++ extern.h	Sun Jan 30 18:44:37 2000
@@ -35,10 +35,39 @@
  *	@(#)extern.h	8.1 (Berkeley) 4/2/94
  */
 
-int	kadm_passwd __P((char *, char *, char *, char *));
-int	kadm5_passwd __P((char *));
-int	krb_check __P((void));
-int	krb_passwd __P((void));
-int	local_passwd __P((char *));
-void	to64 __P((char *, long, int));
-int	yp_passwd __P((char *));
+/* return values from pw_init() and pw_arg_end() */
+enum {
+	PW_USE_FORCE,
+	PW_USE,
+	PW_DONT_USE
+};
+
+void to64(char *, long, int);
+
+#ifdef KERBEROS5
+int	krb5_init __P((const char *, const char **, const char **));
+int	krb5_arg __P((char, const char *));
+int	krb5_arg_end __P((void));
+void	krb5_end __P((void));
+int	krb5_chpw __P((const char *));
+#endif
+#ifdef KERBEROS
+int	krb4_init __P((const char *, const char **, const char **));
+int	krb4_arg __P((char, const char *));
+int	krb4_arg_end __P((void));
+void	krb4_end __P((void));
+int	krb4_chpw __P((const char *));
+#endif
+#ifdef YP
+int	yp_init __P((const char *, const char **, const char **));
+int	yp_arg __P((char, const char *));
+int	yp_arg_end __P((void));
+void	yp_end __P((void));
+int	yp_chpw __P((const char *));
+#endif
+/* local */
+int	local_init __P((const char *, const char **, const char **));
+int	local_arg __P((char, const char *));
+int	local_arg_end __P((void));
+void	local_end __P((void));
+int	local_chpw __P((const char *));
--- ../passwd.orig/local_passwd.c	Sat Jan 22 14:46:58 2000
+++ local_passwd.c	Sun Jan 30 18:44:59 2000
@@ -62,6 +62,7 @@
 static	char   *getnewpasswd __P((struct passwd *, int));
 
 static uid_t uid;
+static int force_local;
 
 char *tempname;
 
@@ -139,8 +140,44 @@
 }
 
 int
-local_passwd(uname)
-	char *uname;
+local_init(progname, argptr, usageptr)
+	const char *progname;
+	const char **argptr, **usageptr;
+{
+	force_local = 0;
+	return (PW_USE);
+}
+
+int
+local_arg(char arg, const char *optarg)
+{
+	switch (arg) {
+	case 'l':
+		force_local = 1;
+		break;
+	default:
+		return(0);
+	}
+	return(1);
+}
+
+int
+local_arg_end()
+{
+	if (force_local)
+		return(PW_USE_FORCE);
+	return(PW_USE);
+}
+
+void
+local_end()
+{
+	/* NOOP */
+}
+
+int
+local_chpw(uname)
+	const char *uname;
 {
 	struct passwd *pw;
 	struct passwd old_pw;
--- ../passwd.orig/passwd.c	Tue Jan 25 20:18:48 2000
+++ passwd.c	Sun Jan 30 20:58:44 2000
@@ -53,28 +53,47 @@
 #include <unistd.h>
 
 #include "extern.h"
+
+static struct pw_module_s {
+	const char *name;
+	const char *args;
+	const char *usage;
+	int (*pw_init) __P((const char *, const char **, const char **));
+	int (*pw_arg) __P((char, const char *));
+	int (*pw_arg_end) __P((void));
+	void (*pw_end) __P((void));
+
+	int (*pw_chpw) __P((const char*));
+	int invalid;
+#define	INIT_INVALID 1
+#define ARG_INVALID 2
+	int use_class;
+} pw_modules[] = {
+#ifdef KERBEROS5
+	{ "Kerberos5", "5ku:", "[-5] [-k] [-u principal]",
+	    krb5_init, krb5_arg, krb5_arg_end, krb5_end, krb5_chpw, 0, 0 },
+#endif
+#ifdef KERBEROS
+	{ "Kerberos4", "4ku:i:r:", "[-4] [-k] [-u user] [-i instance] [-r realm]",
+	    krb4_init, krb4_arg, krb4_arg_end, krb4_end, krb4_chpw, 0, 0 },
+#endif
+#ifdef YP
+	{ "YP", "y", "[-y]",
+	    yp_init, yp_arg, yp_arg_end, yp_end, yp_chpw, 0, 0 },
+#endif
+	/* local */
+	{ "local", "l", "[-l]",
+	    local_init, local_arg, local_arg_end, local_end, local_chpw, 0, 0 },
+	/* terminator */
+	{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
+};
  
 void	usage __P((void)); 
 
-/*
- * Note on configuration:
- *      Generally one would not use both Kerberos and YP
- *      to maintain passwords.
- */
-
-int use_kerberos;
-int use_yp;
-int yppwd;
-int yflag;
-
 extern	char *__progname;		/* from crt0.o */
 
 int	main __P((int, char **));
 
-#ifdef YP
-extern int _yp_check __P((char **));	/* buried deep inside libc */
-#endif
-
 int
 main(argc, argv)
 	int argc;
@@ -83,88 +102,103 @@
 	extern int optind;
 	int ch;
 	char *username;
-#if defined(KERBEROS)
-	char *iflag = 0, *rflag = 0;
-#endif
-#if defined(KERBEROS) || defined(KERBEROS5)
-	char *uflag = 0;
-#endif
-
-#if defined(KERBEROS) || defined(KERBEROS5)
-	if (strcmp(__progname, "kpasswd") == 0)
-		use_kerberos = 1;
-	else
-		use_kerberos = krb_check();
-#endif
-#ifdef	YP
-	use_yp = _yp_check(NULL);
-#endif
+	char optstring[64];  /* if we ever get more than 64 args, shoot me. */
+	const char *curopt, *optopt;
+	int i, j;
+	int valid;
+	int use_always;
+
+	/* allow passwd modules to do argv[0] specific processing */
+	use_always = -1;
+	for (i = 0; pw_modules[i].name != (char *) 0; i++) {
+		pw_modules[i].invalid = 1;
+		valid = (*pw_modules[i].pw_init)(__progname,
+		    &pw_modules[i].args, &pw_modules[i].usage);
+
+		if (valid == PW_USE_FORCE && use_always == -1) {
+			/* only PW_USE_FORCE modules can run. */
+			use_always = i;
+			for (j = 0; j < i; j++)
+				pw_modules[j].invalid |= INIT_INVALID;
+		} else if (valid == PW_USE_FORCE)
+			pw_modules[i].invalid &= ~INIT_INVALID;
+		else if (valid == PW_USE && use_always == -1)
+			pw_modules[i].invalid &= ~INIT_INVALID;
+	}
 
-	if (strcmp(__progname, "yppasswd") == 0) {
-#ifdef YP
-		if (!use_yp)
-			errx(1, "YP not in use.");
-		use_kerberos = 0;
-		yppwd = 1;
-#else
-		errx(1, "YP support not compiled in.");
-#endif
+	/* Build the option string from the individual modules' option
+	 * strings.  Note that two modules can share a single option
+	 * letter. */
+	optstring[0] = '\0';
+	j = 0;
+	for (i = 0; pw_modules[i].name != (char *) 0; i++) {
+		if (pw_modules[i].invalid)
+			continue;
+
+		curopt = pw_modules[i].args;
+		while (*curopt != '\0') {
+			if ((optopt = strchr(optstring, *curopt)) == (char *) 0) {
+				optstring[j++] = *curopt;
+				if (curopt[1] == ':') {
+					curopt++;
+					optstring[j++] = *curopt;
+				}
+				optstring[j] = '\0';
+			} else if ((optopt[1] == ':' && curopt[1] != ':') ||
+			    (optopt[1] != ':' && curopt[1] == ':')) {
+				errx(1, "NetBSD ERROR!  Different password "
+				    "modules have two different ideas about "
+				    "%c argument format.", curopt[0]);
+			}
+			curopt++;
+		}
 	}
 
-	while ((ch = getopt(argc, argv, "lkyi:r:u:")) != -1)
-		switch (ch) {
-		case 'l':		/* change local password file */
-			if (yppwd)
-				usage();
-			use_kerberos = 0;
-			use_yp = 0;
-			break;
-#ifdef KERBEROS
-		case 'i':
-			iflag = optarg;
-			break;
-		case 'r':
-			rflag = optarg;
-			break;
-#endif
-#if defined(KERBEROS) || defined(KERBEROS5)
-		case 'u':
-			uflag = optarg;
-			break;	
-#endif
-		case 'k':		/* change Kerberos password */
-#if defined(KERBEROS) || defined(KERBEROS5)
-			if (yppwd)
-				usage();
-			use_kerberos = 1;
-			use_yp = 0;
-			break;
-#endif
-#ifndef KERBEROS
-		case 'i':
-		case 'r':
-			errx(1, "Kerberos4 support not compiled in.");
-#endif
-#if !defined(KERBEROS) && !defined(KERBEROS5)
-		case 'u':
-			errx(1, "Kerberos support not compiled in.");
-#endif
-		case 'y':		/* change YP password */
-#ifdef	YP
-			if (yppwd)
-				usage();
-			if (!use_yp)
-				errx(1, "YP not in use.");
-			use_kerberos = 0;
-			yflag = 1;
-			break;
-#else
-			errx(1, "YP support not compiled in.");
-#endif
-		default:
+	while ((ch = getopt(argc, argv, optstring)) != -1)
+	{
+		valid = 0;
+		for (i = 0; pw_modules[i].name != (char *) 0; i++) {
+			if (pw_modules[i].invalid)
+				continue;
+			if ((optopt = strchr(pw_modules[i].args, ch)) != (char *) 0) {
+				j = (optopt[1] == ':') ?
+				    ! (*pw_modules[i].pw_arg)(ch, optarg) :
+				    ! (*pw_modules[i].pw_arg)(ch, (char *) 0);
+				if (j != 0)
+					pw_modules[i].invalid |= ARG_INVALID;
+				if (pw_modules[i].invalid)
+					(*pw_modules[i].pw_end)();
+			} else {
+				/* arg doesn't match this module */
+				pw_modules[i].invalid |= ARG_INVALID;
+				(*pw_modules[i].pw_end)();
+			}
+			if (! pw_modules[i].invalid)
+				valid = 1;
+		}
+		if (! valid) {
 			usage();
+			exit(1);
+		}
+	}
+
+	/* select which module to use to actually change the password. */
+	use_always = 0;
+	valid = 0;
+	for (i = 0; pw_modules[i].name != (char *) 0; i++)
+		if (! pw_modules[i].invalid) {
+			pw_modules[i].use_class = (*pw_modules[i].pw_arg_end)();
+			if (pw_modules[i].use_class != PW_DONT_USE)
+				valid = 1;
+			if (pw_modules[i].use_class == PW_USE_FORCE)
+				use_always = 1;
 		}
 
+
+	if (! valid)
+		/* hang the DJ */
+		errx(1, "No valid password module specified.");
+
 	argc -= optind;
 	argv += optind;
 
@@ -176,15 +210,6 @@
 	case 0:
 		break;
 	case 1:
-#ifdef KERBEROS5
-		if (use_kerberos && strcmp(argv[0], username)) {
-			errx(1, "%s\n\t%s\n%s\n",
-			     "to change another user's Kerberos password, do",
-			     "\"kinit <user>; passwd; kdestroy\";",
-			     "to change a user's local passwd, use\
-			     \"passwd -l <user>\"");
-		}
-#endif
 		username = argv[0];
 		break;
 	default:
@@ -192,30 +217,29 @@
 		exit(1);
 	}
 
-#if defined(KERBEROS5)
-	if (use_kerberos)
-		exit(kadm5_passwd(username));
-#elif defined(KERBEROS)
-	if (uflag && (iflag || rflag))
-		errx(1, "-u cannot be used with -r or -i");
-
-	if (use_kerberos)
-		exit(kadm_passwd(username, iflag, rflag, uflag));
-#endif
-#ifdef	YP
-	if (use_yp)
-		exit(yp_passwd(username));
-#endif
-	exit(local_passwd(username));
+	/* allow for fallback to other chpw() methods. */
+	for (i = 0; pw_modules[i].name != (char *) 0; i++) {
+		if ((use_always && pw_modules[i].use_class == PW_USE_FORCE) ||
+		    (!use_always && pw_modules[i].use_class == PW_USE)) {
+			valid = (*pw_modules[i].pw_chpw)(username);
+			(*pw_modules[i].pw_end)();
+			if (valid >= 0)
+				exit(valid);
+			/* return value < 0 indicates continuation. */
+		}
+	}
+	exit(1);
 }
 
 void
 usage()
 {
+	int i;
 
-	if (yppwd)
-		fprintf(stderr, "usage: %s user\n", __progname);
-	else
-		fprintf(stderr, "usage: %s [-l] [-k] [-y] [-i instance] [-r realm] [-u fullname] user\n", __progname);
+	fprintf(stderr, "usage:\n");
+	for (i = 0; pw_modules[i].name != (char *) 0; i++)
+		if (! (pw_modules[i].invalid & INIT_INVALID))
+			fprintf(stderr, "\t%s %s [user]\n", __progname,
+			    pw_modules[i].usage);
 	exit(1);
 }
--- ../passwd.orig/yp_passwd.c	Sun Dec 26 15:27:02 1999
+++ yp_passwd.c	Sun Jan 30 21:48:24 2000
@@ -71,12 +71,13 @@
 
 extern	char *__progname;		/* from crt0.o */
 
-extern	int yflag, yppwd;
+static	int yflag, yppwd;
+static	char yppwdargs[] = "";
+static	char yppwdusage[] = "";
 
 static	char		*getnewpasswd __P((struct passwd *, char **));
-static	int		 ypgetpwnam __P((char *));
+static	int		 ypgetpwnam __P((const char *));
 static	void		 pw_error __P((char *, int, int));
-static	void		 test_local __P((char *));
 
 static uid_t uid;
 char *domain;
@@ -92,25 +93,59 @@
 	errx(eval, "YP passwd database unchanged");
 }
 
-static void
-test_local(username)
-	char *username;
+int
+yp_init(progname, optstr, usage)
+	const char *progname;
+	const char **optstr, **usage;
 {
+	if (strcmp(progname, "yppasswd") == 0) {
+		*optstr = yppwdargs;
+		*usage = yppwdusage;
+		yppwd = 1;
+	} else
+		yppwd = 0;
+	yflag = 0;
+	if (_yp_check(NULL) == 0) {
+		/* can't use YP. */
+		if (yppwd)
+			errx(1, "YP not in use.");
+		return(PW_DONT_USE);
+	}
+	return (yppwd ? PW_USE_FORCE : PW_USE);
+}
 
-	/*
-	 * Something failed recoverably stating that the YP system couldn't
-	 * find this user.  Look for a local passwd entry, and change that
-	 * if and only if we weren't run as yppasswd or with the -y option.
-	 * This function does not return if a local entry is found.
-	 */
-	if (yppwd == 0 && yflag == 0)
-		if ((getpwnam(username) != NULL) && !local_passwd(username))
-			exit(0);
+int
+yp_arg(ch, arg)
+	char ch;
+	const char *arg;
+{
+	switch (ch) {
+	case 'y':
+		yflag = 1;
+		break;
+	default:
+		return(0);
+	}
+	return(1);
+}
+
+int
+yp_arg_end()
+{
+	if (yflag || yppwd)
+		return (PW_USE_FORCE);
+	return (PW_USE);
+}
+
+void
+yp_end()
+{
+	/* NOOP */
 }
 
 int
-yp_passwd(username)
-	char *username;
+yp_chpw(username)
+	const char *username;
 {
 	char *master;
 	int r, rpcport, status;
@@ -124,7 +159,7 @@
 	/*
 	 * Get local domain
 	 */
-	if ((r = yp_get_default_domain(&domain)) != NULL)
+	if ((r = yp_get_default_domain(&domain)) != 0)
 		errx(1, "can't get local YP domain.  Reason: %s",
 		    yperr_string(r));
 
@@ -133,9 +168,10 @@
 	 * the daemon.
 	 */
 	if ((r = yp_master(domain, "passwd.byname", &master)) != 0) {
-		test_local(username);
-		errx(1, "can't find the master YP server.  Reason: %s",
+		warnx("can't find the master YP server.  Reason: %s",
 		    yperr_string(r));
+		/* continuation */
+		return(-1);
 	}
 
 	/*
@@ -143,9 +179,10 @@
 	 */
 	if ((rpcport = getrpcport(master, YPPASSWDPROG,
 	    YPPASSWDPROC_UPDATE, IPPROTO_UDP)) == 0) {
-		test_local(username);
-		errx(1, "master YP server not running yppasswd daemon.\n\t%s\n",
-		    "Can't change password.");
+		warnx("master YP server not running yppasswd daemon.\n\t%s\n",
+		    "Can't change YP password.");
+		/* continuation */
+		return(-1);
 	}
 
 	/*
@@ -158,8 +195,9 @@
 	/* then get user's login identity */
 	if (!ypgetpwnam(username) ||
 	    !(pw = getpwnam(username))) {
-		test_local(username);
-		errx(1, "unknown user %s", username);
+		warnx("YP unknown user %s", username);
+		/* continuation */
+		return(-1);
 	}
 
 	if (uid && uid != pw->pw_uid)
@@ -196,7 +234,7 @@
 	else
 		printf("The YP password has been changed on %s, %s\n",
 		    master, "the master YP passwd server.");
-	exit(0);
+	return(0);
 }
 
 static char *
@@ -263,7 +301,7 @@
 
 static int
 ypgetpwnam(nam)
-	char *nam;
+	const char *nam;
 {
 	char *val;
 	int reason, vallen;

--MGYHOYXEY6WxJCY8--