/*
  vipw, vigr  edit the password or group file
  with -s will edit shadow or gshadow file
 
  Copyright (C) 1997 Guy Maor <maor@ece.utexas.edu>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

  */

#include <config.h>
#include "defines.h"

#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <utime.h>
#include "prototypes.h"
#include "pwio.h"
#include "shadowio.h"
#include "groupio.h"
#include "sgroupio.h"


static const char *progname, *filename, *fileeditname;
static int filelocked = 0, createedit = 0;
static int (*unlock)();

/* local function prototypes */
static int create_backup_file P_((FILE *, const char *, struct stat *));
static void vipwexit P_((const char *, int, int));
static void vipwedit P_((const char *, int (*) P_((void)), int (*) P_((void))));
int main P_((int, char **));

static int
create_backup_file(FILE *fp, const char *backup, struct stat *sb)
{
  struct utimbuf ub;
  FILE *bkfp;
  int c;
  mode_t mask;

  mask = umask(077);
  bkfp = fopen(backup, "w");
  umask(mask);
  if (!bkfp) return -1;

  rewind(fp);
  while ((c = getc(fp)) != EOF) {
    if (putc(c, bkfp) == EOF) break;
  }

  if (c != EOF || fflush(bkfp)) {
    fclose(bkfp);
    unlink(backup);
    return -1;
  }
  if (fclose(bkfp)) {
    unlink(backup);
    return -1;
  }

  ub.actime = sb->st_atime;
  ub.modtime = sb->st_mtime;
  if (utime(backup, &ub) ||
      chmod(backup, sb->st_mode) ||
      chown(backup, sb->st_uid, sb->st_gid)) {
    unlink(backup);
    return -1;
  }
  return 0;
}


static void
vipwexit(const char *msg, int syserr, int ret)
{
  int err = errno;
  if (filelocked) (*unlock)();
  if (createedit) unlink(fileeditname);
  if (msg) fprintf(stderr, "%s: %s", progname, msg);
  if (syserr) fprintf(stderr, ": %s", strerror(err));
  fprintf(stderr, "\n%s: %s is unchanged\n", progname, filename);
  exit(ret);
}

#ifndef DEFAULT_EDITOR
#define DEFAULT_EDITOR "vi"
#endif

static void
vipwedit(const char *file, int (*file_lock) P_((void)), int (*file_unlock) P_((void)))
{
  const char *editor;
  pid_t pid;
  struct stat st1, st2;
  int status;
  FILE *f;
  char filebackup[1024], fileedit[1024];

  snprintf(filebackup, sizeof filebackup, "%s-", file);
  snprintf(fileedit, sizeof fileedit, "%s.edit", file);
  unlock = file_unlock;
  filename = file;
  fileeditname = fileedit;
  
  if (access(file, F_OK)) vipwexit(file, 1, 1);
  if (!file_lock()) vipwexit("Couldn't lock file", errno, 5);
  filelocked = 1;

  /* edited copy has same owners, perm */
  if (stat(file, &st1)) vipwexit(file, 1, 1);
  if (!(f = fopen(file, "r"))) vipwexit(file, 1, 1);
  if (create_backup_file(f, fileedit, &st1))
    vipwexit("Couldn't make backup", errno, 1);
  createedit = 1;
  
  editor = getenv("VISUAL");
  if (!editor)
    editor = getenv("EDITOR");
  if (!editor)
    editor = DEFAULT_EDITOR;
  
  if ((pid = fork()) == -1) vipwexit("fork", 1, 1);
  else if (!pid) {
    execlp(editor, editor, fileedit, (char *) 0);
    fprintf(stderr, "%s: %s: %s\n", progname, editor, strerror(errno));
    exit(1);
  }

  for (;;) {
    pid = waitpid(pid, &status, WUNTRACED);
    if (WIFSTOPPED(status)) {
      kill(getpid(), SIGSTOP);
      kill(getpid(), SIGCONT);
    }
    else break;
  }

  if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status))
    vipwexit(editor, 1, 1);

  if (stat(fileedit, &st2)) vipwexit(fileedit, 1, 1);
  if (st1.st_mtime == st2.st_mtime) vipwexit(0, 0, 0);

  /* XXX - here we should check fileedit for errors; if there are any,
     ask the user what to do (edit again, save changes anyway, or quit
     without saving).  Use pwck or grpck to do the check.  --marekm */

  createedit = 0;
  unlink(filebackup);
  link(file, filebackup);
  if (rename(fileedit, file) == -1) {
    fprintf(stderr, "%s: can't restore %s: %s (your changes are in %s)\n",
	    progname, file, strerror(errno), fileedit);
    vipwexit(0,0,1);
  }

  (*file_unlock)();
}


int
main(int argc, char **argv)
{
  int flag;
  int editshadow = 0;
  char *c;
  int e = 1;
  int do_vipw;

  progname = ((c = strrchr(*argv, '/')) ? c+1 : *argv);
  do_vipw = (strcmp(progname, "vigr") != 0);

  while ((flag = getopt(argc, argv, "ghps")) != EOF) {
    switch (flag) {
    case 'p':
      do_vipw = 1;
      break;
    case 'g':
      do_vipw = 0;
      break;
    case 's':
      editshadow = 1;
      break;
    case 'h':
      e = 0;
    default:
      printf("Usage:\n\
`vipw' edits /etc/passwd        `vipw -s' edits /etc/shadow\n\
`vigr' edits /etc/group         `vigr -s' edits /etc/gshadow\n\
");
      exit(e);
    }
  }

  if (do_vipw) {
#ifdef SHADOWPWD
    if (editshadow)
      vipwedit(SHADOW_FILE, spw_lock, spw_unlock);
    else
#endif
      vipwedit(PASSWD_FILE, pw_lock, pw_unlock);
  }
  else {
#ifdef SHADOWGRP
    if (editshadow)
      vipwedit(SGROUP_FILE, sgr_lock, sgr_unlock);
    else
#endif
      vipwedit(GROUP_FILE, gr_lock, gr_unlock);
  }

  return 0;
}
