/*
 * Copyright (c) 2003-2012
 * 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.
 ***************************************************************************/

/*
 * Program to help test pamd manually.
 * Pretend that it's local_pam_authenticate...
 * This can also be done using telnet.
 *
 * The first time pamd-client is run during a test, connect to the main
 * instance of pamd; e.g., pamd-client -v -d pamd-host [pamd-port]
 * Then, either enter a blank line, or type:
 *    USERNAME="someuser"
 * followed by a blank line.
 * If USERNAME is provided, it tests the case where it is not necessary
 * to prompt for the username.
 * pamd will respond with a block (a series of lines, terminated by a blank
 * line - see pamd.c).
 *
 * If additional information is needed (result="prompt"), a session identifier
 * (the "transid") is provided that contains the hostname and port number
 * to which pamd-client must connect to supply this information.  The transid
 * must be passed back with each interaction so that pamd can continue the
 * session.
 * The "prompt" element describes how the prompt should be made to the user
 * and what to name the value when it is passed back.
 *
 * Example:
 * % ./pamd-client -v -d localhost
 * Connecting to localhost:17000
 * Connected
 * Reading from stdin until blank line or EOF...
 *
 * (will wait 1000 secs for reply)
 * <auth_reply transid="10.0.0.124:9919:1347:f6b201f20d787f52" result="prompt">
 * <prompt type="text" label="Login:" varname="AUTH_PROMPT_VAR1"/>
 * </auth_reply>
 *
 * This response from pamd means that it needs the user's reply to the prompt
 * "Login:".
 * Run pamd-client again, this time specifying the session port number
 * (here, it's 9919, which is the second component of the transid).
 * So if the user's login name is "bobo", for example, type:
 *
 * % ./pamd-client -d localhost 9919
 * transid="10.0.0.124:9919:1347:f6b201f20d787f52"
 * AUTH_PROMPT_VAR1="bobo"
 *
 * Then type the blank line that terminates the block of response lines.
 *
 * pamd should respond, asking for the password for "bobo", which is provided
 * much as before except with a different variable name:
 * % ./pamd-client -d localhost 9919
 * transid="10.0.0.124:9919:1347:f6b201f20d787f52"
 * AUTH_PROMPT_VAR2="bobopasswd"
 *
 * Depending on whether the username/password is correct, pamd will emit
 * an appropriate response and the session is terminated.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: pamd-client.c 2594 2012-10-19 17:28:49Z 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 "dacs.h"

static const char *log_module_name = "pamd-client";

static void
dacs_usage(void)
{

  log_msg((LOG_ERROR_LEVEL, "Usage: pamd-client [-v] [-d] hostname [port]"));
  exit(1);
}

int
main(int argc, char **argv)
{
  int dflag, i, sd, vflag;
  char buf[1024], *errmsg, *result;
  char *hostname, *id, *portname;
  in_port_t port;
  Ds *inbuf;
  FILE *fd_in;
  struct timeval timeout;
  Kwv *kwv, *kwv_head;

  dflag = 0;
  vflag = 0;
  portname = NULL;

  for (i = 1; i < argc; i++) {
	if (streq(argv[i], "-v"))
	  vflag = 1;
	else if (streq(argv[i], "-d"))
	  dflag = 1;
	else
	  break;
  }

  if (argv[i] == NULL) {
	dacs_usage();
	/*NOTREACHED*/
  }
  hostname = argv[i];

  if ((portname = argv[++i]) != NULL) {
	if (argv[++i] != NULL) {
	  dacs_usage();
	  /*NOTREACHED*/
	}
  }

  if ((port = pam_get_pamd_port(portname, &errmsg)) == 0) {
	log_msg((LOG_ERROR_LEVEL, "%s", errmsg));
	exit(1);
  }

  /* First command line argument is the hostname, second is the port number */
  if (net_connect_to_server(hostname, port, NULL, &sd) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Connect failed"));
	exit(1);
  }
  log_msg((LOG_DEBUG_LEVEL, "Connected"));

  /* If there's information to pass, do that first. */
  fprintf(stderr, "Reading from stdin until blank line or EOF...\n");
  while (fgets(buf, sizeof(buf), stdin) != NULL) {
	size_t len;
	ssize_t nwritten;

	len = strlen(buf);
	nwritten = write(sd, buf, len);
	if (nwritten != len) {
	  if (nwritten == -1)
		log_err((LOG_ERROR_LEVEL, "write"));
	  else
		log_msg((LOG_ERROR_LEVEL, "Short write"));
	  exit(1);
	}

	if (buf[0] == '\n')
	  break;
  }
  shutdown(sd, SHUT_WR);

  timeout.tv_sec = dflag ? 1000 : 10;
  timeout.tv_usec = 0;
  fprintf(stderr, "(will wait %ld secs for reply)\n", timeout.tv_sec);
  if (net_input_or_timeout(sd, &timeout) != 1)
	exit(1);

  fd_in = fdopen(sd, "r");

  inbuf = ds_init(NULL);
  inbuf->clear_flag = 1;
  if (pamd_get_block(fd_in, inbuf, &kwv_head) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Read error"));
	exit(1);
  }

  if ((result = kwv_lookup_value(kwv_head, "result")) != NULL) {
	if (vflag)
	  fprintf(stderr, "<auth_reply result=\"%s\">\n", result);
	else
	  fprintf(stderr, "%s\n", result);
	exit(0);
  }

  if ((id = kwv_lookup_value(kwv_head, "transid")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Missing transid"));
	exit(1);
  }

  if (vflag)
	fprintf(stderr, "<auth_reply transid=\"%s\" result=\"prompt\">\n", id);
  else
	fprintf(stderr, "Reply to \"%s\"\n", id);

  while (pamd_get_block(fd_in, inbuf, &kwv) == 1) {
	char *label, *type, *varname;

	type = kwv_lookup_value(kwv, "type");
	label = kwv_lookup_value(kwv, "label");
	varname = kwv_lookup_value(kwv, "varname");

	if (type == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "No type?"));
	  exit(1);
	}

	if (vflag) {
	  fprintf(stderr, "<prompt type=\"%s\"", type);
	  if (label != NULL)
		fprintf(stderr, " label=\"%s\"", label);
	  if (varname != NULL)
		fprintf(stderr, " varname=\"%s\"", varname);
	  fprintf(stderr, "/>\n");
	}
	else {
	  if (label != NULL)
		fprintf(stderr, "%s ", label);
	  fprintf(stderr, "(%s", type);
	  if (varname != NULL)
		fprintf(stderr, ", %s", varname);
	  fprintf(stderr, ")\n");
	}
  }
  if (vflag)
	fprintf(stderr, "</auth_reply>\n");

  exit(0);
}
