X-Git-Url: https://git.hcoop.net/hcoop/debian/courier-authlib.git/blobdiff_plain/4bc1ba6f5ecc4e62b1cd99eec25a9a0f510a3d70..c4b6c7dec3b65316d62340fbd218f1ac73cbdcd2:/authldaplib.cpp diff --git a/authldaplib.cpp b/authldaplib.cpp new file mode 100644 index 0000000..d5b71a7 --- /dev/null +++ b/authldaplib.cpp @@ -0,0 +1,1660 @@ +/* +** Copyright 2016 Double Precision, Inc. See COPYING for +** distribution information. +*/ + +#if HAVE_CONFIG_H +#include "courier_auth_config.h" +#endif +#include +#include +#include +#include +#include +#include + +#if HAVE_LBER_H +#include +#endif +#if HAVE_LDAP_H +#include +#endif +#include +#include +#include +#include +#if HAVE_SYS_TIME_H +#include +#endif +#if HAVE_SYS_STAT_H +#include +#endif + +extern "C" { +#include "authldap.h" +#include "auth.h" +#include "authldaprc.h" +#include "courierauthdebug.h" +}; + +#include "authconfigfile.h" +#include + +#ifndef LDAP_OPT_SUCCESS +#define LDAP_OPT_SUCCESS LDAP_SUCCESS +#endif + +/* +** Main connection to the LDAP server, and a separate connection for the +** LDAP_AUTHBIND option. +*/ + +class ldap_connection { + +public: + LDAP *connection; + + ldap_connection() : connection(0) {} + ~ldap_connection() { disconnect(); } + + bool connected() const { return connection != 0; } + + bool connect(); + void disconnect(); + void close(); + +private: + bool enable_tls(); + +public: + + static bool ok(const char *method, int rc) + { + if (rc == 0 || LDAP_NAME_ERROR(rc)) + return true; + + courier_auth_err("%s failed: %s", method, + ldap_err2string(rc)); + return false; + } + + bool bind(const std::string &userid, + const std::string &password) + { + std::vector buffer(password.begin(), password.end()); + struct berval cred; + + cred.bv_len=buffer.size(); + cred.bv_val=&buffer[0]; + + if (connect() && + ok("ldap_sasl_bind_s", + ldap_sasl_bind_s(connection, userid.c_str(), + NULL, &cred, + NULL, NULL, NULL))) + return true; + + disconnect(); + return connect() && + ok("ldap_sasl_bind_s", + ldap_sasl_bind_s(connection, userid.c_str(), + NULL, &cred, + NULL, NULL, NULL)); + } +}; + +ldap_connection main_connection, bind_connection; + + +// Loaded and parsed authldaprc configuration file. + +class authldaprc_file : public courier::auth::config_file { + +public: + + int protocol_version; + int timeout; + int authbind; + int initbind; + int tls; + uid_t uid; + gid_t gid; + + std::string ldap_uri, ldap_binddn, ldap_bindpw, ldap_basedn; + int ldap_deref; + + std::vector auxoptions, auxnames; + + authldaprc_file(); + +private: + bool do_load(); + void do_reload(); +public: +}; + +/* +** There's a memory leak in OpenLDAP 1.2.11, presumably in earlier versions +** too. See http://www.OpenLDAP.org/its/index.cgi?findid=864 for more +** information. To work around the bug, the first time a connection fails +** we stop trying for 60 seconds. After 60 seconds we kill the process, +** and let the parent process restart it. +** +** We'll control this behavior via LDAP_MEMORY_LEAK. Set it to ZERO to turn +** off this behavior (whenever OpenLDAP gets fixed). +*/ + +static time_t ldapfailflag=0; + +static void ldapconnfailure() +{ + const char *p=getenv("LDAP_MEMORY_LEAK"); + + if (!p) + { +#ifdef LDAP_VENDOR_NAME +#ifdef LDAP_VENDOR_VERSION +#define DO_OPENLDAP_CHECK +#endif +#endif + +#ifdef DO_OPENLDAP_CHECK + + /* It's supposed to be fixed in 20019 */ + + if (strcmp(LDAP_VENDOR_NAME, "OpenLDAP") == 0 && + LDAP_VENDOR_VERSION < 20019) + p="1"; + else + p="0"; +#else + p="0"; +#endif + } + + if (atoi(p) && !ldapfailflag) + { + time(&ldapfailflag); + ldapfailflag += 60; + } +} + +static int ldapconncheck() +{ + time_t t; + + if (!ldapfailflag) + return (0); + + time(&t); + + if (t >= ldapfailflag) + exit(0); + return (1); +} + +authldaprc_file::authldaprc_file() + : config_file(AUTHLDAPRC) +{ +} + +bool authldaprc_file::do_load() +{ + bool loaded=true; + + // Frequently-accessed variables. + + if (!config("LDAP_TIMEOUT", timeout, false, "5") || + !config("LDAP_TLS", tls, false, "0")) + { + loaded=false; + } + + if (!config("LDAP_URI", ldap_uri, true)) + { + loaded=false; + } + + ldap_deref=0; + + std::string deref_setting; + + config("LDAP_DEREF", deref_setting, false, ""); + + for (std::string::iterator p=deref_setting.begin(); + p != deref_setting.end(); ++p) + *p=std::tolower(*p); + +#ifdef LDAP_OPT_DEREF + + ldap_deref=LDAP_DEREF_NEVER; + + if (deref_setting == "never") + ldap_deref = LDAP_DEREF_NEVER; + else if (deref_setting == "searching") + ldap_deref = LDAP_DEREF_SEARCHING; + else if (deref_setting == "finding") + ldap_deref = LDAP_DEREF_FINDING; + else if (deref_setting == "always") + ldap_deref = LDAP_DEREF_ALWAYS; + else if (deref_setting != "") + { + loaded=false; + courier_auth_err("authldap: INVALID LDAP_OPT_DEREF"); + } +#endif + uid=0; + gid=0; + + std::string uid_str, gid_str; + + config("LDAP_GLOB_UID", uid_str, false); + config("LDAP_GLOB_GID", gid_str, false); + + if (!uid_str.empty()) + { + std::istringstream i(uid_str); + + i >> uid; + + if (i.fail()) + { + struct passwd *pwent=getpwnam(uid_str.c_str()); + + if (!pwent) + { + courier_auth_err("authldap: INVALID LDAP_GLOB_UID"); + loaded=false; + } + else + { + uid=pwent->pw_uid; + } + } + } + + if (!gid_str.empty()) + { + std::istringstream i(uid_str); + + i >> gid; + + if (i.fail()) + { + struct group *grent=getgrnam(gid_str.c_str()); + + if (!grent) + { + courier_auth_err("authldap: INVALID LDAP_GLOB_GID"); + loaded=false; + } + else + { + gid=grent->gr_gid; + } + } + } + + if (!config("LDAP_AUTHBIND", authbind, false, "0") + || !config("LDAP_INITBIND", initbind, false, "0") + || !config("LDAP_BASEDN", ldap_basedn, true)) + loaded=false; + + if (initbind) + { + if (!config("LDAP_BINDDN", ldap_binddn, true) || + !config("LDAP_BINDPW", ldap_bindpw, true)) + loaded=false; + } + + if (!config("LDAP_PROTOCOL_VERSION", protocol_version, false, "0")) + loaded=false; + + if (protocol_version) + { +#ifndef LDAP_OPT_PROTOCOL_VERSION + courier_auth_err("authldaplib: LDAP_OPT_PROTOCOL_VERSION ignored"); + loaded=false; +#endif + if (0 +#ifdef LDAP_VERSION_MIN + || protocol_version < LDAP_VERSION_MIN +#endif +#ifdef LDAP_VERSION_MAX + || protocol_version > LDAP_VERSION_MAX +#endif + ) + { + protocol_version=0; + courier_auth_err("authldaplib: LDAP_PROTOCOL_VERSION not supported"); + loaded=false; + } + } + + std::string auxoptions_str; + + config("LDAP_AUXOPTIONS", auxoptions_str, ""); + + std::string::iterator p=auxoptions_str.begin(); + + while (p != auxoptions_str.end()) + { + if (*p == ',') + { + ++p; + continue; + } + + std::string::iterator q=p; + + p=std::find(p, auxoptions_str.end(), ','); + + std::string auxoption(q, p); + std::string auxname; + + q=std::find(auxoption.begin(), auxoption.end(), '='); + + if (q != auxoption.end()) + { + auxname=std::string(q+1, auxoption.end()); + auxoption=std::string(auxoption.begin(), q); + } + + auxoptions.push_back(auxoption); + auxnames.push_back(auxname); + } + + return loaded; +} + +void authldaprc_file::do_reload() +{ + // File changed, try to load it again. + + authldaprc_file new_file; + + if (new_file.load(true)) + { + *this=new_file; + DPRINTF("authldap: reloaded %s", filename); + + // If we reloaded the file, close the + // connections, so they can be reopened using + // the new config. + + main_connection.close(); + bind_connection.close(); + } +} + +static authldaprc_file authldaprc; + +#if HAVE_LDAP_TLS +bool ldap_connection::enable_tls() +{ + int version; + + if (!ok("ldap_get_option", + ldap_get_option(connection, LDAP_OPT_PROTOCOL_VERSION, + &version))) + return false; + + if (version < LDAP_VERSION3) + { + version = LDAP_VERSION3; + (void)ldap_set_option (connection, + LDAP_OPT_PROTOCOL_VERSION, + &version); + } + + if (!ok("ldap_start_tls_s", + ldap_start_tls_s(connection, NULL, NULL))) + return false; + + return true; +} + +#else + +bool ldap_connection::enable_tls() +{ + courier_auth_err("authldaplib: TLS not available"); + return (-1); +} +#endif + +bool ldap_connection::connect() +{ + if (connected()) return true; + + DPRINTF("authldaplib: connecting to %s", authldaprc.ldap_uri.c_str()); + + if (ldapconncheck()) + { + DPRINTF("authldaplib: timing out after failed connection"); + return (false); + } + + ldap_initialize(&connection, authldaprc.ldap_uri.c_str()); + + if (connection==NULL) + { + courier_auth_err("cannot connect to LDAP server (%s): %s", + authldaprc.ldap_uri.c_str(), strerror(errno)); + ldapconnfailure(); + } +#ifdef LDAP_OPT_NETWORK_TIMEOUT + else if (authldaprc.timeout > 0) + { + DPRINTF("timeout set to %d", authldaprc.timeout); + ldap_set_option (connection, LDAP_OPT_NETWORK_TIMEOUT, &authldaprc.timeout); +#endif + } + +#ifdef LDAP_OPT_PROTOCOL_VERSION + + if (authldaprc.protocol_version && + !ok("ldap_set_option", + ldap_set_option(connection, + LDAP_OPT_PROTOCOL_VERSION, + (void *) &authldaprc.protocol_version))) + { + disconnect(); + return false; + } + + if (authldaprc.protocol_version) + { + DPRINTF("selected ldap protocol version %d", authldaprc.protocol_version); + } +#endif + + if (authldaprc.tls && !enable_tls()) + { + disconnect(); + return false; + } + +#ifdef LDAP_OPT_DEREF + if (!ok("ldap_set_option", + ldap_set_option(connection, LDAP_OPT_DEREF, + (void *)&authldaprc.ldap_deref))) + { + disconnect(); + return (false); + } +#else + if (!deref_setting.empty()) + { + courier_auth_err("authldaplib: LDAP_OPT_DEREF not available, ignored"); + } +#endif + + return true; +} + +void ldap_connection::disconnect() +{ + close(); + ldapconnfailure(); +} + +void ldap_connection::close() +{ + if (connection == NULL) + return; + + ldap_unbind_ext(connection, 0, 0); + connection=NULL; +} + +static int ldapopen() +{ + if (main_connection.connected()) return 0; + + if (!main_connection.connect()) + return 1; + + if (authldaprc.initbind) + { + /* Bind to server */ + if (courier_authdebug_login_level >= 2) + { + DPRINTF("binding to LDAP server as DN '%s', password '%s'", + authldaprc.ldap_binddn.empty() ? "" + : authldaprc.ldap_binddn.c_str(), + authldaprc.ldap_bindpw.empty() ? "" + : authldaprc.ldap_bindpw.c_str()); + } + else + { + DPRINTF("binding to LDAP server as DN '%s'", + authldaprc.ldap_binddn.empty() ? "" + : authldaprc.ldap_binddn.c_str()); + } + + if (!main_connection.bind(authldaprc.ldap_binddn, + authldaprc.ldap_bindpw)) + { + authldapclose(); + ldapconnfailure(); + return (-1); + } + } + return (0); +} + +class authldaprc_attributes { + +public: + + std::map attributes; + + std::string attribute(const char *name, + const char *default_value, + std::string &return_value) + { + std::string value; + + authldaprc.config(name, value, false, default_value); + + if (!value.empty()) + attributes[value]=&return_value; + return value; + } +}; + +class authldaprc_attribute_vector : public std::vector { + +public: + + authldaprc_attribute_vector(const std::map + &attributes) + { + for (std::map::const_iterator + p=attributes.begin(); p != attributes.end(); ++p) + { + push_back(p->first); + } + } +}; + +class authldaprc_search_attributes { + + std::vector copy_buffer; + +public: + + std::vector all_attributes_ptr; + + authldaprc_search_attributes(const std::vector &attributes) + : copy_buffer(attributes) + { + for (std::vector::iterator + p=copy_buffer.begin(); + p != copy_buffer.end(); ++p) + { + if (p->empty()) + continue; + + p->push_back(0); + all_attributes_ptr.push_back(& (*p)[0]); + } + + all_attributes_ptr.push_back(0); + } + + char **search_attributes() + { + return &all_attributes_ptr[0]; + } +}; + +class authldaprc_search_result : authldaprc_search_attributes { + +public: + + LDAPMessage *ptr; + bool finished; + + authldaprc_search_result(ldap_connection &conn, + const std::string &basedn, + const std::string &query, + const std::vector &attributes, + const struct timeval &timeout) + : authldaprc_search_attributes(attributes), + ptr(NULL), finished(false) + { + struct timeval timeout_copy=timeout; + + if (!conn.connect() || + !conn.ok("ldap_search_ext_s", + ldap_search_ext_s(conn.connection, + basedn.c_str(), + LDAP_SCOPE_SUBTREE, + query.c_str(), + search_attributes(), + 0, + NULL, NULL, + &timeout_copy, + 100, &ptr))) + { + ptr=NULL; + conn.disconnect(); + if (!conn.connect() + || !conn.ok("ldap_search_ext_s", + ldap_search_ext_s(conn.connection, + basedn.c_str(), + LDAP_SCOPE_SUBTREE, + query.c_str(), + search_attributes(), + 0, + NULL, NULL, + &timeout_copy, + 100, &ptr))) + { + ptr=NULL; + } + } + } + + authldaprc_search_result(ldap_connection &conn, + int msgid, + bool all, + const struct timeval &timeout) + :authldaprc_search_attributes(std::vector()), + ptr(NULL), finished(false) + { + while (1) + { + struct timeval timeout_copy=timeout; + + int rc=ldap_result(conn.connection, msgid, all ? 1:0, + &timeout_copy, + &ptr); + + switch (rc) + { + case -1: + DPRINTF("ldap_result() failed"); + ldap_msgfree(ptr); + ptr=NULL; + return; + case 0: + DPRINTF("ldap_result() timed out"); + ldap_msgfree(ptr); + ptr=NULL; + return; + case LDAP_RES_SEARCH_ENTRY: + break; /* deal with below */ + case LDAP_RES_SEARCH_RESULT: + if (ldap_parse_result(conn.connection, ptr, + &rc, + NULL, NULL, NULL, NULL, + 0) != LDAP_SUCCESS) + { + DPRINTF("ldap_parse_result failed"); + ldap_msgfree(ptr); + ptr=NULL; + return; + } + ldap_msgfree(ptr); + ptr=NULL; + if (rc != LDAP_SUCCESS) + { + DPRINTF("search failed: %s", + ldap_err2string(rc)); + return; + } + finished=true; + return; + default: + DPRINTF("ldap_result(): ignored 0x%02X status", + rc); + ldap_msgfree(ptr); + ptr=NULL; + continue; + } + break; + } + } + + bool operator!() const + { + return ptr == NULL; + } + + operator bool() const + { + return ptr != NULL; + } + + ~authldaprc_search_result() + { + if (ptr) + ldap_msgfree(ptr); + } +}; + +static std::vector +authldap_entry_values(LDAP *connection, LDAPMessage *msg, + const std::string &attrname) +{ + std::vector values; + + struct berval **p=ldap_get_values_len(connection, msg, + attrname.c_str()); + + if (p) + { + + size_t n=ldap_count_values_len(p); + + values.reserve(n); + + for (size_t i=0; i(p[i]->bv_val); + + values.push_back(std::string(ptr, ptr+p[i]->bv_len)); + } + + ldap_value_free_len(p); + } + return values; +} + +class authldap_get_values { + + LDAP *connection; + LDAPMessage *entry; + std::string context; + +public: + + authldap_get_values(LDAP *connectionArg, + LDAPMessage *entryArg, + const std::string &contextArg) + : connection(connectionArg), + entry(entryArg), + context(contextArg) + { + } + + bool operator()(const std::string &attrname, + std::string &value) + { + std::vector values= + authldap_entry_values(connection, entry, attrname); + + if (values.empty()) + return false; + + if (values.size() > 1) + { + fprintf(stderr, + "WARN: authldaplib: duplicate attribute %s for %s\n", + attrname.c_str(), + context.c_str()); + } + + value=values[0]; + return true; + } + + std::string options() + { + size_t i; + + std::ostringstream options; + const char *options_sep=""; + + for (i=0; i attribute_vector; + + attribute_vector.push_back(mail_field); + attribute_vector.push_back(uid_field); + attribute_vector.push_back(gid_field); + attribute_vector.push_back(homedir_field); + attribute_vector.push_back(maildir_field); + + for (size_t i=0; i + names=authldap_entry_values(main_connection.connection, + entry, + mail_field); + + if (names.empty()) + { + entry = ldap_next_entry(main_connection.connection, entry); + continue; + } + + size_t n=names.size(); + + if (n > 0) + { + std::string uid_s, gid_s, homedir, maildir; + uid_t uid=authldaprc.uid; + gid_t gid=authldaprc.gid; + + authldap_get_values + get_value(main_connection.connection, entry, + names[0]); + + if (!uid_field.empty()) + get_value(uid_field, uid_s); + + if (!gid_field.empty()) + { + get_value(gid_field, gid_s); + } + + get_value(homedir_field, homedir); + get_value(maildir_field, maildir); + + if (!uid_s.empty()) + std::istringstream(uid_s) + >> uid; + + if (!gid_s.empty()) + std::istringstream(gid_s) + >> gid; + + std::string options=get_value.options(); + + for (size_t j=0; j < n; j++) + { + if (!homedir.empty()) + (*cb_func)(names[j].c_str(), + uid, gid, + homedir.c_str(), + maildir.empty() + ? 0:maildir.c_str(), + options.empty() + ? 0:options.c_str(), + void_arg); + } + } + + entry = ldap_next_entry(main_connection.connection, entry); + } + } + + /* Success */ + (*cb_func)(NULL, 0, 0, NULL, NULL, NULL, void_arg); +} + +static void cpp_authldapclose() +{ + main_connection.disconnect(); + bind_connection.disconnect(); +} + +class authldap_lookup : private authldaprc_attributes { + + struct authinfo auth; + const char *service; + std::string attrname; + std::string user; + const char *pass; + const char *newpass; + const char *authaddr; + +public: + authldap_lookup(const char *serviceArg, + const std::string &attrnameArg, + const std::string &userArg, + const char *passArg, + const char *newpassArg, + const char *authaddrArg); + + int operator()(int (*callback)(struct authinfo *, void *), void *arg); + +private: + int verify_password(const std::string &dn); + int verify_password_myself(const std::string &dn); + int verify_password_authbind(const std::string &dn); +}; + +authldap_lookup::authldap_lookup(const char *serviceArg, + const std::string &attrnameArg, + const std::string &userArg, + const char *passArg, + const char *newpassArg, + const char *authaddrArg) + : service(serviceArg), + attrname(attrnameArg), + user(userArg), + pass(passArg), + newpass(newpassArg), + authaddr(authaddrArg) +{ +} + +int authldap_lookup::operator()(int (*callback)(struct authinfo *, void *), + void *arg) +{ + struct timeval timeout; + + LDAPMessage *entry; + + std::string dn; + + std::string homeDir, mailDir, userPassword, + cryptPassword, + cn, + uidNumber, + gidNumber, quota; + + uid_t au; + gid_t ag; + int rc; + + std::ostringstream query; + + std::string filter; + + authldaprc.config("LDAP_FILTER", filter, false); + + if (!filter.empty()) + { + query << "(&" << filter; + } + + query << "(" << attrname << "=" << user; + + std::string domain; + + authldaprc.config("LDAP_DOMAIN", domain, false); + + if (!domain.empty() && + std::find(user.begin(), user.end(), '@') == user.end()) + query << "@" << domain; + query << ")"; + + if (!filter.empty()) + query << ")"; + + std::string query_str=query.str(); + + DPRINTF("Query: %s", query_str.c_str()); + + timeout.tv_sec=authldaprc.timeout; + timeout.tv_usec=0; + + attribute("LDAP_HOMEDIR", "homeDir", homeDir); + attribute(service && strcmp(service, "courier") == 0 + ? "LDAP_DEFAULTDELIVERY":"LDAP_MAILDIR", 0, mailDir); + attribute("LDAP_FULLNAME", 0, cn); + std::string clearpw_value=attribute("LDAP_CLEARPW", 0, userPassword); + std::string cryptpw_value=attribute("LDAP_CRYPTPW", 0, cryptPassword); + attribute("LDAP_UID", 0, uidNumber); + attribute("LDAP_GID", 0, gidNumber); + attribute("LDAP_MAILDIRQUOTA", 0, quota); + + authldaprc_attribute_vector all_attributes(attributes); + + for (size_t i=0; i"); + + if (dn_p == NULL) + { + DPRINTF("ldap_get_dn failed"); + return -1; + } + + dn=dn_p; + free(dn_p); + + /* Get the pointer on this result */ + entry=ldap_first_entry(main_connection.connection, result.ptr); + if (entry==NULL) + { + DPRINTF("ldap_first_entry failed"); + return -1; + } + + /* print all the raw attributes */ + if (courier_authdebug_login_level >= 2) + { + BerElement *berptr = 0; + char *attr= + ldap_first_attribute(main_connection.connection, entry, &berptr); + + while (attr) + { + std::vector + values=authldap_entry_values(main_connection.connection, + entry, + attr); + for (size_t i=0; i::iterator + p=attributes.begin(); p != attributes.end(); ++p) + { + get_value(p->first, *p->second); + } + + au=authldaprc.uid; + ag=authldaprc.gid; + if (!uidNumber.empty()) + { + std::istringstream(uidNumber) >> au; + } + + if (!gidNumber.empty()) + { + std::istringstream(gidNumber) >> ag; + } + + std::string mailroot; + + authldaprc.config("LDAP_MAILROOT", mailroot, false); + + if (!homeDir.empty() && !mailroot.empty()) + { + homeDir = mailroot + "/" + homeDir; + } + + std::string options=get_value.options(); + + memset(&auth, 0, sizeof(auth)); + + auth.sysuserid= &au; + auth.sysgroupid= ag; + auth.homedir=homeDir.c_str(); + auth.address=authaddr; + auth.fullname=cn.c_str(); + + if (!mailDir.empty()) + auth.maildir=mailDir.c_str(); + + if (!userPassword.empty()) + auth.clearpasswd=userPassword.c_str(); + + if (!cryptPassword.empty()) + auth.passwd=cryptPassword.c_str(); + + if (!quota.empty()) + auth.quota=quota.c_str(); + + if (!options.empty()) + auth.options=options.c_str(); + + rc=0; + + if (au == 0 || ag == 0) + { + courier_auth_err("authldaplib: refuse to authenticate %s: uid=%d, gid=%d (zero uid or gid not permitted)", + user.c_str(), au, ag); + rc= 1; + } + + courier_authdebug_authinfo("DEBUG: authldaplib: ", &auth, + userPassword.c_str(), + cryptPassword.c_str()); + + if (rc == 0) + rc=verify_password(dn); + + std::string newpass_crypt; + + if (rc == 0 && newpass) + { + char *p=authcryptpasswd(newpass, auth.passwd); + + if (!p) + rc=-1; + else + { + newpass_crypt=p; + free(p); + } + + LDAPMod *mods[3]; + int mod_index=0; + + LDAPMod mod_clear, mod_crypt; + char *mod_clear_vals[2], *mod_crypt_vals[2]; + + std::vector clearpw_buffer, cryptpw_buffer; + + if (!clearpw_value.empty() && auth.clearpasswd) + { + clearpw_buffer.insert(clearpw_buffer.end(), + clearpw_value.begin(), + clearpw_value.end()); + + clearpw_buffer.push_back(0); + + mods[mod_index]= &mod_clear; + mod_clear.mod_op=LDAP_MOD_REPLACE; + mod_clear.mod_type=&clearpw_buffer[0]; + mod_clear.mod_values=mod_clear_vals; + + DPRINTF("Modify: %s", mod_clear.mod_type); + + mod_clear_vals[0]=(char *)newpass; + mod_clear_vals[1]=NULL; + ++mod_index; + } + + if (!cryptpw_value.empty() && + !newpass_crypt.empty() && auth.passwd) + { + cryptpw_buffer.insert(cryptpw_buffer.end(), + cryptpw_value.begin(), + cryptpw_value.end()); + + cryptpw_buffer.push_back(0); + + mods[mod_index]= &mod_crypt; + mod_crypt.mod_op=LDAP_MOD_REPLACE; + mod_crypt.mod_type=&cryptpw_buffer[0]; + mod_crypt.mod_values=mod_crypt_vals; + + DPRINTF("Modify: %s", mod_crypt.mod_type); + + newpass_crypt.push_back(0); + mod_crypt_vals[0]=&newpass_crypt[0]; + mod_crypt_vals[1]=NULL; + ++mod_index; + } + if (mod_index == 0) + rc= -1; + else + { + mods[mod_index]=0; + + /* + ** Use the user connection for updating the password. + */ + + int ld_errno= + ldap_modify_ext_s(bind_connection.connected() + ? bind_connection.connection + : main_connection.connection, + dn.c_str(), mods, + NULL, NULL); + + if (ld_errno != LDAP_SUCCESS) + { + rc= -1; + DPRINTF("Password update failed: %s", + ldap_err2string(ld_errno)); + } + } + } + + if (rc == 0 && callback) + { + if (!auth.clearpasswd) + auth.clearpasswd=pass; + rc= (*callback)(&auth, arg); + } + + return (rc); +} + +int authldap_lookup::verify_password(const std::string &dn) +{ + if (!pass) + return 0; + + if (authldaprc.authbind) + return verify_password_authbind(dn); + + return verify_password_myself(dn); +} + +int authldap_lookup::verify_password_authbind(const std::string &dn) +{ + if (!bind_connection.connect()) + return 1; + + if (!bind_connection.bind(dn, pass)) + { + bind_connection.close(); + return 1; + } + + if (authldaprc.protocol_version == 2) + { + // protocol version 2 does not allow rebinds. + + bind_connection.close(); + } + + return 0; +} + +int authldap_lookup::verify_password_myself(const std::string &dn) +{ + if (auth.clearpasswd) + { + if (strcmp(pass, auth.clearpasswd)) + { + if (courier_authdebug_login_level >= 2) + { + DPRINTF("Password for %s: '%s' does not match clearpasswd '%s'", + dn.c_str(), pass, auth.clearpasswd); + } + else + { + DPRINTF("Password for %s does not match", + dn.c_str()); + } + return -1; + } + } + else + { + const char *p=auth.passwd; + + if (!p) + { + DPRINTF("Missing password in LDAP!"); + return -1; + } + + if (authcheckpassword(pass, p)) + { + DPRINTF("Password for %s does not match", + dn.c_str()); + return -1; + } + } + + return 0; +} + +/* +** Replace keywords in the emailmap string. +*/ + +static std::string emailmap_replace(const char *str, + const std::string &user_value, + const std::string &realm_value) +{ + std::ostringstream o; + + while (*str) + { + const char *p=str; + + while (*str && *str != '@') + ++str; + + o << std::string(p, str); + + if (*str != '@') + continue; + + ++str; + + p=str; + while (*str && *str != '@') + ++str; + + std::string key(p, str); + + if (*str) + ++str; + + if (key == "user") + o << user_value; + else if (key == "realm") + o << realm_value; + } + + return o.str(); +} + +static int auth_ldap_try(const char *service, + const char *unquoted_user, const char *pass, + int (*callback)(struct authinfo *, void *), + void *arg, const char *newpass) +{ + std::string user; + + { + char *q=courier_auth_ldap_escape(unquoted_user); + + user=q; + free(q); + } + + if (ldapopen()) return (-1); + + std::string::iterator at=std::find(user.begin(), user.end(), '@'); + + std::string emailmap; + + authldaprc.config("LDAP_EMAILMAP", emailmap, false); + + std::string mail; + + if (!authldaprc.config("LDAP_MAIL", mail, false, "mail")) + return -1; + + if (emailmap.empty() || at == user.end()) + { + authldap_lookup real_lookup(service, mail, user, pass, + newpass, user.c_str()); + + return real_lookup(callback, arg); + } + + std::string user_value(user.begin(), at); + std::string realm_value(++at, user.end()); + + std::string query= + emailmap_replace(emailmap.c_str(), user_value, realm_value); + + DPRINTF("using emailmap search: %s", query.c_str()); + + struct timeval tv; + + tv.tv_sec=authldaprc.timeout; + tv.tv_usec=0; + + std::vector attributes; + + attributes.push_back(""); + + authldaprc.config("LDAP_EMAILMAP_ATTRIBUTE", attributes[0], false); + + if (attributes[0].empty()) + attributes[0]="handle"; + + std::string basedn; + + if (!authldaprc.config("LDAP_EMAILMAP_BASEDN", basedn, true)) + return -1; + + authldaprc_search_result + lookup(main_connection, + basedn, + query, + attributes, + tv); + + if (!lookup) + { + if (main_connection.connection) return (-1); + return (1); + } + + int cnt=ldap_count_entries(main_connection.connection, lookup.ptr); + + if (cnt != 1) + { + courier_auth_err("emailmap: %d entries returned from search %s (but we need exactly 1)", + cnt, query.c_str()); + return -1; + } + + LDAPMessage *entry=ldap_first_entry(main_connection.connection, lookup.ptr); + + if (!entry) + { + courier_auth_err("authldap: unexpected NULL from ldap_first_entry"); + return -1; + } + + authldap_get_values get_value(main_connection.connection, entry, query); + + std::string v; + + get_value(attributes[0], v); + + if (v.empty()) + { + DPRINTF("emailmap: empty attribute"); + return (-1); + } + + + std::string attrname; + + authldaprc.config("LDAP_EMAILMAP_MAIL", attrname, false); + + if (attrname.empty()) + { + attrname=mail; + } + + DPRINTF("emailmap: attribute=%s, value=%s", attrname.c_str(), + v.c_str()); + + authldap_lookup real_lookup(service, attrname, v.c_str(), pass, + newpass, user.c_str()); + + return real_lookup(callback, arg); +} + +// Try the query once. If there's a connection-level error, try again. + +static int auth_ldap_retry(const char *service, + const char *unquoted_user, + const char *pass, + int (*callback)(struct authinfo *, void *), + void *arg, const char *newpass) +{ + int rc=auth_ldap_try(service, unquoted_user, pass, callback, arg, + newpass); + + if (rc > 0) + rc=auth_ldap_try(service, unquoted_user, pass, callback, arg, + newpass); + + return rc; +} + +extern "C" { + +#if 0 +}; +#endif + +int auth_ldap_changepw(const char *dummy, const char *user, + const char *pass, + const char *newpass) +{ + if (!authldaprc.load()) + return 1; + + return auth_ldap_retry("authlib", user, pass, NULL, NULL, newpass); +} + +int authldapcommon(const char *service, + const char *user, const char *pass, + int (*callback)(struct authinfo *, void *), + void *arg) +{ + if (!authldaprc.load()) + return 1; + return (auth_ldap_retry(service, user, pass, callback, arg, NULL)); +} + +void authldapclose() +{ + cpp_authldapclose(); +} + +void auth_ldap_enumerate( void(*cb_func)(const char *name, + uid_t uid, + gid_t gid, + const char *homedir, + const char *maildir, + const char *options, + void *void_arg), + void *void_arg) +{ + if (!authldaprc.load()) + return; + + cpp_auth_ldap_enumerate(cb_func, void_arg); +} + +#if 0 +{ +#endif +}