Back to index

nordugrid-arc-nox  1.1.0~rc6
LDAPQuery.cpp
Go to the documentation of this file.
00001 // -*- indent-tabs-mode: nil -*-
00002 
00003 #ifdef HAVE_CONFIG_H
00004 #include "config.h"
00005 #endif
00006 
00007 #ifdef WIN32
00008 #include <arc/win32.h> 
00009 #endif
00010 
00011 #include <cstring>
00012 #include <iostream>
00013 #include <list>
00014 #include <string>
00015 
00016 #ifdef USE_WIN32_LDAP_API
00017 #include <winldap.h>
00018 
00019 #define timeval l_timeval
00020 
00021 # ifndef ldap_msgtype
00022 # define ldap_msgtype(m) ((m)->lm_msgtype)
00023 # endif
00024 
00025 # ifndef ldap_first_message
00026 # define ldap_first_message ldap_first_entry
00027 # endif
00028 
00029 # ifndef ldap_next_message
00030 # define ldap_next_message ldap_next_entry
00031 # endif
00032 
00033 # ifndef ldap_unbind_ext
00034 # define ldap_unbind_ext(l,s,c) ldap_unbind(l)
00035 # endif
00036 
00037 #else
00038 #include <ldap.h>
00039 #endif
00040 
00041 #include <sys/time.h>
00042 #include <unistd.h>
00043 
00044 #ifdef HAVE_SASL_H
00045 #include <sasl.h>
00046 #endif
00047 #ifdef HAVE_SASL_SASL_H
00048 #include <sasl/sasl.h>
00049 #endif
00050 
00051 #ifndef LDAP_SASL_QUIET
00052 #define LDAP_SASL_QUIET 0  /* Does not exist in Solaris LDAP */
00053 #endif
00054 
00055 #ifndef LDAP_OPT_SUCCESS
00056 #define LDAP_OPT_SUCCESS LDAP_SUCCESS
00057 #endif
00058 
00059 #include <arc/Logger.h>
00060 #include <arc/StringConv.h>
00061 #include <arc/Thread.h>
00062 
00063 #include "LDAPQuery.h"
00064 
00065 namespace Arc {
00066 
00067   Logger LDAPQuery::logger(Logger::rootLogger, "LDAPQuery");
00068 
00069   class ldap_bind_arg {
00070   public:
00071     LDAP *connection;
00072     LogLevel loglevel;
00073     SimpleCondition cond;
00074     bool valid;
00075     bool anonymous;
00076     std::string usersn;
00077     ldap_bind_arg(void):count(2) { };
00078     bool release(void) {
00079       bool freeit = false;
00080       cond.lock();
00081       freeit = ((--count) <= 0);
00082       cond.unlock();
00083       if(freeit) {
00084         if(connection) ldap_unbind_ext(connection,NULL,NULL);
00085         delete this;
00086       }
00087       return freeit;
00088     };
00089   private:
00090     int count;
00091     ~ldap_bind_arg(void) { };
00092   };
00093 
00094   static void ldap_bind_with_timeout(void *arg);
00095 
00096 #if defined (HAVE_SASL_H) || defined (HAVE_SASL_SASL_H)
00097   class sasl_defaults {
00098   public:
00099     sasl_defaults(ldap *ld,
00100                   const std::string& mech,
00101                   const std::string& realm,
00102                   const std::string& authcid,
00103                   const std::string& authzid,
00104                   const std::string& passwd);
00105     ~sasl_defaults() {}
00106 
00107   private:
00108     std::string p_mech;
00109     std::string p_realm;
00110     std::string p_authcid;
00111     std::string p_authzid;
00112     std::string p_passwd;
00113 
00114     friend int my_sasl_interact(ldap *ld,
00115                                 unsigned int flags,
00116                                 void *defaults_,
00117                                 void *interact_);
00118   };
00119 
00120   sasl_defaults::sasl_defaults(ldap *ld,
00121                                const std::string& mech,
00122                                const std::string& realm,
00123                                const std::string& authcid,
00124                                const std::string& authzid,
00125                                const std::string& passwd)
00126     : p_mech(mech),
00127       p_realm(realm),
00128       p_authcid(authcid),
00129       p_authzid(authzid),
00130       p_passwd(passwd) {
00131 
00132     if (p_mech.empty()) {
00133       char *temp;
00134       ldap_get_option(ld, LDAP_OPT_X_SASL_MECH, &temp);
00135       if (temp) {
00136         p_mech = temp;
00137         free(temp);
00138       }
00139     }
00140     if (p_realm.empty()) {
00141       char *temp;
00142       ldap_get_option(ld, LDAP_OPT_X_SASL_REALM, &temp);
00143       if (temp) {
00144         p_realm = temp;
00145         free(temp);
00146       }
00147     }
00148     if (p_authcid.empty()) {
00149       char *temp;
00150       ldap_get_option(ld, LDAP_OPT_X_SASL_AUTHCID, &temp);
00151       if (temp) {
00152         p_authcid = temp;
00153         free(temp);
00154       }
00155     }
00156     if (p_authzid.empty()) {
00157       char *temp;
00158       ldap_get_option(ld, LDAP_OPT_X_SASL_AUTHZID, &temp);
00159       if (temp) {
00160         p_authzid = temp;
00161         free(temp);
00162       }
00163     }
00164   }
00165 
00166 
00167   int my_sasl_interact(ldap*,
00168                        unsigned int flags,
00169                        void *defaults_,
00170                        void *interact_) {
00171 
00172     sasl_interact_t *interact = (sasl_interact_t*)interact_;
00173     sasl_defaults *defaults = (sasl_defaults*)defaults_;
00174 
00175     if (flags == LDAP_SASL_INTERACTIVE)
00176       LDAPQuery::logger.msg(DEBUG, "SASL Interaction");
00177 
00178     while (interact->id != SASL_CB_LIST_END) {
00179 
00180       bool noecho = false;
00181       bool challenge = false;
00182       bool use_default = false;
00183 
00184       switch (interact->id) {
00185       case SASL_CB_GETREALM:
00186         if (defaults && !defaults->p_realm.empty())
00187           interact->defresult = strdup(defaults->p_realm.c_str());
00188         break;
00189 
00190       case SASL_CB_AUTHNAME:
00191         if (defaults && !defaults->p_authcid.empty())
00192           interact->defresult = strdup(defaults->p_authcid.c_str());
00193         break;
00194 
00195       case SASL_CB_USER:
00196         if (defaults && !defaults->p_authzid.empty())
00197           interact->defresult = strdup(defaults->p_authzid.c_str());
00198         break;
00199 
00200       case SASL_CB_PASS:
00201         if (defaults && !defaults->p_passwd.empty())
00202           interact->defresult = strdup(defaults->p_passwd.c_str());
00203         noecho = true;
00204         break;
00205 
00206       case SASL_CB_NOECHOPROMPT:
00207         noecho = true;
00208         challenge = true;
00209         break;
00210 
00211       case SASL_CB_ECHOPROMPT:
00212         challenge = true;
00213         break;
00214       }
00215 
00216       if (flags != LDAP_SASL_INTERACTIVE &&
00217           (interact->defresult || interact->id == SASL_CB_USER))
00218         use_default = true;
00219       else {
00220         if (flags == LDAP_SASL_QUIET)
00221           return 1;
00222 
00223         if (challenge && interact->challenge)
00224           LDAPQuery::logger.msg(DEBUG, "Challenge: %s",
00225                                 interact->challenge);
00226 
00227         if (interact->defresult)
00228           LDAPQuery::logger.msg(DEBUG, "Default: %s",
00229                                 interact->defresult);
00230 
00231         std::string prompt;
00232         std::string input;
00233 
00234         prompt = interact->prompt ?
00235                  std::string(interact->prompt) + ": " : "Interact: ";
00236 
00237         if (noecho)
00238           input = getpass(prompt.c_str());
00239         else {
00240           std::cout << prompt;
00241           std::cin >> input;
00242         }
00243         if (input.empty())
00244           use_default = true;
00245         else {
00246           interact->result = strdup(input.c_str());
00247           interact->len = input.length();
00248         }
00249       }
00250 
00251       if (use_default) {
00252         interact->result = strdup(interact->defresult ?
00253                                   interact->defresult : "");
00254         interact->len = strlen((char*)interact->result);
00255       }
00256 
00257       if (defaults && interact->id == SASL_CB_PASS)
00258         // clear default password after first use
00259         defaults->p_passwd = "";
00260 
00261       interact++;
00262     }
00263     return 0;
00264   }
00265 #endif
00266 
00267 
00268   LDAPQuery::LDAPQuery(const std::string& ldaphost,
00269                        int ldapport,
00270                        int timeout,
00271                        bool anonymous,
00272                        const std::string& usersn)
00273     : host(ldaphost),
00274       port(ldapport),
00275       anonymous(anonymous),
00276       usersn(usersn),
00277       timeout(timeout),
00278       connection(NULL),
00279       messageid(0) {}
00280 
00281 
00282   LDAPQuery::~LDAPQuery() {
00283 
00284     if (connection) {
00285       ldap_unbind_ext(connection, NULL, NULL);
00286       connection = NULL;
00287     }
00288   }
00289 
00290 
00291   // Lock to protect thread-unsafe OpenLDAP functions - 
00292   // currently ldap_initialize.
00293   // TODO: investigate if OpenLDAP unloads cleanly and if
00294   // not make this plugin persistent.
00295   static Glib::Mutex* ldap_lock(void) {
00296     static Glib::Mutex* lock = new Glib::Mutex;
00297     return lock;
00298   }
00299 
00300   bool LDAPQuery::Connect() {
00301 
00302     const int version = LDAP_VERSION3;
00303 
00304     logger.msg(VERBOSE, "LDAPQuery: Initializing connection to %s:%d",
00305                host, port);
00306 
00307     if (connection) {
00308       logger.msg(ERROR, "LDAP connection already open to %s", host);
00309       return false;
00310     }
00311 
00312     ldap_lock()->lock();
00313 #ifdef HAVE_LDAP_INITIALIZE
00314     ldap_initialize(&connection,
00315                     ("ldap://" + host + ':' + tostring(port)).c_str());
00316 #else
00317 #ifdef USE_WIN32_LDAP_API
00318     connection = ldap_init(const_cast<char *>(host.c_str()), port);
00319 #else
00320     connection = ldap_init(host.c_str(), port);
00321 #endif
00322 #endif
00323     ldap_lock()->unlock();
00324 
00325     if (!connection) {
00326       logger.msg(ERROR, "Could not open LDAP connection to %s", host);
00327       return false;
00328     }
00329 
00330     if (!SetConnectionOptions(version)) {
00331       ldap_unbind_ext(connection, NULL, NULL);
00332       connection = NULL;
00333       return false;
00334     }
00335 
00336     ldap_bind_arg* arg = new ldap_bind_arg;
00337 
00338     arg->connection = connection;
00339     arg->loglevel = logger.getThreshold();
00340     arg->valid = true;
00341     arg->anonymous = anonymous;
00342     arg->usersn = usersn;
00343 
00344     if (!Arc::CreateThreadFunction(&ldap_bind_with_timeout, arg)) {
00345       arg->release(); arg->release();
00346       connection = NULL;
00347       logger.msg(ERROR, "Failed to create ldap bind thread (%s)", host);
00348       return false;
00349     }
00350 
00351     if (!arg->cond.wait(1000 * (timeout + 1))) {
00352       arg->release();
00353       connection = NULL;
00354       logger.msg(ERROR, "Ldap bind timeout (%s)", host);
00355       return false;
00356     }
00357 
00358     if (!arg->valid) {
00359       arg->release();
00360       connection = NULL;
00361       logger.msg(ERROR, "Failed to bind to ldap server (%s)", host);
00362       return false;
00363     }
00364     arg->connection = NULL; // keep connection up
00365     arg->release();
00366 
00367     return true;
00368   }
00369 
00370 
00371   bool LDAPQuery::SetConnectionOptions(int version) {
00372 
00373     timeval tout;
00374     tout.tv_sec = timeout;
00375     tout.tv_usec = 0;
00376 
00377 #ifdef LDAP_OPT_NETWORK_TIMEOUT
00378     // solaris does not have LDAP_OPT_NETWORK_TIMEOUT
00379     if (ldap_set_option(connection, LDAP_OPT_NETWORK_TIMEOUT, &tout) !=
00380         LDAP_OPT_SUCCESS) {
00381       logger.msg(ERROR,
00382                  "Could not set LDAP network timeout (%s)", host);
00383       return false;
00384     }
00385 #endif
00386 
00387     if (ldap_set_option(connection, LDAP_OPT_TIMELIMIT, &timeout) !=
00388         LDAP_OPT_SUCCESS) {
00389       logger.msg(ERROR,
00390                  "Could not set LDAP timelimit (%s)", host);
00391       return false;
00392     }
00393 
00394     if (ldap_set_option(connection, LDAP_OPT_PROTOCOL_VERSION, &version) !=
00395         LDAP_OPT_SUCCESS) {
00396       logger.msg(ERROR,
00397                  "Could not set LDAP protocol version (%s)", host);
00398       return false;
00399     }
00400 
00401     return true;
00402   }
00403 
00404   static void ldap_bind_with_timeout(void *arg_) {
00405 
00406     ldap_bind_arg *arg = (ldap_bind_arg*)arg_;
00407 
00408     int ldresult = 0;
00409     if (arg->anonymous) {
00410 #ifdef USE_WIN32_LDAP_API
00411       ldresult = ldap_simple_bind_s(arg->connection, NULL, NULL);
00412 #else
00413       BerValue cred = {
00414         0, const_cast<char*>("")
00415       };
00416       ldresult = ldap_sasl_bind_s(arg->connection, NULL, LDAP_SASL_SIMPLE,
00417                                   &cred, NULL, NULL, NULL);
00418 #endif
00419     }
00420     else {
00421 #if defined (HAVE_SASL_H) || defined (HAVE_SASL_SASL_H)
00422       int ldapflag = LDAP_SASL_QUIET;
00423 #ifdef LDAP_SASL_AUTOMATIC
00424       // solaris does not have LDAP_SASL_AUTOMATIC
00425       if (arg->loglevel >= VERBOSE)
00426         ldapflag = LDAP_SASL_AUTOMATIC;
00427 #endif
00428       sasl_defaults defaults = sasl_defaults(arg->connection,
00429                                              SASLMECH,
00430                                              "",
00431                                              "",
00432                                              arg->usersn,
00433                                              "");
00434       ldresult = ldap_sasl_interactive_bind_s(arg->connection,
00435                                               NULL,
00436                                               SASLMECH,
00437                                               NULL,
00438                                               NULL,
00439                                               ldapflag,
00440                                               my_sasl_interact,
00441                                               &defaults);
00442 #else
00443 #ifdef USE_WIN32_LDAP_API
00444       ldresult = ldap_simple_bind_s(arg->connection, NULL, NULL);
00445 #else
00446       BerValue cred = {
00447         0, const_cast<char*>("")
00448       };
00449       ldresult = ldap_sasl_bind_s(arg->connection, NULL, LDAP_SASL_SIMPLE,
00450                                   &cred, NULL, NULL, NULL);
00451 #endif
00452 #endif
00453     }
00454 
00455     if (ldresult != LDAP_SUCCESS) {
00456       arg->valid = false;
00457     } else {
00458       arg->valid = true;
00459     }
00460     arg->cond.signal();
00461     arg->release();
00462   }
00463 
00464 
00465   bool LDAPQuery::Query(const std::string& base,
00466                         const std::string& filter,
00467                         const std::list<std::string>& attributes,
00468                         URL::Scope scope) {
00469 
00470     if (!Connect())
00471       return false;
00472 
00473     logger.msg(VERBOSE, "LDAPQuery: Querying %s", host);
00474 
00475     logger.msg(DEBUG, "  base dn: %s", base);
00476     if (!filter.empty())
00477       logger.msg(DEBUG, "  filter: %s", filter);
00478     if (!attributes.empty()) {
00479       logger.msg(DEBUG, "  attributes:");
00480       for (std::list<std::string>::const_iterator vs = attributes.begin();
00481            vs != attributes.end(); vs++)
00482         logger.msg(DEBUG, "    %s", *vs);
00483     }
00484 
00485     timeval tout;
00486     tout.tv_sec = timeout;
00487     tout.tv_usec = 0;
00488 
00489     char *filt = (char*)filter.c_str();
00490 
00491     char **attrs;
00492     if (attributes.empty())
00493       attrs = NULL;
00494     else {
00495       attrs = new char*[attributes.size() + 1];
00496       int i = 0;
00497       for (std::list<std::string>::const_iterator vs = attributes.begin();
00498            vs != attributes.end(); vs++, i++)
00499         attrs[i] = (char*)vs->c_str();
00500       attrs[i] = NULL;
00501     }
00502 
00503     int ldresult = ldap_search_ext(connection,
00504 #ifdef USE_WIN32_LDAP_API
00505                                    const_cast<char *>(base.c_str()),
00506 #else
00507                                    base.c_str(),
00508 #endif
00509                                    scope,
00510                                    filt,
00511                                    attrs,
00512                                    0,
00513                                    NULL,
00514                                    NULL,
00515 #ifdef USE_WIN32_LDAP_API
00516                                    timeout,
00517 #else
00518                                    &tout,
00519 #endif
00520                                    0,
00521                                    &messageid);
00522 
00523     if (attrs)
00524       delete[] attrs;
00525 
00526     if (ldresult != LDAP_SUCCESS) {
00527       logger.msg(ERROR, "%s (%s)", ldap_err2string(ldresult), host);
00528       ldap_unbind_ext(connection, NULL, NULL);
00529       connection = NULL;
00530       return false;
00531     }
00532 
00533     return true;
00534   }
00535 
00536 
00537   bool LDAPQuery::Result(ldap_callback callback, void *ref) {
00538 
00539     bool result = HandleResult(callback, ref);
00540 
00541     ldap_unbind_ext(connection, NULL, NULL);
00542     connection = NULL;
00543     messageid = 0;
00544 
00545     return result;
00546   }
00547 
00548 
00549   bool LDAPQuery::HandleResult(ldap_callback callback, void *ref) {
00550 
00551     logger.msg(VERBOSE, "LDAPQuery: Getting results from %s", host);
00552 
00553     if (!messageid) {
00554       logger.msg(ERROR, "Error: no LDAP query started to %s", host);
00555       return false;
00556     }
00557 
00558     timeval tout;
00559     tout.tv_sec = timeout;
00560     tout.tv_usec = 0;
00561 
00562     bool done = false;
00563     int ldresult = 0;
00564     LDAPMessage *res = NULL;
00565 
00566     while (!done && (ldresult = ldap_result(connection,
00567                                             messageid,
00568                                             LDAP_MSG_ONE,
00569                                             &tout,
00570                                             &res)) > 0) {
00571 #ifdef USE_WIN32_LDAP_API
00572       if (ldap_count_entries(connection, res) == 0) {
00573         done = true;
00574         continue;
00575       }
00576 #endif
00577       for (LDAPMessage *msg = ldap_first_message(connection, res); msg;
00578            msg = ldap_next_message(connection, msg)) {
00579 
00580         switch (ldap_msgtype(msg)) {
00581         case LDAP_RES_SEARCH_ENTRY:
00582           HandleSearchEntry(msg, callback, ref);
00583           break;
00584 
00585         case LDAP_RES_SEARCH_RESULT:
00586           done = true;
00587           break;
00588         } // switch
00589       } // for
00590       ldap_msgfree(res);
00591     }
00592 
00593     if (ldresult == 0) {
00594       logger.msg(ERROR, "LDAP query timed out: %s", host);
00595       return false;
00596     }
00597 
00598     if (ldresult == -1) {
00599       logger.msg(ERROR, "%s (%s)", ldap_err2string(ldresult), host);
00600       return false;
00601     }
00602 
00603     return true;
00604   }
00605 
00606 
00607   void LDAPQuery::HandleSearchEntry(LDAPMessage *msg,
00608                                     ldap_callback callback,
00609                                     void *ref) {
00610     char *dn = ldap_get_dn(connection, msg);
00611     callback("dn", dn, ref);
00612     if (dn)
00613       ldap_memfree(dn);
00614 
00615     BerElement *ber = NULL;
00616     for (char *attr = ldap_first_attribute(connection, msg, &ber);
00617          attr; attr = ldap_next_attribute(connection, msg, ber)) {
00618       BerValue **bval;
00619       if ((bval = ldap_get_values_len(connection, msg, attr))) {
00620         for (int i = 0; bval[i]; i++)
00621           callback(attr, (bval[i]->bv_val ? bval[i]->bv_val : ""), ref);
00622         ber_bvecfree(bval);
00623       }
00624       ldap_memfree(attr);
00625     }
00626     if (ber)
00627       ber_free(ber, 0);
00628   }
00629 
00630 } // namespace Arc