]> granicus.if.org Git - shadow/blob - src/vipw.c
* src/chfn.c, man/chfn.1.xml: Add support for long options.
[shadow] / src / vipw.c
1 /*
2   vipw, vigr  edit the password or group file
3   with -s will edit shadow or gshadow file
4
5   Copyright (c) 1997       , Guy Maor <maor@ece.utexas.edu>
6   Copyright (c) 1999 - 2000, Marek Michałkiewicz
7   Copyright (c) 2002 - 2006, Tomasz Kłoczko
8   Copyright (c) 2007 - 2011, Nicolas François
9   All rights reserved.
10
11   This program is free software; you can redistribute it and/or modify
12   it under the terms of the GNU General Public License as published by
13   the Free Software Foundation; either version 2 of the License, or
14   (at your option) any later version.
15
16   This program is distributed in the hope that it will be useful, but
17   WITHOUT ANY WARRANTY; without even the implied warranty of
18   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19   General Public License for more details.
20
21   You should have received a copy of the GNU General Public License
22   along with this program; if not, write to the Free Software
23   Foundation, Inc., 51 Franklin Street, Fifth Floor,
24   Boston, MA 02110-1301, USA.  */
25
26 #include <config.h>
27
28 #ident "$Id$"
29
30 #include <errno.h>
31 #include <getopt.h>
32 #ifdef WITH_SELINUX
33 #include <selinux/selinux.h>
34 #endif                          /* WITH_SELINUX */
35 #include <signal.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <unistd.h>
41 #include <utime.h>
42 #include "defines.h"
43 #include "groupio.h"
44 #include "nscd.h"
45 #include "prototypes.h"
46 #include "pwio.h"
47 #include "sgroupio.h"
48 #include "shadowio.h"
49 /*@-exitarg@*/
50 #include "exitcodes.h"
51 #ifdef WITH_TCB
52 #include <tcb.h>
53 #include "tcbfuncs.h"
54 #endif                          /* WITH_TCB */
55
56 #define MSG_WARN_EDIT_OTHER_FILE _( \
57         "You have modified %s.\n"\
58         "You may need to modify %s for consistency.\n"\
59         "Please use the command '%s' to do so.\n")
60
61 /*
62  * Global variables
63  */
64 const char *Prog;
65
66 static const char *filename, *fileeditname;
67 static bool filelocked = false;
68 static bool createedit = false;
69 static int (*unlock) (void);
70 static bool quiet = false;
71 #ifdef WITH_TCB
72 static const char *user = NULL;
73 static bool tcb_mode = false;
74 #define SHADOWTCB_SCRATCHDIR ":tmp"
75 #endif                          /* WITH_TCB */
76
77 /* local function prototypes */
78 static void usage (int status);
79 static int create_backup_file (FILE *, const char *, struct stat *);
80 static void vipwexit (const char *msg, int syserr, int ret);
81 static void vipwedit (const char *, int (*)(void), int (*)(void));
82
83 /*
84  * usage - display usage message and exit
85  */
86 static void usage (int status)
87 {
88         FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
89         (void) fputs (_("Usage: vipw [options]\n"
90                         "\n"
91                         "Options:\n"), usageout);
92         (void) fputs (_("  -g, --group                   edit group database\n"), usageout);
93         (void) fputs (_("  -h, --help                    display this help message and exit\n"), usageout);
94         (void) fputs (_("  -p, --passwd                  edit passwd database\n"), usageout);
95         (void) fputs (_("  -q, --quiet                   quiet mode\n"), usageout);
96         (void) fputs (_("  -R, --root CHROOT_DIR         directory to chroot into\n"), usageout);
97         (void) fputs (_("  -s, --shadow                  edit shadow or gshadow database\n"), usageout);
98 #ifdef WITH_TCB
99         (void) fputs (_("  -u, --user                    which user's tcb shadow file to edit\n"), usageout);
100 #endif                          /* WITH_TCB */
101         (void) fputs (_("\n"), usageout);
102         exit (status);
103 }
104
105 /*
106  *
107  */
108 static int create_backup_file (FILE * fp, const char *backup, struct stat *sb)
109 {
110         struct utimbuf ub;
111         FILE *bkfp;
112         int c;
113         mode_t mask;
114
115         mask = umask (077);
116         bkfp = fopen (backup, "w");
117         (void) umask (mask);
118         if (NULL == bkfp) {
119                 return -1;
120         }
121
122         c = 0;
123         if (fseeko (fp, 0, SEEK_SET) == 0)
124                 while ((c = getc (fp)) != EOF) {
125                         if (putc (c, bkfp) == EOF) {
126                                 break;
127                         }
128                 }
129         if ((EOF != c) || (ferror (fp) != 0) || (fflush (bkfp) != 0)) {
130                 fclose (bkfp);
131                 unlink (backup);
132                 return -1;
133         }
134         if (fsync (fileno (bkfp)) != 0) {
135                 (void) fclose (bkfp);
136                 unlink (backup);
137                 return -1;
138         }
139         if (fclose (bkfp) != 0) {
140                 unlink (backup);
141                 return -1;
142         }
143
144         ub.actime = sb->st_atime;
145         ub.modtime = sb->st_mtime;
146         if (   (utime (backup, &ub) != 0)
147             || (chmod (backup, sb->st_mode) != 0)
148             || (chown (backup, sb->st_uid, sb->st_gid) != 0)) {
149                 unlink (backup);
150                 return -1;
151         }
152         return 0;
153 }
154
155 /*
156  *
157  */
158 static void vipwexit (const char *msg, int syserr, int ret)
159 {
160         int err = errno;
161
162         if (createedit) {
163                 if (unlink (fileeditname) != 0) {
164                         fprintf (stderr, _("%s: failed to remove %s\n"), Prog, fileeditname);
165                         /* continue */
166                 }
167         }
168         if (filelocked) {
169                 if ((*unlock) () == 0) {
170                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, fileeditname);
171                         SYSLOG ((LOG_ERR, "failed to unlock %s", fileeditname));
172                         /* continue */
173                 }
174         }
175         if (NULL != msg) {
176                 fprintf (stderr, "%s: %s", Prog, msg);
177         }
178         if (0 != syserr) {
179                 fprintf (stderr, ": %s", strerror (err));
180         }
181         (void) fputs ("\n", stderr);
182         if (!quiet) {
183                 fprintf (stdout, _("%s: %s is unchanged\n"), Prog,
184                          filename);
185         }
186         exit (ret);
187 }
188
189 #ifndef DEFAULT_EDITOR
190 #define DEFAULT_EDITOR "vi"
191 #endif
192
193 /*
194  *
195  */
196 static void
197 vipwedit (const char *file, int (*file_lock) (void), int (*file_unlock) (void))
198 {
199         const char *editor;
200         pid_t pid;
201         struct stat st1, st2;
202         int status;
203         FILE *f;
204         /* FIXME: the following should have variable sizes */
205         char filebackup[1024], fileedit[1024];
206         char *to_rename;
207
208         snprintf (filebackup, sizeof filebackup, "%s-", file);
209 #ifdef WITH_TCB
210         if (tcb_mode) {
211                 if (   (mkdir (TCB_DIR "/" SHADOWTCB_SCRATCHDIR, 0700) != 0)
212                     && (errno != EEXIST)) {
213                         vipwexit (_("failed to create scratch directory"), errno, 1);
214                 }
215                 if (shadowtcb_drop_priv () == SHADOWTCB_FAILURE) {
216                         vipwexit (_("failed to drop privileges"), errno, 1);
217                 }
218                 snprintf (fileedit, sizeof fileedit,
219                           TCB_DIR "/" SHADOWTCB_SCRATCHDIR "/.vipw.shadow.%s",
220                           user);
221         } else {
222 #endif                          /* WITH_TCB */
223                 snprintf (fileedit, sizeof fileedit, "%s.edit", file);
224 #ifdef WITH_TCB
225         }
226 #endif                          /* WITH_TCB */
227         unlock = file_unlock;
228         filename = file;
229         fileeditname = fileedit;
230
231         if (access (file, F_OK) != 0) {
232                 vipwexit (file, 1, 1);
233         }
234 #ifdef WITH_SELINUX
235         /* if SE Linux is enabled then set the context of all new files
236            to be the context of the file we are editing */
237         if (is_selinux_enabled () != 0) {
238                 security_context_t passwd_context=NULL;
239                 int ret = 0;
240                 if (getfilecon (file, &passwd_context) < 0) {
241                         vipwexit (_("Couldn't get file context"), errno, 1);
242                 }
243                 ret = setfscreatecon (passwd_context);
244                 freecon (passwd_context);
245                 if (0 != ret) {
246                         vipwexit (_("setfscreatecon () failed"), errno, 1);
247                 }
248         }
249 #endif                          /* WITH_SELINUX */
250 #ifdef WITH_TCB
251         if (tcb_mode && (shadowtcb_gain_priv () == SHADOWTCB_FAILURE)) {
252                 vipwexit (_("failed to gain privileges"), errno, 1);
253         }
254 #endif                          /* WITH_TCB */
255         if (file_lock () == 0) {
256                 vipwexit (_("Couldn't lock file"), errno, 5);
257         }
258         filelocked = true;
259 #ifdef WITH_TCB
260         if (tcb_mode && (shadowtcb_drop_priv () == SHADOWTCB_FAILURE)) {
261                 vipwexit (_("failed to drop privileges"), errno, 1);
262         }
263 #endif                          /* WITH_TCB */
264
265         /* edited copy has same owners, perm */
266         if (stat (file, &st1) != 0) {
267                 vipwexit (file, 1, 1);
268         }
269         f = fopen (file, "r");
270         if (NULL == f) {
271                 vipwexit (file, 1, 1);
272         }
273 #ifdef WITH_TCB
274         if (tcb_mode && (shadowtcb_gain_priv () == SHADOWTCB_FAILURE))
275                 vipwexit (_("failed to gain privileges"), errno, 1);
276 #endif                          /* WITH_TCB */
277         if (create_backup_file (f, fileedit, &st1) != 0) {
278                 vipwexit (_("Couldn't make backup"), errno, 1);
279         }
280         (void) fclose (f);
281         createedit = true;
282
283         editor = getenv ("VISUAL");
284         if (NULL == editor) {
285                 editor = getenv ("EDITOR");
286         }
287         if (NULL == editor) {
288                 editor = DEFAULT_EDITOR;
289         }
290
291         pid = fork ();
292         if (-1 == pid) {
293                 vipwexit ("fork", 1, 1);
294         } else if (0 == pid) {
295                 /* use the system() call to invoke the editor so that it accepts
296                    command line args in the EDITOR and VISUAL environment vars */
297                 char *buf;
298
299                 buf = (char *) malloc (strlen (editor) + strlen (fileedit) + 2);
300                 snprintf (buf, strlen (editor) + strlen (fileedit) + 2,
301                           "%s %s", editor, fileedit);
302                 if (system (buf) != 0) {
303                         fprintf (stderr, "%s: %s: %s\n", Prog, editor,
304                                  strerror (errno));
305                         exit (1);
306                 } else {
307                         exit (0);
308                 }
309         }
310
311         for (;;) {
312                 pid = waitpid (pid, &status, WUNTRACED);
313                 if ((pid != -1) && (WIFSTOPPED (status) != 0)) {
314                         /* The child (editor) was suspended.
315                          * Suspend vipw. */
316                         kill (getpid (), SIGSTOP);
317                         /* wake child when resumed */
318                         kill (pid, SIGCONT);
319                 } else {
320                         break;
321                 }
322         }
323
324         if (   (-1 == pid)
325             || (WIFEXITED (status) == 0)
326             || (WEXITSTATUS (status) != 0)) {
327                 vipwexit (editor, 1, 1);
328         }
329
330         if (stat (fileedit, &st2) != 0) {
331                 vipwexit (fileedit, 1, 1);
332         }
333         if (st1.st_mtime == st2.st_mtime) {
334                 vipwexit (0, 0, 0);
335         }
336 #ifdef WITH_SELINUX
337         /* unset the fscreatecon */
338         if (is_selinux_enabled () != 0) {
339                 if (setfscreatecon (NULL) != 0) {
340                         vipwexit (_("setfscreatecon () failed"), errno, 1);
341                 }
342         }
343 #endif                          /* WITH_SELINUX */
344
345         /*
346          * XXX - here we should check fileedit for errors; if there are any,
347          * ask the user what to do (edit again, save changes anyway, or quit
348          * without saving). Use pwck or grpck to do the check.  --marekm
349          */
350         createedit = false;
351 #ifdef WITH_TCB
352         if (tcb_mode) {
353                 f = fopen (fileedit, "r");
354                 if (NULL == f) {
355                         vipwexit (_("failed to open scratch file"), errno, 1);
356                 }
357                 if (unlink (fileedit) != 0) {
358                         vipwexit (_("failed to unlink scratch file"), errno, 1);
359                 }
360                 if (shadowtcb_drop_priv () == SHADOWTCB_FAILURE) {
361                         vipwexit (_("failed to drop privileges"), errno, 1);
362                 }
363                 if (stat (file, &st1) != 0) {
364                         vipwexit (_("failed to stat edited file"), errno, 1);
365                 }
366                 to_rename = malloc (strlen (file) + 2);
367                 if (NULL == to_rename) {
368                         vipwexit (_("failed to allocate memory"), errno, 1);
369                 }
370                 snprintf (to_rename, strlen (file) + 2, "%s+", file);
371                 if (create_backup_file (f, to_rename, &st1) != 0) {
372                         free (to_rename);
373                         vipwexit (_("failed to create backup file"), errno, 1);
374                 }
375                 (void) fclose (f);
376         } else {
377 #endif                          /* WITH_TCB */
378                 to_rename = fileedit;
379 #ifdef WITH_TCB
380         }
381 #endif                          /* WITH_TCB */
382         unlink (filebackup);
383         link (file, filebackup);
384         if (rename (to_rename, file) == -1) {
385                 fprintf (stderr,
386                          _("%s: can't restore %s: %s (your changes are in %s)\n"),
387                          Prog, file, strerror (errno), to_rename);
388 #ifdef WITH_TCB
389                 if (tcb_mode) {
390                         free (to_rename);
391                 }
392 #endif                          /* WITH_TCB */
393                 vipwexit (0, 0, 1);
394         }
395
396 #ifdef WITH_TCB
397         if (tcb_mode) {
398                 free (to_rename);
399                 if (shadowtcb_gain_priv () == SHADOWTCB_FAILURE) {
400                         vipwexit (_("failed to gain privileges"), errno, 1);
401                 }
402         }
403 #endif                          /* WITH_TCB */
404
405         if ((*file_unlock) () == 0) {
406                 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, fileeditname);
407                 SYSLOG ((LOG_ERR, "failed to unlock %s", fileeditname));
408                 /* continue */
409         }
410         SYSLOG ((LOG_INFO, "file %s edited", fileeditname));
411 }
412
413 int main (int argc, char **argv)
414 {
415         bool editshadow = false;
416         char *a;
417         bool do_vipw;
418
419         Prog = Basename (argv[0]);
420
421         (void) setlocale (LC_ALL, "");
422         (void) bindtextdomain (PACKAGE, LOCALEDIR);
423         (void) textdomain (PACKAGE);
424
425         process_root_flag ("-R", argc, argv);
426
427         do_vipw = (strcmp (Prog, "vigr") != 0);
428
429         OPENLOG (do_vipw ? "vipw" : "vigr");
430
431         {
432                 /*
433                  * Parse the command line options.
434                  */
435                 int c;
436                 static struct option long_options[] = {
437                         {"group", no_argument, NULL, 'g'},
438                         {"help", no_argument, NULL, 'h'},
439                         {"passwd", no_argument, NULL, 'p'},
440                         {"quiet", no_argument, NULL, 'q'},
441                         {"root", required_argument, NULL, 'R'},
442                         {"shadow", no_argument, NULL, 's'},
443 #ifdef WITH_TCB
444                         {"user", required_argument, NULL, 'u'},
445 #endif                          /* WITH_TCB */
446                         {NULL, 0, NULL, '\0'}
447                 };
448                 while ((c = getopt_long (argc, argv,
449 #ifdef WITH_TCB
450                                          "ghpqR:su:",
451 #else                           /* !WITH_TCB */
452                                          "ghpqR:s",
453 #endif                          /* !WITH_TCB */
454                                          long_options, NULL)) != -1) {
455                         switch (c) {
456                         case 'g':
457                                 do_vipw = false;
458                                 break;
459                         case 'h':
460                                 usage (E_SUCCESS);
461                                 break;
462                         case 'p':
463                                 do_vipw = true;
464                                 break;
465                         case 'q':
466                                 quiet = true;
467                                 break;
468                         case 'R': /* no-op, handled in process_root_flag () */
469                                 break;
470                         case 's':
471                                 editshadow = true;
472                                 break;
473 #ifdef WITH_TCB
474                         case 'u':
475                                 user = optarg;
476                                 break;
477 #endif                          /* WITH_TCB */
478                         default:
479                                 usage (E_USAGE);
480                         }
481                 }
482         }
483
484         if (do_vipw) {
485                 if (editshadow) {
486 #ifdef WITH_TCB
487                         if (getdef_bool ("USE_TCB") && (NULL != user)) {
488                                 if (shadowtcb_set_user (user) == SHADOWTCB_FAILURE) {
489                                         fprintf (stderr,
490                                                  _("%s: failed to find tcb directory for %s\n"),
491                                                  Prog, user);
492                                         return E_SHADOW_NOTFOUND;
493                                 }
494                                 tcb_mode = true;
495                         }
496 #endif                          /* WITH_TCB */
497                         vipwedit (spw_dbname (), spw_lock, spw_unlock);
498                         printf (MSG_WARN_EDIT_OTHER_FILE,
499                                 spw_dbname (),
500                                 pw_dbname (),
501                                 "vipw");
502                 } else {
503                         vipwedit (pw_dbname (), pw_lock, pw_unlock);
504                         if (spw_file_present ()) {
505                                 printf (MSG_WARN_EDIT_OTHER_FILE,
506                                         pw_dbname (),
507                                         spw_dbname (),
508                                         "vipw -s");
509                         }
510                 }
511         } else {
512 #ifdef SHADOWGRP
513                 if (editshadow) {
514                         vipwedit (sgr_dbname (), sgr_lock, sgr_unlock);
515                         printf (MSG_WARN_EDIT_OTHER_FILE,
516                                 sgr_dbname (),
517                                 gr_dbname (),
518                                 "vigr");
519                 } else {
520 #endif                          /* SHADOWGRP */
521                         vipwedit (gr_dbname (), gr_lock, gr_unlock);
522 #ifdef SHADOWGRP
523                         if (sgr_file_present ()) {
524                                 printf (MSG_WARN_EDIT_OTHER_FILE,
525                                         gr_dbname (),
526                                         sgr_dbname (),
527                                         "vigr -s");
528                         }
529                 }
530 #endif                          /* SHADOWGRP */
531         }
532
533         nscd_flush_cache ("passwd");
534         nscd_flush_cache ("group");
535
536         return E_SUCCESS;
537 }
538