diff options
-rw-r--r-- | daemon/Makefile.am | 8 | ||||
-rw-r--r-- | daemon/bd.c | 40 | ||||
-rw-r--r-- | daemon/digest.c | 2 | ||||
-rw-r--r-- | daemon/httpauthd.c | 22 | ||||
-rw-r--r-- | daemon/httpauthd.h | 5 | ||||
-rw-r--r-- | daemon/ldap.c | 65 | ||||
-rw-r--r-- | daemon/mysql.c | 751 | ||||
-rw-r--r-- | daemon/ntlm.c | 43 | ||||
-rw-r--r-- | daemon/pgsql.c | 74 | ||||
-rw-r--r-- | daemon/simple.c | 4 | ||||
-rw-r--r-- | daemon/usuals.h | 2 |
11 files changed, 878 insertions, 138 deletions
diff --git a/daemon/Makefile.am b/daemon/Makefile.am index db83f8b..e4eecde 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -12,6 +12,8 @@ LDAP_SOURCES = ldap.c PGSQL_SOURCES = pgsql.c +MYSQL_SOURCES = mysql.c + NTLM_SOURCES = ntlm.c ntlmssp.h ntlmssp.c \ smblib/smblib.c smblib/smblib-util.c smblib/file.c smblib/smb-errors.c \ smblib/exper.c smblib/smblib-api.c smblib/smblib.h smblib/std-defines.h \ @@ -32,8 +34,12 @@ if WITH_PGSQL httpauthd_SOURCES += $(PGSQL_SOURCES) endif +if WITH_MYSQL +httpauthd_SOURCES += $(MYSQL_SOURCES) +endif + httpauthd_CFLAGS = -D_THREAD_SAFE -pthread -DLinux \ -I${top_srcdir}/common/ -I${top_srcdir} httpauthd_LDFLAGS = -pthread -EXTRA_DIST = $(LDAP_SOURCES) $(NTLM_SOURCES) $(PGSQL_SOURCES) +EXTRA_DIST = $(LDAP_SOURCES) $(NTLM_SOURCES) $(PGSQL_SOURCES) $(MYSQL_SOURCES) diff --git a/daemon/bd.c b/daemon/bd.c index 5519cfa..9c3b429 100644 --- a/daemon/bd.c +++ b/daemon/bd.c @@ -174,8 +174,7 @@ static int do_basic_response(ha_request_t* rq, bd_context_t* ctx, const char* he { ha_messagex(rq, LOG_NOTICE, "validated basic user against cache: %s", basic.user); - ret = HA_OK; - goto finally; + RETURN(HA_OK); } /* If we have a user name and password */ @@ -183,8 +182,7 @@ static int do_basic_response(ha_request_t* rq, bd_context_t* ctx, const char* he !basic.password || !basic.password[0]) { ha_messagex(rq, LOG_NOTICE, "no valid basic auth info"); - ret = HA_FALSE; - goto finally; + RETURN(HA_FALSE); } ASSERT(ctx->f_validate_basic); @@ -266,7 +264,7 @@ static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* h if(!dg.client.username) { ha_messagex(rq, LOG_WARNING, "digest response contains no user name"); - goto finally; + RETURN(HA_FALSE); } #ifdef _DEBUG @@ -275,7 +273,7 @@ static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* h if(dg.client.nonce && strcmp(dg.client.nonce, rq->context->digest_debugnonce) != 0) { ha_messagex(rq, LOG_WARNING, "digest response contains invalid nonce"); - goto finally; + RETURN(HA_FALSE); } /* Do a rough hash into the real nonce, for use as a key */ @@ -305,7 +303,7 @@ static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* h if(r == HA_FALSE) ha_messagex(rq, LOG_WARNING, "digest response contains invalid nonce"); - goto finally; + RETURN(r); } /* Check to see if we're stale */ @@ -315,7 +313,7 @@ static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* h dg.client.username); stale = 1; - goto finally; + RETURN(HA_FALSE); } } @@ -340,7 +338,7 @@ static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* h rq->resp_code = HA_SERVER_BADREQ; } - goto finally; + RETURN(ret); } /* @@ -359,19 +357,13 @@ static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* h rec = make_digest_rec(nonce, dg.client.username); if(!rec) - { - ret = HA_CRITERROR; - goto finally; - } + RETURN(HA_CRITERROR); ASSERT(ctx->f_validate_digest); r = ctx->f_validate_digest(rq, dg.client.username, &dg); if(r != HA_OK) - { - ret = r; - goto finally; - } + RETURN(r); /* Save away pertinent information when successful*/ memcpy(rec->ha1, dg.ha1, MD5_LEN); @@ -387,8 +379,7 @@ static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* h if(md5_strcmp(rec->userhash, dg.client.username) != 0) { ha_messagex(NULL, LOG_ERR, "digest response contains invalid username"); - ret = HA_FALSE; - goto finally; + RETURN(HA_FALSE); } /* And do the validation ourselves */ @@ -408,7 +399,7 @@ static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* h ha_messagex(NULL, LOG_WARNING, "digest re-authentication failed for user: %s", dg.client.username); - goto finally; + RETURN(ret); } } @@ -429,10 +420,7 @@ static int do_digest_response(ha_request_t* rq, bd_context_t* ctx, const char* h t = digest_respond(&dg, rq->buf, stale ? nonce : NULL); if(!t) - { - ret = HA_CRITERROR; - goto finally; - } + RETURN(HA_CRITERROR); if(t[0]) ha_addheader(rq, "Authentication-Info", t); @@ -450,7 +438,7 @@ finally: free(rec); /* If nobody above responded then challenge the client again */ - if(rq->resp_code == -1) + if(ret == HA_FALSE && rq->resp_code == -1) return do_digest_challenge(rq, ctx, stale); return ret; @@ -552,8 +540,6 @@ int bd_init(ha_context_t* context) htc.f_freeval = free_hash_object; htc.arg = NULL; hsh_set_table_calls(ctx->cache, &htc); - - ha_messagex(NULL, LOG_INFO, "initialized handler"); } return HA_OK; diff --git a/daemon/digest.c b/daemon/digest.c index bce588e..968399a 100644 --- a/daemon/digest.c +++ b/daemon/digest.c @@ -291,6 +291,8 @@ int digest_pre_check(digest_context_t* dg, const ha_context_t* opts, ha_buffer_t /* Validate the nc */ else { + /* TODO: We need to validate the nc properly. Somehow that's not + * happening here. */ char* e; long nc = strtol(dg->client.nc, &e, 16); diff --git a/daemon/httpauthd.c b/daemon/httpauthd.c index bae758a..4450b7a 100644 --- a/daemon/httpauthd.c +++ b/daemon/httpauthd.c @@ -33,6 +33,7 @@ extern ha_handler_t simple_handler; extern ha_handler_t ldap_handler; extern ha_handler_t ntlm_handler; extern ha_handler_t pgsql_handler; +extern ha_handler_t mysql_handler; /* This is the list of all available handlers */ ha_handler_t* g_handlerlist[] = @@ -46,6 +47,9 @@ ha_handler_t* g_handlerlist[] = #if WITH_PGSQL &pgsql_handler, #endif +#if WITH_MYSQL + &mysql_handler, +#endif &simple_handler, }; @@ -115,6 +119,7 @@ int g_console = 0; /* debug mode read write from console int g_debuglevel = LOG_ERR; /* what gets logged to console */ const char* g_socket = DEFAULT_SOCKET; /* The socket to communicate on */ int g_maxthreads = DEFAULT_MAXTHREADS; /* The maximum number of threads */ +unsigned int g_unique = 0x10000; /* A unique identifier (incremented) */ /* For main loop and signal handlers */ int g_quit = 0; @@ -251,6 +256,9 @@ int main(int argc, char* argv[]) if(sock < 0) err(1, "couldn't open socket"); + i = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&i, sizeof(i)); + /* Unlink the socket file if it exists */ /* TODO: Is this safe? */ unlink(g_socket); @@ -970,7 +978,7 @@ static int httpauth_auth(ha_request_t* rq, int ofd) return HA_OK; } -static int httpauth_set(ha_request_t* rq, int ofd) +static int httpauth_set(ha_request_t* rq, ha_buffer_t* cbuf, int ofd) { httpauth_loaded_t* h; const char* name = rq->req_args[0]; @@ -985,7 +993,8 @@ static int httpauth_set(ha_request_t* rq, int ofd) if(strcasecmp(name, "Domain") == 0) { - rq->digest_domain = value ? value : ""; + /* We need to copy this string so it doesn't get destroyed on next req */ + rq->digest_domain = ha_bufcpy(rq->conn_buf, value ? value : ""); } else if(strcasecmp(name, "Handler") == 0) @@ -1026,6 +1035,7 @@ static int httpauth_set(ha_request_t* rq, int ofd) static int httpauth_processor(int ifd, int ofd) { + ha_buffer_t cbuf; ha_buffer_t buf; ha_request_t rq; int result = -1; @@ -1036,13 +1046,18 @@ static int httpauth_processor(int ifd, int ofd) /* Initialize the memory buffers */ ha_bufinit(&buf); + ha_bufinit(&cbuf); memset(&rq, 0, sizeof(rq)); /* Set up some context stuff */ rq.digest_domain = ""; rq.buf = &buf; + rq.conn_buf = &cbuf; + ha_lock(NULL); + rq.id = g_unique++; + ha_unlock(NULL); if(httpauth_ready(&rq, ofd) == -1) { @@ -1079,7 +1094,7 @@ static int httpauth_processor(int ifd, int ofd) break; case REQTYPE_SET: - r = httpauth_set(&rq, ofd); + r = httpauth_set(&rq, &cbuf, ofd); break; case REQTYPE_QUIT: @@ -1121,6 +1136,7 @@ static int httpauth_processor(int ifd, int ofd) } finally: + ha_buffree(&cbuf); ha_buffree(&buf); return result; } diff --git a/daemon/httpauthd.h b/daemon/httpauthd.h index 680994e..1067d8d 100644 --- a/daemon/httpauthd.h +++ b/daemon/httpauthd.h @@ -164,9 +164,12 @@ typedef struct ha_request ha_context_t* context; const char* digest_domain; - /* The buffer in use */ + /* The buffer in use for the request */ ha_buffer_t* buf; + /* The buffer in use for the connection */ + ha_buffer_t* conn_buf; + int resp_code; /* The response code */ const char* resp_detail; /* The details for response */ ha_header_t resp_headers[HA_MAX_HEADERS]; /* Headers for the response */ diff --git a/daemon/ldap.c b/daemon/ldap.c index 1df7651..e3f6a5f 100644 --- a/daemon/ldap.c +++ b/daemon/ldap.c @@ -296,7 +296,7 @@ static int validate_ldap_password(const ha_request_t* rq, ldap_context_t* ctx, L const char* p; int type; int res = HA_FALSE; - int unknown = 0; + int foundany = 0; ASSERT(entry && ld && ctx && clearpw && rq); @@ -310,6 +310,11 @@ static int validate_ldap_password(const ha_request_t* rq, ldap_context_t* ctx, L pw = *t; type = parse_ldap_password(&pw); + if(type != LDAP_PW_UNKNOWN) + foundany = 1; + else + continue; + switch(type) { case LDAP_PW_CLEAR: @@ -323,7 +328,7 @@ static int validate_ldap_password(const ha_request_t* rq, ldap_context_t* ctx, L case LDAP_PW_CRYPT: /* Not sure if crypt is thread safe */ ha_lock(NULL); - p = crypt(clearpw, pw); + p = (const char*)crypt(clearpw, pw); ha_unlock(NULL); break; @@ -331,10 +336,6 @@ static int validate_ldap_password(const ha_request_t* rq, ldap_context_t* ctx, L p = make_password_sha(rq->buf, clearpw); break; - case LDAP_PW_UNKNOWN: - unknown = 1; - continue; - default: /* Not reached */ ASSERT(0); @@ -357,7 +358,7 @@ static int validate_ldap_password(const ha_request_t* rq, ldap_context_t* ctx, L ldap_value_free(pws); } - if(res == HA_FALSE && unknown) + if(res == HA_FALSE && !foundany) ha_messagex(rq, LOG_ERR, "server does not contain any compatible passwords for user: %s", user); return res; @@ -596,7 +597,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* LDAPMessage* results = NULL; /* freed in finally */ LDAPMessage* entry = NULL; /* no need to free */ struct berval** ha1s = NULL; /* freed manually */ - const char** pws; + const char** pws = NULL; /* freed in finally */ int ret = HA_FALSE; const char* dn = NULL; int r; @@ -606,10 +607,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* ld = get_ldap_connection(rq, ctx); if(!ld) - { - ret = HA_FAILED; - goto finally; - } + RETURN(HA_FAILED); /* * Discover the DN of the user. If there's a DN map string @@ -621,10 +619,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* /* The map can have %u and %r to denote user and realm */ dn = bd_substitute(rq, user, ctx->dnmap); if(!dn) - { - ret = HA_FAILED; - goto finally; - } + RETURN(HA_CRITERROR); ha_messagex(rq, LOG_INFO, "mapped %s to %s", user, dn); } @@ -632,10 +627,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* /* Okay now we contact the LDAP server. */ r = retrieve_user_entry(rq, ctx, ld, user, &dn, &entry, &results); if(r != HA_OK) - { - ret = r; - goto finally; - } + RETURN(r); /* Figure out the users ha1 */ if(ctx->ha1_attr) @@ -662,11 +654,11 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* ret = digest_complete_check(dg, rq->buf); if(ret != HA_FALSE) - goto finally; + RETURN(ret); } else if(r < 0) - goto finally; + RETURN(r); ha1s++; } @@ -691,7 +683,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* ret = digest_complete_check(dg, rq->buf); if(ret != HA_FALSE) - goto finally; + RETURN(ret); } } @@ -730,10 +722,7 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo ld = get_ldap_connection(rq, ctx); if(!ld) - { - ret = HA_FAILED; - goto finally; - } + RETURN(HA_FAILED); /* @@ -746,10 +735,7 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo /* The map can have %u and %r to denote user and realm */ dn = bd_substitute(rq, user, ctx->dnmap); if(!dn) - { - ret = HA_CRITERROR; - goto finally; - } + RETURN(HA_CRITERROR); ha_messagex(rq, LOG_INFO, "mapped %s to %s", user, dn); } @@ -774,10 +760,7 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo { r = retrieve_user_entry(rq, ctx, ld, user, &dn, &entry, &results); if(r != HA_OK) - { - ret = r; - goto finally; - } + RETURN(r); } @@ -790,13 +773,15 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo if(r != LDAP_SUCCESS) { if(r == LDAP_INVALID_CREDENTIALS) - ha_messagex(rq, LOG_WARNING, "basic authentication (via bind) failed for user: %s", - user); - + { + ha_messagex(rq, LOG_WARNING, "basic authentication (via bind) failed for user: %s", user); + RETURN(HA_FALSE); + } else + { report_ldap(rq, "couldn't bind to LDAP server", r); - - goto finally; + RETURN(HA_FAILED); + } } /* It worked! */ diff --git a/daemon/mysql.c b/daemon/mysql.c new file mode 100644 index 0000000..b6885b0 --- /dev/null +++ b/daemon/mysql.c @@ -0,0 +1,751 @@ + +#include "usuals.h" +#include "httpauthd.h" +#include "md5.h" +#include "sha1.h" +#include "bd.h" + +#include <sys/time.h> + +/* Mysql library */ +#include <mysql.h> +#include <errmsg.h> + +/* ------------------------------------------------------------------------------- + * Structures + */ + +#define DB_PW_CLEAR 0 +#define DB_PW_SHA1 1 +#define DB_PW_MD5 2 +#define DB_PW_CRYPT 3 + +/* Our hanler context */ +typedef struct mysql_context +{ + /* Base Handler ------------------------------------------------------ */ + bd_context_t bd; + + /* Settings ---------------------------------------------------------- */ + const char* host; /* The connection host or path */ + unsigned int port; /* The connection port */ + const char* user; /* The pgsql user name */ + const char* password; /* The pgsql password */ + const char* database; /* The database name */ + const char* query; /* The query */ + const char* pw_column; /* The database query to retrieve a password */ + int pw_type; /* The type of password encoded in database */ + const char* ha1_column; /* The database query to retrieve a ha1 */ + + int mysql_max; /* Number of open connections allowed */ + int mysql_timeout; /* Maximum amount of time to dedicate to a query */ + int use_unix_socket; + + /* Context ----------------------------------------------------------- */ + MYSQL** pool; /* Pool of available connections */ + int pool_mark; /* Amount of connections allocated */ +} +mysql_context_t; + +/* Forward declarations for callbacks */ +static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg); +static int validate_basic(ha_request_t* rq, const char* user, const char* password); +static void escape_mysql(const ha_request_t* rq, ha_buffer_t* buf, const char* value); + +/* The defaults for the context */ +static const mysql_context_t mysql_defaults = +{ + BD_CALLBACKS(validate_digest, + validate_basic, escape_mysql), + NULL, /* host */ + 0, /* port */ + NULL, /* user */ + NULL, /* password */ + NULL, /* database */ + NULL, /* query */ + NULL, /* pw_attr */ + DB_PW_CLEAR, /* pw_type */ + NULL, /* ha1_attr */ + 10, /* mysql_max */ + 30, /* mysql_timeout */ + 0, /* use_unix_socket */ + NULL, /* pool */ + 0 /* pool_mark */ +}; + + +/* ------------------------------------------------------------------------------- + * Internal Functions + */ + +static void escape_mysql(const ha_request_t* rq, ha_buffer_t* buf, const char* value) +{ + size_t len; + char* t; + + ASSERT(value); + + len = strlen(value); + + t = (char*)malloc((len * 2) + 1); + if(t != NULL) + { + mysql_escape_string(t, value, len); + ha_bufcpy(buf, t); + free(t); + } +} + +static int dec_mysql_binary(const ha_request_t* rq, const char* enc, + unsigned char* bytes, size_t len) +{ + size_t enclen; + void* d; + + ASSERT(rq && enc && bytes && len); + enclen = strlen(enc); + + /* Hex encoded */ + if(enclen == (len * 2)) + { + d = ha_bufdechex(rq->buf, enc, &enclen); + + if(d && enclen == len) + { + ha_messagex(rq, LOG_DEBUG, "found value in hex encoded format"); + memcpy(bytes, d, len); + return HA_OK; + } + } + + /* TODO: Does mysql have raw binary encoding? */ + + /* B64 Encoded */ + enclen = strlen(enc); + d = ha_bufdec64(rq->buf, enc, &enclen); + + if(d && len == enclen) + { + ha_messagex(rq, LOG_DEBUG, "found value in b64 encoded format"); + memcpy(bytes, d, len); + return HA_OK; + } + + return ha_buferr(rq->buf) ? HA_CRITERROR : HA_FALSE; +} + +static int validate_ha1(ha_request_t* rq, mysql_context_t* ctx, const char* user, + const char* clearpw, const char* dbpw) +{ + unsigned char dbha1[MD5_LEN]; + unsigned char ha1[MD5_LEN]; + const char* p; + + int r = dec_mysql_binary(rq, dbpw, dbha1, MD5_LEN); + + if(r < 0) + return r; + + if(r == HA_OK) + { + digest_makeha1(ha1, user, rq->context->realm, clearpw); + if(memcmp(ha1, dbha1, MD5_LEN) == 0) + return HA_OK; + } + + return HA_FALSE; +} + +static int validate_password(ha_request_t* rq, mysql_context_t* ctx, const char* user, + const char* clearpw, const char* dbpw) +{ + unsigned char buf[SHA1_LEN]; + const char* p; + int r; + + ASSERT(SHA1_LEN > MD5_LEN); + + switch(ctx->pw_type) + { + + /* Clear text */ + case DB_PW_CLEAR: + + if(strcmp(clearpw, dbpw) == 0) + { + ha_messagex(rq, LOG_DEBUG, "found matching clear text password"); + return HA_OK; + } + + break; + + /* Crypt pw */ + case DB_PW_CRYPT: + + ha_lock(NULL); + p = (const char*)crypt(clearpw, dbpw); + ha_unlock(NULL); + + if(p && strcmp(dbpw, p) == 0) + { + ha_messagex(rq, LOG_DEBUG, "found matching crypt password"); + return HA_OK; + } + + break; + + /* MD5 */ + case DB_PW_MD5: + + r = dec_mysql_binary(rq, dbpw, buf, MD5_LEN); + if(r < 0) return r; + + if(r == HA_OK && md5_strcmp(buf, clearpw) == 0) + { + ha_messagex(rq, LOG_DEBUG, "found matching md5 password"); + return HA_OK; + } + + break; + + + /* SHA1 */ + case DB_PW_SHA1: + + r = dec_mysql_binary(rq, dbpw, buf, SHA1_LEN); + if(r < 0) return r; + + if(r == HA_OK && sha1_strcmp(buf, clearpw) == 0) + { + ha_messagex(rq, LOG_DEBUG, "found matching sha1 password"); + return HA_OK; + } + + break; + + default: + ASSERT(0 && "invalid password type"); + break; + }; + + return HA_FALSE; +} + +static MYSQL* get_mysql_connection(const ha_request_t* rq, mysql_context_t* ctx) +{ + MYSQL* my; + MYSQL* r; + int i; + + ASSERT(ctx); + + for(i = 0; i < ctx->mysql_max; i++) + { + /* An open connection in the pool */ + if(ctx->pool[i]) + { + ha_messagex(rq, LOG_DEBUG, "using cached mysql connection"); + my = ctx->pool[i]; + ctx->pool[i] = NULL; + return my; + } + } + + if(ctx->pool_mark >= ctx->mysql_max) + { + ha_messagex(rq, LOG_ERR, "too many open mysql connections"); + return NULL; + } + + my = mysql_init(NULL); + if(!my) + { + ha_messagex(rq, LOG_ERR, "out of memory"); + return NULL; + } + + mysql_options(my, MYSQL_OPT_CONNECT_TIMEOUT, (char*)&(ctx->mysql_timeout)); + /* mysql_options(my, MYSQL_OPT_READ_TIMEOUT, (char*)&(ctx->mysql_timeout)); + mysql_options(my, MYSQL_OPT_WRITE_TIMEOUT, (char*)&(ctx->mysql_timeout)); */ + + /* Apparently mysql_real_connect is not thread safe :( */ + ha_lock(NULL); + r = mysql_real_connect(my, ctx->use_unix_socket ? NULL : ctx->host, + ctx->user, ctx->password, ctx->database, ctx->port, + ctx->use_unix_socket ? ctx->host : NULL, 0); + ha_unlock(NULL); + + if(!r) + { + ha_messagex(rq, LOG_ERR, "error opening mysql connection: %s", mysql_error(my)); + mysql_close(my); + return NULL; + } + + ctx->pool_mark++; + ha_messagex(rq, LOG_DEBUG, "opened new mysql connection (total %d)", ctx->pool_mark); + return my; +} + +static void discard_mysql_connection(const ha_request_t* rq, mysql_context_t* ctx, MYSQL* my) +{ + mysql_close(my); + ctx->pool_mark--; + ha_messagex(rq, LOG_DEBUG, "discarding mysql connection (total %d)", ctx->pool_mark); +} + +static void save_mysql_connection(const ha_request_t* rq, mysql_context_t* ctx, MYSQL* my) +{ + int i, e; + + ASSERT(ctx); + + if(!my) + return; + + switch(mysql_errno(my)) + { + case CR_SOCKET_CREATE_ERROR: + case CR_CONNECTION_ERROR: + case CR_CONN_HOST_ERROR: + case CR_IPSOCK_ERROR: + case CR_UNKNOWN_HOST: + case CR_SERVER_GONE_ERROR: + case CR_VERSION_ERROR: + case CR_WRONG_HOST_INFO: + case CR_LOCALHOST_CONNECTION: + case CR_TCP_CONNECTION: + case CR_SERVER_HANDSHAKE_ERR: + case CR_SERVER_LOST: + case CR_COMMANDS_OUT_OF_SYNC: + break; + + /* Make sure it's worth saving */ + default: + for(i = 0; i < ctx->mysql_max; i++) + { + /* An open connection in the pool */ + if(!ctx->pool[i]) + { + ha_messagex(rq, LOG_DEBUG, "caching mysql connection for later use"); + ctx->pool[i] = my; + my = NULL; + break; + } + } + break; + }; + + if(my != NULL) + discard_mysql_connection(rq, ctx, my); +} + +static int resolve_column(MYSQL_RES* res, const char* column) +{ + int i, fields; + + if(column) + { + fields = mysql_num_fields(res); + for(i = 0; i < fields; i++) + { + if(strcasecmp(column, mysql_fetch_field_direct(res, i)->name) == 0) + return i; + } + } + + return -1; +} + +static int retrieve_user_rows(ha_request_t* rq, mysql_context_t* ctx, + const char* user, MYSQL_RES** results) +{ + MYSQL* my = NULL; + MYSQL_RES* res = NULL; + const char* query; + int ret = HA_OK; + + ASSERT(rq && ctx && user && results); + *results = NULL; + + my = get_mysql_connection(rq, ctx); + if(!my) + RETURN(HA_FAILED); + + ASSERT(ctx->query); + + /* The map can have %u and %r to denote user and realm */ + query = bd_substitute(rq, user, ctx->query); + if(!query) + RETURN(HA_CRITERROR); + + ha_messagex(rq, LOG_DEBUG, "executing query: %s", query); + if(mysql_query(my, query) != 0) + { + ha_messagex(rq, LOG_ERR, "error querying database: %s", mysql_error(my)); + RETURN(HA_FAILED); + } + + res = mysql_store_result(my); + if(!res) + { + if(mysql_field_count(my) == 0) + ha_messagex(rq, LOG_ERR, "mysql query didn't return results: %s", query); + else + ha_messagex(rq, LOG_ERR, "error querying database: %s", mysql_error(my)); + + RETURN(HA_FAILED); + } + + if(mysql_num_rows(res) == 0) + { + ha_messagex(rq, LOG_WARNING, "login failed. couldn't find user: %s", user); + RETURN(HA_FALSE); + } + + ha_messagex(rq, LOG_DEBUG, "received %d result rows", mysql_num_rows(res)); + *results = res; + res = NULL; + +finally: + + if(res != NULL) + mysql_free_result(res); + + /* TODO: Look into what happens if we free a mysql connection + * before processing results: */ + if(my != NULL) + save_mysql_connection(rq, ctx, my); + + return ret; +} + +static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* dg) +{ + mysql_context_t* ctx = (mysql_context_t*)rq->context->ctx_data; + MYSQL_RES* res = NULL; + MYSQL_ROW row; + int ret = HA_FALSE; + int pw_column = -1; + int ha1_column = -1; + int r, i, foundany = 0; + const char* v; + + ASSERT(rq && user && dg); + + ret = retrieve_user_rows(rq, ctx, user, &res); + if(ret != HA_OK) + RETURN(ret); + + ASSERT(res); + + pw_column = resolve_column(res, ctx->pw_column); + ha1_column = resolve_column(res, ctx->ha1_column); + + if(pw_column == -1 && ha1_column == -1) + { + if(mysql_num_fields(res) > 1) + ha_messagex(rq, LOG_WARNING, "query returned more than 1 column, using first as password"); + + pw_column = 0; + } + + while((row = mysql_fetch_row(res)) != NULL) + { + if(pw_column != -1 && ctx->pw_type == DB_PW_CLEAR) + { + v = *(row + pw_column); + if(v != NULL) + { + foundany = 1; + + digest_makeha1(dg->ha1, user, rq->context->realm, v); + ha_messagex(rq, LOG_DEBUG, "testing clear text password for digest auth"); + + /* Run the actual check */ + ret = digest_complete_check(dg, rq->buf); + + if(ret != HA_FALSE) + RETURN(ret); + } + } + + if(ha1_column != -1) + { + v = *(row + ha1_column); + if(v != NULL) + { + ret = dec_mysql_binary(rq, v, dg->ha1, MD5_LEN); + if(ret < 0) + RETURN(ret) + else if(ret == HA_FALSE) + continue; + + foundany = 1; + + /* Run the actual check */ + ret = digest_complete_check(dg, rq->buf); + + if(ret != HA_FALSE) + RETURN(ret); + } + } + } + + if(!foundany) + ha_messagex(rq, LOG_WARNING, "no clear password or ha1 present for user: %s", user); + +finally: + if(res) + mysql_free_result(res); + + return ret; +} + +static int validate_basic(ha_request_t* rq, const char* user, const char* password) +{ + mysql_context_t* ctx = (mysql_context_t*)rq->context->ctx_data; + MYSQL_RES* res = NULL; + MYSQL_ROW row; + int ret = HA_FALSE; + int pw_column = -1; + int ha1_column = -1; + int i, foundany = 0; + const char* v; + + ASSERT(rq && user && password); + + ret = retrieve_user_rows(rq, ctx, user, &res); + if(ret != HA_OK) + RETURN(ret); + + ASSERT(res); + + pw_column = resolve_column(res, ctx->pw_column); + ha1_column = resolve_column(res, ctx->ha1_column); + + if(pw_column == -1 && ha1_column == -1) + { + if(mysql_num_fields(res) > 1) + ha_messagex(rq, LOG_WARNING, "query returned more than 1 column, using first as password"); + pw_column = 0; + } + + + while((row = mysql_fetch_row(res)) != NULL) + { + if(pw_column != -1) + { + v = *(row + pw_column); + if(v != NULL) + + { + foundany = 1; + ret = validate_password(rq, ctx, user, password, v); + if(ret != HA_FALSE) + RETURN(ret); + } + } + + if(ha1_column != -1) + { + v = *(row + ha1_column); + if(v != NULL) + { + foundany = 1; + ret = validate_ha1(rq, ctx, user, password, v); + if(ret != HA_FALSE) + RETURN(ret); + } + } + } + + if(!foundany) + ha_messagex(rq, LOG_WARNING, "no password present for user: %s", user); + +finally: + if(res) + mysql_free_result(res); + + return ret; +} + + +/* ------------------------------------------------------------------------------- + * Handler Functions + */ + +int mysql_config(ha_context_t* context, const char* name, const char* value) +{ + mysql_context_t* ctx = (mysql_context_t*)(context->ctx_data); + + ASSERT(name && value && value[0]); + + if(strcmp(name, "dbserver") == 0) + { + ctx->use_unix_socket = (value[0] == '/'); + ctx->host = value; + return HA_OK; + } + + if(strcmp(name, "dbport") == 0) + { + return ha_confint(name, value, 0, 65535, &(ctx->port)); + } + + if(strcmp(name, "dbuser") == 0) + { + ctx->user = value; + return HA_OK; + } + + if(strcmp(name, "dbpassword") == 0) + { + ctx->password = value; + return HA_OK; + } + + if(strcmp(name, "dbdatabase") == 0) + { + ctx->database = value; + return HA_OK; + } + + if(strcmp(name, "dbquery") == 0) + { + ctx->query = value; + return HA_OK; + } + + if(strcmp(name, "dbpwcolumn") == 0) + { + ctx->pw_column = value; + return HA_OK; + } + + if(strcmp(name, "dbpwtype") == 0) + { + if(strcasecmp(value, "clear") == 0) + ctx->pw_type = DB_PW_CLEAR; + else if(strcasecmp(value, "crypt") == 0) + ctx->pw_type = DB_PW_CRYPT; + else if(strcasecmp(value, "md5") == 0) + ctx->pw_type = DB_PW_MD5; + else if(strcasecmp(value, "sha1") == 0) + ctx->pw_type = DB_PW_SHA1; + else + { + ha_messagex(NULL, LOG_ERR, "invalid value for '%s' (must be 'clear', 'crypt', 'md5' or 'sha1')", name); + return HA_FAILED; + } + + return HA_OK; + } + + if(strcmp(name, "dbha1column") == 0) + { + ctx->ha1_column = value; + return HA_OK; + } + + else if(strcmp(name, "dbmax") == 0) + { + return ha_confint(name, value, 1, 256, &(ctx->mysql_max)); + } + + else if(strcmp(name, "dbtimeout") == 0) + { + /* TODO: Implement database timeouts */ + return ha_confint(name, value, 0, 86400, &(ctx->mysql_timeout)); + } + + return HA_FALSE; +} + +int mysql_initialize(ha_context_t* context) +{ + int r; + + if((r = bd_init(context)) != HA_OK) + return r; + + /* Context specific initialization */ + if(context) + { + mysql_context_t* ctx = (mysql_context_t*)(context->ctx_data); + ASSERT(ctx); + + /* Check for mandatory configuration */ + if(!ctx->database || !ctx->query) + { + ha_messagex(NULL, LOG_ERR, "mysql configuration incomplete. " + "Must have DBDatabase and DBQuery."); + return HA_FAILED; + } + + ASSERT(!ctx->pool); + ASSERT(ctx->mysql_max > 0); + + /* + * Our connection pool. It's the size of our maximum + * amount of pending connections as that's the max + * we'd be able to use at a time anyway. + */ + ctx->pool = (MYSQL**)malloc(sizeof(MYSQL*) * ctx->mysql_max); + if(!ctx->pool) + { + ha_messagex(NULL, LOG_CRIT, "out of memory"); + return HA_CRITERROR; + } + + memset(ctx->pool, 0, sizeof(MYSQL*) * ctx->mysql_max); + ha_messagex(NULL, LOG_INFO, "initialized mysql handler"); + } + + return HA_OK; +} + +void mysql_destroy(ha_context_t* context) +{ + if(context) + { + /* Note: We don't need to be thread safe here anymore */ + mysql_context_t* ctx = (mysql_context_t*)(context->ctx_data); + int i; + + ASSERT(ctx); + + if(ctx->pool) + { + /* Close any connections we have open */ + for(i = 0; i < ctx->mysql_max; i++) + { + if(ctx->pool[i]) + mysql_close(ctx->pool[i]); + } + + /* And free the connection pool */ + free(ctx->pool); + } + } + + bd_destroy(context); + ha_messagex(NULL, LOG_INFO, "uninitialized mysql handler"); +} + + + +/* ------------------------------------------------------------------------------- + * Handler Definition + */ + +ha_handler_t mysql_handler = +{ + "MYSQL", /* The type */ + mysql_initialize, /* Initialization function */ + mysql_destroy, /* Uninitialization routine */ + mysql_config, /* Config routine */ + bd_process, /* Processing routine */ + &mysql_defaults, /* The context defaults */ + sizeof(mysql_context_t) +}; diff --git a/daemon/ntlm.c b/daemon/ntlm.c index 63d4580..ab6e5e4 100644 --- a/daemon/ntlm.c +++ b/daemon/ntlm.c @@ -310,14 +310,14 @@ int ntlm_auth_ntlm(ha_request_t* rq, ntlm_context_t* ctx, void* key, d = ha_bufdec64(rq->buf, header, &len); if(!d || len == 0) - goto finally; + RETURN(HA_FALSE); r = ntlmssp_decode_msg(&ntlmssp, d, len, &flags); if(r != 0) { ha_messagex(rq, LOG_WARNING, "decoding NTLMSSP message failed (error %d)", r); rq->resp_code = HA_SERVER_BADREQ; - goto finally; + RETURN(HA_FALSE); } @@ -350,16 +350,13 @@ int ntlm_auth_ntlm(ha_request_t* rq, ntlm_context_t* ctx, void* key, conn = NULL; if(r < 0) - { - ret = HA_CRITERROR; - } + RETURN(HA_CRITERROR); else { ha_messagex(rq, LOG_ERR, "received out of order NTLM request from client"); rq->resp_code = HA_SERVER_BADREQ; + RETURN(HA_FALSE); } - - goto finally; } @@ -386,10 +383,7 @@ int ntlm_auth_ntlm(ha_request_t* rq, ntlm_context_t* ctx, void* key, conn = makeconnection(rq, ctx); if(!conn) - { - ret = HA_FAILED; - goto finally; - } + RETURN(HA_FAILED); /* Save away any flags given us by ntlm_decode_msg */ conn->flags = flags; @@ -413,7 +407,7 @@ int ntlm_auth_ntlm(ha_request_t* rq, ntlm_context_t* ctx, void* key, } if(ha_buferr(rq->buf)) - goto finally; + RETURN(HA_FALSE); /* * TODO: Our callers need to be able to keep alive @@ -431,16 +425,14 @@ int ntlm_auth_ntlm(ha_request_t* rq, ntlm_context_t* ctx, void* key, conn = NULL; if(r < 0) - { - ret = HA_CRITERROR; - } + RETURN(HA_CRITERROR); else { ha_messagex(rq, LOG_DEBUG, "sending ntlm challenge"); ha_addheader(rq, "WWW-Authenticate", ha_bufdata(rq->buf)); rq->resp_code = HA_SERVER_DECLINE; + RETURN(HA_FALSE); } - goto finally; } /* A response to a challenge */ @@ -457,14 +449,14 @@ int ntlm_auth_ntlm(ha_request_t* rq, ntlm_context_t* ctx, void* key, { ha_messagex(rq, LOG_WARNING, "received out of order NTLM response from client"); rq->resp_code = HA_SERVER_BADREQ; - goto finally; + RETURN(HA_FALSE); } if(!ntlmssp.user) { ha_messagex(rq, LOG_WARNING, "received NTLM response without user name"); rq->resp_code = HA_SERVER_BADREQ; - goto finally; + RETURN(HA_FALSE); } /* We have to lock while going into smblib */ @@ -485,7 +477,7 @@ int ntlm_auth_ntlm(ha_request_t* rq, ntlm_context_t* ctx, void* key, * caller to put in all the proper headers for us. */ ha_messagex(rq, LOG_WARNING, "failed NTLM logon for user '%s'", ntlmssp.user); - ret = HA_FALSE; + RETURN(HA_FALSE); } /* A successful login ends here */ @@ -505,21 +497,18 @@ int ntlm_auth_ntlm(ha_request_t* rq, ntlm_context_t* ctx, void* key, if(!r) { ha_messagex(NULL, LOG_CRIT, "out of memory"); - ret = HA_CRITERROR; - } - else - { - ret = HA_OK; + RETURN(HA_CRITERROR); } - } - goto finally; + RETURN(HA_OK); + } } + break; default: ha_messagex(rq, LOG_WARNING, "received invalid NTLM message (type %d)", ntlmssp.msg_type); rq->resp_code = HA_SERVER_BADREQ; - goto finally; + RETURN(HA_FALSE); }; diff --git a/daemon/pgsql.c b/daemon/pgsql.c index 225bc65..6ed5658 100644 --- a/daemon/pgsql.c +++ b/daemon/pgsql.c @@ -7,7 +7,7 @@ #include <sys/time.h> -/* LDAP library */ +/* Postgresql library */ #include <libpq-fe.h> /* ------------------------------------------------------------------------------- @@ -37,7 +37,7 @@ typedef struct pgsql_context const char* ha1_column; /* The database query to retrieve a ha1 */ int pgsql_max; /* Number of open connections allowed */ - int pgsql_timeout; /* Maximum amount of time to dedicate to an ldap query */ + int pgsql_timeout; /* Maximum amount of time to dedicate to a query */ /* Context ----------------------------------------------------------- */ PGconn** pool; /* Pool of available connections */ @@ -84,11 +84,12 @@ static void escape_pgsql(const ha_request_t* rq, ha_buffer_t* buf, const char* v len = strlen(value); - /* Bit of a hack, we copy the string in twice to give enough room. */ - if((t = (char*)ha_bufmalloc(buf, (len * 2) + 1)) != NULL) + t = (char*)malloc((len * 2) + 1); + if(t != NULL) { PQescapeString(t, value, len); ha_bufcpy(buf, t); + free(t); } } @@ -192,11 +193,11 @@ static int validate_password(ha_request_t* rq, pgsql_context_t* ctx, const char* /* Crypt pw */ case DB_PW_CRYPT: - ha_lock(); - p = crypt(clearpw, dbpw); - ha_unlock(); + ha_lock(NULL); + p = (const char*)crypt(clearpw, dbpw); + ha_unlock(NULL); - if(p && strcmp(clearpw, p) == 0) + if(p && strcmp(dbpw, p) == 0) { ha_messagex(rq, LOG_DEBUG, "found matching crypt password"); return HA_OK; @@ -336,11 +337,11 @@ static int check_pgsql_result(ha_request_t* rq, PGresult* res) ha_messagex(rq, LOG_ERR, "error communicating with pgsql server"); return HA_FAILED; case PGRES_NONFATAL_ERROR: + ha_messagex(rq, LOG_ERR, "warning querying database: %s", PQresultErrorMessage(res)); + return HA_OK; + case PGRES_FATAL_ERROR: ha_messagex(rq, LOG_ERR, "error querying database: %s", PQresultErrorMessage(res)); return HA_FAILED; - case PGRES_FATAL_ERROR: - ha_messagex(rq, LOG_CRIT, "internal error in postgres library"); - return HA_CRITERROR; case PGRES_COPY_OUT: case PGRES_COPY_IN: default: @@ -375,25 +376,19 @@ static int retrieve_user_rows(ha_request_t* rq, pgsql_context_t* ctx, const char* query; int ret = HA_OK; - ASSERT(rq && ctx && user && res); + ASSERT(rq && ctx && user && results); *results = NULL; pg = get_pgsql_connection(rq, ctx); if(!pg) - { - ret = HA_FAILED; - goto finally; - } + RETURN(HA_FAILED); ASSERT(ctx->query); /* The map can have %u and %r to denote user and realm */ query = bd_substitute(rq, user, ctx->query); if(!query) - { - ret = HA_CRITERROR; - goto finally; - } + RETURN(HA_CRITERROR); ha_messagex(rq, LOG_DEBUG, "executing query: %s", query); res = PQexec(pg, query); @@ -401,27 +396,29 @@ static int retrieve_user_rows(ha_request_t* rq, pgsql_context_t* ctx, ret = check_pgsql_result(rq, res); if(ret != HA_OK) - goto finally; + RETURN(ret); if(PQntuples(res) == 0) { ha_messagex(rq, LOG_WARNING, "login failed. couldn't find user: %s", user); - ret = HA_FALSE; - goto finally; + RETURN(HA_FALSE); } if(PQnfields(res) <= 0) { ha_messagex(rq, LOG_ERR, "query returned 0 columns: %s", query); - ret = HA_FAILED; - goto finally; + RETURN(HA_FAILED); } *results = res; + res = NULL; ha_messagex(rq, LOG_DEBUG, "received %d result rows", PQntuples(res)); finally: + if(res != NULL) + PQclear(res); + /* According to libpg we can close/save the connection * before the returned results are freed, no worries there */ if(pg != NULL) @@ -443,7 +440,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* ret = retrieve_user_rows(rq, ctx, user, &res); if(ret != HA_OK) - goto finally; + RETURN(ret); ASSERT(res); @@ -467,12 +464,13 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* foundany = 1; digest_makeha1(dg->ha1, user, rq->context->realm, PQgetvalue(res, i, pw_column)); + ha_messagex(rq, LOG_DEBUG, "testing clear text password for digest auth"); /* Run the actual check */ ret = digest_complete_check(dg, rq->buf); if(ret != HA_FALSE) - goto finally; + RETURN(ret); } } @@ -482,7 +480,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* { ret = dec_pgsql_binary(rq, PQgetvalue(res, i, ha1_column), dg->ha1, MD5_LEN); if(ret < 0) - goto finally; + RETURN(ret) else if(ret == HA_FALSE) continue; @@ -492,7 +490,7 @@ static int validate_digest(ha_request_t* rq, const char* user, digest_context_t* ret = digest_complete_check(dg, rq->buf); if(ret != HA_FALSE) - goto finally; + RETURN(ret); } } } @@ -520,7 +518,7 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo ret = retrieve_user_rows(rq, ctx, user, &res); if(ret != HA_OK) - goto finally; + RETURN(ret); ASSERT(res); @@ -544,7 +542,7 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo foundany = 1; ret = validate_password(rq, ctx, user, password, PQgetvalue(res, i, pw_column)); if(ret != HA_FALSE) - goto finally; + RETURN(ret); } } @@ -555,7 +553,7 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo foundany = 1; ret = validate_ha1(rq, ctx, user, password, PQgetvalue(res, i, ha1_column)); if(ret != HA_FALSE) - goto finally; + RETURN(ret); } } } @@ -625,19 +623,21 @@ int pgsql_config(ha_context_t* context, const char* name, const char* value) if(strcmp(name, "dbpwtype") == 0) { - if(strcmp(value, "clear") == 0) + if(strcasecmp(value, "clear") == 0) ctx->pw_type = DB_PW_CLEAR; - else if(strcmp(value, "crypt") == 0) + else if(strcasecmp(value, "crypt") == 0) ctx->pw_type = DB_PW_CRYPT; - else if(strcmp(value, "md5") == 0) + else if(strcasecmp(value, "md5") == 0) ctx->pw_type = DB_PW_MD5; - else if(strcmp(value, "sha1") == 0) + else if(strcasecmp(value, "sha1") == 0) ctx->pw_type = DB_PW_SHA1; else { ha_messagex(NULL, LOG_ERR, "invalid value for '%s' (must be 'clear', 'crypt', 'md5' or 'sha1')", name); return HA_FAILED; } + + return HA_OK; } if(strcmp(name, "dbha1column") == 0) @@ -676,7 +676,7 @@ int pgsql_init(ha_context_t* context) /* Check for mandatory configuration */ if(!ctx->database || !ctx->query) { - ha_messagex(NULL, LOG_ERR, "configuration incomplete. " + ha_messagex(NULL, LOG_ERR, "pgsql configuration incomplete. " "Must have DBDatabase and DBQuery."); return HA_FAILED; } diff --git a/daemon/simple.c b/daemon/simple.c index 8e4f8df..fca0e28 100644 --- a/daemon/simple.c +++ b/daemon/simple.c @@ -208,7 +208,7 @@ static int validate_basic(ha_request_t* rq, const char* user, const char* passwo ha_lock(NULL); /* Check the password */ - t2 = crypt(password, t); + t2 = (const char*)crypt(password, t); ha_unlock(NULL); @@ -326,7 +326,7 @@ ha_handler_t simple_handler = bd_destroy, /* Uninitialization routine */ simple_config, /* Config routine */ bd_process, /* Processing routine */ - NULL, /* A default context */ + &simple_defaults, /* A default context */ sizeof(simple_context_t) /* Size of the context */ }; diff --git a/daemon/usuals.h b/daemon/usuals.h index 97d355b..020db4a 100644 --- a/daemon/usuals.h +++ b/daemon/usuals.h @@ -28,6 +28,8 @@ #define countof(x) (sizeof(x) / sizeof(x[0])) +#define RETURN(x) { ret = (x); goto finally; } + #ifdef _DEBUG #include "assert.h" #define ASSERT assert |