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

Revision 11956, 10.5 KB checked in by ghudson, 26 years ago (diff)
Ignore the umask when opening the new local file.
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.5 1998-09-24 13:51:50 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] = "-n";
209          args[2] = *argv;
210          args[3] = NULL;
211        }
212      else
213        args[1] = NULL;
214      execv(PATH_KPASSWD_PROG, args);
215      perror("passwd: execv");
216      return 1;
217    }
218}
219
220static void update_passwd_local(const char *username)
221{
222  FILE *fp, *fp_out;
223  char *line = NULL, *userline;
224  int linesize, len, found, fd, count, i, status;
225  struct sigaction action;
226  sigset_t mask, omask;
227  mode_t oldumask;
228
229  len = strlen(username);
230
231  /* Find the line for username in the passwd file. */
232  fp = fopen(PATH_PASSWD, "r");
233  found = 0;
234  while (read_line(fp, &line, &linesize) == 0)
235    {
236      if (strncmp(line, username, len) == 0 && line[len] == ':')
237        {
238          found = 1;
239          break;
240        }
241    }
242  if (!found)
243    {
244      fprintf(stderr,
245              "Can't find %s in %s so not updating local passwd file.\n",
246              username, PATH_PASSWD);
247      exit(1);
248    }
249  fclose(fp);
250  userline = line;
251  line = NULL;
252
253  /* Open the local passwd file for reading. */
254  fp = fopen(PATH_PASSWD_LOCAL, "r");
255  if (fp == NULL)
256    {
257      if (errno != ENOENT)
258        fprintf(stderr, "Can't read %s so not updating local passwd file.\n",
259                PATH_PASSWD_LOCAL);
260      exit(1);
261    }
262
263  sigemptyset(&mask);
264  sigaddset(&mask, SIGHUP);
265  sigaddset(&mask, SIGINT);
266  sigaddset(&mask, SIGQUIT);
267  sigaddset(&mask, SIGTERM);
268
269  /* Open the temporary local passwd file for writing.  We have to do some
270   * clever signal-handling tricks to make sure that tty signals don't
271   * leave the lock file hanging around.
272   */
273  for (i = 0; i < 10; i++)
274    {
275      sigprocmask(SIG_BLOCK, &mask, &omask);
276      oldumask = umask(0);
277      fd = open(PATH_PASSWD_LOCAL_TMP, O_RDWR|O_CREAT|O_EXCL, PLTMP_MODE);
278      umask(oldumask);
279      if (fd != -1)
280        {
281          sigemptyset(&action.sa_mask);
282          action.sa_handler = cleanup;
283          action.sa_flags = 0;
284          sigaction(SIGHUP, &action, NULL);
285          sigaction(SIGINT, &action, NULL);
286          sigaction(SIGQUIT, &action, NULL);
287          sigaction(SIGTERM, &action, NULL);
288        }
289      sigprocmask(SIG_SETMASK, &omask, NULL);
290      if (fd != -1 || errno != EEXIST)
291        break;
292      sleep(1);
293    }
294  if (fd == -1 || (fp_out = fdopen(fd, "w")) == NULL)
295    {
296      fprintf(stderr,
297              "Can't open %s for writing so not updating local passwd file.\n",
298              PATH_PASSWD_LOCAL_TMP);
299      if (fd != -1)
300        {
301          sigprocmask(SIG_BLOCK, &mask, NULL);
302          unlink(PATH_PASSWD_LOCAL_TMP);
303        }
304      exit(1);
305    }
306
307  /* Copy the local passwd file to the temporary file.  Replace the first
308   * line beginning with username with the line we found in the passwd
309   * file.
310   */
311  found = 0;
312  while ((status = read_line(fp, &line, &linesize)) == 0)
313    {
314      if (!found && strncmp(line, username, len) == 0 && line[len] == ':')
315        {
316          fputs(userline, fp_out);
317          found = 1;
318        }
319      else
320        fputs(line, fp_out);
321      putc('\n', fp_out);
322    }
323  free(line);
324  free(userline);
325  fclose(fp);
326
327  /* Block tty signals for the short duration of our lifetime so we don't
328   * erroneously delete the temporary file after giving it up.
329   */
330  sigprocmask(SIG_BLOCK, &mask, NULL);
331
332  if (!found)
333    {
334      /* We didn't actually change the file; don't do an update. */
335      fclose(fp_out);
336      unlink(PATH_PASSWD_LOCAL_TMP);
337      return;
338    }
339
340  if (status < 0 || ferror(fp_out) || fclose(fp_out) == EOF)
341    {
342      fprintf(stderr,
343              "Error copying %s to %s so not updating local passwd file.\n",
344              PATH_PASSWD_LOCAL, PATH_PASSWD_LOCAL_TMP);
345      unlink(PATH_PASSWD_LOCAL_TMP);
346      exit(1);
347    }
348
349  /* Replace the local passwd file with the temporary file. */
350  printf("Updating %s with new passwd entry.\n", PATH_PASSWD_LOCAL);
351  if (rename(PATH_PASSWD_LOCAL_TMP, PATH_PASSWD_LOCAL) == -1)
352    {
353      fprintf(stderr,
354              "Error renaming %s to %s so not updating local passwd file.\n",
355              PATH_PASSWD_LOCAL, PATH_PASSWD_LOCAL_TMP);
356      unlink(PATH_PASSWD_LOCAL_TMP);
357      exit(1);
358    }
359}
360
361/* Read a line from a file into a dynamically allocated buffer,
362 * zeroing the trailing newline if there is one.  The calling routine
363 * may call read_line multiple times with the same buf and bufsize
364 * pointers; *buf will be reallocated and *bufsize adjusted as
365 * appropriate.  The initial value of *buf should be NULL.  After the
366 * calling routine is done reading lines, it should free *buf.  This
367 * function returns 0 if a line was successfully read, 1 if the file
368 * ended, and -1 if there was an I/O error or if it ran out of memory.
369 */
370
371static int read_line(FILE *fp, char **buf, int *bufsize)
372{
373  char *newbuf;
374  int offset = 0, len;
375
376  if (*buf == NULL)
377    {
378      *buf = malloc(128);
379      if (!*buf)
380        return -1;
381      *bufsize = 128;
382    }
383
384  while (1)
385    {
386      if (!fgets(*buf + offset, *bufsize - offset, fp))
387        return (offset != 0) ? 0 : (ferror(fp)) ? -1 : 1;
388      len = offset + strlen(*buf + offset);
389      if ((*buf)[len - 1] == '\n')
390        {
391          (*buf)[len - 1] = 0;
392          return 0;
393        }
394      offset = len;
395
396      /* Allocate more space. */
397      newbuf = realloc(*buf, *bufsize * 2);
398      if (!newbuf)
399        return -1;
400      *buf = newbuf;
401      *bufsize *= 2;
402    }
403}
404
405static void usage(void)
406{
407  fprintf(stderr, "Usage: passwd [-k|-l] [username]\n");
408  exit(1);
409}
410
411static void cleanup(void)
412{
413  unlink(PATH_PASSWD_LOCAL_TMP);
414  exit(1);
415}
Note: See TracBrowser for help on using the repository browser.