/*
  Copyright (C) 1993-2023 Free Software Foundation, Inc.

  This file is part of GNU Inetutils.

  GNU Inetutils 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 3 of the License, or (at
  your option) any later version.

  GNU Inetutils 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, see `http://www.gnu.org/licenses/'. */

/*
 * Copyright (c) 1983, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* Implementation of PAM support for a service `rexec'
 * was done by Mats Erik Andersson.
 *
 * Sample PAM configuration with variations for different systems:
 *
 *   rexec auth     requisite  pam_nologin.so
 *   rexec auth     required   pam_unix.so
 *   # rexec auth   requisite  pam_authtok_get.so
 *   # rexec auth   required   pam_unix_cred.so
 *   # rexec auth   required   pam_unix_auth.so
 *   # rexec auth   required   pam_listfile.so item=user sense=allow \
 *                                 file=/etc/rexec.allow onerr=fail
 *
 *   rexec account  required   pam_unix.so
 *   rexec account  required   pam_time.so
 *   # rexec account requisite pam_roles.so
 *   # rexec account required  pam_unix_account.so
 *   # rexec account required  pam_list.so allow=/etc/rexec.allow
 *
 *   rexec password required   pam_deny.so
 */

#include <config.h>

#include <sys/types.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#ifdef HAVE_SYS_FILIO_H
# include <sys/filio.h>
#endif
#include <sys/socket.h>
#include <sys/time.h>
#include <time.h>

#include <netinet/in.h>

#include <errno.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <grp.h>
#ifdef HAVE_CRYPT_H
# include <crypt.h>
#endif
#include <sys/select.h>
#include <stdarg.h>
#ifdef HAVE_SHADOW_H
# include <shadow.h>
#endif
#include <syslog.h>
#ifdef HAVE_SECURITY_PAM_APPL_H
# include <security/pam_appl.h>
#endif
#ifdef HAVE_GETPWNAM_R
# include <xalloc.h>
#endif

#include <progname.h>
#include <argp.h>
#include <error.h>
#include <attribute.h>
#include "libinetutils.h"

void die (int code, const char *fmt, ...);
int doit (int f, struct sockaddr *fromp, socklen_t fromlen);

#ifdef WITH_PAM
static int pam_rc = PAM_AUTH_ERR;	/* doit() and die() */
static char *password_pam = NULL;	/* doit() and rexec_conv() */
static int pam_flags = PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK;

static pam_handle_t *pam_handle = NULL;	/* doit() and die() */
static int rexec_conv (int, const struct pam_message **,
		       struct pam_response **, void *);
static struct pam_conv pam_conv = { rexec_conv, NULL };
#endif /* WITH_PAM */

static int logging = 0;

static struct argp_option options[] = {
  {"logging", 'l', NULL, 0, "logging of requests and errors", 1},
  {NULL, 0, NULL, 0, NULL, 0}
};

static error_t
parse_opt (int key, char *arg MAYBE_UNUSED,
	   struct argp_state *state MAYBE_UNUSED)
{
  switch (key)
    {
    case 'l':
      ++logging;
      break;

    default:
      return ARGP_ERR_UNKNOWN;
    }
  return 0;
}

const char doc[] =
#ifdef WITH_PAM
  "Remote execution daemon, using PAM module 'rexec'.";
#else /* !WITH_PAM */
  "Remote execution daemon.";
#endif

static struct argp argp = {
  options,
  parse_opt,
  NULL,
  doc,
  NULL,
  NULL,
  NULL
};

/*
 * remote execute server:
 *	username\0
 *	password\0
 *	command\0
 *	data
 */
int
main (int argc, char **argv)
{
  struct sockaddr_storage from;
  socklen_t fromlen;
  int sockfd = STDIN_FILENO;
  int index;

  set_program_name (argv[0]);

  iu_argp_init ("rexecd", default_program_authors);
  argp_parse (&argp, argc, argv, 0, &index, NULL);

  openlog ("rexecd", LOG_PID | LOG_ODELAY | LOG_NOWAIT, LOG_DAEMON);

  if (argc > index)
    /* Record this complaint locally.  */
    syslog (LOG_NOTICE, "%d extra arguments", argc - index);

  fromlen = sizeof (from);
  if (getpeername (sockfd, (struct sockaddr *) &from, &fromlen) < 0)
    {
      syslog (LOG_ERR, "getpeername: %m");
      error (EXIT_FAILURE, errno, "getpeername");
    }

  doit (sockfd, (struct sockaddr *) &from, fromlen);
  exit (EXIT_SUCCESS);
}

/* Set lengths of USER and LOGNAME longer than
 * the REXEC protocol prescribes.  */
char username[32 + sizeof ("USER=")] = "USER=";
char logname[32 + sizeof ("LOGNAME=")] = "LOGNAME=";	/* Identical to USER.  */
char homedir[256 + sizeof ("HOME=")] = "HOME=";
char shell[256 + sizeof ("SHELL=")] = "SHELL=";
char path[sizeof (PATH_DEFPATH) + sizeof ("PATH=")] = "PATH=";
char remotehost[128 + sizeof ("RHOST=")] = "RHOST=";
#ifndef WITH_PAM
char *envinit[] = { homedir, shell, path, username,
  logname, remotehost, NULL
};
#endif
extern char **environ;

char *getstr (const char *);

#ifndef WITH_PAM
static char *
get_user_password (struct passwd *pwd)
{
  char *pw_text = pwd->pw_passwd;
# ifdef HAVE_SHADOW_H
  struct spwd *spwd = getspnam (pwd->pw_name);
  if (spwd)
    pw_text = spwd->sp_pwdp;
# endif
  return pw_text;
}
#endif /* !WITH_PAM */

int
doit (int f, struct sockaddr *fromp, socklen_t fromlen)
{
  char *cmdbuf, *cp;
  char *user, *pass;
#ifndef WITH_PAM
  char *namep, *pw_password;
#endif
#ifdef HAVE_GETPWNAM_R
  char *pwbuf;
  int pwbuflen;
  struct passwd *pwd, pwstor;
#else /* !HAVE_GETPWNAM_R */
  struct passwd *pwd;
#endif
  char rhost[INET6_ADDRSTRLEN];
  int s, ret;
  in_port_t port;
  int pv[2], pid, cc;
  fd_set readfrom, ready;
  char buf[BUFSIZ], sig;
  int one = 1;

#ifdef HAVE_GETPWNAM_R
  pwbuflen = sysconf (_SC_GETPW_R_SIZE_MAX);
  if (pwbuflen <= 0)
    pwbuflen = 1024;		/* Guessing only.  */

  pwbuf = xmalloc (pwbuflen);
#endif /* HAVE_GETPWNAM_R */

  signal (SIGINT, SIG_DFL);
  signal (SIGQUIT, SIG_DFL);
  signal (SIGTERM, SIG_DFL);
#ifdef DEBUG
  {
    int t = open (PATH_TTY, O_RDWR);
    if (t >= 0)
      {
	ioctl (t, TIOCNOTTY, (char *) 0);
	close (t);
      }
  }
#endif
  if (f != STDIN_FILENO)
    {
      dup2 (f, STDIN_FILENO);
      dup2 (f, STDOUT_FILENO);
      dup2 (f, STDERR_FILENO);
    }

  ret = getnameinfo (fromp, fromlen, rhost, sizeof (rhost),
		     NULL, 0, NI_NUMERICHOST);
  if (ret)
    {
      syslog (LOG_WARNING, "getnameinfo: %m");
      strncpy (rhost, "(unknown)", sizeof (rhost));
    }

  if (logging > 1)
    syslog (LOG_INFO, "request from \"%s\"", rhost);

  alarm (60);
  port = 0;
  for (;;)
    {
      char c;
      if (read (f, &c, 1) != 1)
	{
	  if (logging)
	    syslog (LOG_ERR, "main socket: %m");
	  exit (EXIT_FAILURE);
	}
      if (c == 0)
	break;
      port = port * 10 + c - '0';
    }
  alarm (0);

  if (port != 0)
    {
      s = socket (fromp->sa_family, SOCK_STREAM, 0);
      if (s < 0)
	{
	  if (logging)
	    syslog (LOG_ERR, "stderr socket: %m");
	  exit (EXIT_FAILURE);
	}
      setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof (one));
      alarm (60);
      switch (fromp->sa_family)
	{
	case AF_INET:
	  ((struct sockaddr_in *) fromp)->sin_port = htons (port);
	  break;
	case AF_INET6:
	  ((struct sockaddr_in6 *) fromp)->sin6_port = htons (port);
	  break;
	default:
	  syslog (LOG_ERR, "unknown address family %d", fromp->sa_family);
	  exit (EXIT_FAILURE);
	}
      if (connect (s, fromp, fromlen) < 0)
	{
	  /* Use LOG_NOTICE since the remote part might cause
	   * this error by blocking.  We are less probable.
	   */
	  if (logging)
	    syslog (LOG_NOTICE, "connect: %m");
	  exit (EXIT_FAILURE);
	}
      alarm (0);
    }

  user = getstr ("username");
  pass = getstr ("password");
  cmdbuf = getstr ("command");

  setpwent ();

#ifdef HAVE_GETPWNAM_R
  ret = getpwnam_r (user, &pwstor, pwbuf, pwbuflen, &pwd);
  if (ret || pwd == NULL)
#else /* !HAVE_GETPWNAM_R */
  pwd = getpwnam (user);
  if (pwd == NULL)
#endif /* HAVE_GETPWNAM_R */
    {
      if (logging)
	syslog (LOG_WARNING | LOG_AUTH, "no user named \"%s\"", user);
      die (EXIT_FAILURE, "Login incorrect.");
    }

  endpwent ();

#ifndef WITH_PAM
  /* Last need of elevated privilege.  */
  pw_password = get_user_password (pwd);
  if (*pw_password != '\0')
    {
      namep = crypt (pass, pw_password);
      if (strcmp (namep, pw_password))
	{
	  if (logging)
	    syslog (LOG_WARNING | LOG_AUTH, "password failure for \"%s\"",
		    user);
	  die (EXIT_FAILURE, "Password incorrect.");
	}
    }
#else /* WITH_PAM */
  /* Failure at this stage should not disclose server side causes,
   * but only fail almost silently.  Use "Try again" for now.
   */
  password_pam = pass;		/* Needed by pam_conv().  */

  pam_rc = pam_start ("rexec", user, &pam_conv, &pam_handle);
  if (pam_rc != PAM_SUCCESS)
    die (EXIT_FAILURE, "Try again.");

  pam_rc = pam_set_item (pam_handle, PAM_RHOST, rhost);
  if (pam_rc != PAM_SUCCESS)
    die (EXIT_FAILURE, "Try again.");

  pam_rc = pam_authenticate (pam_handle, pam_flags);
  if (pam_rc != PAM_SUCCESS)
    {
      switch (pam_rc)
	{
	case PAM_ABORT:
	  /* This is potentially severe.  No communication!  */
	  pam_end (pam_handle, pam_rc);
	  exit (EXIT_FAILURE);
	  break;
	case PAM_AUTHINFO_UNAVAIL:
	case PAM_CRED_INSUFFICIENT:
	case PAM_USER_UNKNOWN:
	  die (EXIT_FAILURE, "Login incorrect.");
	  break;
	case PAM_AUTH_ERR:
	case PAM_MAXTRIES:
	default:
	  die (EXIT_FAILURE, "Password incorrect.");
	  break;
	}
    }

  pam_rc = pam_acct_mgmt (pam_handle, pam_flags);
  if (pam_rc != PAM_SUCCESS)
    {
      switch (pam_rc)
	{
	case PAM_NEW_AUTHTOK_REQD:
	case PAM_PERM_DENIED:
	  die (EXIT_FAILURE, "Password incorrect.");
	  break;
	case PAM_ACCT_EXPIRED:
	case PAM_AUTH_ERR:
	case PAM_USER_UNKNOWN:
	default:
	  die (EXIT_FAILURE, "Login incorrect.");
	  break;
	}
    }
#endif /* WITH_PAM */

#ifdef HAVE_SETLOGIN
  if (setlogin (pwd->pw_name) < 0)
    syslog (LOG_ERR, "setlogin() failed: %m");
#endif

  /* Step down from superuser personality.
   *
   * The changing of group membership will seldomly
   * fail, but a relevant message is passed just in
   * case.  These messages are non-standard.
   */
  if (setgid ((gid_t) pwd->pw_gid) < 0)
    {
#ifdef WITH_PAM
      pam_rc = PAM_ABORT;
#endif
      syslog (LOG_DEBUG | LOG_AUTH, "setgid(gid = %d): %m", pwd->pw_gid);
      die (EXIT_FAILURE, "Failed group protections.");
    }

#ifdef HAVE_INITGROUPS
  if (initgroups (pwd->pw_name, pwd->pw_gid) < 0)
    {
# ifdef WITH_PAM
      pam_rc = PAM_ABORT;
# endif
      syslog (LOG_DEBUG | LOG_AUTH, "initgroups(%s, %d): %m",
	      pwd->pw_name, pwd->pw_gid);
      die (EXIT_FAILURE, "Failed group protections.");
    }
#endif

#ifdef WITH_PAM
  pam_rc = pam_setcred (pam_handle, PAM_SILENT | PAM_ESTABLISH_CRED);
  if (pam_rc != PAM_SUCCESS)
    {
      syslog (LOG_ERR | LOG_AUTH, "pam_setcred: %s",
	      pam_strerror (pam_handle, pam_rc));
      pam_rc = PAM_SUCCESS;	/* Only report the above anomaly.  */
    }
#endif /* WITH_PAM */

  if (setuid ((uid_t) pwd->pw_uid) < 0)
    {
#ifdef WITH_PAM
      pam_rc = PAM_ABORT;
#endif
      syslog (LOG_DEBUG | LOG_AUTH, "setuid(uid = %d): %m", pwd->pw_uid);
      die (EXIT_FAILURE, "Failed user identity.");
    }

  if (port)
    {
      pipe (pv);
      pid = fork ();
      if (pid == -1)
	{
	  if (logging)
	    syslog (LOG_ERR, "forking for \"%s\": %m", user);
#ifdef WITH_PAM
	  pam_rc = PAM_ABORT;
#endif
	  die (EXIT_FAILURE, "Try again.");
	}

      if (pid)
	{
#ifdef WITH_PAM
	  /* This process steps down from PAM now,
	   * the child continues communication.  */
	  pam_handle = NULL;
# ifdef PAM_DATA_SILENT
	  pam_rc |= PAM_DATA_SILENT;
# endif
#endif /* WITH_PAM */
	  close (STDIN_FILENO);
	  close (STDOUT_FILENO);
	  close (STDERR_FILENO);
	  close (f);
	  close (pv[1]);
	  FD_ZERO (&readfrom);
	  FD_SET (s, &readfrom);
	  FD_SET (pv[0], &readfrom);
	  ioctl (pv[1], FIONBIO, (char *) &one);
	  /* should set s nbio! */
	  do
	    {
	      int maxfd = s;
	      ready = readfrom;
	      if (pv[0] > maxfd)
		maxfd = pv[0];
	      select (maxfd + 1, (fd_set *) & ready,
		      (fd_set *) NULL, (fd_set *) NULL,
		      (struct timeval *) NULL);
	      if (FD_ISSET (s, &ready))
		{
		  if (read (s, &sig, 1) <= 0)
		    FD_CLR (s, &readfrom);
		  else
		    killpg (pid, sig);
		}
	      if (FD_ISSET (pv[0], &ready))
		{
		  cc = read (pv[0], buf, sizeof (buf));
		  if (cc <= 0)
		    {
		      shutdown (s, 1 + 1);
		      FD_CLR (pv[0], &readfrom);
		    }
		  else
		    write (s, buf, cc);
		}
	    }
	  while (FD_ISSET (pv[0], &readfrom) || FD_ISSET (s, &readfrom));
	  exit (EXIT_SUCCESS);
	}			/* Parent process.  */

#ifdef HAVE_SETLOGIN
      /* Not sufficient to call setpgid() on BSD systems.  */
      if (getsid ((pid_t) 0) != getpid ())
	if (setsid () < 0)
	  syslog (LOG_ERR, "setsid() failed: %m");
#elif defined HAVE_SETPGID	/* !HAVE_SETLOGIN */
      setpgid (0, getpid ());
#endif

      close (s);
      close (pv[0]);
      dup2 (pv[1], STDERR_FILENO);
      close (pv[1]);
    }

  if (f > 2)
    close (f);

  /* Last point of failure due to incorrect user settings.  */
  if (chdir (pwd->pw_dir) < 0)
    {
      if (logging)
	syslog (LOG_NOTICE | LOG_AUTH, "\"%s\" uses invalid \"%s\"",
		user, pwd->pw_dir);
#ifdef WITH_PAM
      pam_rc = PAM_ABORT;
#endif
      die (EXIT_FAILURE, "No remote directory.");
    }

#ifdef WITH_PAM
  /* Refresh knowledge of user, which might have been
   * remapped by the PAM stack during conversation.
   */
  free (user);
  pam_rc = pam_get_item (pam_handle, PAM_USER, (const void **) &user);
  if (pam_rc != PAM_SUCCESS)
    die (EXIT_FAILURE, "Try again.");

# ifdef HAVE_GETPWNAM_R
  ret = getpwnam_r (user, &pwstor, pwbuf, pwbuflen, &pwd);
  if (ret || pwd == NULL)
# else/* !HAVE_GETPWNAM_R */
  pwd = getpwnam (user);
  if (pwd == NULL)
# endif/* HAVE_GETPWNAM_R */
    {
      syslog (LOG_ERR | LOG_AUTH, "no user named \"%s\"", user);
      die (EXIT_FAILURE, "Login incorrect.");
    }
#endif /* WITH_PAM */

  strcat (path, PATH_DEFPATH);
  strncat (homedir, pwd->pw_dir, sizeof (homedir) - sizeof ("HOME=") - 1);
  strncat (shell, pwd->pw_shell, sizeof (shell) - sizeof ("SHELL=") - 1);
  strncat (username, pwd->pw_name, sizeof (username) - sizeof ("USER=") - 1);
  strncat (logname, pwd->pw_name, sizeof (logname) - sizeof ("LOGNAME=") - 1);
  strncat (remotehost, rhost, sizeof (remotehost) - sizeof ("RHOST=") - 1);

#ifdef WITH_PAM
  if (pam_getenv (pam_handle, "PATH") == NULL)
    (void) pam_putenv (pam_handle, path);
  if (pam_getenv (pam_handle, "HOME") == NULL)
    (void) pam_putenv (pam_handle, homedir);
  if (pam_getenv (pam_handle, "SHELL") == NULL)
    (void) pam_putenv (pam_handle, shell);
  if (pam_getenv (pam_handle, "USER") == NULL)
    (void) pam_putenv (pam_handle, username);
  if (pam_getenv (pam_handle, "LOGNAME") == NULL)
    (void) pam_putenv (pam_handle, logname);
  if (pam_getenv (pam_handle, "RHOST") == NULL)
    (void) pam_putenv (pam_handle, remotehost);

  environ = pam_getenvlist (pam_handle);
#else /* !WITH_PAM */
  environ = envinit;
#endif /* !WITH_PAM */

  if (*pwd->pw_shell == '\0')
    pwd->pw_shell = PATH_BSHELL;

  cp = strrchr (pwd->pw_shell, '/');
  if (cp)
    cp++;
  else
    cp = pwd->pw_shell;

  /* This is the 7th step in the protocol standard.
   * All authentication has been successful, and the
   * execution can be handed over to the requested shell.
   */
  write (STDERR_FILENO, "\0", 1);
  if (logging)
    syslog (LOG_INFO, "accepted user \"%s\" from %s", user, rhost);

  execl (pwd->pw_shell, cp, "-c", cmdbuf, NULL);
  if (logging)
    syslog (LOG_ERR, "execl fails for \"%s\": %m", user);
#ifdef WITH_PAM
  pam_end (pam_handle, PAM_SUCCESS);
#endif
  error (EXIT_FAILURE, errno, "executing %s", pwd->pw_shell);

  return -1;
}

void
die (int code, const char *fmt, ...)
{
  va_list ap;
  char buf[BUFSIZ];
  int n;

  va_start (ap, fmt);
  buf[0] = 1;			/* Error condition.  */
  n = vsnprintf (buf + 1, sizeof buf - 1, fmt, ap);
  va_end (ap);
  if (n + 1 > (int) sizeof buf)
    n = sizeof buf - 1;
  buf[n++] = '\n';
  write (STDERR_FILENO, buf, n);
#ifdef WITH_PAM
  if (pam_handle != NULL)
    pam_end (pam_handle, pam_rc);
#endif
  exit (code);
}

char *
getstr (const char *err)
{
  size_t buf_len = 100;
  char *buf = malloc (buf_len), *end = buf;

  if (!buf)
    die (EXIT_FAILURE, "Out of space reading %s", err);

  do
    {
      /* Oh this is efficient, oh yes.  [But what can be done?] */
      int rd = read (STDIN_FILENO, end, 1);
      if (rd <= 0)
	{
	  if (rd == 0)
	    die (EXIT_FAILURE, "EOF reading %s", err);
	  else
	    error (EXIT_FAILURE, 0, "%s", err);
	}

      end += rd;
      if ((buf + buf_len - end) < (ssize_t) (buf_len >> 3))
	{
	  /* Not very much room left in our buffer, grow it. */
	  size_t end_offs = end - buf;
	  buf_len += buf_len;
	  buf = realloc (buf, buf_len);
	  if (!buf)
	    die (EXIT_FAILURE, "Out of space reading %s", err);
	  end = buf + end_offs;
	}
    }
  while (*(end - 1));

  return buf;
}

#ifdef WITH_PAM
/* Call back function for passing user's password
 * to any PAM module requesting this information.
 */
static int
rexec_conv (int num, const struct pam_message **pam_msg,
	    struct pam_response **pam_resp, void *data MAYBE_UNUSED)
{
  struct pam_response *resp;

  /* Reject composite call-backs.  */
  if (num <= 0 || num > 1)
    return PAM_CONV_ERR;

  /* We only accept password reporting.  */
  if (pam_msg[0]->msg_style != PAM_PROMPT_ECHO_OFF)
    return PAM_CONV_ERR;

  /* Allocate a single response structure,
   * as we are ignoring composite calls.
   *
   * This is an external call-back, so we check
   * for successful allocation ourselves.
   */
  resp = malloc (sizeof (*resp));
  if (resp == NULL)
    return PAM_BUF_ERR;

  /* Prepare response to a single PAM_PROMPT_ECHO_OFF.  */
  resp->resp_retcode = 0;
  resp->resp = strdup (password_pam);
  if (resp->resp == NULL)
    {
      free (resp);
      return PAM_BUF_ERR;
    }

  *pam_resp = resp;

  return PAM_SUCCESS;
}
#endif /* WITH_PAM */
