/*
 * Part of Very Secure FTPd
 * Licence: GPL v2
 * Author: Chris Evans
 * sysdeputil.c
 *
 * Highly system dependent utilities - e.g. authentication, capabilities.
 */

#include "sysdeputil.h"
#include "str.h"
#include "sysutil.h"
#include "utility.h"
#include "secbuf.h"
#include "defs.h"
#include "tunables.h"
#include "builddefs.h"

/* For Linux, this adds nothing :-) */
#include "port/porting_junk.h"

#if (defined(__FreeBSD__) && __FreeBSD__ >= 3)
  #define _FILE_OFFSET_BITS 64
  #define _LARGEFILE_SOURCE 1
  #define _LARGEFILE64_SOURCE 1
#endif

/* For INT_MAX */
#include <limits.h>

/* For fd passing */
#include <sys/types.h>
#include <sys/socket.h>
/* For FreeBSD */
#include <sys/param.h>
#include <sys/uio.h>

#include <sys/prctl.h>
#include <signal.h>

/* Configuration.. here are the possibilities */
#undef VSF_SYSDEP_HAVE_CAPABILITIES
#undef VSF_SYSDEP_HAVE_SETKEEPCAPS
#undef VSF_SYSDEP_HAVE_SETPDEATHSIG
#undef VSF_SYSDEP_HAVE_LINUX_SENDFILE
#undef VSF_SYSDEP_HAVE_FREEBSD_SENDFILE
#undef VSF_SYSDEP_HAVE_HPUX_SENDFILE
#undef VSF_SYSDEP_HAVE_AIX_SENDFILE
#undef VSF_SYSDEP_HAVE_SETPROCTITLE
#undef VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK
#undef VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE
#undef VSF_SYSDEP_HAVE_MAP_ANON
#undef VSF_SYSDEP_NEED_OLD_FD_PASSING
#undef VSF_SYSDEP_HAVE_LINUX_CLONE
#ifdef VSF_BUILD_PAM
  #define VSF_SYSDEP_HAVE_PAM
#endif
#define VSF_SYSDEP_HAVE_SHADOW
#define VSF_SYSDEP_HAVE_USERSHELL
#define VSF_SYSDEP_HAVE_LIBCAP
#define VSF_SYSDEP_HAVE_UTMPX

#define __USE_GNU
#include <utmpx.h>

/* BEGIN config */
#if defined(__linux__)
  #include <errno.h>
  #include <syscall.h>
  #define VSF_SYSDEP_HAVE_LINUX_CLONE
  #include <sched.h>
  #ifndef CLONE_NEWPID
    #define CLONE_NEWPID 0x20000000
  #endif
  #ifndef CLONE_NEWIPC
    #define CLONE_NEWIPC 0x08000000
  #endif
  #ifndef CLONE_NEWNET
    #define CLONE_NEWNET 0x40000000
  #endif
  #include <linux/unistd.h>
  #include <errno.h>
  #include <syscall.h>
#endif

#if defined(__linux__) && !defined(__ia64__) && !defined(__s390__)
  #define VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK
  #include <linux/version.h>
  #if defined(LINUX_VERSION_CODE) && defined(KERNEL_VERSION)
    #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0))
      #define VSF_SYSDEP_HAVE_CAPABILITIES
      #define VSF_SYSDEP_HAVE_LINUX_SENDFILE
      #ifdef PR_SET_KEEPCAPS
        #define VSF_SYSDEP_HAVE_SETKEEPCAPS
      #endif
      #ifdef PR_SET_PDEATHSIG
        #define VSF_SYSDEP_HAVE_SETPDEATHSIG
      #endif
    #endif
  #endif
#endif

#if (defined(__FreeBSD__) && __FreeBSD__ >= 3)
  #define VSF_SYSDEP_HAVE_FREEBSD_SENDFILE
  #define VSF_SYSDEP_HAVE_SETPROCTITLE
#endif

#if defined(__NetBSD__)
  #include <stdlib.h>
  #define VSF_SYSDEP_HAVE_SETPROCTITLE
  #include <sys/param.h>
  #if __NetBSD_Version__ >= 106070000
    #define WTMPX_FILE _PATH_WTMPX
  #else
    #undef VSF_SYSDEP_HAVE_UTMPX
  #endif
#endif

#ifdef __hpux
  #include <sys/socket.h>
  #ifdef SF_DISCONNECT
    #define VSF_SYSDEP_HAVE_HPUX_SENDFILE
  #endif
  #include <sys/param.h>
  #include <sys/pstat.h>
  #ifdef PSTAT_SETCMD
    #define VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE
  #endif
  #undef VSF_SYSDEP_HAVE_UTMPX
#endif

#include <unistd.h>
#include <sys/mman.h>
#ifdef MAP_ANON
  #define VSF_SYSDEP_HAVE_MAP_ANON
#endif

#ifdef __sgi
  #undef VSF_SYSDEP_HAVE_USERSHELL
  #undef VSF_SYSDEP_HAVE_LIBCAP
#endif

#ifdef _AIX
  #undef VSF_SYSDEP_HAVE_USERSHELL
  #undef VSF_SYSDEP_HAVE_LIBCAP
  #undef VSF_SYSDEP_HAVE_UTMPX
  #undef VSF_SYSDEP_HAVE_PAM
  #undef VSF_SYSDEP_HAVE_SHADOW
  #undef VSF_SYSDEP_HAVE_SETPROCTITLE
  #define VSF_SYSDEP_HAVE_AIX_SENDFILE
  #define VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK
  #define VSF_SYSDEP_HAVE_MAP_ANON
#endif

#ifdef __osf__
  #undef VSF_SYSDEP_HAVE_USERSHELL
#endif

#if (defined(__sgi) || defined(__hpux) || defined(__osf__))
  #define VSF_SYSDEP_NEED_OLD_FD_PASSING
#endif

#ifdef __sun
  #define VSF_SYSDEP_HAVE_SOLARIS_SENDFILE
#endif
/* END config */

/* PAM support - we include our own dummy version if the system lacks this */
#include <security/pam_appl.h>

/* No PAM? Try getspnam() with a getpwnam() fallback */
#ifndef VSF_SYSDEP_HAVE_PAM
/* This may hit our own "dummy" include and undef VSF_SYSDEP_HAVE_SHADOW */
#include <shadow.h>
#include <pwd.h>
#include <unistd.h>
#include <crypt.h>
#endif

/* Prefer libcap based capabilities over raw syscall capabilities */
#include <sys/capability.h>

#if defined(VSF_SYSDEP_HAVE_CAPABILITIES) && !defined(VSF_SYSDEP_HAVE_LIBCAP)
#include <linux/unistd.h>
#include <linux/capability.h>
#include <errno.h>
#include <syscall.h>
int capset(cap_user_header_t header, const cap_user_data_t data)
{
  return syscall(__NR_capset, header, data);
}
/* Gross HACK to avoid warnings - linux headers overlap glibc headers */
#undef __NFDBITS
#undef __FDMASK
#endif /* VSF_SYSDEP_HAVE_CAPABILITIES */

#if defined(VSF_SYSDEP_HAVE_LINUX_SENDFILE) || \
    defined(VSF_SYSDEP_HAVE_SOLARIS_SENDFILE)
#include <sys/sendfile.h>
#elif defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE)
#include <sys/types.h>
#include <sys/socket.h>
#elif defined(VSF_SYSDEP_HAVE_HPUX_SENDFILE)
#include <sys/socket.h>
#else /* VSF_SYSDEP_HAVE_LINUX_SENDFILE */
#include <unistd.h>
#endif /* VSF_SYSDEP_HAVE_LINUX_SENDFILE */

#ifdef VSF_SYSDEP_HAVE_SETPROCTITLE
#include <sys/types.h>
#include <unistd.h>
#endif

#ifdef VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK
extern char** environ;
static unsigned int s_proctitle_space = 0;
static int s_proctitle_inited = 0;
static char* s_p_proctitle = 0;
#endif

#ifndef VSF_SYSDEP_HAVE_MAP_ANON
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
static int s_zero_fd = -1;
#endif

/* File private functions/variables */
static int do_sendfile(const int out_fd, const int in_fd,
                       unsigned int num_send, filesize_t start_pos);
static void vsf_sysutil_setproctitle_internal(const char* p_text);
static struct mystr s_proctitle_prefix_str;

/* These two aren't static to avoid OpenBSD build warnings. */
void vsf_insert_uwtmp(const struct mystr* p_user_str,
                      const struct mystr* p_host_str);
void vsf_remove_uwtmp(void);

#ifndef VSF_SYSDEP_HAVE_PAM
int
vsf_sysdep_check_auth(struct mystr* p_user_str,
                      const struct mystr* p_pass_str,
                      const struct mystr* p_remote_host)
{
  const char* p_crypted;
  const struct passwd* p_pwd = getpwnam(str_getbuf(p_user_str));
  (void) p_remote_host;
  if (p_pwd == NULL)
  {
    return 0;
  }
  #ifdef VSF_SYSDEP_HAVE_USERSHELL
  if (tunable_check_shell)
  {
    const char* p_shell;
    while ((p_shell = getusershell()) != NULL)
    {
      if (!vsf_sysutil_strcmp(p_shell, p_pwd->pw_shell))
      {
        break;
      }
    }
    endusershell();
    if (p_shell == NULL)
    {
      return 0;
    }
  }
  #endif
  #ifdef VSF_SYSDEP_HAVE_SHADOW
  {
    const struct spwd* p_spwd = getspnam(str_getbuf(p_user_str));
    if (p_spwd != NULL)
    {
      long curr_time = vsf_sysutil_get_time_sec();
      int days;
      days = curr_time / (60 * 60 * 24);
      if (p_spwd->sp_expire > 0 && p_spwd->sp_expire < days)
      {
        return 0;
      }
      if (p_spwd->sp_lstchg > 0 && p_spwd->sp_max > 0 &&
          p_spwd->sp_lstchg + p_spwd->sp_max < days)
      {
        return 0;
      }
      p_crypted = crypt(str_getbuf(p_pass_str), p_spwd->sp_pwdp);
      if (!vsf_sysutil_strcmp(p_crypted, p_spwd->sp_pwdp))
      {
        return 1;
      }
    }
  }
  #endif /* VSF_SYSDEP_HAVE_SHADOW */
  p_crypted = crypt(str_getbuf(p_pass_str), p_pwd->pw_passwd);
  if (!vsf_sysutil_strcmp(p_crypted, p_pwd->pw_passwd))
  {
    return 1;
  }
  return 0;
}

#else /* VSF_SYSDEP_HAVE_PAM */

#if (defined(__sun) || defined(__hpux)) && \
    !defined(LINUX_PAM) && !defined(_OPENPAM)
/* Sun's PAM doesn't use const here, while Linux-PAM and OpenPAM do */
#define lo_const
#else
#define lo_const const
#endif
typedef lo_const void* pam_item_t;

static pam_handle_t* s_pamh;
static struct mystr s_pword_str;
static int pam_conv_func(int nmsg, const struct pam_message** p_msg,
                         struct pam_response** p_reply, void* p_addata);
static void vsf_auth_shutdown(void);

int
vsf_sysdep_check_auth(struct mystr* p_user_str,
                      const struct mystr* p_pass_str,
                      const struct mystr* p_remote_host)
{
  int retval;
  pam_item_t item;
  const char* pam_user_name = 0;
  struct pam_conv the_conv =
  {
    &pam_conv_func,
    0
  };
  if (s_pamh != 0)
  {
    bug("vsf_sysdep_check_auth");
  }
  str_copy(&s_pword_str, p_pass_str);
  retval = pam_start(tunable_pam_service_name,
                     str_getbuf(p_user_str), &the_conv, &s_pamh);
  if (retval != PAM_SUCCESS)
  {
    s_pamh = 0;
    return 0;
  }
#ifdef PAM_RHOST
  retval = pam_set_item(s_pamh, PAM_RHOST, str_getbuf(p_remote_host));
  if (retval != PAM_SUCCESS)
  {
    (void) pam_end(s_pamh, retval);
    s_pamh = 0;
    return 0;
  }
#endif
#ifdef PAM_TTY
  retval = pam_set_item(s_pamh, PAM_TTY, "ftp");
  if (retval != PAM_SUCCESS)
  {
    (void) pam_end(s_pamh, retval);
    s_pamh = 0;
    return 0;
  }
#endif
#ifdef PAM_RUSER
  retval = pam_set_item(s_pamh, PAM_RUSER, str_getbuf(p_user_str));
  if (retval != PAM_SUCCESS)
  {
    (void) pam_end(s_pamh, retval);
    s_pamh = 0;
    return 0;
  }
#endif
  retval = pam_authenticate(s_pamh, 0);
  if (retval != PAM_SUCCESS)
  {
    (void) pam_end(s_pamh, retval);
    s_pamh = 0;
    return 0;
  }
#ifdef PAM_USER
  retval = pam_get_item(s_pamh, PAM_USER, &item);
  if (retval != PAM_SUCCESS)
  {
    (void) pam_end(s_pamh, retval);
    s_pamh = 0;
    return 0;
  }
  pam_user_name = item;
  str_alloc_text(p_user_str, pam_user_name);
#endif
  retval = pam_acct_mgmt(s_pamh, 0);
  if (retval != PAM_SUCCESS)
  {
    (void) pam_end(s_pamh, retval);
    s_pamh = 0;
    return 0;
  }
  retval = pam_setcred(s_pamh, PAM_ESTABLISH_CRED);
  if (retval != PAM_SUCCESS)
  {
    (void) pam_end(s_pamh, retval);
    s_pamh = 0;
    return 0;
  }
  if (!tunable_session_support)
  {
    /* You're in already! */
    (void) pam_end(s_pamh, retval);
    s_pamh = 0;
    return 1;
  }
  /* Must do this BEFORE opening a session for pam_limits to count us */
  vsf_insert_uwtmp(p_user_str, p_remote_host);
  retval = pam_open_session(s_pamh, 0);
  if (retval != PAM_SUCCESS)
  {
    vsf_remove_uwtmp();
    (void) pam_setcred(s_pamh, PAM_DELETE_CRED);
    (void) pam_end(s_pamh, retval);
    s_pamh = 0;
    return 0;
  }
  /* We MUST ensure the PAM session, utmp, wtmp etc. are cleaned up, however
   * we exit.
   */
  vsf_sysutil_set_exit_func(vsf_auth_shutdown);
  /* You're in dude */
  return 1;
}

static void
vsf_auth_shutdown(void)
{
  if (s_pamh == 0)
  {
    bug("vsf_auth_shutdown");
  }
  (void) pam_close_session(s_pamh, 0);
  (void) pam_setcred(s_pamh, PAM_DELETE_CRED);
  (void) pam_end(s_pamh, PAM_SUCCESS);
  s_pamh = 0;
  vsf_remove_uwtmp();
}

static int
pam_conv_func(int nmsg, const struct pam_message** p_msg,
              struct pam_response** p_reply, void* p_addata)
{
  int i;
  struct pam_response* p_resps = 0;
  (void) p_addata;
  if (nmsg < 0)
  {
    bug("dodgy nmsg in pam_conv_func");
  }
  p_resps = vsf_sysutil_malloc(sizeof(struct pam_response) * nmsg);
  for (i=0; i<nmsg; i++)
  {
    switch (p_msg[i]->msg_style)
    {
      case PAM_PROMPT_ECHO_OFF:
        p_resps[i].resp_retcode = PAM_SUCCESS;
        p_resps[i].resp = (char*) str_strdup(&s_pword_str);
        break;
      case PAM_TEXT_INFO:
      case PAM_ERROR_MSG:
        p_resps[i].resp_retcode = PAM_SUCCESS;
        p_resps[i].resp = 0;
        break;
      case PAM_PROMPT_ECHO_ON:
      default:
        vsf_sysutil_free(p_resps);
        return PAM_CONV_ERR;
        break;
    }
  }
  *p_reply = p_resps;
  return PAM_SUCCESS;
}

#endif /* VSF_SYSDEP_HAVE_PAM */

/* Capabilities support (or lack thereof) */
void
vsf_sysdep_keep_capabilities(void)
{
  if (!vsf_sysdep_has_capabilities_as_non_root())
  {
    bug("asked to keep capabilities, but no support exists");
  }
#ifdef VSF_SYSDEP_HAVE_SETKEEPCAPS
  {
    int retval = prctl(PR_SET_KEEPCAPS, 1);
    if (vsf_sysutil_retval_is_error(retval))
    {
      die("prctl");
    }
  }
#endif /* VSF_SYSDEP_HAVE_SETKEEPCAPS */
}
#if !defined(VSF_SYSDEP_HAVE_CAPABILITIES) && !defined(VSF_SYSDEP_HAVE_LIBCAP)

int
vsf_sysdep_has_capabilities(void)
{
  return 0;
}

int
vsf_sysdep_has_capabilities_as_non_root(void)
{
  return 0;
}

void
vsf_sysdep_adopt_capabilities(unsigned int caps)
{
  (void) caps;
  bug("asked to adopt capabilities, but no support exists");
}

#else /* VSF_SYSDEP_HAVE_CAPABILITIES || VSF_SYSDEP_HAVE_LIBCAP */

static int do_checkcap(void);

int
vsf_sysdep_has_capabilities_as_non_root(void)
{
  static int s_prctl_checked;
  static int s_runtime_prctl_works;
  if (!s_prctl_checked)
  {
  #ifdef VSF_SYSDEP_HAVE_SETKEEPCAPS
    /* Clarity: note embedded call to prctl() syscall */
    if (!vsf_sysutil_retval_is_error(prctl(PR_SET_KEEPCAPS, 0)))
    {
      s_runtime_prctl_works = 1;
    }
  #endif /* VSF_SYSDEP_HAVE_SETKEEPCAPS */
    s_prctl_checked = 1;
  }
  return s_runtime_prctl_works;
}

int
vsf_sysdep_has_capabilities(void)
{
  /* Even though compiled with capabilities, the runtime system may lack them.
   * Also, RH7.0 kernel headers advertise a 2.4.0 box, but on a 2.2.x kernel!
   */
  static int s_caps_checked;
  static int s_runtime_has_caps;
  if (!s_caps_checked)
  {
    s_runtime_has_caps = do_checkcap();
    s_caps_checked = 1;
  }
  return s_runtime_has_caps;
}
  
  #ifndef VSF_SYSDEP_HAVE_LIBCAP
static int
do_checkcap(void)
{
  /* EFAULT (EINVAL if page 0 mapped) vs. ENOSYS */
  int retval = capset(0, 0);
  if (!vsf_sysutil_retval_is_error(retval) ||
      vsf_sysutil_get_error() != kVSFSysUtilErrNOSYS)
  {
    return 1;
  }
  return 0;
}

void
vsf_sysdep_adopt_capabilities(unsigned int caps)
{
  /* n.b. yes I know I should be using libcap!! */
  int retval;
  struct __user_cap_header_struct cap_head;
  struct __user_cap_data_struct cap_data;
  __u32 cap_mask = 0;
  if (!caps)
  {
    bug("asked to adopt no capabilities");
  }
  vsf_sysutil_memclr(&cap_head, sizeof(cap_head));
  vsf_sysutil_memclr(&cap_data, sizeof(cap_data));
  cap_head.version = _LINUX_CAPABILITY_VERSION;
  cap_head.pid = 0;
  if (caps & kCapabilityCAP_CHOWN)
  {
    cap_mask |= (1 << CAP_CHOWN);
  }
  if (caps & kCapabilityCAP_NET_BIND_SERVICE)
  {
    cap_mask |= (1 << CAP_NET_BIND_SERVICE);
  }
  cap_data.effective = cap_data.permitted = cap_mask;
  cap_data.inheritable = 0;
  retval = capset(&cap_head, &cap_data);
  if (retval != 0)
  {
    die("capset");
  }
}

  #else /* VSF_SYSDEP_HAVE_LIBCAP */
static int
do_checkcap(void)
{
  cap_t current_caps = cap_get_proc();
  cap_free(current_caps);
  if (current_caps != NULL)
  {
    return 1;
  }
  return 0;
}

void
vsf_sysdep_adopt_capabilities(unsigned int caps)
{
  int retval;
  cap_value_t cap_value;
  cap_t adopt_caps = cap_init();
  if (caps & kCapabilityCAP_CHOWN)
  {
    cap_value = CAP_CHOWN;
    cap_set_flag(adopt_caps, CAP_EFFECTIVE, 1, &cap_value, CAP_SET);
    cap_set_flag(adopt_caps, CAP_PERMITTED, 1, &cap_value, CAP_SET);
  }
  if (caps & kCapabilityCAP_NET_BIND_SERVICE)
  {
    cap_value = CAP_NET_BIND_SERVICE;
    cap_set_flag(adopt_caps, CAP_EFFECTIVE, 1, &cap_value, CAP_SET);
    cap_set_flag(adopt_caps, CAP_PERMITTED, 1, &cap_value, CAP_SET);
  }
  retval = cap_set_proc(adopt_caps);
  if (retval != 0)
  {
    die("cap_set_proc");
  }
  cap_free(adopt_caps);
}

  #endif /* !VSF_SYSDEP_HAVE_LIBCAP */
#endif /* VSF_SYSDEP_HAVE_CAPABILITIES || VSF_SYSDEP_HAVE_LIBCAP */

int
vsf_sysutil_sendfile(const int out_fd, const int in_fd,
                     filesize_t* p_offset, filesize_t num_send,
                     unsigned int max_chunk)
{
  /* Grr - why is off_t signed? */
  if (*p_offset < 0 || num_send < 0)
  {
    die("invalid offset or send count in vsf_sysutil_sendfile");
  }
  if (max_chunk == 0)
  {
    max_chunk = INT_MAX;
  }
  while (num_send > 0)
  {
    int retval;
    unsigned int send_this_time;
    if (num_send > max_chunk)
    {
      send_this_time = max_chunk;
    }
    else
    {
      send_this_time = (unsigned int) num_send;
    }
    /* Keep input file position in line with sendfile() calls */
    vsf_sysutil_lseek_to(in_fd, *p_offset);
    retval = do_sendfile(out_fd, in_fd, send_this_time, *p_offset);
    if (vsf_sysutil_retval_is_error(retval) || retval == 0)
    {
      return retval;
    }
    num_send -= retval;
    *p_offset += retval;
  }
  return 0;
}

static int do_sendfile(const int out_fd, const int in_fd,
                       unsigned int num_send, filesize_t start_pos)
{
  /* Probably should one day be shared with instance in ftpdataio.c */
  static char* p_recvbuf;
  unsigned int total_written = 0;
  int retval;
  enum EVSFSysUtilError error;
  (void) start_pos;
  (void) error;
#if defined(VSF_SYSDEP_HAVE_LINUX_SENDFILE) || \
    defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE) || \
    defined(VSF_SYSDEP_HAVE_HPUX_SENDFILE) || \
    defined(VSF_SYSDEP_HAVE_AIX_SENDFILE) || \
    defined(VSF_SYSDEP_HAVE_SOLARIS_SENDFILE)
  if (tunable_use_sendfile)
  {
    static int s_sendfile_checked;
    static int s_runtime_sendfile_works;
    if (!s_sendfile_checked || s_runtime_sendfile_works)
    {
      do
      {
  #ifdef VSF_SYSDEP_HAVE_LINUX_SENDFILE
        retval = sendfile(out_fd, in_fd, NULL, num_send);
  #elif defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE)
        {
          /* XXX - start_pos will truncate on 32-bit machines - can we
           * say "start from current pos"?
           */
          off_t written = 0;
          retval = sendfile(in_fd, out_fd, start_pos, num_send, NULL,
                            &written, 0);
          /* Translate to Linux-like retval */
          if (written > 0)
          {
            retval = (int) written;
          }
        }
  #elif defined(VSF_SYSDEP_HAVE_SOLARIS_SENDFILE)
        {
          size_t written = 0;
          struct sendfilevec the_vec;
          vsf_sysutil_memclr(&the_vec, sizeof(the_vec));
          the_vec.sfv_fd = in_fd;
          the_vec.sfv_off = start_pos;
          the_vec.sfv_len = num_send;
          retval = sendfilev(out_fd, &the_vec, 1, &written);
          /* Translate to Linux-like retval */
          if (written > 0)
          {
            retval = (int) written;
          }
        }
  #elif defined(VSF_SYSDEP_HAVE_AIX_SENDFILE)
        {
          struct sf_parms sf_iobuf;
          vsf_sysutil_memclr(&sf_iobuf, sizeof(sf_iobuf));
          sf_iobuf.header_data = NULL;
          sf_iobuf.header_length = 0;
          sf_iobuf.trailer_data = NULL;
          sf_iobuf.trailer_length = 0;
          sf_iobuf.file_descriptor = in_fd;
          sf_iobuf.file_offset = start_pos;
          sf_iobuf.file_bytes = num_send;

          retval = send_file((int*)&out_fd, &sf_iobuf, 0);
          if (retval >= 0)
          {
            retval = sf_iobuf.bytes_sent;
          }
        }
  #else /* must be VSF_SYSDEP_HAVE_HPUX_SENDFILE */
        {
          retval = sendfile(out_fd, in_fd, start_pos, num_send, NULL, 0);
        }
  #endif /* VSF_SYSDEP_HAVE_LINUX_SENDFILE */
        error = vsf_sysutil_get_error();
        vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, out_fd);
      }
      while (vsf_sysutil_retval_is_error(retval) &&
             error == kVSFSysUtilErrINTR);
      if (!s_sendfile_checked)
      {
        s_sendfile_checked = 1;
        if (!vsf_sysutil_retval_is_error(retval) ||
            error != kVSFSysUtilErrNOSYS)
        {
          s_runtime_sendfile_works = 1;
        }
      }
      if (!vsf_sysutil_retval_is_error(retval))
      {
        return retval;
      }
      if (s_runtime_sendfile_works && error != kVSFSysUtilErrINVAL &&
          error != kVSFSysUtilErrOPNOTSUPP)
      {
        return retval;
      }
      /* Fall thru to normal implementation. We won't check again. NOTE -
       * also falls through if sendfile() is OK but it returns EINVAL. For
       * Linux this means the file was not page cache backed. Original
       * complaint was trying to serve files from an NTFS filesystem!
       */
    }
  }
#endif /* VSF_SYSDEP_HAVE_LINUX_SENDFILE || VSF_SYSDEP_HAVE_FREEBSD_SENDFILE */
  if (p_recvbuf == 0)
  {
    vsf_secbuf_alloc(&p_recvbuf, VSFTP_DATA_BUFSIZE);
  }
  while (1)
  {
    unsigned int num_read;
    unsigned int num_written;
    unsigned int num_read_this_time = VSFTP_DATA_BUFSIZE;
    if (num_read_this_time > num_send)
    {
      num_read_this_time = num_send;
    }
    retval = vsf_sysutil_read(in_fd, p_recvbuf, num_read_this_time);
    if (retval < 0)
    {
      return retval;
    }
    else if (retval == 0)
    {
      return -1;
    }
    num_read = (unsigned int) retval;
    retval = vsf_sysutil_write_loop(out_fd, p_recvbuf, num_read);
    if (retval < 0)
    {
      return retval;
    }
    num_written = (unsigned int) retval;
    total_written += num_written;
    if (num_written != num_read)
    {
      return num_written;
    }
    if (num_written > num_send)
    {
      bug("num_written bigger than num_send in do_sendfile");
    }
    num_send -= num_written;
    if (num_send == 0)
    {
      /* Bingo! */
      return total_written;
    }
  }
}

void
vsf_sysutil_set_proctitle_prefix(const struct mystr* p_str)
{
  str_copy(&s_proctitle_prefix_str, p_str);
}

/* This delegation is common to all setproctitle() implementations */
void
vsf_sysutil_setproctitle_str(const struct mystr* p_str)
{
  vsf_sysutil_setproctitle(str_getbuf(p_str));
}

void
vsf_sysutil_setproctitle(const char* p_text)
{
  struct mystr proctitle_str = INIT_MYSTR;
  str_copy(&proctitle_str, &s_proctitle_prefix_str);
  if (!str_isempty(&proctitle_str))
  {
    str_append_text(&proctitle_str, ": ");
  }
  str_append_text(&proctitle_str, p_text);
  vsf_sysutil_setproctitle_internal(str_getbuf(&proctitle_str));
  str_free(&proctitle_str);
}

#ifdef VSF_SYSDEP_HAVE_SETPROCTITLE
void
vsf_sysutil_setproctitle_init(int argc, const char* argv[])
{
  (void) argc;
  (void) argv;
}

void
vsf_sysutil_setproctitle_internal(const char* p_buf)
{
  setproctitle("%s", p_buf);
}
#elif defined(VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE)
void
vsf_sysutil_setproctitle_init(int argc, const char* argv[])
{
  (void) argc;
  (void) argv;
}

void
vsf_sysutil_setproctitle_internal(const char* p_buf)
{
  struct mystr proctitle_str = INIT_MYSTR;
  union pstun p;
  str_alloc_text(&proctitle_str, "vsftpd: ");
  str_append_text(&proctitle_str, p_buf);
  p.pst_command = str_getbuf(&proctitle_str);
  pstat(PSTAT_SETCMD, p, 0, 0, 0);
  str_free(&proctitle_str);
}
#elif defined(VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK)
void
vsf_sysutil_setproctitle_init(int argc, const char* argv[])
{
  int i;
  char** p_env = environ;
  if (s_proctitle_inited)
  {
    bug("vsf_sysutil_setproctitle_init called twice");
  }
  s_proctitle_inited = 1;
  if (argv[0] == 0)
  {
    die("no argv[0] in vsf_sysutil_setproctitle_init");
  }
  for (i=0; i<argc; i++)
  {
    s_proctitle_space += vsf_sysutil_strlen(argv[i]) + 1;
    if (i > 0)
    {
      argv[i] = 0;
    }
  }
  while (*p_env != 0)
  {
    s_proctitle_space += vsf_sysutil_strlen(*p_env) + 1;
    p_env++;
  }
  /* Oops :-) */
  environ = 0;
  s_p_proctitle = (char*) argv[0];
  vsf_sysutil_memclr(s_p_proctitle, s_proctitle_space);
}

void
vsf_sysutil_setproctitle_internal(const char* p_buf)
{
  struct mystr proctitle_str = INIT_MYSTR;
  unsigned int to_copy;
  if (!s_proctitle_inited)
  {
    bug("vsf_sysutil_setproctitle: not initialized");
  }
  vsf_sysutil_memclr(s_p_proctitle, s_proctitle_space);
  if (s_proctitle_space < 32)
  {
    return;
  }
  str_alloc_text(&proctitle_str, "vsftpd: ");
  str_append_text(&proctitle_str, p_buf);
  to_copy = str_getlen(&proctitle_str);
  if (to_copy > s_proctitle_space - 1)
  {
    to_copy = s_proctitle_space - 1;
  }
  vsf_sysutil_memcpy(s_p_proctitle, str_getbuf(&proctitle_str), to_copy);
  str_free(&proctitle_str);
  s_p_proctitle[to_copy] = '\0';
}
#else /* VSF_SYSDEP_HAVE_SETPROCTITLE */
void
vsf_sysutil_setproctitle_init(int argc, const char* argv[])
{
  (void) argc;
  (void) argv;
}

void
vsf_sysutil_setproctitle_internal(const char* p_buf)
{
  (void) p_buf;
}
#endif /* VSF_SYSDEP_HAVE_SETPROCTITLE */

#ifdef VSF_SYSDEP_HAVE_MAP_ANON
void
vsf_sysutil_map_anon_pages_init(void)
{
}

void*
vsf_sysutil_map_anon_pages(unsigned int length)
{
  char* retval = mmap(0, length, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANON, -1, 0);
  if (retval == MAP_FAILED)
  {
    die("mmap");
  }
  return retval;
}
#else /* VSF_SYSDEP_HAVE_MAP_ANON */
void
vsf_sysutil_map_anon_pages_init(void)
{
  if (s_zero_fd != -1)
  {
    bug("vsf_sysutil_map_anon_pages_init called twice");
  }
  s_zero_fd = open("/dev/zero", O_RDWR);
  if (s_zero_fd < 0)
  {
    die("could not open /dev/zero");
  }
}

void*
vsf_sysutil_map_anon_pages(unsigned int length)
{
  char* retval = mmap(0, length, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE, s_zero_fd, 0);
  if (retval == MAP_FAILED)
  {
    die("mmap");
  }
  return retval;
}
#endif /* VSF_SYSDEP_HAVE_MAP_ANON */

#ifndef VSF_SYSDEP_NEED_OLD_FD_PASSING

void
vsf_sysutil_send_fd(int sock_fd, int send_fd)
{
  int retval;
  struct msghdr msg;
  struct cmsghdr* p_cmsg;
  struct iovec vec;
  char cmsgbuf[CMSG_SPACE(sizeof(send_fd))];
  int* p_fds;
  char sendchar = 0;
  msg.msg_control = cmsgbuf;
  msg.msg_controllen = sizeof(cmsgbuf);
  p_cmsg = CMSG_FIRSTHDR(&msg);
  p_cmsg->cmsg_level = SOL_SOCKET;
  p_cmsg->cmsg_type = SCM_RIGHTS;
  p_cmsg->cmsg_len = CMSG_LEN(sizeof(send_fd));
  p_fds = (int*)CMSG_DATA(p_cmsg);
  *p_fds = send_fd;
  msg.msg_controllen = p_cmsg->cmsg_len;
  msg.msg_name = NULL;
  msg.msg_namelen = 0;
  msg.msg_iov = &vec;
  msg.msg_iovlen = 1;
  msg.msg_flags = 0;
  /* "To pass file descriptors or credentials you need to send/read at
   * least on byte" (man 7 unix)
   */
  vec.iov_base = &sendchar;
  vec.iov_len = sizeof(sendchar);
  retval = sendmsg(sock_fd, &msg, 0);
  if (retval != 1)
  {
    die("sendmsg");
  }
}

int
vsf_sysutil_recv_fd(const int sock_fd)
{
  int retval;
  struct msghdr msg;
  char recvchar;
  struct iovec vec;
  int recv_fd;
  char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))];
  struct cmsghdr* p_cmsg;
  int* p_fd;
  vec.iov_base = &recvchar;
  vec.iov_len = sizeof(recvchar);
  msg.msg_name = NULL;
  msg.msg_namelen = 0;
  msg.msg_iov = &vec;
  msg.msg_iovlen = 1;
  msg.msg_control = cmsgbuf;
  msg.msg_controllen = sizeof(cmsgbuf);
  msg.msg_flags = 0;
  /* In case something goes wrong, set the fd to -1 before the syscall */
  p_fd = (int*)CMSG_DATA(CMSG_FIRSTHDR(&msg));
  *p_fd = -1;  
  retval = recvmsg(sock_fd, &msg, 0);
  if (retval != 1)
  {
    die("recvmsg");
  }
  p_cmsg = CMSG_FIRSTHDR(&msg);
  if (p_cmsg == NULL)
  {
    die("no passed fd");
  }
  /* We used to verify the returned cmsg_level, cmsg_type and cmsg_len here,
   * but Linux 2.0 totally uselessly fails to fill these in.
   */
  p_fd = (int*)CMSG_DATA(p_cmsg);
  recv_fd = *p_fd;
  if (recv_fd == -1)
  {
    die("no passed fd");
  }
  return recv_fd;
}

#else /* !VSF_SYSDEP_NEED_OLD_FD_PASSING */

void
vsf_sysutil_send_fd(int sock_fd, int send_fd)
{
  int retval;
  char send_char = 0;
  struct msghdr msg;
  struct iovec vec;
  vec.iov_base = &send_char;
  vec.iov_len = 1;
  msg.msg_name = NULL;
  msg.msg_namelen = 0;
  msg.msg_iov = &vec;
  msg.msg_iovlen = 1;
  msg.msg_accrights = (caddr_t) &send_fd;
  msg.msg_accrightslen = sizeof(send_fd);
  retval = sendmsg(sock_fd, &msg, 0);
  if (retval != 1)
  {
    die("sendmsg");
  }
}

int
vsf_sysutil_recv_fd(int sock_fd)
{
  int retval;
  struct msghdr msg;
  struct iovec vec;
  char recv_char;
  int recv_fd = -1;
  vec.iov_base = &recv_char;
  vec.iov_len = 1;
  msg.msg_name = NULL;
  msg.msg_namelen = 0;
  msg.msg_iov = &vec;
  msg.msg_iovlen = 1;
  msg.msg_accrights = (caddr_t) &recv_fd;
  msg.msg_accrightslen = sizeof(recv_fd);
  retval = recvmsg(sock_fd, &msg, 0);
  if (retval != 1)
  {
    die("recvmsg");
  }
  if (recv_fd == -1)
  {
    die("no passed fd");
  }
  return recv_fd;
}

#endif /* !VSF_SYSDEP_NEED_OLD_FD_PASSING */

#ifndef VSF_SYSDEP_HAVE_UTMPX

void
vsf_insert_uwtmp(const struct mystr* p_user_str,
                 const struct mystr* p_host_str)
{
  (void) p_user_str;
  (void) p_host_str;
}

void
vsf_remove_uwtmp(void)
{
}

#else /* !VSF_SYSDEP_HAVE_UTMPX */

/* IMHO, the pam_unix module REALLY should be doing this in its SM component */
/* Statics */
static int s_uwtmp_inserted;
static struct utmpx s_utent;

void
vsf_insert_uwtmp(const struct mystr* p_user_str,
                 const struct mystr* p_host_str)
{
  if (sizeof(s_utent.ut_line) < 16)
  {
    return;
  }
  if (s_uwtmp_inserted)
  {
    bug("vsf_insert_uwtmp");
  }
  {
    struct mystr line_str = INIT_MYSTR;
    str_alloc_text(&line_str, "vsftpd:");
    str_append_ulong(&line_str, vsf_sysutil_getpid());
    if (str_getlen(&line_str) >= sizeof(s_utent.ut_line))
    {
      str_free(&line_str);
      return;
    }
    vsf_sysutil_strcpy(s_utent.ut_line, str_getbuf(&line_str),
                       sizeof(s_utent.ut_line));
    str_free(&line_str);
  }
  s_uwtmp_inserted = 1;
  s_utent.ut_type = USER_PROCESS;
  s_utent.ut_pid = vsf_sysutil_getpid();
  vsf_sysutil_strcpy(s_utent.ut_user, str_getbuf(p_user_str),
                     sizeof(s_utent.ut_user));
  vsf_sysutil_strcpy(s_utent.ut_host, str_getbuf(p_host_str),
                     sizeof(s_utent.ut_host));
  s_utent.ut_tv.tv_sec = vsf_sysutil_get_time_sec();
  setutxent();
  (void) pututxline(&s_utent);
  endutxent();
  updwtmpx(WTMPX_FILE, &s_utent);
}

void
vsf_remove_uwtmp(void)
{
  if (!s_uwtmp_inserted)
  {
    return;
  }
  s_uwtmp_inserted = 0;
  s_utent.ut_type = DEAD_PROCESS;
  vsf_sysutil_memclr(s_utent.ut_user, sizeof(s_utent.ut_user));
  vsf_sysutil_memclr(s_utent.ut_host, sizeof(s_utent.ut_host));
  s_utent.ut_tv.tv_sec = 0;
  setutxent();
  (void) pututxline(&s_utent);
  endutxent();
  s_utent.ut_tv.tv_sec = vsf_sysutil_get_time_sec();
  updwtmpx(WTMPX_FILE, &s_utent);
}

#endif /* !VSF_SYSDEP_HAVE_UTMPX */

void
vsf_set_die_if_parent_dies()
{
#ifdef VSF_SYSDEP_HAVE_SETPDEATHSIG
  if (prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) != 0)
  {
    die("prctl");
  }
#endif
}

void
vsf_set_term_if_parent_dies()
{
#ifdef VSF_SYSDEP_HAVE_SETPDEATHSIG
  if (prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) != 0)
  {
    die("prctl");
  }
#endif
}

int
vsf_sysutil_fork_isolate_failok()
{
#ifdef VSF_SYSDEP_HAVE_LINUX_CLONE
  static int cloneflags_work = 1;
  if (cloneflags_work)
  {
    int ret = syscall(__NR_clone, CLONE_NEWPID | CLONE_NEWIPC | SIGCHLD, NULL);
    if (ret != -1 || (errno != EINVAL && errno != EPERM))
    {
      if (ret == 0)
      {
        vsf_sysutil_post_fork();
      }
      return ret;
    }
    cloneflags_work = 0;
  }
#endif
  return vsf_sysutil_fork_failok();
}

int
vsf_sysutil_fork_newnet()
{
#ifdef VSF_SYSDEP_HAVE_LINUX_CLONE
  static int cloneflags_work = 1;
  if (cloneflags_work)
  {
    int ret = syscall(__NR_clone, CLONE_NEWNET | SIGCHLD, NULL);
    if (ret != -1 || (errno != EINVAL && errno != EPERM))
    {
      if (ret == 0)
      {
        vsf_sysutil_post_fork();
      }
      return ret;
    }
    cloneflags_work = 0;
  }
#endif
  return vsf_sysutil_fork();
}

int
vsf_sysutil_getpid_nocache(void)
{
#ifdef VSF_SYSDEP_HAVE_LINUX_CLONE
  /* Need to defeat the glibc pid caching because we need to hit a raw
   * sys_clone() above.
   */
  return syscall(__NR_getpid);
#else
  return getpid();
#endif
}
