/*
 * 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.
 ***************************************************************************/

/* 
 * NTML authentication using Samba
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: local_ntlm_auth.c 2590 2012-03-19 18:06:05Z brachman $";
#endif

#include "dacs.h"

#ifdef DACS_OS_LINUX
/* Make sure comparison_fn_t is defined... */
#undef __COMPAR_FN_T
#ifndef __USE_GNU
#define __USE_GNU
#endif
#include <search.h>
#endif

/*
 * Samba includes
 * XXX yes, this is hideous; Samba just does not export a clean, useful
 * API, yet it provides some functionality that I have not found elsewhere
 * (for example, libntlm is poorly documented and incomplete).  This is
 * especially true as Samba moved from 3.2 to 3.5.  So to get at the Samba
 * functions we need to assemble this brittle kludge-erama and hold our nose.
 * Perhaps there is a simpler way.
 */
#ifdef OLD
#include "includes.h"
#else
#include "include/config.h"
#include "../lib/replace/replace.h"
#include "include/version.h"
#include "../libcli/util/ntstatus.h"
#include "include/dynconfig.h"
#include "include/dynconfig.h"
#include "../lib/talloc/talloc.h"

#define True true
#define False false
#define Undefined (-1)

extern bool lp_is_in_client(void);
extern bool lp_load(const char *pszFname, bool global_only, bool save_defaults,
					bool add_ipc, bool initialize_globals);
extern const char *lp_winbind_separator(void);
extern bool lp_do_parameter(int snum, const char *pszParmName,
							const char *pszParmValue);
extern char *get_myname(TALLOC_CTX *ctx);
extern void strupper_m(char *s);
extern bool resolve_name(const char *name, struct sockaddr_storage *return_ss,
						 int name_type, bool prefer_ipv4);
extern void load_case_tables(void);
extern NTSTATUS cli_full_connection(void **output_cli, 
									const char *my_name, const char *dest_host, 
									struct sockaddr_storage *dest_ss, int port,
									const char *service,
									const char *service_type, const char *user,
									const char *domain, const char *password,
									int flags, int signing_state, bool *retry);
extern void cli_shutdown(void *cli);
extern TALLOC_CTX *talloc_tos(void);
#endif

static const char *log_module_name = "local_ntlm_auth";

#ifndef PROG

/*
 * Microsoft Windows NTLM authentication using Samba
 * We assume the caller destroys the plaintext password as soon as we
 * return.
 * SMB_PORT is typically 445 (microsoft-ds) or 139 (netbios-ssn);
 * if it is 0, Samba will use a sequence of default ports until one works.
 *
 * Return 0 if authentication succeeds, -1 otherwise.
 */
int
local_ntlm_auth(char *username, char *password, char *aux,
				int smb_port, char *smb_server, char *smb_domain)
{
  int st;
  char *ntlm_password, *ntlm_username, *domain;
  NTSTATUS nt_status;
  void *cli;
  const char *fname;
#if SAMBA_VERSION_MAJOR == 3 && SAMBA_VERSION_MINOR >= 2
  char *myname;
  struct sockaddr_storage server_ip;
  bool in_client = lp_is_in_client();
#elif SAMBA_VERSION_MAJOR == 3 \
  && SAMBA_VERSION_MINOR == 0 \
  && SAMBA_VERSION_RELEASE > 22
  /* NOTE: This may not be compatible with versions prior to 3.0.25b. */
  struct in_addr server_ip;
  pstring myname;
  extern BOOL in_client;
#else
#error "Sorry, DACS does not support the configured Samba distribution."
#endif

  if (smb_server == NULL)
	return(-1);

  in_client = True;
#if SAMBA_VERSION_MAJOR == 3 \
  && SAMBA_VERSION_MINOR == 0 \
  && SAMBA_VERSION_RELEASE > 22
  fname = dyn_CONFIGFILE;
  load_case_tables();
  /* An extra argument was added... */
  if (!lp_load(fname, True, False, False, True)) {
#elif SAMBA_VERSION_MAJOR == 3 && SAMBA_VERSION_MINOR >= 2
  load_case_tables();
  /* An extra argument was added... */
  fname = get_dyn_CONFIGFILE();
  if (!lp_load(fname, True, False, False, True)) {
#else
  /* For posterity... */
  fname = dyn_CONFIGFILE;
  if (!lp_load(fname, True, False, False)) {
#endif
	const char *p;

	/*
	 * If we can load a Samba configuration file, then use it; but if not,
	 * we can probably still proceed.  Apparently the only essential
	 * thing is that Globals.szWinbindSeparator is initialized so that
	 * USERNAME is parsed correctly by libsmb/cliconnect.c:cli_session_setup().
	 */
	log_msg((LOG_NOTICE_LEVEL, "Can't load %s... proceeding", fname));
	if ((p = lp_winbind_separator()) == NULL || *p == '\0')
	  lp_do_parameter(-1, "winbind separator", "\\");
  }

#if SAMBA_VERSION_MAJOR == 3 && SAMBA_VERSION_MINOR >= 2
  myname = get_myname(talloc_tos());
#else
  get_myname(myname);
#endif
  strupper_m(myname);

  /* Resolve the IP address */
#if SAMBA_VERSION_MAJOR == 3 && SAMBA_VERSION_MINOR >= 5
  if (!resolve_name(smb_server, &server_ip, 0x20, 1))  {
#else
  if (!resolve_name(smb_server, &server_ip, 0x20))  {
#endif
	log_msg((LOG_ERROR_LEVEL, "Unable to resolve server \"%s\"", smb_server));
	return(-1);
  }
	
  if (smb_domain != NULL)
	domain = smb_domain;
  else
	domain = "";
  if (username != NULL)
	ntlm_username = username;
  else
	ntlm_username = "";
  if (password != NULL)
	ntlm_password = password;
  else
	ntlm_password = "";

  log_msg((LOG_INFO_LEVEL, "Authenticating: server=\"%s:%d\", domain=\"%s\"",
		   smb_server, smb_port, domain));
  log_msg((LOG_DEBUG_LEVEL | LOG_SENSITIVE_FLAG,
		   "Authenticating: username=\"%s\", password=\"%s\"",
		   ntlm_username, ntlm_password));

  cli = NULL;
  nt_status = cli_full_connection(&cli, myname, smb_server,
								  &server_ip, smb_port,
								  "IPC$", "IPC",
								  ntlm_username, domain,
								  ntlm_password, 0, Undefined, NULL);
	
  if (NT_STATUS_IS_OK(nt_status)) {
	log_msg((LOG_INFO_LEVEL, "Auth succeeded for username=\"%s\"",
			 ntlm_username));
	st = 0;
  }
  else {
	log_msg((LOG_ALERT_LEVEL, "Auth failed for username=\"%s\": (%s)\n",
			 ntlm_username, nt_errstr(nt_status)));
	st = -1;
  }

  if (cli != NULL)
	cli_shutdown(cli);

  return(st);
}

#else
int
main(int argc, char **argv)
{
  int emitted_dtd, i, samba_port;
  char *errmsg, *jurisdiction, *username, *password, *aux;
  char *samba_domain, *samba_server;
  Auth_reply_ok ok;
  Kwv *kwv;

  errmsg = "internal";
  emitted_dtd = 0;
  username = password = aux = jurisdiction = NULL;
  samba_server = samba_domain = NULL;
  samba_port = -1;

  if (dacs_init(DACS_LOCAL_SERVICE, &argc, &argv, &kwv, &errmsg) == -1) {
	/* If we fail here, we may not have a DTD with which to reply... */
  fail:
	if (password != NULL)
	  strzap(password);
	if (aux != NULL)
	  strzap(aux);
	if (emitted_dtd) {
	  printf("%s\n", make_xml_auth_reply_failed(NULL, NULL));
	  emit_xml_trailer(stdout);
	}
	if (errmsg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "Failed: reason=%s", errmsg));

	exit(1);
  }

  /* This must go after initialization. */
  emitted_dtd = emit_xml_header(stdout, "auth_reply");

  if (argc > 1) {
	errmsg = "Usage: unrecognized parameter";
	goto fail;
  }

  for (i = 0; i < kwv->nused; i++) {
	if (streq(kwv->pairs[i]->name, "USERNAME") && username == NULL)
	  username = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "PASSWORD") && password == NULL)
	  password = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "AUXILIARY") && aux == NULL)
	  aux = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "DACS_JURISDICTION")
			 && jurisdiction == NULL)
	  jurisdiction = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "DACS_VERSION"))
	  ;
	else if (streq(kwv->pairs[i]->name, "SAMBA_SERVER")
			 && samba_server == NULL)
	  samba_server = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "SAMBA_DOMAIN")
			 && samba_domain == NULL)
	  samba_domain = kwv->pairs[i]->val;
	else if (streq(kwv->pairs[i]->name, "SAMBA_PORT")
			 && samba_port == -1) {
	  if (strnum(kwv->pairs[i]->val, STRNUM_I, &samba_port) == -1) {
		log_msg((LOG_ALERT_LEVEL, "Invalid SAMBA_PORT: \"%s\"",
				 kwv->pairs[i]->val));
		errmsg = "Invalid SAMBA_PORT parameter";
		goto fail;
	  }
	}
	else
	  log_msg((LOG_TRACE_LEVEL, "Parameter: '%s'", kwv->pairs[i]->name));
  }

  /* Verify that we're truly responsible for DACS_JURISDICTION */
  if (dacs_verify_jurisdiction(jurisdiction) == -1) {
	errmsg = "Missing or incorrect DACS_JURISDICTION";
	goto fail;
  }

  if (username == NULL) {
	errmsg = "No USERNAME specified";
	goto fail;
  }

  if (password == NULL) {
	errmsg = "No PASSWORD specified";
	goto fail;
  }

  if (samba_server == NULL) {
	errmsg = "No SAMBA_SERVER specified";
	goto fail;
  }

  if (samba_port == -1) {
	log_msg((LOG_TRACE_LEVEL, "Using default samba port"));
	samba_port = 0;
  }

  if (local_ntlm_auth(username, password, aux, samba_port,
					  samba_server, samba_domain) == -1) {
	errmsg = "Username/Password/Aux incorrect";
	goto fail;
  }

  if (password != NULL)
	strzap(password);
  if (aux != NULL)
	strzap(aux);

  ok.username = username;
  /* If this wasn't specified, dacs_authenticate will use the default. */
  ok.lifetime = kwv_lookup_value(kwv, "CREDENTIALS_LIFETIME_SECS");
  ok.roles_reply = NULL;
  printf("%s\n", make_xml_auth_reply_ok(&ok));

  emit_xml_trailer(stdout);
  exit(0);
}
#endif
