
/*-
 * Copyright (c) 1995 The Apache Group. All rights reserved.
 * 
 *
 * Apache httpd license
 * ====================
 * 
 *
 * This is the license for the Apache Server. It covers all the
 * files which come in this distribution, and should never be removed.
 * 
 * The "Apache Group" has based this server, called "Apache", on
 * public domain code distributed under the name "NCSA httpd 1.3".
 * 
 * NCSA httpd 1.3 was placed in the public domain by the National Center 
 * for Supercomputing Applications at the University of Illinois 
 * at Urbana-Champaign.
 * 
 * As requested by NCSA we acknowledge,
 * 
 *  "Portions developed at the National Center for Supercomputing
 *   Applications at the University of Illinois at Urbana-Champaign."
 *
 * Copyright on the sections of code added by the "Apache Group" belong
 * to the "Apache Group" and/or the original authors. The "Apache Group" and
 * authors hereby grant permission for their code, along with the
 * public domain NCSA code, to be distributed under the "Apache" name.
 * 
 * Reuse of "Apache Group" code outside of the Apache distribution should
 * be acknowledged with the following quoted text, to be included with any new
 * work;
 * 
 * "Portions developed by the "Apache Group", taken with permission 
 *  from the Apache Server   http://www.apache.org/apache/   "
 *
 *
 * Permission is hereby granted to anyone to redistribute Apache under
 * the "Apache" name. We do not grant permission for the resale of Apache, but
 * we do grant permission for vendors to bundle Apache free with other software,
 * or to charge a reasonable price for redistribution, provided it is made
 * clear that Apache is free. Permission is also granted for vendors to 
 * sell support for for Apache. We explicitly forbid the redistribution of 
 * Apache under any other name.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE 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 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.
 * 
 */



/*
 * http_auth_msql: authentication
 * 
 * Rob McCool & Brian Behlendorf.
 * 
 * Adapted to Shambhala by rst.
 */

/*
 * converted to use mSQL by Vivek Khera <khera@kciLink.com>
 * only has user/passwords in mSQL database.  A suitable table would be:
 *
 * CREATE TABLE user_info (
 *   user CHAR(30) PRIMARY KEY,
 *   password CHAR(20) NOT NULL,
 *     [ any other fields if needed ]
 * )
 *
 * User must be a unique, non-empty field.  Length is however long you
 * want it to be.  Password length of 20 follows new-style crypt() usage;
 * the older crypt uses shorter encrypted passwords.  Any other fields in
 * the named table will be ignored.  The actual field names are configurable
 * using the parameters listed below.  The defaults are "user" and "password"
 * respectively, for the user ID and the password.  If you like to store
 * passwords in clear text, set AuthMSQLCryptedPasswords to Off.  I think this
 * is a bad idea, but people have requested it.
 *
 * Usage in per-directory access conf file:
 *
 *  AuthName mSQL Testing
 *  AuthType Basic
 *  AuthGroupFile /dev/null
 *  AuthMSQLHost localhost
 *  AuthMSQLDB www_data
 *  AuthMSQLUserTable user_info
 *
 *  <Limit GET POST>
 *  require valid-user
 *  </Limit>
 *
 * The following parameters are optional in the config file.  The defaults
 * values are shown here.
 *
 *  AuthMSQLNameField user
 *  AuthMSQLPasswordField password
 *  AuthMSQLCryptedPasswords On
 * 
 * the Host of "localhost" means use the mSQL socket instead of a TCP
 * connection to the database.  DB is the database name on the server,
 * and UserTable is the actual table name within that database.
 *
 * Groups are not implemented in mSQL.  Use the original flat file or
 * the Apache DBM version.
 *
 * $Id: mod_auth_msql.c,v 1.7 1995/12/02 18:47:30 khera Exp $
 */

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include <msql.h>

/*
 * msqlhost is host name. localhost means use Unix Domain socket for mSQL.
 * msqlDB is the database name on that host.
 * msqlpwtable is the table name for passwords.  uses fields "user","password".
 * The "user" field must be "not null" and unique.  "password" is encrypted.
 * the user field must not have a ' (single quote) character in it.
 */
typedef struct  {
    char *msqlhost;
    char *msqlDB;
    char *msqlpwtable;
    char *msqlNameField;
    char *msqlPasswordField;
    int  msqlCrypted;
} msql_auth_config_rec;

static
void *create_msql_auth_dir_config (pool *p, char *d)
{
  msql_auth_config_rec *m = pcalloc (p, sizeof(msql_auth_config_rec));
  if (!m) return NULL;		/* failure to get memory is a bad thing */

  /* need these defaults for compatibility with prior versions */
  m->msqlNameField = "user";
  m->msqlPasswordField = "password";
  m->msqlCrypted = 1;
  return (void *)m;
}

static
char *set_crypted_password (cmd_parms *cmd, void *mrec, int arg) {
  ((msql_auth_config_rec *)mrec)->msqlCrypted = arg;
  return NULL;
}

static
command_rec msql_auth_cmds[] = {
{ "AuthMSQLHost", set_string_slot,
    (void*)XtOffsetOf(msql_auth_config_rec, msqlhost),
    OR_AUTHCFG, TAKE1, "mSQL server hostname" },
{ "AuthMSQLDB", set_string_slot,
    (void*)XtOffsetOf(msql_auth_config_rec, msqlDB),
    OR_AUTHCFG, TAKE1, "mSQL database name" },
{ "AuthMSQLUserTable", set_string_slot,
    (void*)XtOffsetOf(msql_auth_config_rec, msqlpwtable),
    OR_AUTHCFG, TAKE1, "mSQL table name" },
{ "AuthMSQLNameField", set_string_slot,
    (void*)XtOffsetOf(msql_auth_config_rec, msqlNameField),
    OR_AUTHCFG, TAKE1, "mSQL User ID field name within table" },
{ "AuthMSQLPasswordField", set_string_slot,
    (void*)XtOffsetOf(msql_auth_config_rec, msqlPasswordField),
    OR_AUTHCFG, TAKE1, "mSQL Password field name within table" },
{ "AuthMSQLCryptedPasswords", set_crypted_password,
    NULL, OR_AUTHCFG, FLAG, "mSQL passwords are stored encrypted if On" },
{ NULL }
};

module msql_auth_module;

/*
 * get password from database
 */
static
char *get_msql_pw(request_rec *r, char *user, msql_auth_config_rec *m) {
    int msqlSock;
    m_result *result;
    m_row data;
    char *pw = NULL;
    char *host;
    char query[MAX_STRING_LEN];

    if (!m->msqlhost || strcmp(m->msqlhost,"localhost") == 0) {
      host = NULL;
    } else {
      host = m->msqlhost;
    }
    
    if((msqlSock=msqlConnect(host)) < 0) {
        log_reason (msqlErrMsg, r->uri, r);
	return NULL;
    }

    if (msqlSelectDB(msqlSock,m->msqlDB) < 0) {
        log_reason (msqlErrMsg, r->uri, r);
	return NULL;
    }

    sprintf(query,"SELECT %s FROM %s WHERE %s = '%s'",
	    m->msqlPasswordField, m->msqlpwtable,
	    m->msqlNameField, user);
    if (msqlQuery(msqlSock, query) < 0) {
        log_reason (msqlErrMsg, r->uri, r);
	return NULL;
    }

    result = msqlStoreResult();
    if (msqlNumRows(result) == 1) {
        data = msqlFetchRow(result);
	if (data[0]) {
	  pw = palloc (r->pool, strlen(data[0]) + 1);
	  strcpy(pw,data[0]);
	} else {		/* no password in mSQL table -- returns NULL */
	  log_reason ("mSQL user has no valid password", r->uri, r);
	  return NULL;
	}
    }

    msqlFreeResult(result);
    msqlClose(msqlSock);

    return pw; 
}

static
int msql_authenticate_basic_user (request_rec *r)
{
    msql_auth_config_rec *sec =
      (msql_auth_config_rec *)get_module_config (r->per_dir_config,
						&msql_auth_module);
    conn_rec *c = r->connection;
    char *sent_pw, *real_pw;
    char errstr[MAX_STRING_LEN];
    int res;
    
    if ((res = get_basic_auth_pw (r, &sent_pw)))
        return res;
    
    if(!sec->msqlpwtable)
        return DECLINED;
	
    if(!(real_pw = get_msql_pw(r, c->user, sec))) {
        sprintf(errstr,"mSQL user `%s' not found", c->user);
	log_reason (errstr, r->uri, r);
	note_basic_auth_failure (r);
	return AUTH_REQUIRED;
    }    

    if(strcmp(real_pw, sec->msqlCrypted ? crypt(sent_pw,real_pw) : sent_pw)) {
      sprintf(errstr,"user %s: password mismatch",c->user);
      log_reason (errstr, r->uri, r);
      note_basic_auth_failure (r);
      return AUTH_REQUIRED;
    }
    return OK;
}
    

module msql_auth_module = {
   STANDARD_MODULE_STUFF,
   NULL,			/* initializer */
   create_msql_auth_dir_config,	/* dir config creater */
   NULL,			/* dir merger --- default is to override */
   NULL,			/* server config */
   NULL,			/* merge server config */
   msql_auth_cmds,		/* command table */
   NULL,			/* handlers */
   NULL,			/* filename translation */
   msql_authenticate_basic_user,	/* check_user_id */
   NULL,			/* check auth */
   NULL,			/* check access */
   NULL,			/* type_checker */
   NULL,			/* fixups */
   NULL				/* logger */
};
