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

Revision 15305, 11.2 KB checked in by ghudson, 24 years ago (diff)
The Linux /usr/bin/passwd doesn't like non-root users specifying a username even if it's the same as the running user. So don't pass a username if the running user is not root and a username was not specified on the command line.
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.14 2000-11-19 06:46:13 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(int sig);
68
69int main(int argc, char **argv)
70{
71  extern int optind;
72  int c, local = 0, krb = 0, rval, status, n;
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 > 0) ? 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          n = 0;
177          args[n++] = "passwd";
178#ifdef PASSWD_NEEDS_LFLAG
179          /* Some passwd programs need a -l flag to specify the local
180           * password.
181           */
182          args[n++] = "-l";
183#endif
184          /* Pass a username if we're running as root or if one was
185           * given on the command line.
186           */
187          if (ruid == 0 || argc > 0)
188            args[n++] = username;
189          args[n] = NULL;
190          execv(PATH_PASSWD_PROG, args);
191          perror("passwd: execv");
192          _exit(1);
193          /* gcc (2.95 at least) doesn't realize that _exit() exits. */
194          return 1;
195        }
196      else
197        {
198          /* Wait for the child to complete. */
199          while ((rval = waitpid(pid, &status, 0)) == -1 && errno == EINTR)
200            ;
201          if (rval == -1)
202            {
203              perror("passwd: wait");
204              return 1;
205            }
206          /* If the child exited abnormally, assume that it printed an
207           * error message.
208           */
209          if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
210            return 1;
211
212          update_passwd_local(username);
213          return 0;
214        }
215    }
216  else
217    {
218      printf("Running Kerberos password-changing program.\n");
219      setuid(ruid);
220      args[0] = "kpasswd";
221      if (*argv)
222        {
223          args[1] = "-n";
224          args[2] = *argv;
225          args[3] = NULL;
226        }
227      else
228        args[1] = NULL;
229      execv(PATH_KPASSWD_PROG, args);
230      perror("passwd: execv");
231      return 1;
232    }
233}
234
235static void update_passwd_local(const char *username)
236{
237  FILE *fp, *fp_out;
238  char *line = NULL, *userline;
239  int linesize, len, found, fd, i, status;
240  struct sigaction action;
241  sigset_t mask, omask;
242  mode_t oldumask;
243
244  len = strlen(username);
245
246  /* Find the line for username in the passwd file. */
247  fp = fopen(PATH_PASSWD, "r");
248  if (fp == NULL)
249    {
250      fprintf(stderr, "Can't open %s so not updating local passwd file.\n",
251              PATH_PASSWD);
252      exit(1);
253    }
254  found = 0;
255  while (read_line(fp, &line, &linesize) == 0)
256    {
257      if (strncmp(line, username, len) == 0 && line[len] == ':')
258        {
259          found = 1;
260          break;
261        }
262    }
263  if (!found)
264    {
265      fprintf(stderr,
266              "Can't find %s in %s so not updating local passwd file.\n",
267              username, PATH_PASSWD);
268      exit(1);
269    }
270  fclose(fp);
271  userline = line;
272  line = NULL;
273
274  /* Open the local passwd file for reading. */
275  fp = fopen(PATH_PASSWD_LOCAL, "r");
276  if (fp == NULL)
277    {
278      if (errno != ENOENT)
279        fprintf(stderr, "Can't read %s so not updating local passwd file.\n",
280                PATH_PASSWD_LOCAL);
281      exit(1);
282    }
283
284  sigemptyset(&mask);
285  sigaddset(&mask, SIGHUP);
286  sigaddset(&mask, SIGINT);
287  sigaddset(&mask, SIGQUIT);
288  sigaddset(&mask, SIGTERM);
289
290  /* Open the temporary local passwd file for writing.  We have to do some
291   * clever signal-handling tricks to make sure that tty signals don't
292   * leave the lock file hanging around.
293   */
294  for (i = 0; i < 10; i++)
295    {
296      sigprocmask(SIG_BLOCK, &mask, &omask);
297      oldumask = umask(0);
298      fd = open(PATH_PASSWD_LOCAL_TMP, O_RDWR|O_CREAT|O_EXCL, PLTMP_MODE);
299      umask(oldumask);
300      if (fd != -1)
301        {
302          sigemptyset(&action.sa_mask);
303          action.sa_handler = cleanup;
304          action.sa_flags = 0;
305          sigaction(SIGHUP, &action, NULL);
306          sigaction(SIGINT, &action, NULL);
307          sigaction(SIGQUIT, &action, NULL);
308          sigaction(SIGTERM, &action, NULL);
309        }
310      sigprocmask(SIG_SETMASK, &omask, NULL);
311      if (fd != -1 || errno != EEXIST)
312        break;
313      sleep(1);
314    }
315  if (fd == -1 || (fp_out = fdopen(fd, "w")) == NULL)
316    {
317      fprintf(stderr,
318              "Can't open %s for writing so not updating local passwd file.\n",
319              PATH_PASSWD_LOCAL_TMP);
320      if (fd != -1)
321        {
322          sigprocmask(SIG_BLOCK, &mask, NULL);
323          unlink(PATH_PASSWD_LOCAL_TMP);
324        }
325      exit(1);
326    }
327
328  /* Copy the local passwd file to the temporary file.  Replace the first
329   * line beginning with username with the line we found in the passwd
330   * file.
331   */
332  found = 0;
333  while ((status = read_line(fp, &line, &linesize)) == 0)
334    {
335      if (!found && strncmp(line, username, len) == 0 && line[len] == ':')
336        {
337          fputs(userline, fp_out);
338          found = 1;
339        }
340      else
341        fputs(line, fp_out);
342      putc('\n', fp_out);
343    }
344  free(line);
345  free(userline);
346  fclose(fp);
347
348  /* Block tty signals for the short duration of our lifetime so we don't
349   * erroneously delete the temporary file after giving it up.
350   */
351  sigprocmask(SIG_BLOCK, &mask, NULL);
352
353  if (!found)
354    {
355      /* We didn't actually change the file; don't do an update. */
356      fclose(fp_out);
357      unlink(PATH_PASSWD_LOCAL_TMP);
358      return;
359    }
360
361  if (status < 0 || ferror(fp_out) || fclose(fp_out) == EOF)
362    {
363      fprintf(stderr,
364              "Error copying %s to %s so not updating local passwd file.\n",
365              PATH_PASSWD_LOCAL, PATH_PASSWD_LOCAL_TMP);
366      unlink(PATH_PASSWD_LOCAL_TMP);
367      exit(1);
368    }
369
370  /* Replace the local passwd file with the temporary file. */
371  printf("Updating %s with new passwd entry.\n", PATH_PASSWD_LOCAL);
372  if (rename(PATH_PASSWD_LOCAL_TMP, PATH_PASSWD_LOCAL) == -1)
373    {
374      fprintf(stderr,
375              "Error renaming %s to %s so not updating local passwd file.\n",
376              PATH_PASSWD_LOCAL, PATH_PASSWD_LOCAL_TMP);
377      unlink(PATH_PASSWD_LOCAL_TMP);
378      exit(1);
379    }
380}
381
382/* Read a line from a file into a dynamically allocated buffer,
383 * zeroing the trailing newline if there is one.  The calling routine
384 * may call read_line multiple times with the same buf and bufsize
385 * pointers; *buf will be reallocated and *bufsize adjusted as
386 * appropriate.  The initial value of *buf should be NULL.  After the
387 * calling routine is done reading lines, it should free *buf.  This
388 * function returns 0 if a line was successfully read, 1 if the file
389 * ended, and -1 if there was an I/O error or if it ran out of memory.
390 */
391
392static int read_line(FILE *fp, char **buf, int *bufsize)
393{
394  char *newbuf;
395  int offset = 0, len;
396
397  if (*buf == NULL)
398    {
399      *buf = malloc(128);
400      if (!*buf)
401        return -1;
402      *bufsize = 128;
403    }
404
405  while (1)
406    {
407      if (!fgets(*buf + offset, *bufsize - offset, fp))
408        return (offset != 0) ? 0 : (ferror(fp)) ? -1 : 1;
409      len = offset + strlen(*buf + offset);
410      if ((*buf)[len - 1] == '\n')
411        {
412          (*buf)[len - 1] = 0;
413          return 0;
414        }
415      offset = len;
416
417      /* Allocate more space. */
418      newbuf = realloc(*buf, *bufsize * 2);
419      if (!newbuf)
420        return -1;
421      *buf = newbuf;
422      *bufsize *= 2;
423    }
424}
425
426static void usage(void)
427{
428  fprintf(stderr, "Usage: passwd [-k|-l] [username]\n");
429  exit(1);
430}
431
432static void cleanup(int sig)
433{
434  unlink(PATH_PASSWD_LOCAL_TMP);
435  exit(1);
436}
Note: See TracBrowser for help on using the repository browser.