/*
 * Copyright (c) 2003-2011
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*****************************************************************************
 * COPYRIGHT AND PERMISSION NOTICE
 * 
 * Copyright (c) 2001-2003 The Queen in Right of Canada
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, and/or sell
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that both the
 * above copyright notice(s) and this permission notice appear in supporting
 * documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE 
 * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
 * SOFTWARE.
 * 
 * Except as contained in this notice, the name of a copyright holder shall not
 * be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization of the
 * copyright holder.
 ***************************************************************************/

/*
 * The DACS PAM daemon (pamd)
 *
 * Pamd will typically be started through inetd when local_pam_authenticate
 * connects to a configured port and this is what is expected by default.
 * Pamd's stdin will come from local_pam_authenticate and its stdout will
 * be read by local_pam_authenticate.
 *
 * In brief, here is what typically happens:
 * 1) local_pam_authenticate connects to pamd and sends it the value of
 *    USERNAME
 * 2) pamd uses the PAM API to set the username and is told it needs to
 *    get a password for that username
 * 3) pamd tells local_pam_authenticate that it needs to prompt for a
 *    password and how to re-connect to this instance of pamd later
 *    (when it has gotten the password from the user)
 * 4) local_pam_authenticate reconnects to the pamd instance and passes it
 *    the password
 * 5) the procedure could return to step 3 to ask for some
 *    additional information, or return a success or failure indication
 *
 * Note that if pamd is run on a different host than
 * local_pam_authenticate, the connection should occur over a secure
 * communication channel (e.g., SSL), or at least not be snoopable.
 *
 * Suggested configuration
 * To /etc/services, add:
 *   dacs-pamd   17000/tcp  # DACS pamd
 * To /etc/inetd.conf add:
 *   dacs-pamd stream tcp nowait root /usr/local/dacs/sbin/pamd pamd
 *
 * For those who don't want to use inetd, pamd can also be started manually,
 * with or without a port number configured in /etc/services.
 * You can configure the port number using the DACS directive PAMD_PORT; both
 * local_pamd_authenticate and pamd will use that port.
 *
 * Why is this daemon needed?
 * The architecture of PAM involves a single process conducting an
 * authentication transaction or "conversation" with a user.  But when DACS is
 * used in a web service context, each element of the conversation consists of
 * an HTTP request/response.  The PAM daemon is required to maintain PAM
 * state information for each authentication transaction.
 *
 * It goes kinda like this:
 *
 * 1. User --> dacs_authenticate --> local_pam_authenticate --> pamd --> PAM
 *
 * 2. User <-- dacs_authenticate <-- local_pam_authenticate <-- pamd <-- PAM
 *         HTML/FORM (query)     XML                      kwv
 *
 * 3. User --> dacs_authenticate --> local_pam_authenticate --> pamd --> PAM
 *         HTML/FORM (response)  Args                     Args       Args
 *
 * Steps 2 and 3 are repeated until authentication succeeds or fails, or an
 * error occurs.  Both dacs_authenticate and local_pam_authenticate are
 * invoked multiple times, but pamd fields just one process to which
 * local_pam_authenticate will connect one or more times.
 *
 * For example, step 1 may or may not provide a USERNAME.  If it does not,
 * step 2 will return an HTML FORM to the user that prompts for USERNAME.
 * Step 3 would return that USERNAME to the pamd process, which would
 * then result in an HTML FORM returned to the user that prompts for the
 * PASSWORD.  Step 4 would submit PASSWORD to the pamd process, which would
 * most likely return a success or failure indication to dacs_authenticate.
 *
 * When operating in secure mode, the client must supply valid DACS
 * administrative credentials in each block it sends, including the first,
 * as the value of the CREDENTIALS variable.
 *
 * Pamd does not need to execute on the same host as local_pam_authenticate,
 * but it currently looks at the DACS configuration, so it must run at
 * a DACS jurisdiction.
 *
 * Pamd produces a description of the prompts requested by the PAM module or
 * modules configured for the purpose and then waits until either a reply is
 * received or a timeout occurs. The description of the prompt(s) is passed
 * back to local_pam_authenticate, then the DACS authentication service, and
 * then finally presented to the user.
 * Pamd tags each prompt with a unique transaction identifier that allows
 * local_pam_authenticate to correlate prompts to their replies and a
 * particular invocation of pamd.
 *
 * dacs_authenticate will again be invoked by the user, passing it the
 * information required by pamd as arguments. These arguments will be
 * forwarded to the local_pam_authenticate, which will in turn forward the
 * arguments to pamd. If pamd prompts for additional information, the
 * procedure will be repeated.
 *
 * If all replies to its prompts are correct and successfully delivered to
 * pamd, authentication succeeds.
 *
 * PAM is configured for this application through the system's usual way of
 * doing so; e.g., in the single file pam.conf or the directory /etc/pam.d.
 *
 * When the server starts, its stdin is produced by pamlib.c:pam_auth() and its
 * stdout is read by pamlib.c:pam_auth().
 *
 * See "Making Login Services Independent of Authentication Technologies"
 * by Samar and Lai for background about PAM:
 *   http://www.sun.com/software/solaris/pam/pam.external.pdf
 *
 * Pamd emits two kinds of messages.  These are read by local_pam_authenticate.
 * A message consists of a sequence of lines, each line containing a
 * keyword=value pair.
 *
 * The first message type is a Prompt message, which consists of a
 * Prompt-header-block followed by one or more Prompt-blocks.
 * The second message type is a Termination message, which consists of a
 * Termination-block.
 *
 * Termination-block:
 * result="ok" | result="failed"
 * username="<username>"
 *
 * Prompt-header-block:
 * transid="<idstring>"
 *
 * Prompt-block:
 * type="password" | "text" | "error" | "label"
 * label="<string>"
 * varname="<varname>"
 *
 * <varname> is AUTH_PROMPT_VAR_PREFIX followed by a monotonically increasing
 * integer.
 *
 * Pamd receives messages produced by local_pam_authenticate:
 *
 * [Initially and optional:]
 * USERNAME="xxxkxk"
 * CREDENTIALS="...."
 *
 * [One block reply per prompt:]
 * ID="zzz"
 * <varname>="xxx"
 * <varname>="yyy"
 * ...
 * [blank line, then next block]
 *
 * Like all authentication modules, local_pam_authenticate returns an XML
 * document to dacs_authenticate that conforms to the auth_reply DTD:
 *
 * <auth_reply>
 *  <ok username="xxxx"/>
 * </auth_reply>
 *
 * <auth_reply>
 *  <prompts transid="slslslslsl">
 *   <prompt type="text" label="Username? " varname="var0"/>
 *  </prompts>
 * </auth_reply>
 *
 * Because local_pam_authenticate is a prompted authentication style,
 * dacs_authenticate will emit either an XML document conforming to the
 * dacs_auth_reply DTD or an HTML FORM like:
 *
 * <form method="post"
 *     action="http://fedroot.com/cgi-bin/dacs/dacs_authenticate">
 *  <table>
 *   <tr><td>label1</td><td><input type=text name=DACS_AUTH_ARG1></td></tr>
 *   <tr><td>label2</td><td><input type=text name=DACS_AUTH_ARG2></td></tr>
 *  </table>
 *  <input type=hidden name=FORMAT value=HTML>
 *  <input type=hidden name=TRANSID value=xxyyzz>
 *  <input type=submit value="Submit">
 * </form>
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2011\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: pamd.c 2528 2011-09-23 21:54:05Z brachman $";
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <security/pam_appl.h>
#include <signal.h>

#include "dacs.h"

static const char *log_module_name = "pamd";

static int http_flag;				/* Non-zero means run as a web service. */
static int fork_flag;				/* Non-zero means fork worker processes. */
static int inetd_flag;				/* Non-zero if started from inetd(8). */
static struct sockaddr_in *server_name = NULL;
static int secure_mode = 1;
static int server_sd = -1;
static pam_handle_t *pamh = NULL;
static char *pamd_hostname = NULL;
static Pam_auth_tid *current_tid = NULL;
static FILE *fp_in, *fp_err;
static int fd_out = -1;

/*
 * Older versions of pam_strerror() didn't take the pam_handle_t argument.
 */
static const char *
pam_strerr(pam_handle_t *h, int err)
{
#ifdef PAM_STRERROR_USES_PAMH
  return(pam_strerror(h, err));
#else
  return(pam_strerror(err));
#endif
}

/*
 * This is the PAM conversation function (a callback).
 * PAM repeatedly calls this function, telling us what it needs.  We pass
 * this information back to local_pam_authenticate and wait for a reply
 * (from another instance of local_pam_authenticate), which we pass back to
 * PAM.
 * This continues until authentication succeeds or fails, or an error occurs.
 * Return PAM_SUCCESS if successful, otherwise a PAM error code.
 */
static int
pamd_conv(int num_msg, const struct pam_message **msg,
          struct pam_response **resp, void *appdata_ptr)
{
  int i, new_sd, st, varnum;
  char *cookie, *p, *username;
  struct pam_response *reply;
  struct sockaddr from;
  struct timeval timeout;
  Ds *inbuf, *outbuf;
  Kwv *kwv;
  Pam_auth_tid *tid;
  static int start_varnum = 1;

  log_msg((LOG_TRACE_LEVEL, "pamd_conv: entry num_msg=%d", num_msg));
  username = NULL;
  if (pam_get_item(pamh, PAM_USER, (const void **) &username) == PAM_SUCCESS
	  && username != NULL)
	log_msg((LOG_TRACE_LEVEL, "pamd_conv: PAM_USER=\"%s\"", username));
  else {
	log_msg((LOG_TRACE_LEVEL, "pamd_conv: PAM_USER unknown"));
	username = NULL;
  }

  if (num_msg <= 0)
	return(PAM_CONV_ERR);

  outbuf = ds_init(NULL);

  /*
   * Tell the client what information to display and prompt for and
   * where to send the reply.
   * At this point, fd_out is connected to the client's input and
   * fp_in is connected to the client's output.
   */
  log_msg((LOG_DEBUG_LEVEL, "pamd_conv: reply to port %u",
		   ntohs(server_name->sin_port)));
  if (current_tid == NULL)
	current_tid = pam_new_tid(inet_ntoa(server_name->sin_addr),
							  ds_xprintf("%u", ntohs(server_name->sin_port)));
  ds_asprintf(outbuf, "transid=\"%s\"\n", current_tid->str);
  log_msg((LOG_DEBUG_LEVEL, "pamd_conv: sending transid=\"%s\"",
		   current_tid->str));
  if (username != NULL)
	ds_asprintf(outbuf, "username=\"%s\"\n", username);
  ds_asprintf(outbuf, "\n");

  log_msg((LOG_DEBUG_LEVEL, "pamd_conv: sending variables..."));
  varnum = start_varnum;
  for (i = 0; i < num_msg; i++) {
	switch (msg[i]->msg_style) {
	case PAM_PROMPT_ECHO_OFF:
	  /* Obtain a string without echoing any text */
	  ds_asprintf(outbuf, "type=\"password\"\n");
	  log_msg((LOG_DEBUG_LEVEL, "  pamd_conv: var%d: type=\"password\"",
			   varnum));
	  if (msg[i]->msg != NULL) {
		ds_asprintf(outbuf, "label=\"%s\"\n", msg[i]->msg);
		log_msg((LOG_DEBUG_LEVEL, "  pamd_conv: var%d: label=\"%s\"",
				 varnum, msg[i]->msg));
	  }
	  ds_asprintf(outbuf, "varname=\"%s%d\"\n", AUTH_PROMPT_VAR_PREFIX, varnum);
	  log_msg((LOG_DEBUG_LEVEL, "  pamd_conv: var%d: varname=\"%s%d\"",
			   varnum, AUTH_PROMPT_VAR_PREFIX, varnum));
	  ds_asprintf(outbuf, "\n");
	  break;

	case PAM_PROMPT_ECHO_ON:
	  /* Obtain a string whilst echoing text */
	  ds_asprintf(outbuf, "type=\"text\"\n");
	  log_msg((LOG_DEBUG_LEVEL, "  pamd_conv: var%d: type=\"text\"", varnum));
	  if (msg[i]->msg != NULL) {
		ds_asprintf(outbuf, "label=\"%s\"\n", msg[i]->msg);
		log_msg((LOG_DEBUG_LEVEL, "  pamd_conv: var%d: label=\"%s\"",
				 varnum, msg[i]->msg));
	  }
	  ds_asprintf(outbuf, "varname=\"%s%d\"\n", AUTH_PROMPT_VAR_PREFIX, varnum);
	  log_msg((LOG_DEBUG_LEVEL, "  pamd_conv: var%d: varname=\"%s%d\"",
			   varnum, AUTH_PROMPT_VAR_PREFIX, varnum));
	  ds_asprintf(outbuf, "\n");
	  break;

	case PAM_ERROR_MSG:
	  /* Display an error */
	  if (msg[i]->msg == NULL)
		break;
	  ds_asprintf(outbuf, "type=\"error\"\n");
	  ds_asprintf(outbuf, "label=\"%s\"\n", msg[i]->msg);
	  log_msg((LOG_DEBUG_LEVEL, "  pamd_conv: var%d: type=\"error\"", varnum));
	  log_msg((LOG_DEBUG_LEVEL, "  pamd_conv: var%d: label=\"%s\"",
			   varnum, msg[i]->msg));
	  ds_asprintf(outbuf, "\n");
	  break;

	case PAM_TEXT_INFO:
	  /* Display some text. */
	  if (msg[i]->msg == NULL)
		break;
	  ds_asprintf(outbuf, "type=\"label\"\n");
	  ds_asprintf(outbuf, "label=\"%s\"\n", msg[i]->msg);
	  log_msg((LOG_DEBUG_LEVEL, "  pamd_conv:  var%d: type=\"label\"", varnum));
	  log_msg((LOG_DEBUG_LEVEL, "  pamd_conv:  var%d: label=\"%s\"",
			   varnum, msg[i]->msg));
	  ds_asprintf(outbuf, "\n");
	  break;

	default:
	  return(PAM_CONV_ERR);
	  /*NOTREACHED*/
	}

	varnum++;
  }
  log_msg((LOG_DEBUG_LEVEL, "pamd_conv: end of variables"));

  if (net_write(fd_out, ds_buf(outbuf), ds_len(outbuf)) == -1) {
	log_err((LOG_ERROR_LEVEL, "net_write"));
	return(PAM_SYSTEM_ERR);
  }
  log_msg((LOG_DEBUG_LEVEL, "pamd_conv: wrote %d bytes", ds_len(outbuf)));
  ds_free(outbuf);

  fclose(fp_in);
  fp_in = NULL;

  shutdown(fd_out, SHUT_WR);
  close(fd_out);
  fd_out = -1;
  log_msg((LOG_TRACE_LEVEL, "pamd_conv: flushed and closed streams"));

  /*
   * Wait for the client to reconnect.
   * Even if no information is required, we need the client to reply in
   * case we're going to prompt for more or to send the client the final
   * result.
   */
  timeout.tv_sec = PAMD_USER_TIMEOUT_SECS;
  timeout.tv_usec = 0;
  log_msg((LOG_DEBUG_LEVEL,
		   "pamd_conv: waiting %ld seconds for reply", timeout.tv_sec));
  if ((new_sd = net_accept_or_timeout(server_sd, &from, &timeout)) == -1)
	return(PAM_CONV_ERR);
  log_msg((LOG_DEBUG_LEVEL, "pamd_conv: received connection"));

  /* Set up streams to talk to the client. */
  if (dup2(new_sd, 0) == -1) {
	log_err((LOG_ERROR_LEVEL, "dup2"));
	return(PAM_CONV_ERR);
  }
  if (dup2(new_sd, 1) == -1) {
	log_err((LOG_ERROR_LEVEL, "dup2"));
	return(PAM_CONV_ERR);
  }
  fd_out = 1;

  if ((fp_in = fdopen(0, "r")) == NULL) {
	log_err((LOG_ERROR_LEVEL, "Cannot open fp_in"));
	return(PAM_CONV_ERR);
  }

  /* Read the client's reply. */
  log_msg((LOG_DEBUG_LEVEL, "pamd_conv: reading reply..."));
  inbuf = ds_init(NULL);
  inbuf->clear_flag = 1;
  inbuf->delnl_flag = 1;
  inbuf->crnl_flag = 1;
  if ((st = pamd_get_block(fp_in, inbuf, &kwv)) <= 0) {
	log_msg((LOG_ERROR_LEVEL, "pamd_conv: no client reply, st=%d", st));
	return(PAM_CONV_ERR);
  }

  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG, "Read:\n%s", ds_buf(inbuf)));

  if ((cookie = kwv_lookup_value(kwv, "CREDENTIALS")) != NULL) {
	int is_dacs_cookie;
	Cookie *c;
	Credentials *admin_cr;

	log_msg((LOG_TRACE_LEVEL, "pamd_conv: got credentials: %s", cookie));
	if ((c = cookie_parse(cookie, &is_dacs_cookie)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Cookie is invalid"));
	  return(PAM_CONV_ERR);
	}
	if (cookie_to_credentials(c, NULL, &admin_cr) == -1
		|| !is_dacs_admin(admin_cr)) {
	  log_msg((LOG_ERROR_LEVEL, "Credentials are invalid"));
	  return(PAM_CONV_ERR);
	}
	log_msg((LOG_TRACE_LEVEL, "pamd_conv: credentials are valid"));
	strzap(cookie);
  }
  else {
	log_msg((LOG_TRACE_LEVEL, "pamd_conv: no credentials"));
	if (secure_mode) {
	  log_msg((LOG_ERROR_LEVEL, "Secure mode enabled, credentials required"));
	  return(PAM_CONV_ERR);
	}
  }

  if ((p = kwv_lookup_value(kwv, "TRANSID")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "pamd_conv: no TRANSID"));
	return(PAM_CONV_ERR);
  }
  if (!streq(p, current_tid->str)) {
	log_msg((LOG_ERROR_LEVEL, "pamd_conv: incorrect tid"));
	return(PAM_CONV_ERR);
  }
  if ((tid = pam_parse_tid(p)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Invalid transid: parse error"));
	return(PAM_CONV_ERR);
  }
  if (tid->pid != getpid()) {
	log_msg((LOG_ERROR_LEVEL, "Invalid transid: pid mismatch"));
	return(PAM_CONV_ERR);
  }

  reply = (struct pam_response *) calloc(num_msg, sizeof(struct pam_response));

  varnum = start_varnum;
  for (i = 0; i < num_msg; i++) {
	char *varname;

	reply[i].resp = NULL;
	reply[i].resp_retcode = 0;
	varname = ds_xprintf("%s%d", AUTH_PROMPT_VAR_PREFIX, varnum);
	if ((p = kwv_lookup_value(kwv, varname)) != NULL) {
	  reply[i].resp = strdup(p);
	  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
			   "pamd_conv: Reply %d is \"%s\"", varnum, p));
	}
	varnum++;
  }

  kwv_free(kwv);

  start_varnum = varnum;
  *resp = reply;
  reply = NULL;

  log_msg((LOG_TRACE_LEVEL, "pamd_conv: returning PAM_SUCCESS"));

  return(PAM_SUCCESS);
}

static MAYBE_UNUSED void
valid_fds(void)
{
  int i;

  for (i = 0; i < 10; i++) {
	if (dup2(i, i) != -1)
	  log_msg((LOG_TRACE_LEVEL, "fd%d", i));
  }
}

/*
 * Start to service a new request.
 * Our fp_in comes from the client and our stdout is returned to the client.
 * Return 0 if authentication succeeded, 1 if a PAM error occurred,
 * or -1 if an internal error occurred.
 */
static int
do_pamd(char *policy)
{
  int e, st;
  char *cookie, *p, *pam_username, *username;
  Ds *inbuf;
  Kwv *kwv;
  static const struct pam_conv conv = { pamd_conv, NULL };

  if ((e = pam_start(policy, NULL, &conv, &pamh)) != PAM_SUCCESS) {
	log_msg((LOG_ERROR_LEVEL, "pam_start() failed: %s", pam_strerr(pamh, e)));
	return(-1);
  }
  log_msg((LOG_TRACE_LEVEL, "pam_start ok"));

#ifdef HAVE_PAM_FAIL_DELAY
  /* This does not delay here, it registers a delay if one is needed by PAM */
  pam_fail_delay(pamh, 2000000);
#endif

  inbuf = ds_init(NULL);
  username = NULL;
  cookie = NULL;
  log_msg((LOG_DEBUG_LEVEL, "do_pamd: waiting for initial input block..."));
  if (pamd_get_block(fp_in, inbuf, &kwv) == 1) {
	cookie = kwv_lookup_value(kwv, "CREDENTIALS");
	username = kwv_lookup_value(kwv, "USERNAME");
  }

  if (cookie != NULL) {
	int is_dacs_cookie;
	Cookie *c;
	Credentials *admin_cr;

	log_msg((LOG_TRACE_LEVEL, "do_pamd: got credentials: %s", cookie));
	if ((c = cookie_parse(cookie, &is_dacs_cookie)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Cookie is invalid"));
	  return(PAM_CONV_ERR);
	}
	if (cookie_to_credentials(c, NULL, &admin_cr) == -1
		|| !is_dacs_admin(admin_cr)) {
	  log_msg((LOG_ERROR_LEVEL, "Credentials are invalid"));
	  return(PAM_CONV_ERR);
	}
	log_msg((LOG_TRACE_LEVEL, "do_pamd: credentials are valid"));
	strzap(cookie);
  }
  else {
	log_msg((LOG_TRACE_LEVEL, "do_pamd: no credentials"));
	if (secure_mode) {
	  log_msg((LOG_ERROR_LEVEL, "Secure mode enabled, credentials required"));
	  return(PAM_CONV_ERR);
	}
  }

  if (username != NULL) {
	if (pam_set_item(pamh, PAM_USER, (const void *) username) != PAM_SUCCESS) {
	  log_msg((LOG_ERROR_LEVEL, "pam_set_item() failed"));
	  return(-1);
	}
	log_msg((LOG_DEBUG_LEVEL, "do_pamd: set username to \"%s\"", username));
  }
  else
	log_msg((LOG_DEBUG_LEVEL, "do_pamd: no username"));

  kwv_free(kwv);

  /* Create a socket where this process will listen for replies. */
  if (net_make_server_socket(pamd_hostname, 0, &server_sd, &server_name) == -1)
	return(-1);

  /*
   * Hand off control to PAM, which will call pamd_conv().
   * When pam_authenticate() returns, we will have the final result, which
   * is returned to the client (local_pam_authenticate), and we're done.
   */
  log_msg((LOG_DEBUG_LEVEL, "do_pamd: calling pam_authenticate"));
  e = pam_authenticate(pamh, PAM_SILENT);
  log_msg((LOG_TRACE_LEVEL, "do_pamd: pam_authenticate returned e=%d", e));

  switch (e) {
  case PAM_SUCCESS:
	log_msg((LOG_DEBUG_LEVEL, "do_pamd: success"));
	break;

  case PAM_AUTH_ERR:
  case PAM_USER_UNKNOWN:
  case PAM_MAXTRIES:
  default:
	log_msg((LOG_ERROR_LEVEL, "do_pamd: failed: %s", pam_strerr(pamh, e)));
	break;
  }

  if (pam_get_item(pamh, PAM_USER, (const void **) &p) == PAM_SUCCESS
	  && p != NULL)
	pam_username = strdup(p);
  else
	pam_username = NULL;

  pam_end(pamh, e);

  if (e == PAM_SUCCESS && pam_username != NULL) {
	printf("result=\"ok\"\n");
	printf("username=\"%s\"\n", pam_username);
	log_msg((LOG_DEBUG_LEVEL, "do_pamd: result=\"ok\""));
	log_msg((LOG_DEBUG_LEVEL, "do_pamd: username=\"%s\"", pam_username));
	st = 0;
  }
  else {
	if (fd_out == -1)
	  log_msg((LOG_ERROR_LEVEL, "do_pamd: failed, could not reconnect"));
	else {
	  printf("result=\"failed\"\n");
	  log_msg((LOG_DEBUG_LEVEL, "do_pamd: result=\"failed\""));

	  if (pam_username != NULL) {
		printf("username=\"%s\"\n", pam_username);
		log_msg((LOG_DEBUG_LEVEL, "do_pamd: username=\"%s\"", pam_username));
	  }
	}
	st = 1;
  }

  return(st);
}

/*
 * Listen to requests for pamd service on PORT.
 */
static int
pamd_server(in_port_t port, char *policy)
{
  int sd, st;
  struct sockaddr_in *sin;

  log_msg((LOG_DEBUG_LEVEL, "inetd_flag=%d, http_flag=%d, fork_flag=%d",
		   inetd_flag, http_flag, fork_flag));

  if (inetd_flag)
	close(2);
  else if (!http_flag) {
	if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
	  log_err((LOG_ERROR_LEVEL, "socket"));
	  return(-1);
	}

	if ((sin = net_make_sockaddr(pamd_hostname, port)) == NULL)
	  return(-1);

	st = bind(sd, (const struct sockaddr *) sin, sizeof(struct sockaddr_in));
	if (st == -1) {
	  log_err((LOG_ERROR_LEVEL, "bind"));
	  return(-1);
	}

	if (listen(sd, 1) == -1) {
	  log_err((LOG_ERROR_LEVEL, "listen"));
	  return(-1);
	}
  }

  /*
   * Field requests, either one at a time or concurrently.
   * XXX Might want to limit request rate, etc.
   */
  while (1) {
	int cd, n;
	char *rhost, *rport, remotehost[128], remoteport[16];
	pid_t pid;
	struct sockaddr from;
	socklen_t fromlen;

	rhost = NULL;
	rport = NULL;
	if (inetd_flag) {
	  fromlen = sizeof(from);
	  if (getpeername(0, &from, &fromlen) == -1) {
		log_err((LOG_ERROR_LEVEL, "getpeername"));
		fromlen = 0;
	  }
	}
	else if (http_flag) {
	  rhost = getenv("REMOTE_ADDR");
	  rport = getenv("REMOTE_PORT");
	  fromlen = 0;
	}
	else {
	  log_msg((LOG_DEBUG_LEVEL,
			   "pamd_server: awaiting a new connection on port %u...", port));
	  fromlen = sizeof(from);
	  if ((cd = accept(sd, &from, &fromlen)) == -1) {
		log_err((LOG_ERROR_LEVEL, "accept"));
		continue;
	  }
	}

	if (fromlen) {
	  if (getnameinfo(&from, fromlen, remotehost, sizeof(remotehost),
					  remoteport, sizeof(remoteport),
					  NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
		rhost = remotehost;
		rport = remoteport;
	  }
	}

	log_msg((LOG_TRACE_LEVEL, "pamd_server: new connection from %s:%s",
			 (rhost != NULL) ? rhost : "???",
			 (rport != NULL) ? rport : "???"));

	if (!(inetd_flag || http_flag)) {
	  /*
	   * We want fp_in to come from the new connection and fd_out to
	   * go to the new connection.
	   */
#ifdef NOTDEF
	  close(0);
	  if ((n = dup(cd)) == -1) {
#endif
	if (dup2(cd, 0) == -1) {
		log_err((LOG_ERROR_LEVEL, "dup"));
		return(-1);
	  }
	  log_msg((LOG_TRACE_LEVEL, "pamd_server: dup(%d)=%d", cd, n));
	  if ((fp_in = fdopen(0, "r")) == NULL) {
		log_err((LOG_ERROR_LEVEL, "Cannot open fp_in"));
		return(-1);
	  }

#ifdef NOTDEF
	  close(1);
	  if ((n = dup(cd)) == -1) {
#endif
	if (dup2(cd, 1) == -1) {
		log_err((LOG_ERROR_LEVEL, "dup"));
		return(-1);
	  }
	  log_msg((LOG_TRACE_LEVEL, "pamd_server: dup(%d)=%d", cd, n));
	  fd_out = 1;
	  close(cd);
	}

	if (inetd_flag || !fork_flag) {
	  if ((st = do_pamd(policy)) == -1)
		log_msg((LOG_ERROR_LEVEL, "pamd failed"));

	  log_msg((LOG_TRACE_LEVEL, "pamd_server: request terminated"));

	  exit(st == -1 ? 1 : 0);
	  /*NOTREACHED*/
	}

	log_msg((LOG_DEBUG_LEVEL, "pamd_server: forking..."));
	if ((pid = fork()) == 0) {
	  /* Child */
	  if ((st = do_pamd(policy)) == -1)
		log_msg((LOG_ERROR_LEVEL, "pamd failed"));

	  log_msg((LOG_TRACE_LEVEL, "pamd_server: child is terminating"));

	  exit(st == -1 ? 1 : 0);
	  /*NOTREACHED*/
	}
	else
	  (void) signal(SIGCHLD, SIG_IGN);

	/* Parent */
	if (pid == -1) {
	  log_err((LOG_ERROR_LEVEL, "fork"));
	  if (http_flag) {
		exit(1);
		/*NOTREACHED*/
	  }

	  /* Parent continues */
	  continue;
	}

	log_msg((LOG_TRACE_LEVEL, "pamd_server: child pid is %d", pid));
	if (http_flag) {
	  exit(0);
	  /*NOTREACHED*/
	}

	close(cd);
	/* XXX could wait here for completion */
  }
}

static void
dacs_usage(void)
{

  fprintf(fp_err, "Usage: pamd [dacsoptions] [-d] [-p #] [-f]\n");
  fprintf(fp_err, "dacsoptions: %s\n", standard_command_line_usage);
  fprintf(fp_err, "-daemon:   run as a daemon\n");
  fprintf(fp_err, "-fork:     daemon fields each request in a new process\n");
  fprintf(fp_err, "-h host:   hostname to use (if more than one)\n");
  fprintf(fp_err, "-inetd:    run from inetd (default)\n");
  fprintf(fp_err, "-nofork:   daemon fields one request, then exits\n");
  fprintf(fp_err, "-p #:      use port # instead of any configured port\n");
  fprintf(fp_err, "-policy name: use name as the PAM policy (default is \"%s\")\n", PAMD_DEFAULT_POLICY);
  fprintf(fp_err, "-secure:   require DACS credentials (default)\n");
  fprintf(fp_err, "-unsecure: do not require DACS credentials\n");

  exit(1);
}

/*
 * There are two exclusive modes of operation: inetd and daemon
 * In the former mode, the program is started by inetd with file
 * descriptors set in the usual way.
 * In the latter mode, the program is started in the background where it
 * listens to a well-known port for connection requests; a request can be
 * handled until completion before the next one begins (default), or a new
 * process can be started to field each request (-fork).
 */
int
main(int argc, char **argv)
{
  int i;
  char *errmsg, *policy, *portname;
  in_port_t port;
  DACS_app_type app_type;
  Kwv *kwv;

  fork_flag = 0;
  http_flag = 0;
  inetd_flag = 1;
  secure_mode = 1;
  portname = NULL;

  fp_in = stdin;
  fd_out = 1;
  fp_err = stderr;

  if (getenv("REMOTE_USER") != NULL) {
	http_flag = 1;
	inetd_flag = 0;
	emit_plain_header(stdout);
	app_type = DACS_LOCAL_SERVICE;
	fclose(fp_err);
	fp_err = NULL;
  }
  else if (isatty(2))
	app_type = DACS_UTILITY;
  else
	app_type = DACS_SERVER;

  errmsg = NULL;
  if (dacs_init(app_type, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	if (errmsg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "%s", errmsg));

	if (http_flag)
	  emit_plain_trailer(stdout);

	exit(1);
  }

  policy = PAMD_DEFAULT_POLICY;
  for (i = 1; should_use_argv && i < argc; i++) {
	if (streq(argv[i], "-inetd")) {
	  inetd_flag = 1;
	  fork_flag = 0;
	}
	else if (streq(argv[i], "-daemon"))
	  inetd_flag = 0;
	else if (streq(argv[i], "-fork")) {
	  fork_flag = 1;
	  inetd_flag = 0;
	}
	else if (streq(argv[i], "-h")) {
	  if (++i == argc) {
		dacs_usage();
		/*NOTREACHED*/
	  }
	  pamd_hostname = argv[i];
	}
	else if (streq(argv[i], "-http")) {
	  http_flag = 1;
	  inetd_flag = 0;
	  fork_flag = 0;
	}
	else if (streq(argv[i], "-nofork")) {
	  fork_flag = 0;
	  inetd_flag = 0;
	}
	else if (streq(argv[i], "-p")) {
	  if (++i == argc) {
		dacs_usage();
		/*NOTREACHED*/
	  }
	  portname = argv[i];
	}
	else if (streq(argv[i], "-policy")) {
	  if (++i == argc) {
		dacs_usage();
		/*NOTREACHED*/
	  }
	  policy = argv[i];
	}
	else if (streq(argv[i], "-secure"))
	  secure_mode = 1;
	else if (streq(argv[i], "-unsecure"))
	  secure_mode = 0;
	else {
	  dacs_usage();
	  /*NOTREACHED*/
	}
  }

  log_msg((LOG_INFO_LEVEL, "Secure mode is %s", secure_mode ? "on" : "off"));

  if (pamd_hostname == NULL) {
	char hostname[MAXHOSTNAMELEN];

	/* PAMD_HOST is used to select the interface (IP address) to use. */
	if ((pamd_hostname = conf_val(CONF_PAMD_HOST)) == NULL) {
	  if (gethostname(hostname, MAXHOSTNAMELEN) == -1) {
		log_err((LOG_ERROR_LEVEL, "gethostname"));
		errmsg = "Cannot get hostname";
		goto fail;
	  }
	  pamd_hostname = strdup(hostname);
	}
  }

  if ((port = pam_get_pamd_port(portname, &errmsg)) == 0)
	goto fail;

  if (http_flag) {
	/*
	 * XXX this mode involves pamd being started via CGI, returning an
	 * HTTP response, and then operating as a "detached" server to field
	 * the session.  This is kind of nice because a regular ACL can restrict
	 * HTTP access to DACS internal use and subsequent session operations
	 * using a secret key.
	 * But it does not fit in well with CGI process structuring and there
	 * are complications with Apache sending kill signals to child processes,
	 * so it does not appear to be worth the effort.
	 */
	log_msg((LOG_ERROR_LEVEL, "Sorry, -http flag does not work (yet)"));
	goto fail;
  }

#ifdef NOTDEF
  /*
   * This problem appears to have disappeared...
   */
  if (fork_flag) {
	/*
	 * XXX when dlopen() is called by the forked process
	 * in the PAM library (openpam_dynamic.c:openpam_dynamic) to load a PAM
	 * module the process crashes... dunno why yet
	 */
	log_msg((LOG_ERROR_LEVEL, "Sorry, -daemon flag does not work (yet)"));
	goto fail;
  }
#endif

  if (pamd_server(port, policy) == -1) {
	log_msg((LOG_TRACE_LEVEL, "Authentication error occurred"));
	goto fail;
  }

  if (http_flag)
	emit_plain_trailer(stdout);

  exit(0);
}
