source: trunk/athena/bin/passwd/passwd.c @ 12299

Revision 12299, 10.5 KB checked in by ghudson, 26 years ago (diff)
The krb5 passwd program (which we're going back to for the 8.3 release) doesn't take a -n argument.
Line 
1/* Copyright 1998 by the Massachusetts Institute of Technology.
2 *
3 * Permission to use, copy, modify, and distribute this
4 * software and its documentation for any purpose and without
5 * fee is hereby granted, provided that the above copyright
6 * notice appear in all copies and that both that copyright
7 * notice and this permission notice appear in supporting
8 * documentation, and that the name of M.I.T. not be used in
9 * advertising or publicity pertaining to distribution of the
10 * software without specific, written prior permission.
11 * M.I.T. makes no representations about the suitability of
12 * this software for any purpose.  It is provided "as is"
13 * without express or implied warranty.
14 */
15
16/* This program implements a passwd glue program which selects between
17 * the Kerberos and local password-changing programs, and updates the
18 * local passwd file if the local password-changing program is selected.
19 */
20
21static const char rcsid[] = "$Id: passwd.c,v 1.6 1998-12-31 23:44:31 ghudson Exp $";
22
23#include <sys/types.h>
24#include <sys/stat.h>
25#include <sys/wait.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <unistd.h>
30#include <fcntl.h>
31#include <errno.h>
32#include <pwd.h>
33#include <signal.h>
34#include <al.h>
35
36#define PATH_KPASSWD_PROG       "/usr/athena/bin/kpasswd"
37#define PATH_PASSWD_PROG        "/usr/bin/passwd"
38
39/* This is a little non-intuitive.  PATH_PASSWD gives the pathname of
40 * the file which contains the encrypted password string.
41 * PATH_PASSWD_LOCAL gives the local (authoritative) copy of
42 * PATH_PASSWD.  PATH_PASSWD_LOCAL_TMP gives a temporary filename for
43 * updating PATH_PASSWD_LOCAL.
44 */
45#if defined(HAVE_MASTER_PASSWD)
46#define PATH_PASSWD             "/etc/master.passwd"
47#elif defined(HAVE_SHADOW)
48#define PATH_PASSWD             "/etc/shadow"
49#else
50#define PATH_PASSWD             "/etc/passwd"
51#endif
52#define PATH_PASSWD_LOCAL       PATH_PASSWD ".local"
53#define PATH_PASSWD_LOCAL_TMP   PATH_PASSWD_LOCAL ".tmp"
54
55/* Temp file should be mode 600 on a master.passwd or shadow system,
56 * 644 otherwise.
57 */
58#if defined(HAVE_MASTER_PASSWD) || defined(HAVE_SHADOW)
59#define PLTMP_MODE (S_IWUSR|S_IRUSR)
60#else
61#define PLTMP_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
62#endif
63
64static void update_passwd_local(const char *username);
65static int read_line(FILE *fp, char **buf, int *bufsize);
66static void usage(void);
67static void cleanup();
68
69int main(int argc, char **argv)
70{
71  extern int optind;
72  int c, local = 0, krb = 0, rval, status;
73  char *args[4], *runner, *username;
74  pid_t pid;
75  uid_t ruid = getuid();
76  struct passwd *pwd;
77
78  while ((c = getopt(argc, argv, "lk")) != -1)
79    {
80      switch (c)
81        {
82        case 'l':
83          local = 1;
84          break;
85        case 'k':
86          krb = 1;
87          break;
88        default:
89          usage();
90        }
91    }
92  argc -= optind;
93  argv += optind;
94  if ((local && krb) || argc > 1)
95    usage();
96
97  /* Figure out the username who is allegedly running this program. */
98  runner = getenv("USER");
99  if (!runner)
100    {
101      pwd = getpwuid(ruid);
102      if (!pwd)
103        {
104          fprintf(stderr, "passwd: can't determine running user.\n");
105          return 1;
106        }
107      runner = strdup(pwd->pw_name);
108      if (!runner)
109        {
110          fprintf(stderr, "passwd: out of memory.\n");
111          return 1;
112        }
113    }
114
115  if (!local && !krb)
116    {
117      /* Decide via a heuristic test whether to run local or Kerberos
118       * password-changing program.  If the user running the program
119       * is root or is a local account according to /etc/athena/access,
120       * then we use the local passwd program; otherwise we use
121       * kpasswd.
122       */
123      if (ruid == 0 || al_is_local_acct(runner) == 1)
124        local = 1;
125    }
126
127  if (local)
128    {
129      /* Figure out which user's password is being changed. */
130      username = (argc == 1) ? argv[0] : runner;
131
132      /* If we're not run by root, make sure username matches our ruid
133       * in the passwd file.  This is perhaps overly paranoid, since
134       * /usr/bin/passwd should error out if the user is unauthorized,
135       * but we don't want to let users update other users' local passwd
136       * entries if /usr/bin/passwd doesn't properly flag the error.
137       */
138      if (ruid != 0)
139        {
140          pwd = getpwnam(username);
141          if (!pwd)
142            {
143              fprintf(stderr, "passwd: Can't find uid for username %s.\n",
144                      username);
145              return 1;
146            }
147          if (!pwd || pwd->pw_uid != ruid)
148            {
149              fprintf(stderr, "passwd: username/ruid mismatch: %s has uid %lu,"
150                      " but ruid is %lu.\n", username, pwd->pw_uid, ruid);
151              return 1;
152            }
153        }
154
155      printf("Running local password-changing program.\n");
156      pid = fork();
157      if (pid == -1)
158        {
159          perror("passwd: fork");
160          return 1;
161        }
162      else if (pid == 0)
163        {
164          setuid(ruid);
165          args[0] = "passwd";
166#ifdef PASSWD_NEEDS_LFLAG
167          /* Some passwd programs need a -l flag to specify the local
168           * password.
169           */
170          args[1] = "-l";
171          args[2] = username;
172          args[3] = NULL;
173#else
174          args[1] = username;
175          args[2] = NULL;
176#endif
177          execv(PATH_PASSWD_PROG, args);
178          perror("passwd: execv");
179          _exit(1);
180        }
181      else
182        {
183          /* Wait for the child to complete. */
184          while ((rval = waitpid(pid, &status, 0)) == -1 && errno == EINTR)
185            ;
186          if (rval == -1)
187            {
188              perror("passwd: wait");
189              return 1;
190            }
191          /* If the child exited abnormally, assume that it printed an
192           * error message.
193           */
194          if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
195            return 1;
196
197          update_passwd_local(username);
198          return 0;
199        }
200    }
201  else
202    {
203      printf("Running Kerberos password-changing program.\n");
204      setuid(ruid);
205      args[0] = "kpasswd";
206      if (*argv)
207        {
208          args[1] = *argv;
209          args[2] = NULL;
210        }
211      else
212        args[1] = NULL;
213      execv(PATH_KPASSWD_PROG, args);
214      perror("passwd: execv");
215      return 1;
216    }
217}
218
219static void update_passwd_local(const char *username)
220{
221  FILE *fp, *fp_out;
222  char *line = NULL, *userline;
223  int linesize, len, found, fd, count, i, status;
224  struct sigaction action;
225  sigset_t mask, omask;
226  mode_t oldumask;
227
228  len = strlen(username);
229
230  /* Find the line for username in the passwd file. */
231  fp = fopen(PATH_PASSWD, "r");
232  found = 0;
233  while (read_line(fp, &line, &linesize) == 0)
234    {
235      if (strncmp(line, username, len) == 0 && line[len] == ':')
236        {
237          found = 1;
238          break;
239        }
240    }
241  if (!found)
242    {
243      fprintf(stderr,
244              "Can't find %s in %s so not updating local passwd file.\n",
245              username, PATH_PASSWD);
246      exit(1);
247    }
248  fclose(fp);
249  userline = line;
250  line = NULL;
251
252  /* Open the local passwd file for reading. */
253  fp = fopen(PATH_PASSWD_LOCAL, "r");
254  if (fp == NULL)
255    {
256      if (errno != ENOENT)
257        fprintf(stderr, "Can't read %s so not updating local passwd file.\n",
258                PATH_PASSWD_LOCAL);
259      exit(1);
260    }
261
262  sigemptyset(&mask);
263  sigaddset(&mask, SIGHUP);
264  sigaddset(&mask, SIGINT);
265  sigaddset(&mask, SIGQUIT);
266  sigaddset(&mask, SIGTERM);
267
268  /* Open the temporary local passwd file for writing.  We have to do some
269   * clever signal-handling tricks to make sure that tty signals don't
270   * leave the lock file hanging around.
271   */
272  for (i = 0; i < 10; i++)
273    {
274      sigprocmask(SIG_BLOCK, &mask, &omask);
275      oldumask = umask(0);
276      fd = open(PATH_PASSWD_LOCAL_TMP, O_RDWR|O_CREAT|O_EXCL, PLTMP_MODE);
277      umask(oldumask);
278      if (fd != -1)
279        {
280          sigemptyset(&action.sa_mask);
281          action.sa_handler = cleanup;
282          action.sa_flags = 0;
283          sigaction(SIGHUP, &action, NULL);
284          sigaction(SIGINT, &action, NULL);
285          sigaction(SIGQUIT, &action, NULL);
286          sigaction(SIGTERM, &action, NULL);
287        }
288      sigprocmask(SIG_SETMASK, &omask, NULL);
289      if (fd != -1 || errno != EEXIST)
290        break;
291      sleep(1);
292    }
293  if (fd == -1 || (fp_out = fdopen(fd, "w")) == NULL)
294    {
295      fprintf(stderr,
296              "Can't open %s for writing so not updating local passwd file.\n",
297              PATH_PASSWD_LOCAL_TMP);
298      if (fd != -1)
299        {
300          sigprocmask(SIG_BLOCK, &mask, NULL);
301          unlink(PATH_PASSWD_LOCAL_TMP);
302        }
303      exit(1);
304    }
305
306  /* Copy the local passwd file to the temporary file.  Replace the first
307   * line beginning with username with the line we found in the passwd
308   * file.
309   */
310  found = 0;
311  while ((status = read_line(fp, &line, &linesize)) == 0)
312    {
313      if (!found && strncmp(line, username, len) == 0 && line[len] == ':')
314        {
315          fputs(userline, fp_out);
316          found = 1;
317        }
318      else
319        fputs(line, fp_out);
320      putc('\n', fp_out);
321    }
322  free(line);
323  free(userline);
324  fclose(fp);
325
326  /* Block tty signals for the short duration of our lifetime so we don't
327   * erroneously delete the temporary file after giving it up.
328   */
329  sigprocmask(SIG_BLOCK, &mask, NULL);
330
331  if (!found)
332    {
333      /* We didn't actually change the file; don't do an update. */
334      fclose(fp_out);
335      unlink(PATH_PASSWD_LOCAL_TMP);
336      return;
337    }
338
339  if (status < 0 || ferror(fp_out) || fclose(fp_out) == EOF)
340    {
341      fprintf(stderr,
342              "Error copying %s to %s so not updating local passwd file.\n",
343              PATH_PASSWD_LOCAL, PATH_PASSWD_LOCAL_TMP);
344      unlink(PATH_PASSWD_LOCAL_TMP);
345      exit(1);
346    }
347
348  /* Replace the local passwd file with the temporary file. */
349  printf("Updating %s with new passwd entry.\n", PATH_PASSWD_LOCAL);
350  if (rename(PATH_PASSWD_LOCAL_TMP, PATH_PASSWD_LOCAL) == -1)
351    {
352      fprintf(stderr,
353              "Error renaming %s to %s so not updating local passwd file.\n",
354              PATH_PASSWD_LOCAL, PATH_PASSWD_LOCAL_TMP);
355      unlink(PATH_PASSWD_LOCAL_TMP);
356      exit(1);
357    }
358}
359
360/* Read a line from a file into a dynamically allocated buffer,
361 * zeroing the trailing newline if there is one.  The calling routine
362 * may call read_line multiple times with the same buf and bufsize
363 * pointers; *buf will be reallocated and *bufsize adjusted as
364 * appropriate.  The initial value of *buf should be NULL.  After the
365 * calling routine is done reading lines, it should free *buf.  This
366 * function returns 0 if a line was successfully read, 1 if the file
367 * ended, and -1 if there was an I/O error or if it ran out of memory.
368 */
369
370static int read_line(FILE *fp, char **buf, int *bufsize)
371{
372  char *newbuf;
373  int offset = 0, len;
374
375  if (*buf == NULL)
376    {
377      *buf = malloc(128);
378      if (!*buf)
379        return -1;
380      *bufsize = 128;
381    }
382
383  while (1)
384    {
385      if (!fgets(*buf + offset, *bufsize - offset, fp))
386        return (offset != 0) ? 0 : (ferror(fp)) ? -1 : 1;
387      len = offset + strlen(*buf + offset);
388      if ((*buf)[len - 1] == '\n')
389        {
390          (*buf)[len - 1] = 0;
391          return 0;
392        }
393      offset = len;
394
395      /* Allocate more space. */
396      newbuf = realloc(*buf, *bufsize * 2);
397      if (!newbuf)
398        return -1;
399      *buf = newbuf;
400      *bufsize *= 2;
401    }
402}
403
404static void usage(void)
405{
406  fprintf(stderr, "Usage: passwd [-k|-l] [username]\n");
407  exit(1);
408}
409
410static void cleanup(void)
411{
412  unlink(PATH_PASSWD_LOCAL_TMP);
413  exit(1);
414}
Note: See TracBrowser for help on using the repository browser.