summaryrefslogtreecommitdiff
path: root/rpc.yppasswdd/update.c
diff options
context:
space:
mode:
Diffstat (limited to 'rpc.yppasswdd/update.c')
-rw-r--r--rpc.yppasswdd/update.c1072
1 files changed, 1072 insertions, 0 deletions
diff --git a/rpc.yppasswdd/update.c b/rpc.yppasswdd/update.c
new file mode 100644
index 0000000..4c656c5
--- /dev/null
+++ b/rpc.yppasswdd/update.c
@@ -0,0 +1,1072 @@
+/* Copyright (c) 1999, 2000, 2001, 2005, 2006, 2010, 2011, 2012, 2014, 2015, 2016 Thorsten Kukuk
+ Author: Thorsten Kukuk <kukuk@suse.de>
+
+ The YP Server is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ version 2 as published by the Free Software Foundation.
+
+ The YP Server is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with the YP Server; see the file COPYING. If
+ not, write to the Free Software Foundation, Inc., 51 Franklin Street,
+ Suite 500, Boston, MA 02110-1335, USA. */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <alloca.h>
+#include <stdlib.h>
+#include <crypt.h>
+#include <shadow.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <rpc/rpc.h>
+#include <rpcsvc/yp_prot.h>
+#define passwd xpasswd
+#include <rpcsvc/yppasswd.h>
+#undef passwd
+#include <paths.h>
+#include <pwd.h>
+#include "yppwd_local.h"
+#include "log_msg.h"
+
+#ifndef CHECKROOT
+/* Set to 0 if you don't want to check against the root password
+ of the NIS master server. */
+#define CHECKROOT 1
+#endif
+
+#ifndef _PATH_PASSWD
+#define _PATH_PASSWD "/etc/passwd"
+#endif
+#ifndef _PATH_SHADOW
+#define _PATH_SHADOW "/etc/shadow"
+#endif
+#ifndef _PATH_SHELLS
+#define _PATH_SHELLS "/etc/shells"
+#endif
+
+/* How often to retry locking the passwd file... */
+#define MAX_RETRIES 5
+
+char *path_passwd = _PATH_PASSWD;
+char *path_passwd_tmp = NULL;
+char *path_passwd_old = NULL;
+char *path_shadow = _PATH_SHADOW;
+char *path_shadow_tmp = NULL;
+char *path_shadow_old = NULL;
+
+/* Will be set by the main function */
+char *external_update_program = NULL;
+
+static bool_t adjuct_used = FALSE;
+
+static int external_update_env (yppasswd *yppw);
+static int external_update_pipe (yppasswd *yppw);
+static int update_files (yppasswd *yppw, int *shadow_changed,
+ int *passwd_changed, int *chfn, int *chsh);
+
+/* Argument validation. Avoid \n... (ouch).
+ We can't use isprint, because people may use 8bit chars which
+ aren't recognized as printable in the default locale. */
+static int
+validate_string (char *what, char *str)
+{
+ while (*str && *str != ':' && (unsigned char)*str >= 32)
+ ++str
+ ;
+ if (*str == '\0')
+ return 1;
+
+ log_msg ("Invalid characters in %s argument: \"%s\"", what, str);
+
+ return 0;
+}
+
+/* Check that nobody tries to change special NIS entries beginning
+ with +/- and that all chracters are allowed. */
+static inline int
+validate_args (struct xpasswd *pw)
+{
+ if (pw->pw_name[0] == '-' || pw->pw_name[0] == '+')
+ {
+ log_msg ("attempt to modify NIS passwd entry \"%s\"", pw->pw_name);
+ return 0;
+ }
+
+ return validate_string ("password", pw->pw_passwd)
+ && validate_string ("shell", pw->pw_shell)
+ && validate_string ("gecos", pw->pw_gecos);
+}
+
+static int
+shell_ok (char *shell)
+{
+ char buffer[1024];
+ FILE *fp;
+
+ if ((fp = fopen (_PATH_SHELLS, "r")) == NULL)
+ {
+ log_msg ("can't open %s", _PATH_SHELLS);
+ return 0;
+ }
+ while (fgets (buffer, sizeof (buffer), fp) != NULL)
+ {
+ buffer[sizeof (buffer) - 1] = '\0';
+ if (!strncmp (buffer, shell, strcspn (buffer, " \t\n")))
+ {
+ fclose (fp);
+ return 1;
+ }
+ }
+
+ fclose (fp);
+ return 0;
+}
+
+/* Read shadow file manually, to handle different colons count.
+ When we use passwd.adjunct, shadow file contains 6 colons, but if
+ we don't use passwd.adjunct, shadow file contains 8 colons.
+ This function can handle both counts, but fgetspent doesn't */
+static struct spwd *
+fgetspent_adjunct(FILE *fp)
+{
+ static char line_buffer[1024];
+ char *buffer_mark;
+ struct spwd* result;
+ int i, colons = 0;
+
+ /* Reserve two bytes for theoretic colons */
+ while (fgets(line_buffer, sizeof(line_buffer) - 2, fp) != NULL)
+ {
+ /* We don't need a new line character in the end */
+ if ((buffer_mark = strchr(line_buffer, '\n')) != NULL)
+ buffer_mark[0] = '\0';
+
+ /* Skip commented or empty lines */
+ if (line_buffer[0] == '\0' || line_buffer[0] == '#')
+ continue;
+
+ /* Count number of colons in the line */
+ for (i = 0; line_buffer[i] != '\0'; ++i)
+ if (line_buffer[i] == ':')
+ ++colons;
+
+ /* When we use passwd.adjunct, shadow file contains 6 colons,
+ but we need 8 colons to properly parse the line, so we
+ just add two colons to the end of the line */
+ if (colons == 6)
+ {
+ strcat(line_buffer, "::");
+ adjuct_used = TRUE;
+ }
+
+ /* Try to parse the line, if not success, read the next line */
+ if ((result = sgetspent(line_buffer)) != NULL)
+ return result;
+
+ }
+ return NULL;
+}
+
+/* Write an entry to the given stream.
+ When we use passwd.adjunct, shadow file contains 6 colons, but if
+ we don't use passwd.adjunct, shadow file contains 8 colons.
+ This function can handle both counts, but putspent doesn't */
+static int
+putspent_adjunct (const struct spwd *p, FILE *stream)
+{
+ int errors = 0;
+
+ if (!adjuct_used)
+ return putspent(p, stream);
+
+ flockfile (stream);
+
+ if (fprintf (stream, "%s:%s:::::", p->sp_namp, p->sp_pwdp ? p->sp_pwdp : "") < 0)
+ ++errors;
+
+ if (putc_unlocked ('\n', stream) == EOF)
+ ++errors;
+
+ funlockfile (stream);
+
+ return errors ? -1 : 0;
+}
+
+/* Check if the password the user supplied matches the old one */
+static int
+password_ok (char *plain, char *crypted, char *root)
+{
+ char *crypted_new;
+ if (crypted[0] == '\0')
+ return 1;
+ crypted_new = crypt (plain, crypted);
+ if (crypted_new == NULL)
+ {
+ log_msg ("crypt() call failed.");
+ return 0;
+ }
+ if (strcmp (crypted_new, crypted) == 0)
+ return 1;
+#if CHECKROOT
+ crypted_new = crypt (plain, root);
+ if (crypted_new == NULL)
+ {
+ log_msg ("crypt() call failed.");
+ return 0;
+ }
+ if (strcmp (crypted_new, root) == 0)
+ return 1;
+#endif
+
+ return 0;
+}
+
+static inline int
+is_allowed_to_change (const struct spwd *sp)
+{
+ long now;
+
+ if (sp->sp_lstchg == 0 || sp->sp_lstchg == -1)
+ return 1;
+
+ now = time ((time_t *) 0) / (24L*3600L);
+
+ if (sp->sp_min > sp->sp_max)
+ return 0; /* Minimum is bigger then maximum */
+ if (sp->sp_min > 0 && now <= (sp->sp_lstchg + sp->sp_min))
+ return 0; /* It is to early to change password */
+ if (sp->sp_inact >= 0 && sp->sp_max >= 0 &&
+ now >= (sp->sp_lstchg + sp->sp_max + sp->sp_inact))
+ return 0; /* It is to late to change password */
+
+ return 1;
+}
+
+/*********************************************************************
+ * The Update Handler *
+ *********************************************************************/
+
+int *
+yppasswdproc_pwupdate_1 (yppasswd *yppw, struct svc_req *rqstp)
+{
+ char namebuf6[INET6_ADDRSTRLEN];
+ int shadow_changed = 0, passwd_changed = 0, chsh = 0, chfn = 0;
+ int retries;
+ static int res; /* I hate static variables */
+ struct netconfig *nconf;
+ const struct netbuf *rqhost = svc_getrpccaller (rqstp->rq_xprt);
+
+ nconf = getnetconfigent (rqstp->rq_xprt->xp_netid);
+
+ /* Be careful here with the debug option. You can see the old
+ and new password in clear text !! */
+ if (debug_flag)
+ {
+ log_msg ("yppasswdproc_pwupdate(\"%s\") [From: %s port %d]",
+ yppw->newpw.pw_name,
+ taddr2ipstr (nconf, rqhost,
+ namebuf6, sizeof (namebuf6)),
+ taddr2port (nconf, rqhost));
+ log_msg ("\toldpass..: %s", yppw->oldpass);
+ log_msg ("\tpw_name..: %s", yppw->newpw.pw_name);
+ log_msg ("\tpw_passwd: %s", yppw->newpw.pw_passwd);
+ log_msg ("\tpw_gecos.: %s", yppw->newpw.pw_gecos);
+ log_msg ("\tpw_dir...: %s", yppw->newpw.pw_dir);
+ log_msg ("\tpw_shell.: %s", yppw->newpw.pw_shell);
+ }
+
+ res = 1; /* res = 1 means no success */
+
+ /* Check if somebody tries to make trouble with not allowed characters */
+ if (!validate_args (&yppw->newpw))
+ {
+ log_msg ("update %.12s (uid=%d) from host %s failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid,
+ taddr2ipstr (nconf, rqhost,
+ namebuf6, sizeof (namebuf6)));
+ freenetconfigent (nconf);
+ return &res;
+ }
+
+ /* ATTENTION: The external program needs to do the password checking! */
+ if (external_update_program)
+ {
+ struct passwd *pw;
+
+ if ((pw = getpwnam (yppw->newpw.pw_name)) == NULL)
+ {
+ log_msg ("user %s not found", yppw->newpw.pw_name);
+ freenetconfigent (nconf);
+ return &res;
+ }
+ /* Do we need to update the GECOS information and are we allowed
+ to do it ? */
+ chfn = (strcmp (pw->pw_gecos, yppw->newpw.pw_gecos) != 0);
+ if (chfn && !allow_chfn)
+ {
+ log_msg ("update %.12s (uid=%d) from host %s rejected",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid,
+ taddr2ipstr (nconf, rqhost,
+ namebuf6, sizeof (namebuf6)));
+ log_msg ("chfn not permitted");
+ freenetconfigent (nconf);
+ return &res;
+ }
+
+ /* Do we need to update the shell adn are we allowed to do it ? */
+ chsh = (strcmp (pw->pw_shell, yppw->newpw.pw_shell) != 0);
+ if (chsh)
+ {
+ if (!allow_chsh)
+ {
+ log_msg ("update %.12s (uid=%d) from host %s rejected",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid,
+ taddr2ipstr (nconf, rqhost,
+ namebuf6, sizeof (namebuf6)));
+ log_msg ("chsh not permitted");
+ freenetconfigent (nconf);
+ return &res;
+ }
+ if (!shell_ok (yppw->newpw.pw_shell))
+ {
+ log_msg ("update %.12s (uid=%d) from host %s rejected",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid,
+ taddr2ipstr (nconf, rqhost,
+ namebuf6, sizeof (namebuf6)));
+ log_msg ("invalid shell: %s", yppw->newpw.pw_shell);
+ freenetconfigent (nconf);
+ return &res;
+ }
+ }
+
+ if (x_flag)
+ {
+ res = external_update_pipe (yppw);
+ freenetconfigent (nconf);
+ return &res;
+ }
+ else
+ {
+ res = external_update_env (yppw);
+ if (res >= 2)
+ {
+ freenetconfigent (nconf);
+ return &res;
+ }
+ }
+ passwd_changed = 1; /* We don't know exactly what was changed. */
+ shadow_changed = 1; /* So build everything new. */
+ }
+ else
+ {
+ /* Lock the passwd file. We retry several times. */
+ retries = 0;
+ while (lckpwdf () && retries < MAX_RETRIES)
+ {
+ sleep (1);
+ ++retries;
+ }
+
+ if (retries == MAX_RETRIES)
+ {
+ log_msg ("update %.12s (uid=%d) from host %s failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid,
+ taddr2ipstr (nconf, rqhost,
+ namebuf6, sizeof (namebuf6)));
+ log_msg ("password file locked");
+ freenetconfigent (nconf);
+ return &res;
+ }
+
+ res = update_files (yppw, &shadow_changed, &passwd_changed,
+ &chfn, &chsh);
+
+ ulckpwdf ();
+ }
+
+ /* Fork off process to rebuild NIS passwd.* maps. */
+ if (res == 0)
+ /* The child (-E program) may exit(1), which means success, but
+ don't run pwupdate. Bad, we tell the user that there was an
+ error. Needs to be fixed later. */
+ {
+ int c;
+
+ if ((c = fork ()) < 0)
+ {
+ /* Do NOT restore old password file. Someone else may already
+ * be using the new one. */
+ log_msg ("update %.12s (uid=%d) from host %s failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid,
+ taddr2ipstr (nconf, rqhost,
+ namebuf6, sizeof (namebuf6)));
+ log_msg ("Couldn't fork map update process: %s", strerror (errno));
+ freenetconfigent (nconf);
+ return &res;
+ }
+
+ if (c == 0) /* We are the child */
+ {
+ if (shadow_changed)
+ execlp (MAP_UPDATE_PATH, MAP_UPDATE, "shadow", NULL);
+ else
+ execlp (MAP_UPDATE_PATH, MAP_UPDATE, "passwd", NULL);
+ log_msg ("Error: couldn't exec map update process: %s",
+ strerror (errno));
+ exit (1);
+ }
+
+ log_msg ("update %.12s (uid=%d) from host %s successful",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid,
+ taddr2ipstr (nconf, rqhost,
+ namebuf6, sizeof (namebuf6)));
+ if (chsh || chfn)
+ {
+ log_msg ("Shell %schanged (%s), GECOS %schanged (%s).",
+ chsh ? "" : "un", yppw->newpw.pw_shell,
+ chfn ? "" : "un", yppw->newpw.pw_gecos);
+ }
+ }
+
+ freenetconfigent (nconf);
+ return &res;
+}
+
+/*
+ return code:
+ 0: success
+ 1: error
+*/
+static int
+update_files (yppasswd *yppw, int *shadow_changed,
+ int *passwd_changed, int *chfn, int *chsh)
+{
+ struct passwd *pw;
+ struct spwd *spw = NULL;
+ int gotit = 0;
+ FILE *oldpf = NULL, *newpf = NULL, *oldsf = NULL, *newsf = NULL;
+ struct stat passwd_stat, shadow_stat;
+ char *rootpass = "x";
+
+#if CHECKROOT
+ if ((pw = getpwnam ("root")) != NULL)
+ {
+ if (strcmp (pw->pw_passwd, "x") == 0)
+ {
+ struct spwd *spw;
+
+ if ((spw = getspnam ("root")) != NULL)
+ {
+ rootpass = alloca (strlen (spw->sp_pwdp) + 1);
+ strcpy (rootpass, spw->sp_pwdp);
+ }
+ }
+ else
+ {
+ rootpass = alloca (strlen (pw->pw_passwd) + 1);
+ strcpy (rootpass, pw->pw_passwd);
+ }
+ }
+#endif
+
+ /* Open the passwd file for reading. We can't use getpwent and
+ friends here. */
+ if ((oldpf = fopen (path_passwd, "r")) == NULL)
+ {
+ log_msg ("update %.12s (uid=%d) failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("Can't open %s: %m", path_passwd);
+ return 1;
+ }
+
+ if (fstat (fileno (oldpf), &passwd_stat) < 0)
+ {
+ log_msg ("update %.12s (uid=%d) failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("Can't stat %s: %m", path_passwd);
+ fclose (oldpf);
+ return 1;
+ }
+
+ /* Open a temp passwd file */
+ if ((newpf = fopen (path_passwd_tmp, "w+")) == NULL)
+ {
+ log_msg ("update %.12s (uid=%d) failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("Can't open %s: %m", path_passwd_tmp);
+ fclose (oldpf);
+ return 1;
+ }
+ chmod (path_passwd_tmp, passwd_stat.st_mode);
+ if (chown (path_passwd_tmp, passwd_stat.st_uid, passwd_stat.st_gid) == -1)
+ {
+ log_msg ("chown failed: %s", strerror (errno));
+ fclose (oldpf);
+ fclose (newpf);
+ unlink (path_passwd_tmp);
+ return 1;
+ }
+
+ /* Open the shadow file for reading. */
+ if ((oldsf = fopen (path_shadow, "r")) != NULL)
+ {
+ if (fstat (fileno (oldsf), &shadow_stat) < 0)
+ {
+ log_msg ("update %.12s (uid=%d) failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("Can't stat %s: %m", path_shadow);
+ fclose (oldpf);
+ fclose (newpf);
+ fclose (oldsf);
+ return 1;
+ }
+
+ if ((newsf = fopen (path_shadow_tmp, "w+")) == NULL)
+ {
+ int err = errno;
+
+ log_msg ("update %.12s (uid=%d) failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("Can't open %s.tmp: %s",
+ path_passwd, strerror (err));
+ fclose (oldsf);
+ fclose (newpf);
+ fclose (oldpf);
+ return 1;
+ }
+ chmod (path_shadow_tmp, shadow_stat.st_mode);
+ if (chown (path_shadow_tmp, shadow_stat.st_uid,
+ shadow_stat.st_gid) == -1)
+ {
+ log_msg ("chown failed", strerror (errno));
+ fclose (newsf);
+ fclose (oldsf);
+ fclose (newpf);
+ fclose (oldpf);
+ return 1;
+ }
+ }
+
+ /* Loop over all passwd entries */
+ while ((pw = fgetpwent (oldpf)) != NULL)
+ {
+ /* check if this is the uid we want to change. A few
+ sanity checks added for consistency. */
+ if ((uid_t)yppw->newpw.pw_uid == pw->pw_uid &&
+ (uid_t)yppw->newpw.pw_gid == pw->pw_gid &&
+ !strcmp (yppw->newpw.pw_name, pw->pw_name) && !gotit)
+ {
+ ++gotit;
+
+ /* Check the password. At first check for a shadow password. */
+ if (oldsf != NULL &&
+ ((pw->pw_passwd[0] == 'x' && pw->pw_passwd[1] == '\0') ||
+ (pw->pw_passwd[0] == '#' && pw->pw_passwd[1] == '#')))
+ {
+ /* Search for the shadow entry of this user */
+ while ((spw = fgetspent_adjunct (oldsf)) != NULL)
+ {
+ if (strcmp (yppw->newpw.pw_name, spw->sp_namp) == 0)
+ {
+ if (!password_ok (yppw->oldpass, spw->sp_pwdp, rootpass))
+ {
+ log_msg ("update %.12s (uid=%d) failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("Invalid password.");
+ goto error;
+ }
+ /* Password is ok, leave while loop */
+ break;
+ }
+ else if (putspent_adjunct (spw, newsf) < 0)
+ {
+ log_msg ("update %.12s (uid=%d) failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("Error while writing new shadow file: %m");
+ goto error;
+ }
+ }
+ }
+
+ /* We don't have a shadow password file or we don't find the
+ user in it. */
+ if (spw == NULL &&
+ !password_ok (yppw->oldpass, pw->pw_passwd, rootpass))
+ {
+ log_msg ("update %.12s (uid=%d) rekected",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("Invalid password.");
+ goto error;
+ }
+
+ /*If the new password is not valid,
+ ignore it. User wishes to change GECOS or SHELL in this case. */
+ if (yppw->newpw.pw_passwd != NULL &&
+ !((yppw->newpw.pw_passwd[0] == 'x' ||
+ yppw->newpw.pw_passwd[0] == '*') &&
+ yppw->newpw.pw_passwd[1] == '\0') &&
+ yppw->newpw.pw_passwd[0] != '\0' &&
+ !(yppw->newpw.pw_passwd[0] == '#' &&
+ yppw->newpw.pw_passwd[1] == '#'))
+ {
+ if (spw)
+ {
+ /* test if password is expired */
+ if (spw->sp_pwdp[0] != '!')
+ {
+ if (is_allowed_to_change (spw))
+ {
+ time_t now;
+
+ time(&now);
+ /* set the new passwd */
+ spw->sp_pwdp = yppw->newpw.pw_passwd;
+ spw->sp_lstchg = (long int)now / (24L*3600L);
+ *shadow_changed = 1;
+ }
+ else
+ {
+ log_msg ("update %.12s (uid=%d) rejected",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("now < minimum age for `%s'",
+ spw->sp_namp);
+ goto error;
+ }
+ }
+ if (putspent_adjunct (spw, newsf) < 0)
+ {
+ log_msg ("update %.12s (uid=%d) failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("Error while writing new shadow file: %m");
+ *shadow_changed = 0;
+ goto error;
+ }
+
+ /* Copy all missing entries */
+ while ((spw = fgetspent_adjunct (oldsf)) != NULL)
+ if (putspent_adjunct (spw, newsf) < 0)
+ {
+ log_msg ("update %.12s (uid=%d) failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("Error while writing new shadow file: %m");
+ *shadow_changed = 0;
+ goto error;
+ }
+ }
+ else /* No shadow entry */
+ {
+ /* set the new passwd */
+ pw->pw_passwd = yppw->newpw.pw_passwd;
+ *passwd_changed = 1;
+ }
+ } /* end changing password */
+ else if (spw)
+ spw = NULL;
+
+ /* Handle chsh and chfn here*/
+
+ /* Do we need to update the GECOS information and are we allowed
+ to do it ? */
+ if (strcmp (pw->pw_gecos, yppw->newpw.pw_gecos) != 0)
+ {
+ if (!allow_chfn)
+ {
+ log_msg ("update %.12s (uid=%d) rejected",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("chfn not permitted");
+ *passwd_changed = 0;
+ goto error;
+ }
+ pw->pw_gecos = yppw->newpw.pw_gecos;
+ *chfn = 1;
+ *passwd_changed = 1;
+ }
+
+ /* Do we need to update the shell adn are we allowed to do it ? */
+ if (strcmp (pw->pw_shell, yppw->newpw.pw_shell) != 0)
+ {
+ if (!allow_chsh)
+ {
+ log_msg ("update %.12s (uid=%d) rejected",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("chsh not permitted");
+ *passwd_changed = 0;
+ goto error;
+ }
+ if (!shell_ok (yppw->newpw.pw_shell))
+ {
+ log_msg ("update %.12s (uid=%d) rejected",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("invalid shell: %s", yppw->newpw.pw_shell);
+ *passwd_changed = 0;
+ goto error;
+ }
+ pw->pw_shell = yppw->newpw.pw_shell;
+ *chsh = 1;
+ *passwd_changed = 1;
+ }
+ } /* Found the entry */
+ /* write the passwd entry to tmp file */
+ if (putpwent (pw, newpf) < 0)
+ {
+ int err = errno;
+
+ log_msg ("update %.12s (uid=%d) failed",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ log_msg ("Error while writing new password file: %s",
+ strerror (err));
+ *passwd_changed = 0;
+ break;
+ }
+ /* fflush (newpf); */
+ } /* while */
+ error:
+ if (newpf) fclose (newpf);
+ if (oldpf) fclose (oldpf);
+ if (newsf) fclose (newsf);
+ if (oldsf) fclose (oldsf);
+ /* If one of them is non-NULL, an error ocured. */
+ if (pw || spw)
+ {
+ unlink (path_passwd_tmp);
+ unlink (path_shadow_tmp);
+ return 1;
+ }
+ if (*shadow_changed)
+ {
+ unlink (path_shadow_old);
+ if (link (path_shadow, path_shadow_old) == -1)
+ log_msg ("Cannot create backup file %s: %s",
+ path_shadow_old, strerror (errno));
+ if (rename (path_shadow_tmp, path_shadow) == -1)
+ {
+ log_msg ("Cannot move temporary file %s to %s: %s",
+ path_shadow_tmp, path_shadow, strerror (errno));
+ *shadow_changed = 0;
+ }
+ }
+ else
+ unlink (path_shadow_tmp);
+
+ if (*passwd_changed)
+ {
+ unlink (path_passwd_old);
+ if (link (path_passwd, path_passwd_old) == -1)
+ log_msg ("Cannot create backup file %s: %s",
+ path_passwd_old, strerror (errno));
+ if (rename (path_passwd_tmp, path_passwd) == -1)
+ {
+ log_msg ("Cannot move temporary file %s to %s: %s",
+ path_passwd_tmp, path_passwd, strerror (errno));
+ *passwd_changed = 0;
+ }
+ }
+ else
+ unlink (path_passwd_tmp);
+
+ return !(*shadow_changed || *passwd_changed);
+}
+
+static int
+external_update_env (yppasswd *yppw)
+{
+ int res = 0;
+ int itmp = fork ();
+
+ if (itmp)
+ { /* Parent - try to get exit status */
+ itmp = waitpid (itmp, &res, 0);
+
+ if (itmp < 0)
+ res = 2;
+ else
+ res = WEXITSTATUS(res);
+ }
+ else
+ { /* Child - run external update program */
+ setenv ("YP_PASSWD_OLD", yppw->oldpass, 1);
+ setenv ("YP_PASSWD_NEW", yppw->newpw.pw_passwd, 1);
+ setenv ("YP_USER", yppw->newpw.pw_name, 1);
+ setenv ("YP_GECOS", yppw->newpw.pw_gecos, 1);
+ setenv ("YP_SHELL", yppw->newpw.pw_shell, 1);
+ execlp (external_update_program, external_update_program, NULL);
+ _exit (1); /* fall-through */
+ }
+ return res;
+}
+
+/*===============================================================*
+ *
+ * If rpc.yppasswdd is run with the -execute option, instead of
+ * trying to manually modify the system passwd and/or shadow files,
+ * we instead try to run the program designated by the -execute
+ * option.
+ *
+ * We open a pair of pipes to communicate with the password-changing
+ * program. We write to the program's stdin a single line in the
+ * form:
+ *
+ * <username> o:<oldpass> p:<password> s:<shell> g:<gcos>\n
+ *
+ * where <oldpass>, <password>, <shell>, and <gcos> are all expanded
+ * into the information from the NIS passwd client. The <oldpass> bit
+ * is mandatory, and is to be used by the external program to validate
+ * permissions to change the user's information. The p:, s:, and g:
+ * fields will be present if those attributes have changed. If any of
+ * those fields have not changed, we won't include that part of the
+ * line, so if only the password has changed, we'll write something
+ * like
+ *
+ * broccol o:<oldpass> p:e6GYrKvFKVBXw\n
+ *
+ * and if we just change the shell, it'll look like
+ *
+ * broccol o:<oldpass> s:/bin/tcsh\n
+ *
+ * In return, we read output from the program. If the program sends
+ *
+ * OK[...]\n
+ *
+ * we return a code indicating a successful password information
+ * change. If the program does not emit OK as the first two
+ * characters to its stdout, we interpret that as failure and we
+ * report a failure to the NIS client.
+ *
+ * Note that the program executed is fully responsible for any
+ * NIS build and propagation issues, as well as for checking
+ * the submitted shell out for validity.
+ *
+ *===============================================================*/
+
+static void
+remove_password (char *str)
+{
+ char *ptr = strstr (str, " o:");
+
+ if (ptr != NULL)
+ {
+ ptr+=3;
+ while (*ptr && *ptr != ' ')
+ *ptr++ = 'X';
+ }
+
+ ptr = strstr (str, " p:");
+ if (ptr != NULL)
+ {
+ ptr+=3;
+ while (*ptr && *ptr != ' ')
+ *ptr++ = 'X';
+ }
+}
+
+static int
+external_update_pipe (yppasswd *yppw)
+{
+ struct passwd *newpw; /* passwd struct passed by the client */
+ int res, pid, tochildpipe[2], toparentpipe[2];
+ FILE *fp;
+ char childresponse[1024];
+
+ char *password = NULL;
+ char *shell = NULL;
+ char *gcos = NULL;
+
+ char *parentmsg;
+ size_t msglen;
+
+ /* - */
+
+ newpw = (struct passwd *)&yppw->newpw;
+ res = 1;
+
+ /*
+ * determine what information we have to change
+ */
+
+ if (newpw->pw_passwd && *(newpw->pw_passwd))
+ password = newpw->pw_passwd;
+
+ if (allow_chsh && newpw->pw_shell && *(newpw->pw_shell))
+ shell = newpw->pw_shell;
+
+ if (allow_chfn && newpw->pw_gecos && *(newpw->pw_gecos))
+ gcos = newpw->pw_gecos;
+
+ if (!password && !shell && !gcos)
+ {
+ log_msg ("update %.12s (uid=%d) failed - no information to change",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ return res;
+ }
+
+ /*
+ * create the pipe we'll use to write to the stdin of the password
+ * change utility we're going to call.
+ */
+
+ if (pipe(tochildpipe) < 0)
+ {
+ log_msg ("update %.12s (uid=%d) failed - could not create child pipe",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ return res;
+ }
+
+ if (pipe(toparentpipe) < 0)
+ {
+ log_msg ("update %.12s (uid=%d) failed - could not create parent pipe",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ return res;
+ }
+
+ if ((pid = fork()) < 0)
+ {
+ log_msg ("update %.12s (uid=%d) failed - could not fork",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid);
+ return res;
+ }
+
+ if (pid == 0)
+ {
+ /*
+ * the child executes this code..
+ */
+
+ /*
+ * make the read side of the pipe our stdin for the password
+ * change utilit
+ */
+
+ if (tochildpipe[0] != 0)
+ {
+ close(0);
+ dup2(tochildpipe[0], 0);
+ }
+
+ /*
+ * we're not going to write to ourselves
+ */
+
+ close(tochildpipe[1]);
+
+ /*
+ * make the write side of our end of the pipe stdout
+ */
+
+ if (toparentpipe[1] != 1)
+ {
+ close(1);
+ dup2(toparentpipe[1], 1);
+ }
+
+ /*
+ * we're not going to read from ourselves
+ */
+
+ close (toparentpipe[0]);
+
+ execl (external_update_program, external_update_program, NULL);
+ exit (1);
+ }
+
+ /*
+ * the parent executes this code
+ */
+
+ close (tochildpipe[0]);
+ close (toparentpipe[1]);
+
+ /*
+ * construct our message
+ */
+ msglen = strlen (yppw->newpw.pw_name) + strlen (yppw->oldpass) + 10;
+ if (password)
+ msglen += strlen (password) + 3;
+ if (shell)
+ msglen += strlen (shell) + 3;
+ if (gcos)
+ msglen += strlen (gcos) + 3;
+
+ if ((parentmsg = malloc (msglen)) == NULL)
+ {
+ log_msg ("rpc.yppasswdd: out of memory");
+ return res;
+ }
+
+ strcpy (parentmsg, yppw->newpw.pw_name);
+ strcat (parentmsg, " o:");
+ strcat (parentmsg, yppw->oldpass);
+ strcat (parentmsg, " ");
+
+ if (password)
+ {
+ strcat (parentmsg, "p:");
+ strcat (parentmsg, password);
+ strcat (parentmsg, " ");
+ }
+
+ if (shell)
+ {
+ strcat (parentmsg, "s:");
+ strcat (parentmsg, shell);
+ strcat (parentmsg, " ");
+ }
+
+ if (gcos)
+ {
+ strcat(parentmsg, "g:");
+ strcat(parentmsg, gcos);
+ }
+
+ /*
+ * write the message to our child
+ */
+
+ fp = fdopen(tochildpipe[1], "w");
+ fprintf(fp, "%s\n", parentmsg);
+ fclose(fp);
+
+ /*
+ * get output from the child
+ */
+
+ fp = fdopen(toparentpipe[0], "r");
+ if (!fgets(childresponse, 1024, fp))
+ {
+ childresponse[0] = '\0';
+ log_msg ("fgets() call failed or EOF.");
+ }
+ fclose(fp);
+
+ if (!debug_flag)
+ remove_password (parentmsg);
+
+ if (strspn(childresponse, "OK") < 2)
+ {
+ log_msg ("update %.12s (uid=%d) failed. Change request: %s",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid, parentmsg);
+ log_msg ("Response was '%s'", childresponse);
+ free (parentmsg);
+ return res;
+ }
+
+ log_msg ("update %.12s (uid=%d) successful. Change request: %s",
+ yppw->newpw.pw_name, yppw->newpw.pw_uid, parentmsg);
+
+ free (parentmsg);
+ res = 0;
+
+ return res;
+}