/*
   cvsd - chroot wrapper to run `cvs pserver' more securely.

   cvsd was originally written by Chris Black <cblack@mokey.com>,
   http://cblack.mokey.com/. That was until release 0.6

   cvsd versions up till 0.8b3 were maintained by Philippe Kehl
   <phkehl@gmx.net>, http://guv.ethz.ch/~flip/cvsd/,
   http://www.oinkzwurgl.org/software/cvsd/.

   after that Arthur de Jong <arthur@ch.tudelft.nl>
   took up the work and did a complete rewrite,
   http://ch.tudelft.nl/~arthur/cvsd.

   Copyright (C) 1999 Chris Black.
   Copyright (C) 2000 Philippe Kehl.
   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006 Arthur de Jong.

   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, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/


#include "config.h"

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif /* HAVE_GETOPT_H */
#ifndef HAVE_GETOPT_LONG
#include "getopt_long.h"
#endif /* not HAVE_GETOPT_LONG */
#ifndef HAVE_DAEMON
#include "daemon.h"
#endif /* not HAVE_DAEMON */
#ifndef HAVE_GETADDRINFO
#include "getinfos.h"
#endif /* not HAVE_GETADDRINFO */

#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif /* HAVE_NETDB_H */
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif /* HAVE_FCNTL_H */
#ifdef USE_LIBWRAP
#ifdef HAVE_TCPD_H
#include <tcpd.h>
#endif /* HAVE_TCPD_H */
#endif /* USE_LIBWRAP */
#ifdef HAVE_GRP_H
#include <grp.h>
#endif /* HAVE_GRP_H */
#ifdef USE_CAPABILITIES
#include <sys/capability.h>
#include <sys/prctl.h>
#endif /* USE_CAPABILITIES */

#include "xmalloc.h"
#ifdef HAVE_SETRLIMIT
#include "reslimit.h"
#endif /* HAVE_SETRLIMIT */
#include "cfg.h"
#include "cfgfile.h"
#include "log.h"


/* the definition of the environment */
extern char **environ;


/* the number of children spawned */
static volatile int cvsd_numchildren=0;


/* the exit flag to indicate that a signal was received */
static volatile int cvsd_exitsignal=0;

/* the server sockets */
#define MAXSERVERSOCKETS 16
static int cvsd_serversockets[MAXSERVERSOCKETS];
static int cvsd_serversocketnum=0;


/* the number of seconds to sleep when no more
   connections can be listened for */
#define SLEEPSECS 5


/* libwrap logging settings */
#ifdef USE_LIBWRAP
int allow_severity=LOG_INFO;
int deny_severity=LOG_NOTICE;
#endif /* USE_LIBWRAP */


/* display version information */
static void display_version(FILE *fp)
{
  fprintf(fp,"%s\n",PACKAGE_STRING);
  fprintf(fp,"Written by Chris Black, Philippe Kehl and Arthur de Jong.\n\n");
  fprintf(fp,"Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Chris Black,\n"
             "Philippe Kehl and Arthur de Jong.\n"
             "This is free software; see the source for copying conditions.  There is NO\n"
             "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
}


/* display usage information to stdout and exit(status) */
static void display_usage(FILE *fp, const char *program_name)
{
  fprintf(fp,"Usage: %s [OPTION]...\n",program_name);
  fprintf(fp,"chroot wrapper to run `cvs pserver' more securely.\n");
  fprintf(fp,"  -f, --config=FILE  use FILE as configfile (default %s)\n",DEFAULT_CONFIGFILE);
  fprintf(fp,"  -d, --debug        don't fork and print debugging to stderr\n");
  fprintf(fp,"      --help         display this help and exit\n");
  fprintf(fp,"      --version      output version information and exit\n");
  fprintf(fp,"\n"
             "Report bugs to <%s>.\n",PACKAGE_BUGREPORT);
}


/* the definition of options for getopt(). see getopt(2) */
static struct option const cvsd_options[] =
{
  { "config",      required_argument, NULL, 'f' },
  { "debug",       no_argument,       NULL, 'd' },
  { "help",        no_argument,       NULL, 'h' },
  { "version",     no_argument,       NULL, 'V' },
  { NULL, 0, NULL, 0 }
};
#define CVSD_OPTIONSTRING "f:dhV"


/* parse command line options and save settings in struct  */
static void parse_cmdline(int argc,char *argv[],struct cvsd_cfg *cfg)
{
  int optc;
  while ((optc=getopt_long(argc,argv,CVSD_OPTIONSTRING,cvsd_options,NULL))!=-1)
  {
    switch (optc)
    {
    case 'f': /* -f, --config=FILE  use FILE as configfile */
      if (optarg[0]!='/')
      {
        fprintf(stderr,"%s: configfile '%s' must be an absolute path\n",
                argv[0],optarg);
        exit(1);
      }
      cfg->configfile=xstrdup(optarg);
      break;
    case 'd': /* -d, --debug        don't fork and print debugging to stderr */
      cfg->debugging=1;
      log_setdefaultloglevel(LOG_DEBUG);
      break;
    case 'h': /*     --help         display this help and exit */
      display_usage(stdout,argv[0]);
      exit(0);
    case 'V': /*     --version      output version information and exit */
      display_version(stdout);
      exit(0);
    case ':': /* missing required parameter */
    case '?': /* unknown option character or extraneous parameter */
    default:
      fprintf(stderr,"Try `%s --help' for more information.\n",
              argv[0]);
      exit(1);
    }
  }
  /* check for remaining arguments */
  if (optind<argc)
  {
    fprintf(stderr,"%s: unrecognized option `%s'\n",
            argv[0],argv[optind]);
    fprintf(stderr,"Try `%s --help' for more information.\n",
            argv[0]);
    exit(1);
  }
}


/* get a name of a signal with a given signal number */
static const char *signame(int signum)
{
  switch (signum)
  {
  case SIGHUP:  return "SIGHUP";  /* Hangup detected */
  case SIGINT:  return "SIGINT";  /* Interrupt from keyboard */
  case SIGQUIT: return "SIGQUIT"; /* Quit from keyboard */
  case SIGILL:  return "SIGILL";  /* Illegal Instruction */
  case SIGABRT: return "SIGABRT"; /* Abort signal from abort(3) */
  case SIGFPE:  return "SIGFPE";  /* Floating point exception */
  case SIGKILL: return "SIGKILL"; /* Kill signal */
  case SIGSEGV: return "SIGSEGV"; /* Invalid memory reference */
  case SIGPIPE: return "SIGPIPE"; /* Broken pipe */
  case SIGALRM: return "SIGALRM"; /* Timer signal from alarm(2) */
  case SIGTERM: return "SIGTERM"; /* Termination signal */
  case SIGUSR1: return "SIGUSR1"; /* User-defined signal 1 */
  case SIGUSR2: return "SIGUSR2"; /* User-defined signal 2 */
  case SIGCHLD: return "SIGCHLD"; /* Child stopped or terminated */
  case SIGCONT: return "SIGCONT"; /* Continue if stopped */
  case SIGSTOP: return "SIGSTOP"; /* Stop process */
  case SIGTSTP: return "SIGTSTP"; /* Stop typed at tty */
  case SIGTTIN: return "SIGTTIN"; /* tty input for background process */
  case SIGTTOU: return "SIGTTOU"; /* tty output for background process */
#ifdef SIGBUS
  case SIGBUS:  return "SIGBUS";  /* Bus error */
#endif
#ifdef SIGPOLL
  case SIGPOLL: return "SIGPOLL"; /* Pollable event */
#endif
#ifdef SIGPROF
  case SIGPROF: return "SIGPROF"; /* Profiling timer expired */
#endif
#ifdef SIGSYS
  case SIGSYS:  return "SIGSYS";  /* Bad argument to routine */
#endif
#ifdef SIGTRAP
  case SIGTRAP: return "SIGTRAP"; /* Trace/breakpoint trap */
#endif
#ifdef SIGURG
  case SIGURG:  return "SIGURG";  /* Urgent condition on socket */
#endif
#ifdef SIGVTALRM
  case SIGVTALRM: return "SIGVTALRM"; /* Virtual alarm clock */
#endif
#ifdef SIGXCPU
  case SIGXCPU: return "SIGXCPU"; /* CPU time limit exceeded */
#endif
#ifdef SIGXFSZ
  case SIGXFSZ: return "SIGXFSZ"; /* File size limit exceeded */
#endif
  default:      return "UNKNOWN";
  }
}


/* check for and handle dying child processes */
static void reap_deadchildren(void)
{
  int status;
  pid_t cpid;
  /* loop until all pending child processes have finished
     (kill all the zombies) */
  while ((cpid=waitpid(-1,&status,WNOHANG))>0)
  {
    /* shouldn't have a race here because we don't have threads */
    cvsd_numchildren--;
    if (WIFSIGNALED(status))
    {
      log_log(LOG_INFO,"cvs command exited on signal %s (%d) with exit-status %d",
                   signame((int)WTERMSIG(status)),
                   (int)WTERMSIG(status),
                   (int)WEXITSTATUS(status));
    }
    else
    {
      log_log(LOG_INFO,"cvs command exited%s with exit-status %d",
                   WIFEXITED(status)?"":" abnormally",
                   (int)WEXITSTATUS(status));
    }
  }
}

/* signal handler for dying children */
static RETSIGTYPE sigchld_handler(int signum)
{
}


/* signal handler for closing down */
static RETSIGTYPE sigexit_handler(int signum)
{
  cvsd_exitsignal=signum;
}


/* do cleaning up before terminating */
static void exithandler(void)
{
  int i;
  for (i=0;i<=cvsd_serversocketnum;i++)
    if (close(cvsd_serversockets[i]))
      log_log(LOG_WARNING,"problem closing server socket (ignored): %s",strerror(errno));
  log_log(LOG_INFO,"version %s bailing out",VERSION);
}


/* handle a connection by doing fork() and stuff */
static void handleconnection(int csock,const struct cvsd_cfg *cfg)
{
  pid_t cpid;
  int i,m;

  /* we have a possible race here? (probably atomic) */
  cvsd_numchildren++;

  /* fork off a child to handle the connection */
  switch (cpid=fork())
  {
  case 0: /* we are the child */
    /* set limits */
    setreslimits();
    /* connect socket to stdin/stdout/stderr */
    if ( (dup2(csock,0)<0) || /* stdin */
         (dup2(csock,1)<0) || /* stdout */
         (dup2(csock,2)<0) )  /* stderr */
    {
      log_log(LOG_ERR,"dup failed: %s",strerror(errno));
      _exit(1);
    }
    /* close all other file descriptors */
    m=sysconf(_SC_OPEN_MAX);
    for (i=3;i<m;i++)
      close(i);
    /* execute cvs */
    execve(cfg->cvscmd,cfg->cvsargs,cfg->cvsenv);
    /* if we are here there has been an error */
    /* we can't log since we don't have any useful file descriptors */
    close(0);
    close(1);
    close(2);
    _exit(1);
    break;
  case -1: /* we are the parent, but have an error */
    log_log(LOG_ERR,"fork() failed: %s",strerror(errno));
    exit(1);
    break;
  }

  /* we are the parent and have a cpid */
  log_log(LOG_DEBUG,"debug: fork() succeeded (child pid=%d)",(int)cpid);

  /* close the socket to prevent confusion */
  if (close(csock))
    log_log(LOG_WARNING,"problem closing connection socket in parent (ignored): %s",strerror(errno));
}


/* create socket, bind and listen on the specified address */
static void listenonaddresses(const struct cvsd_addrs *addrs)
{
  int flag;
  int sock;
  char host[80],serv[40];
  int i;
  struct addrinfo *last=NULL;
  struct addrinfo *addr;
  int count=0;

  for (addr=addrs->addrs;addr!=NULL;addr=addr->ai_next)
  {

    switch (i=getnameinfo(addr->ai_addr,addr->ai_addrlen,
                          host,80,serv,40,
                          NI_NUMERICHOST|NI_NUMERICSERV)<0)
    {
      case 0:
        log_log(LOG_DEBUG,"debug: binding %s %s family=%d socktype=%d protocol=%d",host,serv,
                      addr->ai_family,addr->ai_socktype,addr->ai_protocol);
        break;
      case EAI_SYSTEM:
        log_log(LOG_DEBUG,"debug: getnameinfo() failed: %s",strerror(errno));
        break;
      default:
        log_log(LOG_DEBUG,"debug: getnameinfo() failed: %s",gai_strerror(i));
        break;
    }

    /* ignore unix domain sockets (i.e. named sockets) */
    if (addr->ai_family==PF_UNIX)
    {
      log_log(LOG_DEBUG,"debug: do not try unix domain socket (family %d)",addr->ai_family);
      continue;
    }

    /* create socket */
    if ((sock=socket(addr->ai_family,addr->ai_socktype,addr->ai_protocol))<0)
    {
      if ((errno==EAFNOSUPPORT)||(errno==EINVAL))
      {
        log_log(LOG_DEBUG,"debug: socket() failed (ignored): %s",strerror(errno));
        continue;
      }
      log_log(LOG_ERR,"cannot create socket: %s",strerror(errno));
      exit(1);
    }

    /* bind socket even if port is in close wait state */
    flag=1;
    if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(int))!=0)
    {
      log_log(LOG_ERR,"setsockopt(SO_REUSEADDR) failed: %s",strerror(errno));
      if (close(sock))
        log_log(LOG_WARNING,"problem closing socket: %s",strerror(errno));
      exit(1);
    }

#ifdef TODO
    /* close the socket fast (SO_LINGER) */
    struct linger {
                    int   l_onoff;    /* linger active */
                    int   l_linger;   /* how many seconds to linger for */
                };
    if (setsockopt(sock,SOL_SOCKET,SO_LINGER,&**,sizeof(**))!=0)
    {
      log_log(LOG_ERR,"setsockopt(SO_LINGER) failed: %s",strerror(errno));
      if (close(sock))
        log_log(LOG_WARNING,"problem closing socket: %s",strerror(errno));
      exit(1);
    }
#endif /* TODO */

    /* do not block on accept() */
    if ((i=fcntl(sock,F_GETFL,0))<0)
    {
      log_log(LOG_ERR,"fctnl(F_GETFL) failed: %s",strerror(errno));
      if (close(sock))
        log_log(LOG_WARNING,"problem closing socket: %s",strerror(errno));
      exit(1);
    }
    if (fcntl(sock,F_SETFL,i|O_NONBLOCK)<0)
    {
      log_log(LOG_ERR,"fctnl(F_SETFL,O_NONBLOCK) failed: %s",strerror(errno));
      if (close(sock))
        log_log(LOG_WARNING,"problem closing socket: %s",strerror(errno));
      exit(1);
    }

    /* bind the socket to the specified port */
    if (bind(sock,addr->ai_addr,addr->ai_addrlen)<0)
    {
      if ((errno==EADDRINUSE)&&(last!=NULL)&&
          (last->ai_family==AF_INET6)&&
          (addr->ai_family==AF_INET))
      {
        log_log(LOG_DEBUG,"debug: bind() failed (ignored): %s",strerror(errno));
        if (close(sock))
          log_log(LOG_WARNING,"problem closing socket (ignored): %s",strerror(errno));
        continue;
      }
      log_log(LOG_ERR,"bind() failed: %s",strerror(errno));
      if (close(sock))
        log_log(LOG_WARNING,"problem closing socket: %s",strerror(errno));
      exit(1);
    }

    /* start listening for connections */
    if (listen(sock,1)<0)
    {
      log_log(LOG_ERR,"listen() failed: %s",strerror(errno));
      if (close(sock))
        log_log(LOG_WARNING,"problem closing socket: %s",strerror(errno));
      exit(1);
    }

    /* add the allocated socket to the list of ok sockets */
    if (cvsd_serversocketnum>=MAXSERVERSOCKETS)
    {
      log_log(LOG_ERR,"maximum number of listening serversockets reached: edit MAXSERVERSOCKETS in cvsd.c and recompile!");
      if (close(sock))
        log_log(LOG_WARNING,"problem closing socket: %s",strerror(errno));
      exit(1);
    }
    cvsd_serversockets[cvsd_serversocketnum++]=sock;

    /* log stuff */
    switch (i=getnameinfo(addr->ai_addr,addr->ai_addrlen,
                          host,80,serv,40,
                          NI_NUMERICHOST|NI_NUMERICSERV)<0)
    {
      case 0:
        log_log(LOG_INFO,"listening on %s %s",host,serv);
        break;
      case EAI_SYSTEM:
        log_log(LOG_ERR,"getnameinfo() failed (ignored): %s",strerror(errno));
        break;
      default:
        log_log(LOG_ERR,"getnameinfo() failed (ignored): %s",gai_strerror(i));
        break;
    }

    last=addr;
    count++;
  } /* next addr */

  if (count<=0)
  {
    log_log(LOG_ERR,"failed binding any address for %s %s: %s",
                addrs->node,addrs->service,strerror(errno));
    exit(1);
  }

}


/* accept a connection on the socket and spawn a child cvs */
static void acceptconnection(const struct cvsd_cfg *cfg)
{
  int csock;
  fd_set fds;
  int i;
  int j;
  int max=0;
  struct sockaddr_storage addr;
  socklen_t alen;
  char hostbuf[80],serv[40];
  char *host;
#ifdef USE_LIBWRAP
  struct request_info rinfo;
#endif /* USE_LIBWRAP */

  /* clear the set */
  FD_ZERO(&fds);
  for (i=0;i<cvsd_serversocketnum;i++)
  {
    FD_SET(cvsd_serversockets[i],&fds);
    if (cvsd_serversockets[i]+1>max)
      max=cvsd_serversockets[i]+1;
  }

  /* wait for something to change */
  if (select(max,&fds,NULL,NULL,NULL)<0)
  {
    if (errno==EINTR)
    {
      log_log(LOG_DEBUG,"debug: select() failed (ignored): %s",strerror(errno));
      return;
    }
    log_log(LOG_ERR,"select() failed: %s",strerror(errno));
    exit(1);
  }

  for (i=0;i<cvsd_serversocketnum;i++)
  {
    if (FD_ISSET(cvsd_serversockets[i],&fds))
    {

      /* accept a new connection */
      alen=(socklen_t)sizeof(struct sockaddr_storage);
      csock=accept(cvsd_serversockets[i],(struct sockaddr *)&addr,&alen);
      if (csock<0)
      {
        if ((errno==EINTR)||(errno==EAGAIN)||(errno==EWOULDBLOCK))
        {
          log_log(LOG_DEBUG,"debug: accept() failed (ignored): %s",strerror(errno));
          continue; /* this is 'normal' */
        }
        log_log(LOG_ERR,"accept() failed: %s",strerror(errno));
        continue;
      }

      /* look up other side of connection and log it */
      host=hostbuf;
      switch (j=getnameinfo((struct sockaddr *)&addr,alen,
                            host,80,serv,40,
                            NI_NUMERICHOST|NI_NUMERICSERV)<0)
      {
        case 0: break;
        case EAI_SYSTEM:
          log_log(LOG_ERR,"getnameinfo() failed: %s",strerror(errno));
          if (close(csock))
            log_log(LOG_WARNING,"problem closing socket: %s",strerror(errno));
          continue;
        default:
          log_log(LOG_ERR,"getnameinfo() failed: %s",gai_strerror(j));
          if (close(csock))
            log_log(LOG_WARNING,"problem closing socket: %s",strerror(errno));
          continue;
      }
      /* convert IPv4-mapped-IPv6 address to an IPv4 one */
      if ((strncmp(host,"::ffff:",7)==0)&&
          (strchr(host+7,':')==NULL))
        host+=7;

#ifdef USE_LIBWRAP
      /* find out what hosts.{allow,deny} think about this */
      request_init(&rinfo,RQ_FILE,csock,RQ_DAEMON,PACKAGE,0);
      fromhost(&rinfo); /* not documented, but needed somehow */
      if(!hosts_access(&rinfo))
      {
        log_log(LOG_INFO,"connection from %s %s refused by tcp wrappers",host,serv);
        if (close(csock))
          log_log(LOG_WARNING,"problem closing socket: %s",strerror(errno));
        continue;
      }
#endif /* USE_LIBWRAP */

      /* log connection */
      log_log(LOG_INFO,"connection from %s %s",host,serv);

      /* make sure O_NONBLOCK is not inherited */
      if ((j=fcntl(csock,F_GETFL,0))<0)
      {
        log_log(LOG_ERR,"fctnl(F_GETFL) failed: %s",strerror(errno));
        if (close(csock))
          log_log(LOG_WARNING,"problem closing socket: %s",strerror(errno));
        exit(1);
      }
      if (fcntl(csock,F_SETFL,j&~O_NONBLOCK)<0)
      {
        log_log(LOG_ERR,"fctnl(F_SETFL,~O_NONBLOCK) failed: %s",strerror(errno));
        if (close(csock))
          log_log(LOG_WARNING,"problem closing socket: %s",strerror(errno));
        exit(1);
      }

      /* handle the connection */
      handleconnection(csock,cfg);

    }
  }
}


/* write the current process id to the specified file */
static void write_pidfile(const char *filename)
{
  FILE *fp;
  if (filename!=NULL)
  {
    umask(0022);
    if ((fp=fopen(filename,"w"))==NULL)
    {
      log_log(LOG_ERR,"cannot open pid file (%s): %s",filename,strerror(errno));
      exit(1);
    }
    if (fprintf(fp,"%d\n",(int)getpid())<=0)
    {
      log_log(LOG_ERR,"error writing pid file (%s)",filename);
      exit(1);
    }
    if (fclose(fp))
    {
      log_log(LOG_ERR,"error writing pid file (%s): %s",filename,strerror(errno));
      exit(1);
    }
  }
}


/* try to install signal handler and check result */
static void install_sighandler(int signum,RETSIGTYPE (*handler) (int))
{
  struct sigaction act;
  memset(&act,0,sizeof(struct sigaction));
  act.sa_handler=handler;
  sigemptyset(&act.sa_mask);
  act.sa_flags=SA_RESTART|SA_NOCLDSTOP;
  if (sigaction(signum,&act,NULL)!=0)
  {
    log_log(LOG_ERR,"error installing signal handler for '%s': %s",signame(signum),strerror(errno));
    exit(1);
  }
}


/* the main program... */
int main(int argc,char *argv[])
{
  int i;
  int j;
  char *cvs_cmdline=NULL;
  struct cvsd_addrs *tmp,*nxt;
  struct cvsd_cfg *cfg;
#ifdef USE_CAPABILITIES
  cap_t caps;
#endif /* USE_CAPABILITIES */

  /* load the default configuration */
  cfg=cfg_new();

  /* clear the environment */
  environ=cfg->cvsenv;

  /* parse the command line */
  parse_cmdline(argc,argv,cfg);

  /* begin building command line */
  cfg_addcvsarg(cfg,"cvs");
  cfg_addcvsarg(cfg,"-f"); /* do not read ~/.cvsrc */
#ifdef DEBUG
  cfg_addcvsarg(cfg,"-t"); /* verbose */
#endif /* DEBUG */

  /* read the configuration file */
  read_configfile(cfg->configfile,cfg);

  /* specify cvs_command if not set earlier */
  if (cfg->cvscmd==NULL)
  {
    cfg->cvscmd=( (cfg->rootjail!=NULL) &&
                 (strcmp(cfg->rootjail,"none")!=0) )?"/bin/cvs":CVS_LOCATION;
  }
  log_log(LOG_DEBUG,"debug: cvscmd: %s",cfg->cvscmd);

  /* add cvs action */
  cfg_addcvsarg(cfg,"pserver"); /* run cvs as pserver */

  /* dump all arguments */
  for (i=0;cfg->cvsargs[i]!=NULL;i++)
    log_log(LOG_DEBUG,"debug: cvsargs[%d]: %s",i,cfg->cvsargs[i]);

  /* complete environment for cvs */
  cfg->cvsenv[CVSUMASK_IDX]=(char *)xmalloc(13*sizeof(char));
  i=snprintf(cfg->cvsenv[CVSUMASK_IDX],13,"CVSUMASK=%03o",cfg->umask);
  if ( (i<0) || (i>12) )
  {
    log_log(LOG_ERR,"fatal error saving umask in environment (probably a bug)");
    exit(1);
  }

  /* dump environment */
  for (i=0;cfg->cvsenv[i]!=NULL;i++)
    log_log(LOG_DEBUG,"debug: cvsenv[%d]: %s",i,cfg->cvsenv[i]);

  /* default address */
  if (cfg->addresses==NULL)
    cfg_addaddress(cfg,NULL,0,DEFAULT_ADDR,DEFAULT_PORT);

  /* daemonize */
  if ((!cfg->debugging)&&(daemon(0,0)<0))
  {
    log_log(LOG_ERR,"unable to daemonize: %s",strerror(errno));
    exit(1);
  }

  /* intilialize logging */
  if (!cfg->debugging)
    log_startlogging();
#ifdef DEBUG
  log_log(LOG_INFO,"version %s starting (debugging enabled)",VERSION);
#else /* DEBUG */
  log_log(LOG_INFO,"version %s starting",VERSION);
#endif /* not DEBUG */

  /* install handler to close stuff off on exit and log notice */
  atexit(exithandler);

  /* write pidfile */
  write_pidfile(cfg->pidfile);

  /* set the umask */
  umask(cfg->umask);

  /* start listening on addresses from configfile */
  for (tmp=cfg->addresses;tmp!=NULL;tmp=nxt)
  {
    listenonaddresses(tmp);
    nxt=tmp->next;
    freeaddrinfo(tmp->addrs);
    free(tmp->node);
    free(tmp->service);
    free(tmp);
  }
  cfg->addresses=NULL;

  /* do the chroot stuff */
  if ( (cfg->rootjail!=NULL) &&
       (strcmp(cfg->rootjail,"none")!=0) )
  {
    if (chdir(cfg->rootjail)!=0)
    {
      log_log(LOG_ERR,"cannot chdir(%s): %s",cfg->rootjail,strerror(errno));
      exit(1);
    }
    if (chroot(cfg->rootjail)!=0)
    {
      log_log(LOG_ERR,"cannot chroot(%s): %s",cfg->rootjail,strerror(errno));
      exit(1);
    }
    log_log(LOG_DEBUG,"debug: chroot(%s) done",cfg->rootjail);
  }
  /* just to be sure */
  chdir("/");

  /* renice */
  errno=0; /* for strange nice implementations */
  nice(cfg->nice);
  if (errno!=0)
  {
    log_log(LOG_ERR,"cannot nice(%d): %s",(int)cfg->nice,strerror(errno));
    exit(1);
  }
  log_log(LOG_DEBUG,"debug: nice(%d) done",(int)cfg->nice);

#ifdef HAVE_SETGROUPS
  /* drop all supplemental groups */
  if (setgroups(0,NULL)<0)
  {
    log_log(LOG_WARNING,"cannot setgroups(0,NULL) (ignored): %s",strerror(errno));
  }
  else
  {
    log_log(LOG_DEBUG,"debug: setgroups(0,NULL) done");
  }
#else /* HAVE_SETGROUPS */
  log_log(LOG_DEBUG,"debug: setgroups() not available");
#endif /* not HAVE_SETGROUPS */

#ifdef USE_CAPABILITIES
  /* if there are any capabilities defined, set them to be kept
     across setuid() calls so we can limit them later on */
  if (cfg->capabilities!=NULL)
  {
    if (prctl(PR_SET_KEEPCAPS,1))
    {
      log_log(LOG_ERR,"cannot prctl(PR_SET_KEEPCAPS,1): %s",strerror(errno));
      exit(1);
    }
    log_log(LOG_DEBUG,"debug: prctl(PR_SET_KEEPCAPS,1) done");
    /* dump the current capabilities */
    caps=cap_get_proc();
    log_log(LOG_DEBUG,"debug: current capabilities: %s",cap_to_text(caps,NULL));
    cap_free(caps);
  }
#endif /* USE_CAPABILITIES */

  /* change to cvs gid */
  if (cfg->gid!=NOGID)
  {
    if (setgid(cfg->gid)!=0)
    {
      log_log(LOG_ERR,"cannot setgid(%d): %s",(int)cfg->gid,strerror(errno));
      exit(1);
    }
    log_log(LOG_DEBUG,"debug: setgid(%d) done",cfg->gid);
  }

  /* change to cvs uid */
  if (cfg->uid!=NOUID)
  {
    if (setuid(cfg->uid)!=0)
    {
      log_log(LOG_ERR,"cannot setuid(%d): %s",(int)cfg->uid,strerror(errno));
      exit(1);
    }
    log_log(LOG_DEBUG,"debug: setuid(%d) done",cfg->uid);
  }

#ifdef USE_CAPABILITIES
  /* if there are any capabilities defined, limit them now */
  if (cfg->capabilities!=NULL)
  {
    /* dump the current capabilities */
    caps=cap_get_proc();
    log_log(LOG_DEBUG,"debug: current capabilities: %s",cap_to_text(caps,NULL));
    cap_free(caps);
    /* limit the capabilities */
    if (cap_set_proc(cfg->capabilities)!=0)
    {
      log_log(LOG_ERR,"cannot cap_set_proc(%s): %s",cap_to_text(cfg->capabilities,NULL),strerror(errno));
      exit(1);
    }
    log_log(LOG_DEBUG,"debug: cap_set_proc(%2) done",cap_to_text(cfg->capabilities,NULL));
    /* we no longer need this so we should free it */
    cap_free(cfg->capabilities);
    /* dump the current capabilities */
    caps=cap_get_proc();
    log_log(LOG_DEBUG,"debug: current capabilities: %s",cap_to_text(caps,NULL));
    cap_free(caps);
  }
#endif /* USE_CAPABILITIES */

  /* build cvs command line */
  j=strlen(cfg->cvscmd)+1;
  for (i=1;cfg->cvsargs[i]!=NULL;i++)
    j+=strlen(cfg->cvsargs[i])+1;
  cvs_cmdline=(char *)xmalloc(j*sizeof(char));
  strcpy(cvs_cmdline,cfg->cvscmd);
  for (i=1;cfg->cvsargs[i]!=NULL;i++)
  {
    strcat(cvs_cmdline," ");
    strcat(cvs_cmdline,cfg->cvsargs[i]);
  }
  log_log(LOG_DEBUG,"debug: cvs command to execute: '%s'",cvs_cmdline);

  /* install signalhandler for terminating children */
  install_sighandler(SIGCHLD,sigchld_handler);

  /* install signalhandlers for some other signals */
  install_sighandler(SIGHUP, sigexit_handler);
  install_sighandler(SIGINT, sigexit_handler);
  install_sighandler(SIGQUIT,sigexit_handler);
  install_sighandler(SIGILL, sigexit_handler);
  install_sighandler(SIGABRT,sigexit_handler);
  install_sighandler(SIGSEGV,sigexit_handler);
  install_sighandler(SIGPIPE,sigexit_handler);
  install_sighandler(SIGALRM,sigexit_handler);
  install_sighandler(SIGTERM,sigexit_handler);
  install_sighandler(SIGUSR1,sigexit_handler);
  install_sighandler(SIGUSR2,sigexit_handler);

  log_log(LOG_INFO,"accepting connections");

  /* start waiting for incoming connections */
  while (cvsd_exitsignal==0)
  {
    /* if we are ready to handle a connection do so */
    if ((cfg->maxconnections==0)||(cvsd_numchildren<cfg->maxconnections))
    {
      /* wait for a new connection */
      acceptconnection(cfg);
      /* handle exiting child processes */
      reap_deadchildren();
    }
    else /* not ready for connection */
    {
      /* log that the connections are full */
      log_log(LOG_INFO,"too many connections (%d), not accepting new connections",cvsd_numchildren);
      /* wait until children have died */
      while (cvsd_numchildren>=cfg->maxconnections)
      {
        /* sleep unblocks on a signal */
        sleep(SLEEPSECS);
        /* handle exiting child processes */
        reap_deadchildren();
      }
      /* log that the connections are full */
      log_log(LOG_INFO,"resuming accepting new connections");
    }
  }

  /* print something about received signals */
  if (cvsd_exitsignal!=0)
  {
    log_log(LOG_INFO,"caught signal %s (%d), shutting down",
                 signame(cvsd_exitsignal),cvsd_exitsignal);
  }

  return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1