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 */ };
