On Wed, May 05, 2004 at 03:09:19PM +0100, Patrick Welche wrote:
> While trying to get mod_auth_pgsql-2.0.2b1 to work with 22 April CVS-httpd,
> I found that the reason I could not authenticate against a postgresql
> server was that mod_auth_pgsql received an empty user field in the
> request structure. Strangely, it received the correct password from the
> server.. How can this be?

I gave up trying to fix the above and wrote the following instead. Is there
a procedure for having such modules included in the core distribution, with
pertinent additional autoconf glue?

Cheers,

Patrick
/* Copyright 2004 Patrick Welche
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Purpose:
 *     Provides authentication against a PostgreSQL server
 *     http://www.postgresql.org/
 *
 * Author:
 *     Adapted from (mod_authn_dbm by Rob McCool and Brian Behlendorf)
 *     and from (Cyrus sasl sql auxprop plugin by Ken Murchinson,
 *                                                           Simon Loader & PW)
 *     by Patrick Welche
 *
 * $Id: mod_authn_pgsql.c,v 1.2 2004/05/13 10:41:00 prlw1 Exp $
 */

#include "httpd.h"
#include "http_config.h"
#include "http_log.h"

#include "ap_provider.h"
#include "mod_auth.h"
#include "libpq-fe.h"

typedef struct
{
  /* PostgreSQL connection data */
  const char *authn_pgsql_host;
  const char *authn_pgsql_hostaddr;
  const char *authn_pgsql_port;
  const char *authn_pgsql_dbname;
  const char *authn_pgsql_user;
  const char *authn_pgsql_password;
  const char *authn_pgsql_connect_timeout;
  const char *authn_pgsql_options;
  const char *authn_pgsql_sslmode;
  const char *authn_pgsql_service;
  /* module data */
  const char *authn_pgsql_select;
} authn_pgsql_config_rec;

module AP_MODULE_DECLARE_DATA authn_pgsql_module;

static void *create_authn_pgsql_dir_config(apr_pool_t *p, char *d)
{
  authn_pgsql_config_rec *conf=apr_pcalloc(p,sizeof(authn_pgsql_config_rec));

  return conf;
}

static const command_rec authn_pgsql_cmds[] =
{
  AP_INIT_TAKE1("AuthPGSQLHost", ap_set_string_slot,
      (void *)APR_OFFSETOF(authn_pgsql_config_rec, authn_pgsql_host),
      OR_AUTHCFG,
      "hostname of PostgreSQL database server"),
  AP_INIT_TAKE1("AuthPGSQLHostaddr", ap_set_string_slot,
      (void *)APR_OFFSETOF(authn_pgsql_config_rec, authn_pgsql_hostaddr),
      OR_AUTHCFG,
      "numeric IP address of PostgreSQL database server"),
  AP_INIT_TAKE1("AuthPGSQLPort", ap_set_string_slot,
      (void *)APR_OFFSETOF(authn_pgsql_config_rec, authn_pgsql_port),
      OR_AUTHCFG,
      "port number of the server host, or socket file name extension"),
  AP_INIT_TAKE1("AuthPGSQLDatabase", ap_set_string_slot,
      (void *)APR_OFFSETOF(authn_pgsql_config_rec, authn_pgsql_dbname),
      OR_AUTHCFG,
      "database name"),
  AP_INIT_TAKE1("AuthPGSQLUser", ap_set_string_slot,
      (void *)APR_OFFSETOF(authn_pgsql_config_rec, authn_pgsql_user),
      OR_AUTHCFG,
      "user name on database server"),
  AP_INIT_TAKE1("AuthPGSQLPassword", ap_set_string_slot,
      (void *)APR_OFFSETOF(authn_pgsql_config_rec, authn_pgsql_password),
      OR_AUTHCFG,
      "password on database server"),
  AP_INIT_TAKE1("AuthPGSQLConnectionTimeout", ap_set_string_slot,
      (void *)APR_OFFSETOF(authn_pgsql_config_rec, authn_pgsql_connect_timeout),
      OR_AUTHCFG,
      "maximum wait for connection in seconds"),
  AP_INIT_TAKE1("AuthPGSQLOptions", ap_set_string_slot,
      (void *)APR_OFFSETOF(authn_pgsql_config_rec, authn_pgsql_options),
      OR_AUTHCFG,
      "PostgreSQL command-line options"),
  AP_INIT_TAKE1("AuthPGSQLSSLMode", ap_set_string_slot,
      (void *)APR_OFFSETOF(authn_pgsql_config_rec, authn_pgsql_sslmode),
      OR_AUTHCFG,
      "priority of SSL connection negotiation (disable|allow|prefer|require)"),
  AP_INIT_TAKE1("AuthPGSQLService", ap_set_string_slot,
      (void *)APR_OFFSETOF(authn_pgsql_config_rec, authn_pgsql_service),
      OR_AUTHCFG,
      "service name for additional connection parameters in pg_service.conf"),
  AP_INIT_TAKE1("AuthPGSQLSelect", ap_set_string_slot,
      (void *)APR_OFFSETOF(authn_pgsql_config_rec, authn_pgsql_select),
      OR_AUTHCFG,
      "select statement - %u and %p are replaced by the userid and password 
respectively, '%' is escaped by doubling as \"%%\""),
  {NULL}
};

static char *build_conninfo(authn_pgsql_config_rec *pg, apr_pool_t *pool)
{
  char *conninfo, *sep;
  apr_size_t conninfo_len=0;

  /* +2 for quotes */
  if(pg->authn_pgsql_host)
      conninfo_len+=2+strlen("host=")+strlen(pg->authn_pgsql_hostaddr);
  if(pg->authn_pgsql_hostaddr)
      conninfo_len+=2+strlen(" hostaddr=")+strlen(pg->authn_pgsql_hostaddr);
  if(pg->authn_pgsql_port)
      conninfo_len+=2+strlen(" port=")+strlen(pg->authn_pgsql_port);
  if(pg->authn_pgsql_dbname)
      conninfo_len+=2+strlen(" dbname=")+strlen(pg->authn_pgsql_dbname);
  if(pg->authn_pgsql_user)
      conninfo_len+=2+strlen(" user=")+strlen(pg->authn_pgsql_user);
  if(pg->authn_pgsql_password)
      conninfo_len+=2+strlen(" password=")+strlen(pg->authn_pgsql_password);
  if(pg->authn_pgsql_connect_timeout)
      conninfo_len+=2+strlen(" 
connect_timeout=")+strlen(pg->authn_pgsql_connect_timeout);
  if(pg->authn_pgsql_options)
    conninfo_len+=2+strlen(" options=")+strlen(pg->authn_pgsql_options);
  if(pg->authn_pgsql_sslmode)
      conninfo_len+=2+strlen(" sslmode=")+strlen(pg->authn_pgsql_sslmode);
  if(pg->authn_pgsql_service)
      conninfo_len+=2+strlen(" service=")+strlen(pg->authn_pgsql_service);

  conninfo=apr_palloc(pool,conninfo_len);

  conninfo[0]='\0';
  sep="";
  if(pg->authn_pgsql_host)
    {
      strcat(conninfo, sep);
      strcat(conninfo, "host='");
      strcat(conninfo, pg->authn_pgsql_host);
      strcat(conninfo, "'");
      sep=" ";
    }
  if(pg->authn_pgsql_hostaddr)
    {
      strcat(conninfo, sep);
      strcat(conninfo, "hostaddr='");
      strcat(conninfo, pg->authn_pgsql_hostaddr);
      strcat(conninfo, "'");
      sep=" ";
    }
  if(pg->authn_pgsql_port)
    {
      strcat(conninfo, sep);
      strcat(conninfo, "port='");
      strcat(conninfo, pg->authn_pgsql_port);
      strcat(conninfo, "'");
      sep=" ";
    }
  if(pg->authn_pgsql_dbname)
    {
      strcat(conninfo, sep);
      strcat(conninfo, "dbname='");
      strcat(conninfo, pg->authn_pgsql_dbname);
      strcat(conninfo, "'");
      sep=" ";
    }
  if(pg->authn_pgsql_user)
    {
      strcat(conninfo, sep);
      strcat(conninfo, "user='");
      strcat(conninfo, pg->authn_pgsql_user);
      strcat(conninfo, "'");
      sep=" ";
    }
  if(pg->authn_pgsql_password)
    {
      strcat(conninfo, sep);
      strcat(conninfo, "password='");
      strcat(conninfo, pg->authn_pgsql_password);
      strcat(conninfo, "'");
      sep=" ";
    }
  if(pg->authn_pgsql_connect_timeout)
    {
      strcat(conninfo, sep);
      strcat(conninfo, "connect_timeout='");
      strcat(conninfo, pg->authn_pgsql_connect_timeout);
      strcat(conninfo, "'");
      sep=" ";
    }
  if(pg->authn_pgsql_options)
    {
      strcat(conninfo, sep);
      strcat(conninfo, "options='");
      strcat(conninfo, pg->authn_pgsql_options);
      strcat(conninfo, "'");
      sep=" ";
    }
  if(pg->authn_pgsql_sslmode)
    {
      strcat(conninfo, sep);
      strcat(conninfo, "sslmode='");
      strcat(conninfo, pg->authn_pgsql_sslmode);
      strcat(conninfo, "'");
      sep=" ";
    }
  if(pg->authn_pgsql_service)
    {
      strcat(conninfo, sep);
      strcat(conninfo, "service='");
      strcat(conninfo, pg->authn_pgsql_service);
      strcat(conninfo, "'");
      sep=" ";
    }

  return conninfo;
}

char *build_query(const char *select , const char *raw_user,
                  const char *raw_password, apr_pool_t *pool)
{
  char *query, *user, *password, *q;
  const char *c, *p;
  int nc=0, nu=0, np=0;
  size_t rulen, rplen, ulen, plen;

  for(c=select;*c!='\0';++c)
    {
      if(*c=='%')
        {
          switch(*(++c))
            {
              case '%': ++nc; break;
              case 'u': ++nu; break;
              case 'p': ++np; break;
              default:  ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool,
                               "\"%%%c\" not allowed in AuthPGSQLSelect",*c);
                        return NULL;
                        break;
            }
        }
    }

  rulen=strlen(raw_user);
  rplen=strlen(raw_password);
  user    =apr_palloc(pool,2*rulen+1);
  password=apr_palloc(pool,2*rplen+1);
  ulen=PQescapeString(user,    raw_user,    rulen);
  plen=PQescapeString(password,raw_password,rplen);

  query=apr_palloc(pool,strlen(select)+nu*ulen+np*plen+nc+1);

  p=select;
  q=query;
  while((c=strchr(p,'%')))
    {
      memcpy(q,p,c-p); q+=c-p;
      switch(*(++c))
        {
          case '%': *q++ = '%'; break;
          case 'u': memcpy(q,user,    ulen); q+=ulen; break;
          case 'p': memcpy(q,password,plen); q+=plen; break;
          default:  ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool,
                           "\"%%%c\" not allowed in AuthPGSQLSelect",*c);
                    return NULL;
                    break;
        }
      p=++c;
    }

  memcpy(q,p,strlen(p)+1);

  return query;
}

static authn_status check_pgsql_pw(request_rec *r, const char *user,
                                   const char *password)
{
  authn_pgsql_config_rec *pg = ap_get_module_config(r->per_dir_config,
                                                    &authn_pgsql_module);
  PGconn *db;
  PGresult *res;

  if(pg->authn_pgsql_select==NULL || *(pg->authn_pgsql_select)=='\0')
    {
      ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                    "mod_authn_pgsql: AuthPGSQLSelect must be set");
      return AUTH_GENERAL_ERROR;
    }

  db=PQconnectdb(build_conninfo(pg,r->pool));
  if(PQstatus(db)!=CONNECTION_OK)
    {
      ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                    "mod_authn_pgsql: %s",PQerrorMessage(db));
      return AUTH_GENERAL_ERROR;
    }

  res=PQexec(db,build_query(pg->authn_pgsql_select,user,password,r->pool));
  if(PQresultStatus(res)==PGRES_TUPLES_OK)
    {
      int n=PQntuples(res);
      PQclear(res);
      PQfinish(db);
      switch(n)
        {
          case 0:  return AUTH_DENIED;  break;
          case 1:  return AUTH_GRANTED; break;
          default: ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                 "Select \"%s\" returned %i tuples",
                                 pg->authn_pgsql_select,n);
                   return AUTH_GENERAL_ERROR; break;
        }
    }
  else
    {
      ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                    "mod_authn_pgsql: %s",PQerrorMessage(db));
      PQclear(res);
      PQfinish(db);
      return AUTH_GENERAL_ERROR;
    }
}

/* Given a user and realm, expected to return AUTH_USER_FOUND if we
 * can find a md5 hash of 'user:realm:password'
 */
static authn_status get_pgsql_realm_hash(request_rec *r, const char *user,
                                         const char *realm, char **rethash)
{
  ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                "get_realm_hash not implemented for pgsql provider");

  return AUTH_GENERAL_ERROR;
}

static const authn_provider authn_pgsql_provider =
{
  &check_pgsql_pw,
  &get_pgsql_realm_hash
};

static void register_hooks(apr_pool_t *p)
{
  ap_register_provider(p, AUTHN_PROVIDER_GROUP, "pgsql", "0",
                       &authn_pgsql_provider);
}   
    
module AP_MODULE_DECLARE_DATA authn_pgsql_module =
{
  STANDARD20_MODULE_STUFF,
  create_authn_pgsql_dir_config, /* dir config creater */
  NULL,                          /* dir merger --- default is to override */
  NULL,                          /* server config */
  NULL,                          /* merge server config */
  authn_pgsql_cmds,              /* command apr_table_t */
  register_hooks                 /* register hooks */
};  

Reply via email to