]> granicus.if.org Git - shadow/blob - src/vipw.c
* libmisc/console.c, libmisc/motd.c, libmisc/setupenv.c,
[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 - 2008, 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 static const char *progname, *filename, *fileeditname;
65 static bool filelocked = false;
66 static bool createedit = false;
67 static int (*unlock) (void);
68 static bool quiet = false;
69 #ifdef WITH_TCB
70 static const char *user = NULL;
71 static bool tcb_mode = false;
72 #define SHADOWTCB_SCRATCHDIR ":tmp"
73 #endif                          /* WITH_TCB */
74
75 /* local function prototypes */
76 static void usage (int status);
77 static int create_backup_file (FILE *, const char *, struct stat *);
78 static void vipwexit (const char *msg, int syserr, int ret);
79 static void vipwedit (const char *, int (*)(void), int (*)(void));
80
81 /*
82  * usage - display usage message and exit
83  */
84 static void usage (int status)
85 {
86         FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
87         (void) fputs (_("Usage: vipw [options]\n"
88                         "\n"
89                         "Options:\n"), usageout);
90         (void) fputs (_("  -g, --group                   edit group database\n"), usageout);
91         (void) fputs (_("  -h, --help                    display this help message and exit\n"), usageout);
92         (void) fputs (_("  -p, --passwd                  edit passwd database\n"), usageout);
93         (void) fputs (_("  -q, --quiet                   quiet mode\n"), usageout);
94         (void) fputs (_("  -s, --shadow                  edit shadow or gshadow database\n"), usageout);
95 #ifdef WITH_TCB
96         (void) fputs (_("  -u, --user                    which user's tcb shadow file to edit\n"), usageout);
97 #endif                          /* WITH_TCB */
98         (void) fputs (_("\n"), usageout);
99         exit (status);
100 }
101
102 /*
103  *
104  */
105 static int create_backup_file (FILE * fp, const char *backup, struct stat *sb)
106 {
107         struct utimbuf ub;
108         FILE *bkfp;
109         int c;
110         mode_t mask;
111
112         mask = umask (077);
113         bkfp = fopen (backup, "w");
114         (void) umask (mask);
115         if (NULL == bkfp) {
116                 return -1;
117         }
118
119         c = 0;
120         if (fseeko (fp, 0, SEEK_SET) == 0)
121                 while ((c = getc (fp)) != EOF) {
122                         if (putc (c, bkfp) == EOF) {
123                                 break;
124                         }
125                 }
126         if ((EOF != c) || (ferror (fp) != 0) || (fflush (bkfp) != 0)) {
127                 fclose (bkfp);
128                 unlink (backup);
129                 return -1;
130         }
131         if (fsync (fileno (bkfp)) != 0) {
132                 (void) fclose (bkfp);
133                 unlink (backup);
134                 return -1;
135         }
136         if (fclose (bkfp) != 0) {
137                 unlink (backup);
138                 return -1;
139         }
140
141         ub.actime = sb->st_atime;
142         ub.modtime = sb->st_mtime;
143         if (   (utime (backup, &ub) != 0)
144             || (chmod (backup, sb->st_mode) != 0)
145             || (chown (backup, sb->st_uid, sb->st_gid) != 0)) {
146                 unlink (backup);
147                 return -1;
148         }
149         return 0;
150 }
151
152 /*
153  *
154  */
155 static void vipwexit (const char *msg, int syserr, int ret)
156 {
157         int err = errno;
158
159         if (createedit) {
160                 if (unlink (fileeditname) != 0) {
161                         fprintf (stderr, _("%s: failed to remove %s\n"), progname, fileeditname);
162                         /* continue */
163                 }
164         }
165         if (filelocked) {
166                 if ((*unlock) () == 0) {
167                         fprintf (stderr, _("%s: failed to unlock %s\n"), progname, fileeditname);
168                         SYSLOG ((LOG_ERR, "failed to unlock %s", fileeditname));
169                         /* continue */
170                 }
171         }
172         if (NULL != msg) {
173                 fprintf (stderr, "%s: %s", progname, msg);
174         }
175         if (0 != syserr) {
176                 fprintf (stderr, ": %s", strerror (err));
177         }
178         (void) fputs ("\n", stderr);
179         if (!quiet) {
180                 fprintf (stdout, _("%s: %s is unchanged\n"), progname,
181                          filename);
182         }
183         exit (ret);
184 }
185
186 #ifndef DEFAULT_EDITOR
187 #define DEFAULT_EDITOR "vi"
188 #endif
189
190 /*
191  *
192  */
193 static void
194 vipwedit (const char *file, int (*file_lock) (void), int (*file_unlock) (void))
195 {
196         const char *editor;
197         pid_t pid;
198         struct stat st1, st2;
199         int status;
200         FILE *f;
201         /* FIXME: the following should have variable sizes */
202         char filebackup[1024], fileedit[1024];
203         char *to_rename;
204
205         snprintf (filebackup, sizeof filebackup, "%s-", file);
206 #ifdef WITH_TCB
207         if (tcb_mode) {
208                 if (   (mkdir (TCB_DIR "/" SHADOWTCB_SCRATCHDIR, 0700) != 0)
209                     && (errno != EEXIST)) {
210                         vipwexit (_("failed to create scratch directory"), errno, 1);
211                 }
212                 if (shadowtcb_drop_priv () == SHADOWTCB_FAILURE) {
213                         vipwexit (_("failed to drop privileges"), errno, 1);
214                 }
215                 snprintf (fileedit, sizeof fileedit,
216                           TCB_DIR "/" SHADOWTCB_SCRATCHDIR "/.vipw.shadow.%s",
217                           user);
218         } else {
219 #endif                          /* WITH_TCB */
220                 snprintf (fileedit, sizeof fileedit, "%s.edit", file);
221 #ifdef WITH_TCB
222         }
223 #endif                          /* WITH_TCB */
224         unlock = file_unlock;
225         filename = file;
226         fileeditname = fileedit;
227
228         if (access (file, F_OK) != 0) {
229                 vipwexit (file, 1, 1);
230         }
231 #ifdef WITH_SELINUX
232         /* if SE Linux is enabled then set the context of all new files
233            to be the context of the file we are editing */
234         if (is_selinux_enabled ()) {
235                 security_context_t passwd_context=NULL;
236                 int ret = 0;
237                 if (getfilecon (file, &passwd_context) < 0) {
238                         vipwexit (_("Couldn't get file context"), errno, 1);
239                 }
240                 ret = setfscreatecon (passwd_context);
241                 freecon (passwd_context);
242                 if (0 != ret) {
243                         vipwexit (_("setfscreatecon () failed"), errno, 1);
244                 }
245         }
246 #endif                          /* WITH_SELINUX */
247 #ifdef WITH_TCB
248         if (tcb_mode && (shadowtcb_gain_priv () == SHADOWTCB_FAILURE)) {
249                 vipwexit (_("failed to gain privileges"), errno, 1);
250         }
251 #endif                          /* WITH_TCB */
252         if (file_lock () == 0) {
253                 vipwexit (_("Couldn't lock file"), errno, 5);
254         }
255         filelocked = true;
256 #ifdef WITH_TCB
257         if (tcb_mode && (shadowtcb_drop_priv () == SHADOWTCB_FAILURE)) {
258                 vipwexit (_("failed to drop privileges"), errno, 1);
259         }
260 #endif                          /* WITH_TCB */
261
262         /* edited copy has same owners, perm */
263         if (stat (file, &st1) != 0) {
264                 vipwexit (file, 1, 1);
265         }
266         f = fopen (file, "r");
267         if (NULL == f) {
268                 vipwexit (file, 1, 1);
269         }
270 #ifdef WITH_TCB
271         if (tcb_mode && (shadowtcb_gain_priv () == SHADOWTCB_FAILURE))
272                 vipwexit (_("failed to gain privileges"), errno, 1);
273 #endif                          /* WITH_TCB */
274         if (create_backup_file (f, fileedit, &st1) != 0) {
275                 vipwexit (_("Couldn't make backup"), errno, 1);
276         }
277         (void) fclose (f);
278         createedit = true;
279
280         editor = getenv ("VISUAL");
281         if (NULL == editor) {
282                 editor = getenv ("EDITOR");
283         }
284         if (NULL == editor) {
285                 editor = DEFAULT_EDITOR;
286         }
287
288         pid = fork ();
289         if (-1 == pid) {
290                 vipwexit ("fork", 1, 1);
291         } else if (0 == pid) {
292                 /* use the system() call to invoke the editor so that it accepts
293                    command line args in the EDITOR and VISUAL environment vars */
294                 char *buf;
295
296                 buf = (char *) malloc (strlen (editor) + strlen (fileedit) + 2);
297                 snprintf (buf, strlen (editor) + strlen (fileedit) + 2,
298                           "%s %s", editor, fileedit);
299                 if (system (buf) != 0) {
300                         fprintf (stderr, "%s: %s: %s\n", progname, editor,
301                                  strerror (errno));
302                         exit (1);
303                 } else {
304                         exit (0);
305                 }
306         }
307
308         for (;;) {
309                 pid = waitpid (pid, &status, WUNTRACED);
310                 if ((pid != -1) && (WIFSTOPPED (status) != 0)) {
311                         /* The child (editor) was suspended.
312                          * Suspend vipw. */
313                         kill (getpid (), SIGSTOP);
314                         /* wake child when resumed */
315                         kill (pid, SIGCONT);
316                 } else {
317                         break;
318                 }
319         }
320
321         if (   (-1 == pid)
322             || (WIFEXITED (status) == 0)
323             || (WEXITSTATUS (status) != 0)) {
324                 vipwexit (editor, 1, 1);
325         }
326
327         if (stat (fileedit, &st2) != 0) {
328                 vipwexit (fileedit, 1, 1);
329         }
330         if (st1.st_mtime == st2.st_mtime) {
331                 vipwexit (0, 0, 0);
332         }
333 #ifdef WITH_SELINUX
334         /* unset the fscreatecon */
335         if (is_selinux_enabled ()) {
336                 if (setfscreatecon (NULL)) {
337                         vipwexit (_("setfscreatecon () failed"), errno, 1);
338                 }
339         }
340 #endif                          /* WITH_SELINUX */
341
342         /*
343          * XXX - here we should check fileedit for errors; if there are any,
344          * ask the user what to do (edit again, save changes anyway, or quit
345          * without saving). Use pwck or grpck to do the check.  --marekm
346          */
347         createedit = false;
348 #ifdef WITH_TCB
349         if (tcb_mode) {
350                 f = fopen (fileedit, "r");
351                 if (NULL == f) {
352                         vipwexit (_("failed to open scratch file"), errno, 1);
353                 }
354                 if (unlink (fileedit) != 0) {
355                         vipwexit (_("failed to unlink scratch file"), errno, 1);
356                 }
357                 if (shadowtcb_drop_priv () == SHADOWTCB_FAILURE) {
358                         vipwexit (_("failed to drop privileges"), errno, 1);
359                 }
360                 if (stat (file, &st1) != 0) {
361                         vipwexit (_("failed to stat edited file"), errno, 1);
362                 }
363                 to_rename = malloc (strlen (file) + 2);
364                 if (NULL == to_rename) {
365                         vipwexit (_("failed to allocate memory"), errno, 1);
366                 }
367                 snprintf (to_rename, strlen (file) + 2, "%s+", file);
368                 if (create_backup_file (f, to_rename, &st1) != 0) {
369                         free (to_rename);
370                         vipwexit (_("failed to create backup file"), errno, 1);
371                 }
372                 (void) fclose (f);
373         } else {
374 #endif                          /* WITH_TCB */
375                 to_rename = fileedit;
376 #ifdef WITH_TCB
377         }
378 #endif                          /* WITH_TCB */
379         unlink (filebackup);
380         link (file, filebackup);
381         if (rename (to_rename, file) == -1) {
382                 fprintf (stderr,
383                          _("%s: can't restore %s: %s (your changes are in %s)\n"),
384                          progname, file, strerror (errno), to_rename);
385 #ifdef WITH_TCB
386                 if (tcb_mode) {
387                         free (to_rename);
388                 }
389 #endif                          /* WITH_TCB */
390                 vipwexit (0, 0, 1);
391         }
392
393 #ifdef WITH_TCB
394         if (tcb_mode) {
395                 free (to_rename);
396                 if (shadowtcb_gain_priv () == SHADOWTCB_FAILURE) {
397                         vipwexit (_("failed to gain privileges"), errno, 1);
398                 }
399         }
400 #endif                          /* WITH_TCB */
401
402         if ((*file_unlock) () == 0) {
403                 fprintf (stderr, _("%s: failed to unlock %s\n"), progname, fileeditname);
404                 SYSLOG ((LOG_ERR, "failed to unlock %s", fileeditname));
405                 /* continue */
406         }
407         SYSLOG ((LOG_INFO, "file %s edited", fileeditname));
408 }
409
410 int main (int argc, char **argv)
411 {
412         bool editshadow = false;
413         char *a;
414         bool do_vipw;
415
416         (void) setlocale (LC_ALL, "");
417         (void) bindtextdomain (PACKAGE, LOCALEDIR);
418         (void) textdomain (PACKAGE);
419
420         progname = ((a = strrchr (*argv, '/')) ? a + 1 : *argv);
421         do_vipw = (strcmp (progname, "vigr") != 0);
422
423         OPENLOG (do_vipw ? "vipw" : "vigr");
424
425         {
426                 /*
427                  * Parse the command line options.
428                  */
429                 int c;
430                 static struct option long_options[] = {
431                         {"group", no_argument, NULL, 'g'},
432                         {"help", no_argument, NULL, 'h'},
433                         {"passwd", no_argument, NULL, 'p'},
434                         {"quiet", no_argument, NULL, 'q'},
435                         {"shadow", no_argument, NULL, 's'},
436 #ifdef WITH_TCB
437                         {"user", required_argument, NULL, 'u'},
438 #endif                          /* WITH_TCB */
439                         {NULL, 0, NULL, '\0'}
440                 };
441                 while ((c = getopt_long (argc, argv,
442 #ifdef WITH_TCB
443                                          "ghpqsu:",
444 #else                           /* !WITH_TCB */
445                                          "ghpqs",
446 #endif                          /* !WITH_TCB */
447                                          long_options, NULL)) != -1) {
448                         switch (c) {
449                         case 'g':
450                                 do_vipw = false;
451                                 break;
452                         case 'h':
453                                 usage (E_SUCCESS);
454                                 break;
455                         case 'p':
456                                 do_vipw = true;
457                                 break;
458                         case 'q':
459                                 quiet = true;
460                                 break;
461                         case 's':
462                                 editshadow = true;
463                                 break;
464 #ifdef WITH_TCB
465                         case 'u':
466                                 user = optarg;
467                                 break;
468 #endif                          /* WITH_TCB */
469                         default:
470                                 usage (E_USAGE);
471                         }
472                 }
473         }
474
475         if (do_vipw) {
476                 if (editshadow) {
477 #ifdef WITH_TCB
478                         if (getdef_bool ("USE_TCB") && (NULL != user)) {
479                                 if (shadowtcb_set_user (user) == SHADOWTCB_FAILURE) {
480                                         fprintf (stderr,
481                                                  _("%s: failed to find tcb directory for %s\n"),
482                                                  progname, user);
483                                         return E_SHADOW_NOTFOUND;
484                                 }
485                                 tcb_mode = true;
486                         }
487 #endif                          /* WITH_TCB */
488                         vipwedit (spw_dbname (), spw_lock, spw_unlock);
489                         printf (MSG_WARN_EDIT_OTHER_FILE,
490                                 spw_dbname (),
491                                 pw_dbname (),
492                                 "vipw");
493                 } else {
494                         vipwedit (pw_dbname (), pw_lock, pw_unlock);
495                         if (spw_file_present ()) {
496                                 printf (MSG_WARN_EDIT_OTHER_FILE,
497                                         pw_dbname (),
498                                         spw_dbname (),
499                                         "vipw -s");
500                         }
501                 }
502         } else {
503 #ifdef SHADOWGRP
504                 if (editshadow) {
505                         vipwedit (sgr_dbname (), sgr_lock, sgr_unlock);
506                         printf (MSG_WARN_EDIT_OTHER_FILE,
507                                 sgr_dbname (),
508                                 gr_dbname (),
509                                 "vigr");
510                 } else {
511 #endif                          /* SHADOWGRP */
512                         vipwedit (gr_dbname (), gr_lock, gr_unlock);
513 #ifdef SHADOWGRP
514                         if (sgr_file_present ()) {
515                                 printf (MSG_WARN_EDIT_OTHER_FILE,
516                                         gr_dbname (),
517                                         sgr_dbname (),
518                                         "vigr -s");
519                         }
520                 }
521 #endif                          /* SHADOWGRP */
522         }
523
524         nscd_flush_cache ("passwd");
525         nscd_flush_cache ("group");
526
527         return E_SUCCESS;
528 }
529