/* * jabberd - Jabber Open Source Server * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney, * Ryan Eatmon, Robert Norris * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA */ /* this plugin authenticates against an LDAP directory by attempting to bind * as the user. It won't store or retrieve any actual data, so only the * plaintext mechanism is available. * * !!! this doesn't do any caching. It really should. * * !!! this blocks for every auth. We're stuck with this until authreg can * return a pending state. The timeout helps, but its still icky. */ #include "c2s.h" #include #include #define AR_LDAP_FLAGS_NONE (0x0) #define AR_LDAP_FLAGS_STARTTLS (0x1) #define AR_LDAP_FLAGS_SSL (0x2) #define AR_LDAP_FLAGS_V3 (0x4) #define AR_LDAP_FLAGS_RECONNECT (0x8) #define AR_LDAP_FLAGS_DISABLE_REFERRALS (0x10) #define AR_LDAP_FLAGS_APPEND_REALM (0x20) /** internal structure, holds our data */ typedef struct moddata_st { authreg_t ar; LDAP *ld; char *host; long port; int flags; int timeout; char *binddn; char *bindpw; char *uidattr; xht basedn; char *default_basedn; } *moddata_t; /** utility function to get ld_errno */ static int _ldap_get_lderrno(LDAP *ld) { int ld_errno; ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &ld_errno); return ld_errno; } /** entry-point function for following referrals, required in some cases by Active Directory */ static int rebindProc(LDAP *ld, LDAP_CONST char *url, ber_tag_t request, ber_int_t msgid, void *mdata) { moddata_t data = mdata; data->ld = ld; if(ldap_simple_bind_s(data->ld, data->binddn, data->bindpw)) { log_write(data->ar->c2s->log, LOG_ERR, "ldap: bind failed (to %s): %s", url, ldap_err2string(_ldap_get_lderrno(data->ld))); ldap_unbind_s(data->ld); data->ld = NULL; return NULL; } return LDAP_SUCCESS; } /** connect to the ldap host */ static int _ldap_connect(moddata_t data) { char url[1024]; int version = (data->flags & AR_LDAP_FLAGS_V3) ? 3 : 2; struct timeval timeout = {data->timeout,0}; /* ssl "wrappermode" */ if(data->flags & AR_LDAP_FLAGS_SSL) { snprintf(url, sizeof(url), "ldaps://%s:%d", data->host, data->port); ldap_initialize(&data->ld, url); } /* non-SSL connect method */ else data->ld = ldap_init(data->host, data->port); if(data->ld != NULL) { /* explicitly set ldap version for all connections */ if(ldap_set_option(data->ld, LDAP_OPT_PROTOCOL_VERSION, &version)) { log_write(data->ar->c2s->log, LOG_ERR, "ldap: couldn't use version %d: %s", version, ldap_err2string(_ldap_get_lderrno(data->ld))); ldap_unbind_s(data->ld); data->ld = NULL; return 1; } /* starttls */ if(data->flags & AR_LDAP_FLAGS_STARTTLS) { if(ldap_start_tls_s(data->ld, NULL, NULL)) { log_write(data->ar->c2s->log, LOG_ERR, "ldap: couldn't start TLS: %s", ldap_err2string(_ldap_get_lderrno(data->ld))); ldap_unbind_s(data->ld); data->ld = NULL; return 1; } } /* referrals */ if(data->flags & AR_LDAP_FLAGS_DISABLE_REFERRALS) { if( ldap_set_option( data->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF ) != LDAP_OPT_SUCCESS ) { log_write(data->ar->c2s->log, LOG_ERR, "ldap: couldn't set Referrals Off: %s", ldap_err2string(_ldap_get_lderrno(data->ld))); ldap_unbind_s(data->ld); data->ld = NULL; return 1; } } /* timeout */ if( ldap_set_option( data->ld, LDAP_OPT_NETWORK_TIMEOUT, &timeout ) != LDAP_OPT_SUCCESS || ldap_set_option( data->ld, LDAP_OPT_TIMEOUT, &timeout ) != LDAP_OPT_SUCCESS ) { log_write(data->ar->c2s->log, LOG_ERR, "ldap: couldn't set Timeout: %s", ldap_err2string(_ldap_get_lderrno(data->ld))); ldap_unbind_s(data->ld); data->ld = NULL; return 1; } } else { log_write(data->ar->c2s->log, LOG_ERR, "ldap: connect to server at %s:%d failed", data->host, data->port); return 1; } return 0; } /** Reconnect */ static int _ldap_reconnect(moddata_t data) { if (data->ld != NULL) { ldap_unbind_s(data->ld); data->ld = NULL; } return (_ldap_connect(data)); } /** do a search, return the dn */ static char *_ldap_search(moddata_t data, char *realm, char *username) { char filter[1024], *dn, *no_attrs[] = { NULL }, *basedn; LDAPMessage *result, *entry; basedn = xhash_get(data->basedn, realm); if(basedn == NULL) basedn = data->default_basedn; if(basedn == NULL) { log_write(data->ar->c2s->log, LOG_ERR, "ldap: no basedn specified for realm '%s'", realm); ldap_unbind_s(data->ld); data->ld = NULL; return NULL; } if (data->flags & AR_LDAP_FLAGS_RECONNECT) { if (_ldap_reconnect(data)) { log_write(data->ar->c2s->log, LOG_ERR, "ldap: reconnect failed: %s realm: %s basedn: %s binddn: %s pass: %s", ldap_err2string(_ldap_get_lderrno(data->ld)), realm, basedn, data->binddn, data->bindpw ); return NULL; } } if(ldap_simple_bind_s(data->ld, data->binddn, data->bindpw) && (_ldap_connect(data) || ldap_simple_bind_s(data->ld, data->binddn, data->bindpw))) { log_write(data->ar->c2s->log, LOG_ERR, "ldap: bind failed: %s realm: %s basedn: %s binddn: %s pass: %s", ldap_err2string(_ldap_get_lderrno(data->ld)), realm, basedn, data->binddn, data->bindpw ); ldap_unbind_s(data->ld); data->ld = NULL; return NULL; } if (data->flags & AR_LDAP_FLAGS_APPEND_REALM) { snprintf(filter, 1024, "(%s=%s@%s)", data->uidattr, username, realm); } else { snprintf(filter, 1024, "(%s=%s)", data->uidattr, username); } if(ldap_set_rebind_proc(data->ld, &rebindProc, data)) { log_write(data->ar->c2s->log, LOG_ERR, "ldap: set_rebind_proc failed: %s", ldap_err2string(_ldap_get_lderrno(data->ld))); ldap_unbind_s(data->ld); data->ld = NULL; return NULL; } if(ldap_search_s(data->ld, basedn, LDAP_SCOPE_SUBTREE, filter, no_attrs, 0, &result)) { log_write(data->ar->c2s->log, LOG_ERR, "ldap: search %s failed: %s", filter, ldap_err2string(_ldap_get_lderrno(data->ld))); ldap_unbind_s(data->ld); data->ld = NULL; return NULL; } entry = ldap_first_entry(data->ld, result); if(entry == NULL) { ldap_msgfree(result); return NULL; } dn = ldap_get_dn(data->ld, entry); ldap_msgfree(result); log_debug(ZONE, "got dn '%s' from realm '%s', user '%s'", dn, realm, username); return dn; } /** do we have this user? */ static int _ldap_user_exists(authreg_t ar, char *username, char *realm) { char *dn; moddata_t data; if(xhash_iter_first((xht) ar->private)) do { xhash_iter_get((xht) ar->private, NULL, (void *) &data); if( ! (data->ld == NULL && _ldap_connect(data)) ) { dn = _ldap_search(data, realm, username); if (dn != NULL) { ldap_memfree(dn); return 1; } } } while(xhash_iter_next((xht) ar->private)); return 0; } /** check the password */ static int _ldap_check_password(authreg_t ar, char *username, char *realm, char password[257]) { moddata_t data; char *dn; if(password[0] == '\0') return 1; if(xhash_iter_first((xht) ar->private)) do { xhash_iter_get((xht) ar->private, NULL, (void *) &data); if( ! (data->ld == NULL && _ldap_connect(data)) ) { dn = _ldap_search(data, realm, username); if (dn != NULL) { if(ldap_simple_bind_s(data->ld, dn, password) ) { if(_ldap_get_lderrno(data->ld) != LDAP_INVALID_CREDENTIALS) { log_write(data->ar->c2s->log, LOG_ERR, "ldap: bind as '%s' on host '%s' failed: %s", dn,data->host, ldap_err2string(_ldap_get_lderrno(data->ld))); ldap_unbind_s(data->ld); data->ld = NULL; } ldap_memfree(dn); } else { ldap_memfree(dn); return 0; } } } } while(xhash_iter_next((xht) ar->private)); return 1; } /** shut me down */ static void _ldap_free(authreg_t ar) { moddata_t data; if(xhash_iter_first((xht) ar->private)) do { xhash_iter_get((xht) ar->private, NULL, (void *) &data); if(data->ld != NULL) ldap_unbind_s(data->ld); xhash_free(data->basedn); free(data); } while(xhash_iter_next((xht) ar->private)); xhash_free((xht) ar->private); return; } /** start me up */ int ar_init(authreg_t ar) { moddata_t data; char ldap_entry[128]; char *host, *realm,*priority; config_elem_t basedn; int i,l=0; xht domains; domains = xhash_new(17); ldap_entry[15]='\0'; /* while we have more ldap entries*/ do { if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.host", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.host"); host = config_get_one(ar->c2s->config, ldap_entry, 0); if(host == NULL) { log_write(ar->c2s->log, LOG_ERR, "ldap: no host specified in config file"); return 1; } if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.basedn", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.basedn"); basedn = config_get(ar->c2s->config, ldap_entry); if(basedn == NULL) { log_write(ar->c2s->log, LOG_ERR, "ldap: no basedns specified in config file"); return 1; } data = (moddata_t) malloc(sizeof(struct moddata_st)); memset(data, 0, sizeof(struct moddata_st)); data->basedn = xhash_new(101); for(i = 0; i < basedn->nvalues; i++) { realm = (basedn->attrs[i] != NULL) ? j_attr((const char **) basedn->attrs[i], "realm") : NULL; if(realm == NULL) data->default_basedn = basedn->values[i]; else xhash_put(data->basedn, realm, basedn->values[i]); log_debug(ZONE, "realm '%s' has base dn '%s'", realm, basedn->values[i]); } log_write(ar->c2s->log, LOG_NOTICE, "ldap: configured %d realms", i); data->host = host; if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.port", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.port"); data->port = j_atoi(config_get_one(ar->c2s->config, ldap_entry, 0), 389); if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.timeout", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.timeout"); data->timeout = j_atoi(config_get_one(ar->c2s->config, ldap_entry, 0), 5); data->flags = AR_LDAP_FLAGS_NONE; if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.reconnect", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.reconnect"); if(config_get(ar->c2s->config, ldap_entry) != NULL) data->flags |= AR_LDAP_FLAGS_RECONNECT; if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.v3", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.v3"); if(config_get(ar->c2s->config, ldap_entry) != NULL) data->flags |= AR_LDAP_FLAGS_V3; if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.startls", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.startls"); if(config_get(ar->c2s->config, ldap_entry) != NULL) data->flags |= AR_LDAP_FLAGS_STARTTLS; if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.ssl", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.ssl"); if(config_get(ar->c2s->config, ldap_entry) != NULL) data->flags |= AR_LDAP_FLAGS_SSL; if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.disablereferrals", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.disablereferrals"); if(config_get(ar->c2s->config, ldap_entry) != NULL) data->flags |= AR_LDAP_FLAGS_DISABLE_REFERRALS; if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.append-realm", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.append-realm"); if(config_get(ar->c2s->config, ldap_entry) != NULL) data->flags |= AR_LDAP_FLAGS_APPEND_REALM; if((data->flags & AR_LDAP_FLAGS_STARTTLS) && (data->flags & AR_LDAP_FLAGS_SSL)) { log_write(ar->c2s->log, LOG_ERR, "ldap: not possible to use both SSL and starttls"); return 1; } if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.binddn", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.binddn"); data->binddn = config_get_one(ar->c2s->config, ldap_entry, 0); if(data->binddn != NULL) { if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.bindpw", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.bindpw"); data->bindpw = config_get_one(ar->c2s->config, ldap_entry, 0); } if (l>0) snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d.uidattr", l ); else snprintf(ldap_entry, sizeof(ldap_entry), "authreg.ldap.uidattr"); data->uidattr = config_get_one(ar->c2s->config, ldap_entry, 0); if(data->uidattr == NULL) data->uidattr = "uid"; data->ar = ar; if(_ldap_connect(data)) { xhash_free(data->basedn); free(data); return 1; } xhash_put(domains, data->host, data); l++; snprintf(ldap_entry,sizeof(ldap_entry), "authreg.ldap%d", l ); } while ( config_count(ar->c2s->config, ldap_entry) > 0 ); ar->private = domains; ar->user_exists = _ldap_user_exists; ar->check_password = _ldap_check_password; ar->free = _ldap_free; return 0; }