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

Revision 14591, 11.1 KB checked in by rbasch, 25 years ago (diff)
Test for failure to open the passwd file in update_passwd_local().
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.13 2000-04-11 21:18:37 rbasch 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(int sig);
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   * Unfortunately, getenv("USER") yields the wrong answer if the user
99   * has done an "su", so fall back to that only if ruid isn't in the
100   * passwd file.
101   */
102  pwd = getpwuid(ruid);
103  if (pwd)
104    runner = pwd->pw_name;
105  else
106    {
107      runner = getenv("USER");
108      if (!runner)
109        {
110          fprintf(stderr, "passwd: can't determine running user.\n");
111          return 1;
112        }
113    }
114
115  /* runner currently points to static or environment storage; make a
116   * copy of it in allocated memory.
117   */
118  runner = strdup(runner);
119  if (!runner)
120    {
121      fprintf(stderr, "passwd: out of memory.\n");
122      return 1;
123    }
124
125  if (!local && !krb)
126    {
127      /* Decide via a heuristic test whether to run local or Kerberos
128       * password-changing program.  If the user running the program
129       * is root or is a local account according to /etc/athena/access,
130       * then we use the local passwd program; otherwise we use
131       * kpasswd.
132       */
133      if (ruid == 0 || al_is_local_acct(runner) == 1)
134        local = 1;
135    }
136
137  if (local)
138    {
139      /* Figure out which user's password is being changed. */
140      username = (argc == 1) ? argv[0] : runner;
141
142      /* If we're not run by root, make sure username matches our ruid
143       * in the passwd file.  This is perhaps overly paranoid, since
144       * /usr/bin/passwd should error out if the user is unauthorized,
145       * but we don't want to let users update other users' local passwd
146       * entries if /usr/bin/passwd doesn't properly flag the error.
147       */
148      if (ruid != 0)
149        {
150          pwd = getpwnam(username);
151          if (!pwd)
152            {
153              fprintf(stderr, "passwd: Can't find uid for username %s.\n",
154                      username);
155              return 1;
156            }
157          if (!pwd || pwd->pw_uid != ruid)
158            {
159              fprintf(stderr, "passwd: username/ruid mismatch: %s has uid %lu,"
160                      " but ruid is %lu.\n", username,
161                      (unsigned long) pwd->pw_uid, (unsigned long) ruid);
162              return 1;
163            }
164        }
165
166      printf("Running local password-changing program for %s.\n", username);
167      pid = fork();
168      if (pid == -1)
169        {
170          perror("passwd: fork");
171          return 1;
172        }
173      else if (pid == 0)
174        {
175          setuid(ruid);
176          args[0] = "passwd";
177#ifdef PASSWD_NEEDS_LFLAG
178          /* Some passwd programs need a -l flag to specify the local
179           * password.
180           */
181          args[1] = "-l";
182          args[2] = username;
183          args[3] = NULL;
184#else
185          args[1] = username;
186          args[2] = NULL;
187#endif
188          execv(PATH_PASSWD_PROG, args);
189          perror("passwd: execv");
190          _exit(1);
191          /* gcc (2.95 at least) doesn't realize that _exit() exits. */
192          return 1;
193        }
194      else
195        {
196          /* Wait for the child to complete. */
197          while ((rval = waitpid(pid, &status, 0)) == -1 && errno == EINTR)
198            ;
199          if (rval == -1)
200            {
201              perror("passwd: wait");
202              return 1;
203            }
204          /* If the child exited abnormally, assume that it printed an
205           * error message.
206           */
207          if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
208            return 1;
209
210          update_passwd_local(username);
211          return 0;
212        }
213    }
214  else
215    {
216      printf("Running Kerberos password-changing program.\n");
217      setuid(ruid);
218      args[0] = "kpasswd";
219      if (*argv)
220        {
221          args[1] = "-n";
222          args[2] = *argv;
223          args[3] = NULL;
224        }
225      else
226        args[1] = NULL;
227      execv(PATH_KPASSWD_PROG, args);
228      perror("passwd: execv");
229      return 1;
230    }
231}
232
233static void update_passwd_local(const char *username)
234{
235  FILE *fp, *fp_out;
236  char *line = NULL, *userline;
237  int linesize, len, found, fd, i, status;
238  struct sigaction action;
239  sigset_t mask, omask;
240  mode_t oldumask;
241
242  len = strlen(username);
243
244  /* Find the line for username in the passwd file. */
245  fp = fopen(PATH_PASSWD, "r");
246  if (fp == NULL)
247    {
248      fprintf(stderr, "Can't open %s so not updating local passwd file.\n",
249              PATH_PASSWD);
250      exit(1);
251    }
252  found = 0;
253  while (read_line(fp, &line, &linesize) == 0)
254    {
255      if (strncmp(line, username, len) == 0 && line[len] == ':')
256        {
257          found = 1;
258          break;
259        }
260    }
261  if (!found)
262    {
263      fprintf(stderr,
264              "Can't find %s in %s so not updating local passwd file.\n",
265              username, PATH_PASSWD);
266      exit(1);
267    }
268  fclose(fp);
269  userline = line;
270  line = NULL;
271
272  /* Open the local passwd file for reading. */
273  fp = fopen(PATH_PASSWD_LOCAL, "r");
274  if (fp == NULL)
275    {
276      if (errno != ENOENT)
277        fprintf(stderr, "Can't read %s so not updating local passwd file.\n",
278                PATH_PASSWD_LOCAL);
279      exit(1);
280    }
281
282  sigemptyset(&mask);
283  sigaddset(&mask, SIGHUP);
284  sigaddset(&mask, SIGINT);
285  sigaddset(&mask, SIGQUIT);
286  sigaddset(&mask, SIGTERM);
287
288  /* Open the temporary local passwd file for writing.  We have to do some
289   * clever signal-handling tricks to make sure that tty signals don't
290   * leave the lock file hanging around.
291   */
292  for (i = 0; i < 10; i++)
293    {
294      sigprocmask(SIG_BLOCK, &mask, &omask);
295      oldumask = umask(0);
296      fd = open(PATH_PASSWD_LOCAL_TMP, O_RDWR|O_CREAT|O_EXCL, PLTMP_MODE);
297      umask(oldumask);
298      if (fd != -1)
299        {
300          sigemptyset(&action.sa_mask);
301          action.sa_handler = cleanup;
302          action.sa_flags = 0;
303          sigaction(SIGHUP, &action, NULL);
304          sigaction(SIGINT, &action, NULL);
305          sigaction(SIGQUIT, &action, NULL);
306          sigaction(SIGTERM, &action, NULL);
307        }
308      sigprocmask(SIG_SETMASK, &omask, NULL);
309      if (fd != -1 || errno != EEXIST)
310        break;
311      sleep(1);
312    }
313  if (fd == -1 || (fp_out = fdopen(fd, "w")) == NULL)
314    {
315      fprintf(stderr,
316              "Can't open %s for writing so not updating local passwd file.\n",
317              PATH_PASSWD_LOCAL_TMP);
318      if (fd != -1)
319        {
320          sigprocmask(SIG_BLOCK, &mask, NULL);
321          unlink(PATH_PASSWD_LOCAL_TMP);
322        }
323      exit(1);
324    }
325
326  /* Copy the local passwd file to the temporary file.  Replace the first
327   * line beginning with username with the line we found in the passwd
328   * file.
329   */
330  found = 0;
331  while ((status = read_line(fp, &line, &linesize)) == 0)
332    {
333      if (!found && strncmp(line, username, len) == 0 && line[len] == ':')
334        {
335          fputs(userline, fp_out);
336          found = 1;
337        }
338      else
339        fputs(line, fp_out);
340      putc('\n', fp_out);
341    }
342  free(line);
343  free(userline);
344  fclose(fp);
345
346  /* Block tty signals for the short duration of our lifetime so we don't
347   * erroneously delete the temporary file after giving it up.
348   */
349  sigprocmask(SIG_BLOCK, &mask, NULL);
350
351  if (!found)
352    {
353      /* We didn't actually change the file; don't do an update. */
354      fclose(fp_out);
355      unlink(PATH_PASSWD_LOCAL_TMP);
356      return;
357    }
358
359  if (status < 0 || ferror(fp_out) || fclose(fp_out) == EOF)
360    {
361      fprintf(stderr,
362              "Error copying %s to %s so not updating local passwd file.\n",
363              PATH_PASSWD_LOCAL, PATH_PASSWD_LOCAL_TMP);
364      unlink(PATH_PASSWD_LOCAL_TMP);
365      exit(1);
366    }
367
368  /* Replace the local passwd file with the temporary file. */
369  printf("Updating %s with new passwd entry.\n", PATH_PASSWD_LOCAL);
370  if (rename(PATH_PASSWD_LOCAL_TMP, PATH_PASSWD_LOCAL) == -1)
371    {
372      fprintf(stderr,
373              "Error renaming %s to %s so not updating local passwd file.\n",
374              PATH_PASSWD_LOCAL, PATH_PASSWD_LOCAL_TMP);
375      unlink(PATH_PASSWD_LOCAL_TMP);
376      exit(1);
377    }
378}
379
380/* Read a line from a file into a dynamically allocated buffer,
381 * zeroing the trailing newline if there is one.  The calling routine
382 * may call read_line multiple times with the same buf and bufsize
383 * pointers; *buf will be reallocated and *bufsize adjusted as
384 * appropriate.  The initial value of *buf should be NULL.  After the
385 * calling routine is done reading lines, it should free *buf.  This
386 * function returns 0 if a line was successfully read, 1 if the file
387 * ended, and -1 if there was an I/O error or if it ran out of memory.
388 */
389
390static int read_line(FILE *fp, char **buf, int *bufsize)
391{
392  char *newbuf;
393  int offset = 0, len;
394
395  if (*buf == NULL)
396    {
397      *buf = malloc(128);
398      if (!*buf)
399        return -1;
400      *bufsize = 128;
401    }
402
403  while (1)
404    {
405      if (!fgets(*buf + offset, *bufsize - offset, fp))
406        return (offset != 0) ? 0 : (ferror(fp)) ? -1 : 1;
407      len = offset + strlen(*buf + offset);
408      if ((*buf)[len - 1] == '\n')
409        {
410          (*buf)[len - 1] = 0;
411          return 0;
412        }
413      offset = len;
414
415      /* Allocate more space. */
416      newbuf = realloc(*buf, *bufsize * 2);
417      if (!newbuf)
418        return -1;
419      *buf = newbuf;
420      *bufsize *= 2;
421    }
422}
423
424static void usage(void)
425{
426  fprintf(stderr, "Usage: passwd [-k|-l] [username]\n");
427  exit(1);
428}
429
430static void cleanup(int sig)
431{
432  unlink(PATH_PASSWD_LOCAL_TMP);
433  exit(1);
434}
Note: See TracBrowser for help on using the repository browser.