// $Id: rpm-fake-resolver.c,v 1.16 2006/01/22 18:17:24 ensc Exp $    --*- c -*--

// Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
//  
// 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; version 2 of the License.
//  
// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Protocol:
// 1. startup
// 2. initialize (setuid, ctx-migrate, chroot, ...)
// 3. send "." token to fd 3
// 4. wait one character on fd 1
// 5. process this character and consume further characters from fd 1 as far
//    as needed
// 6. go to 3) (or exit)

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include "internal.h"
#include "vserver.h"
#include "util.h"

#include <getopt.h>
#include <stdlib.h>
#include <stdbool.h>
#include <grp.h>
#include <pwd.h>
#include <fcntl.h>
#include <errno.h>

#define ENSC_WRAPPERS_PREFIX	"rpm-fake-resolver: "
#define ENSC_WRAPPERS_VSERVER	1
#define ENSC_WRAPPERS_UNISTD	1
#define ENSC_WRAPPERS_IO	1
#define ENSC_WRAPPERS_FCNTL	1
#include <wrappers.h>

#define MAX_RQSIZE	0x1000

int wrapper_exit_code = 1;

struct ArgInfo {
    xid_t		ctx;
    uid_t		uid;
    gid_t		gid;
    bool		do_fork;
    bool		in_ctx;
    char const *	pid_file;
    char const *	chroot;
    uint32_t		caps;
    int			flags;
};

static struct option const
CMDLINE_OPTIONS[] = {
  { "help",     no_argument,  0, 'h' },
  { "version",  no_argument,  0, 'v' },
  { 0,0,0,0 }
};

static void
showHelp(int fd, char const *cmd, int res)
{
  WRITE_MSG(fd, "Usage:  ");
  WRITE_STR(fd, cmd);
  WRITE_MSG(fd,
	    " [-c <ctx>] [-u <uid>] [-g <gid>] [-r <chroot>] [-s] [-n]\n"
	    "Please report bugs to " PACKAGE_BUGREPORT "\n");
  exit(res);
}

static void
showVersion()
{
  WRITE_MSG(1,
	    "rpm-fake-resolver " VERSION " -- NSS resovler for rpm-fake\n"
	    "This program is part of " PACKAGE_STRING "\n\n"
	    "Copyright (C) 2003 Enrico Scholz\n"
	    VERSION_COPYRIGHT_DISCLAIMER);
  exit(0);
}


inline static void
parseArgs(struct ArgInfo *args, int argc, char *argv[])
{
  while (1) {
    int		c = getopt_long(argc, argv, "F:C:c:u:g:r:ns", CMDLINE_OPTIONS, 0);
    if (c==-1) break;

    switch (c) {
      case 'h'		:  showHelp(1, argv[0], 0);
      case 'v'		:  showVersion();

      case 'c'		:  args->ctx     = atoi(optarg); break;
      case 'u'		:  args->uid     = atoi(optarg); break;
      case 'g'		:  args->gid     = atoi(optarg); break;
      case 'F'		:  args->flags   = atoi(optarg); break;
      case 'C'		:  args->caps    = atoi(optarg); break;
      case 'r'		:  args->chroot  = optarg;   break;
      case 'n'		:  args->do_fork = false;    break;
      case 's'		:  args->in_ctx  = true;     break;
      default		:
	WRITE_MSG(2, "Try '");
	WRITE_STR(2, argv[0]);
	WRITE_MSG(2, " --help\" for more information.\n");
	exit(1);
	break;
    }
  }

  if (optind!=argc) {
    WRITE_MSG(2, "No further options allowed; aborting ...\n");
    exit(1);
  }
  
  if (args->chroot==0) {
    WRITE_MSG(2, "No chroot specified; aborting...\n");
    exit(1);
  }
}

static void
sendResult(bool state, uint32_t res)
{
  if (state) {
    static uint8_t	ONE = 1;
    Ewrite(1, &ONE, sizeof ONE);
  }
  else {
    static uint8_t	ZERO = 0;
    Ewrite(1, &ZERO, sizeof ZERO);
  }

  Ewrite(1, &res, sizeof res);
}

static void
do_getpwnam()
{
  uint32_t	len;

  if (EreadAll(0, &len, sizeof len) &&
      len<MAX_RQSIZE) {
    char		buf[len+1];
    struct passwd *	res = 0;
    
    if (EreadAll(0, buf, len)) {
      buf[len] = '\0';
      res = getpwnam(buf);
    }
    
    if (res!=0) sendResult(true,  res->pw_uid);
    else        sendResult(false, -1);
  }
  // TODO: logging
}

static void
do_getgrnam()
{
  uint32_t	len;

  if (EreadAll(0, &len, sizeof len) &&
      len<MAX_RQSIZE) {
    char		buf[len+1];
    struct group *	res = 0;
    
    if (EreadAll(0, buf, len)) {
      buf[len] = '\0';
      res = getgrnam(buf);
    }
    
    if (res!=0) sendResult(true,  res->gr_gid);
    else        sendResult(false, -1);
  }
  // TODO: logging
}

static void
do_closenss()
{
  uint8_t	what;

  if (EreadAll(0, &what, sizeof what)) {
    switch (what) {
      case 'p'	:  endpwent(); break;
      case 'g'	:  endgrent(); break;
      default	:  break;
    }
  }
}

static void
run()
{
  uint8_t	c;

  while (EwriteAll(3, ".", 1),
	 EreadAll (0, &c, sizeof c)) {
    switch (c) {
      case 'P'	:  do_getpwnam(); break;
      case 'G'	:  do_getgrnam(); break;
      case 'Q'	:  exit(0);
      case 'C'	:  do_closenss(); break;
      case '.'	:  Ewrite(1, ".", 1); break;
      default	:  Ewrite(1, "?", 1); break;
    }
  }
}

static void
daemonize(struct ArgInfo const UNUSED * args, int pid_fd)
{
  int		p[2];
  pid_t		pid;
  char		c;
  
  Epipe(p);
  pid = Efork();
  
  if (pid!=0) {
    if (pid_fd!=-1) {
      char	buf[sizeof(id_t)*3 + 2];
      size_t	l;

      l = utilvserver_fmt_uint(buf, pid);
      Ewrite(pid_fd, buf, l);
      Ewrite(pid_fd, "\n", 1);
    }
    _exit(0);
  }
  Eclose(p[1]);
  TEMP_FAILURE_RETRY(read(p[0], &c, 1));
  Eclose(p[0]);
}

static void
activateContext(xid_t xid, bool in_ctx,
		uint32_t UNUSED xid_caps, int UNUSED xid_flags)
{
  if (in_ctx) {
    struct vc_ctx_flags		flags = {
      .flagword = 0,
      .mask     = VC_VXF_STATE_SETUP,
    };

    Evc_set_cflags(xid, &flags);
  }
  else if (vc_isSupported(vcFEATURE_MIGRATE))
      Evc_ctx_migrate(xid);
  else {
#ifdef VC_ENABLE_API_COMPAT
    Evc_new_s_context(xid, xid_caps, xid_flags);
#else
    WRITE_MSG(2, ENSC_WRAPPERS_PREFIX "can not change context: migrate kernel feature missing and 'compat' API disabled\n");
    exit(wrapper_exit_code);
#endif
  }
}

int main(int argc, char * argv[])
{
  struct ArgInfo	args = {
    .ctx      = VC_DYNAMIC_XID,
    .uid      = 99,
    .gid      = 99,
    .do_fork  = true,
    .pid_file = 0,
    .chroot   = 0,
    .in_ctx   = false,
    .flags    = S_CTX_INFO_LOCK,
  };
  int			pid_fd = -1;

#ifndef __dietlibc__
#  warning  *** rpm-fake-resolver is built against glibc; please do not report errors before trying a dietlibc version ***
  WRITE_MSG(2,
	    "***  rpm-fake-resolver was built with glibc;  please do  ***\n"
	    "***  not report errors before trying a dietlibc version. ***\n");
#endif

  parseArgs(&args, argc, argv);
  if (args.pid_file && args.do_fork)
    pid_fd = EopenD(args.pid_file, O_CREAT|O_WRONLY, 0644);
  
  if (args.chroot) Echroot(args.chroot);
  Echdir("/");

  activateContext(args.ctx, args.in_ctx, args.caps, args.flags);
  Esetgroups(0, &args.gid);
  Esetgid(args.gid);
  Esetuid(args.uid);

  if (args.do_fork) daemonize(&args, pid_fd);
  if (pid_fd!=-1)   close(pid_fd);
  run();
}
