/* ldap-nss.c - main file for NSS interface This file was part of the nss_ldap library which has been forked into the nss-ldapd library. Copyright (C) 1997-2006 Luke Howard Copyright (C) 2006, 2007 West Consulting Copyright (C) 2006, 2007 Arthur de Jong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include #include #include #include #ifdef HAVE_STRINGS_H #include #endif #include #include #include #include #include #include #include #ifdef HAVE_SYS_UN_H #include #endif #include #include #ifdef HAVE_LDAP_SSL_H #include #endif #ifdef HAVE_GSSLDAP_H #include #endif #ifdef HAVE_GSSSASL_H #include #endif #ifdef HAVE_MALLOC_H #include #endif #if defined(HAVE_THREAD_H) #include #elif defined(HAVE_PTHREAD_H) #include #endif /* Try to handle systems with both SASL libraries installed */ #if defined(HAVE_SASL_SASL_H) && defined(HAVE_SASL_AUXPROP_REQUEST) #include #elif defined(HAVE_SASL_H) #include #endif #ifdef HAVE_GSSAPI_H #include #elif defined(HAVE_GSSAPI_GSSAPI_KRB5_H) #include #include #endif #include "ldap-nss.h" #include "pagectrl.h" #include "common.h" #include "log.h" #include "cfg.h" #include "attmap.h" #include "compat/ldap.h" /* * LS_INIT only used for enumeration contexts */ #define LS_INIT(state) do { state.ls_type = LS_TYPE_INDEX; state.ls_retry = 0; state.ls_info.ls_index = -1; } while (0) /* * convenient wrapper around pointer into global config list, and a * connection to an LDAP server. */ struct ldap_session { /* the connection */ LDAP *ls_conn; /* timestamp of last activity */ time_t ls_timestamp; /* has session been connected? */ int is_connected; /* index into ldc_uris: currently connected LDAP uri */ int ls_current_uri; }; MYLDAP_SESSION *myldap_create_session(void) { MYLDAP_SESSION *session; /* allocate memory for the session storage */ session=(struct ldap_session *)malloc(sizeof(struct ldap_session)); if (session==NULL) { log_log(LOG_CRIT,"malloc() failed to allocate memory"); exit(EXIT_FAILURE); } /* initialize the session */ session->ls_conn=NULL; session->ls_timestamp=0; session->is_connected=0; session->ls_current_uri=0; /* return the new session */ return session; } static enum nss_status do_map_error(int rc) { switch (rc) { case LDAP_SUCCESS: case LDAP_SIZELIMIT_EXCEEDED: case LDAP_TIMELIMIT_EXCEEDED: return NSS_STATUS_SUCCESS; break; case LDAP_NO_SUCH_ATTRIBUTE: case LDAP_UNDEFINED_TYPE: case LDAP_INAPPROPRIATE_MATCHING: case LDAP_CONSTRAINT_VIOLATION: case LDAP_TYPE_OR_VALUE_EXISTS: case LDAP_INVALID_SYNTAX: case LDAP_NO_SUCH_OBJECT: case LDAP_ALIAS_PROBLEM: case LDAP_INVALID_DN_SYNTAX: case LDAP_IS_LEAF: case LDAP_ALIAS_DEREF_PROBLEM: case LDAP_FILTER_ERROR: return NSS_STATUS_NOTFOUND; break; case LDAP_SERVER_DOWN: case LDAP_TIMEOUT: case LDAP_UNAVAILABLE: case LDAP_BUSY: #ifdef LDAP_CONNECT_ERROR case LDAP_CONNECT_ERROR: #endif /* LDAP_CONNECT_ERROR */ case LDAP_LOCAL_ERROR: case LDAP_INVALID_CREDENTIALS: default: return NSS_STATUS_UNAVAIL; } } static int do_sasl_interact(LDAP UNUSED(*ld),unsigned UNUSED(flags),void *defaults,void *_interact) { char *authzid=(char *)defaults; sasl_interact_t *interact=(sasl_interact_t *)_interact; while (interact->id!=SASL_CB_LIST_END) { if (interact->id!=SASL_CB_USER) return LDAP_PARAM_ERROR; if (authzid!=NULL) { interact->result=authzid; interact->len=strlen(authzid); } else if (interact->defresult!=NULL) { interact->result=interact->defresult; interact->len=strlen(interact->defresult); } else { interact->result=""; interact->len=0; } interact++; } return LDAP_SUCCESS; } /* this returns an LDAP result code */ static int do_bind(MYLDAP_SESSION *session) { int rc; char *binddn,*bindarg; int usesasl; /* * If we're running as root, let us bind as a special * user, so we can fake shadow passwords. */ /* TODO: store this information in the session */ if ((geteuid()==0)&&(nslcd_cfg->ldc_rootbinddn!=NULL)) { binddn=nslcd_cfg->ldc_rootbinddn; usesasl=nslcd_cfg->ldc_rootusesasl; bindarg=nslcd_cfg->ldc_rootusesasl?nslcd_cfg->ldc_rootsaslid:nslcd_cfg->ldc_rootbindpw; } else { binddn=nslcd_cfg->ldc_binddn; usesasl=nslcd_cfg->ldc_usesasl; bindarg=nslcd_cfg->ldc_usesasl?nslcd_cfg->ldc_saslid:nslcd_cfg->ldc_bindpw; } if (!usesasl) { /* do a simple bind */ log_log(LOG_DEBUG,"simple bind as %s",binddn); rc=ldap_simple_bind_s(session->ls_conn,binddn,bindarg); if (rc!=LDAP_SUCCESS) log_log(LOG_ERR,"ldap_simple_bind_s() failed: %s: %s",ldap_err2string(rc),strerror(errno)); return rc; } else { /* do a SASL bind */ log_log(LOG_DEBUG,"SASL bind as %s",binddn); if (nslcd_cfg->ldc_sasl_secprops!=NULL) { rc=ldap_set_option(session->ls_conn,LDAP_OPT_X_SASL_SECPROPS,(void *)nslcd_cfg->ldc_sasl_secprops); if (rc!=LDAP_SUCCESS) { log_log(LOG_ERR,"unable to set SASL security properties: %s",ldap_err2string(rc)); return -1; } } rc=ldap_sasl_interactive_bind_s(session->ls_conn,binddn,"GSSAPI",NULL,NULL, LDAP_SASL_QUIET, do_sasl_interact,(void *)bindarg); return rc; } } /* * This function is called by the LDAP library when chasing referrals. * It is configured with the ldap_set_rebind_proc() below. */ static int do_rebind(LDAP *UNUSED(ld),LDAP_CONST char UNUSED(*url), ber_tag_t UNUSED(request), ber_int_t UNUSED(msgid),void *arg) { return do_bind((MYLDAP_SESSION *)arg); } /* * Close the global session, sending an unbind. * Closes connection to the LDAP server. */ static void do_close(MYLDAP_SESSION *session) { log_log(LOG_DEBUG,"==> do_close"); if (session->ls_conn!=NULL) ldap_unbind(session->ls_conn); session->ls_conn=NULL; session->is_connected=0; log_log(LOG_DEBUG,"<== do_close"); } static int do_ssl_options(void) { /* TODO: save return value of ldap_set_option() and include it in the error message */ /* rand file */ if (nslcd_cfg->ldc_tls_randfile!=NULL) { if (ldap_set_option(NULL,LDAP_OPT_X_TLS_RANDOM_FILE, nslcd_cfg->ldc_tls_randfile)!=LDAP_SUCCESS) { log_log(LOG_ERR,"setting of LDAP_OPT_X_TLS_RANDOM_FILE failed"); return LDAP_OPERATIONS_ERROR; } } /* ca cert file */ if (nslcd_cfg->ldc_tls_cacertfile!=NULL) { if (ldap_set_option(NULL,LDAP_OPT_X_TLS_CACERTFILE, nslcd_cfg->ldc_tls_cacertfile)!=LDAP_SUCCESS) { log_log(LOG_ERR,"setting of LDAP_OPT_X_TLS_CACERTFILE failed"); return LDAP_OPERATIONS_ERROR; } } /* ca cert directory */ if (nslcd_cfg->ldc_tls_cacertdir!=NULL) { if (ldap_set_option(NULL,LDAP_OPT_X_TLS_CACERTDIR, nslcd_cfg->ldc_tls_cacertdir)!=LDAP_SUCCESS) { log_log(LOG_ERR,"setting of LDAP_OPT_X_TLS_CACERTDIR failed"); return LDAP_OPERATIONS_ERROR; } } /* require cert? */ if (nslcd_cfg->ldc_tls_checkpeer > -1) { if (ldap_set_option(NULL,LDAP_OPT_X_TLS_REQUIRE_CERT, &nslcd_cfg->ldc_tls_checkpeer)!=LDAP_SUCCESS) { log_log(LOG_ERR,"setting of LDAP_OPT_X_TLS_REQUIRE_CERT failed"); return LDAP_OPERATIONS_ERROR; } } /* set cipher suite, certificate and private key: */ if (nslcd_cfg->ldc_tls_ciphers != NULL) { if (ldap_set_option(NULL,LDAP_OPT_X_TLS_CIPHER_SUITE, nslcd_cfg->ldc_tls_ciphers)!=LDAP_SUCCESS) { log_log(LOG_ERR,"setting of LDAP_OPT_X_TLS_CIPHER_SUITE failed"); return LDAP_OPERATIONS_ERROR; } } if (nslcd_cfg->ldc_tls_cert != NULL) { if (ldap_set_option(NULL,LDAP_OPT_X_TLS_CERTFILE, nslcd_cfg->ldc_tls_cert)!=LDAP_SUCCESS) { log_log(LOG_ERR,"setting of LDAP_OPT_X_TLS_CERTFILE failed"); return LDAP_OPERATIONS_ERROR; } } if (nslcd_cfg->ldc_tls_key != NULL) { if (ldap_set_option(NULL,LDAP_OPT_X_TLS_KEYFILE, nslcd_cfg->ldc_tls_key)!=LDAP_SUCCESS) { log_log(LOG_ERR,"setting of LDAP_OPT_X_TLS_KEYFILE failed"); return LDAP_OPERATIONS_ERROR; } } return LDAP_SUCCESS; } /* * Opens connection to an LDAP server, sets all connection options * and binds to the server. This returns a simple (0/-1) status code. * TODO: this should return an LDAP error code */ static int do_open(MYLDAP_SESSION *session) { struct timeval tv; int rc; time_t current_time; int sd=-1; log_log(LOG_DEBUG,"do_open()"); /* check if the idle time for the connection has expired */ if (session->is_connected&&nslcd_cfg->ldc_idle_timelimit) { time(¤t_time); if ((session->ls_timestamp+nslcd_cfg->ldc_idle_timelimit)is_connected) { log_log(LOG_DEBUG,"do_open(): using cached session"); return 0; } /* we should build a new session now */ session->ls_conn=NULL; session->ls_timestamp=0; session->is_connected=0; /* open the connection */ rc=ldap_initialize(&(session->ls_conn),nslcd_cfg->ldc_uris[session->ls_current_uri]); if (rc!=LDAP_SUCCESS) { log_log(LOG_WARNING,"ldap_initialize(%s) failed: %s: %s", nslcd_cfg->ldc_uris[session->ls_current_uri], ldap_err2string(rc),strerror(errno)); return -1; } else if (session->ls_conn==NULL) { log_log(LOG_WARNING,"ldap_initialize() returned NULL"); return -1; } /* turn on debugging */ if (nslcd_cfg->ldc_debug) { ber_set_option(NULL,LBER_OPT_DEBUG_LEVEL,&nslcd_cfg->ldc_debug); ldap_set_option(NULL,LDAP_OPT_DEBUG_LEVEL,&nslcd_cfg->ldc_debug); } /* the rebind function that is called when chasing referrals, see http://publib.boulder.ibm.com/infocenter/iseries/v5r3/topic/apis/ldap_set_rebind_proc.htm http://www.openldap.org/software/man.cgi?query=ldap_set_rebind_proc&manpath=OpenLDAP+2.4-Release */ /* TODO: probably only set this if we should chase referrals */ ldap_set_rebind_proc(session->ls_conn,do_rebind,session); /* set the protocol version to use */ ldap_set_option(session->ls_conn,LDAP_OPT_PROTOCOL_VERSION,&nslcd_cfg->ldc_version); ldap_set_option(session->ls_conn,LDAP_OPT_DEREF,&nslcd_cfg->ldc_deref); ldap_set_option(session->ls_conn,LDAP_OPT_TIMELIMIT,&nslcd_cfg->ldc_timelimit); tv.tv_sec=nslcd_cfg->ldc_bind_timelimit; tv.tv_usec=0; ldap_set_option(session->ls_conn,LDAP_OPT_TIMEOUT,&tv); ldap_set_option(session->ls_conn,LDAP_OPT_NETWORK_TIMEOUT,&tv); ldap_set_option(session->ls_conn,LDAP_OPT_REFERRALS,nslcd_cfg->ldc_referrals?LDAP_OPT_ON:LDAP_OPT_OFF); ldap_set_option(session->ls_conn,LDAP_OPT_RESTART,nslcd_cfg->ldc_restart?LDAP_OPT_ON:LDAP_OPT_OFF); /* if SSL is desired, then enable it */ if (nslcd_cfg->ldc_ssl_on==SSL_LDAPS) { int tls=LDAP_OPT_X_TLS_HARD; if (ldap_set_option(session->ls_conn,LDAP_OPT_X_TLS,&tls)!=LDAP_SUCCESS) { do_close(session); log_log(LOG_DEBUG,"<== do_open(TLS setup failed)"); return -1; } /* set up SSL context */ if (do_ssl_options()!=LDAP_SUCCESS) { do_close(session); log_log(LOG_DEBUG,"<== do_open(SSL setup failed)"); return -1; } } /* bind to the server */ rc=do_bind(session); if (rc!=LDAP_SUCCESS) { /* log actual LDAP error code */ log_log(LOG_WARNING,"failed to bind to LDAP server %s: %s: %s", nslcd_cfg->ldc_uris[session->ls_current_uri], ldap_err2string(rc),strerror(errno)); do_close(session); return -1; } /* disable keepalive on a LDAP connection socket */ if (ldap_get_option(session->ls_conn,LDAP_OPT_DESC,&sd)==0) { int off=0; /* ignore errors */ (void)setsockopt(sd,SOL_SOCKET,SO_KEEPALIVE,(void *)&off,sizeof(off)); (void)fcntl(sd,F_SETFD,FD_CLOEXEC); } /* update last activity and finish off state */ time(&(session->ls_timestamp)); session->is_connected=1; log_log(LOG_DEBUG,"do_open(): connected to %s",nslcd_cfg->ldc_uris[session->ls_current_uri]); return 0; } /* * Wrapper around ldap_result() to skip over search references * and deal transparently with the last entry. */ static enum nss_status do_result_async(struct ent_context *context) { int rc = LDAP_UNAVAILABLE; enum nss_status stat = NSS_STATUS_TRYAGAIN; struct timeval tv, *tvp; int parserc; LDAPControl **resultControls; if (nslcd_cfg->ldc_timelimit==LDAP_NO_LIMIT) tvp=NULL; else { tv.tv_sec=nslcd_cfg->ldc_timelimit; tv.tv_usec=0; tvp=&tv; } do { if (context->ec_res!=NULL) { ldap_msgfree(context->ec_res); context->ec_res=NULL; } rc=ldap_result(context->session->ls_conn,context->ec_msgid,LDAP_MSG_ONE,tvp,&(context->ec_res)); switch (rc) { case -1: case 0: if (ldap_get_option(context->session->ls_conn,LDAP_OPT_ERROR_NUMBER,&rc)!=LDAP_SUCCESS) rc=LDAP_UNAVAILABLE; log_log(LOG_ERR,"could not get LDAP result: %s",ldap_err2string(rc)); stat=NSS_STATUS_UNAVAIL; break; case LDAP_RES_SEARCH_ENTRY: stat=NSS_STATUS_SUCCESS; break; case LDAP_RES_SEARCH_RESULT: /* NB: this frees context->ec_res */ resultControls=NULL; context->ec_cookie=NULL; parserc=ldap_parse_result(context->session->ls_conn,context->ec_res,&rc,NULL, NULL,NULL,&resultControls,1); if ((parserc!=LDAP_SUCCESS)&&(parserc!=LDAP_MORE_RESULTS_TO_RETURN)) { stat = NSS_STATUS_UNAVAIL; ldap_abandon(context->session->ls_conn, context->ec_msgid); log_log(LOG_ERR,"could not get LDAP result: %s",ldap_err2string(rc)); } else if (resultControls!=NULL) { /* See if there are any more pages to come */ parserc=ldap_parse_page_control(context->session->ls_conn, resultControls,NULL, &(context->ec_cookie)); /* TODO: handle the above return code?? */ ldap_controls_free(resultControls); stat=NSS_STATUS_NOTFOUND; } else stat=NSS_STATUS_NOTFOUND; context->ec_res=NULL; context->ec_msgid=-1; break; default: stat = NSS_STATUS_UNAVAIL; break; } } while (rc==LDAP_RES_SEARCH_REFERENCE); /* update timestamp on success */ if (stat==NSS_STATUS_SUCCESS) time(&(context->session->ls_timestamp)); return stat; } /* * This function initializes an enumeration context. * * It could be done from the default constructor, under Solaris, but we * delay it until the setXXent() function is called. */ void _nss_ldap_ent_context_init(struct ent_context *context,MYLDAP_SESSION *session) { context->session=session; context->ec_cookie=NULL; context->ec_res=NULL; context->ec_msgid=-1; LS_INIT(context->ec_state); } /* * Clears a given context. */ void _nss_ldap_ent_context_cleanup(struct ent_context *context) { if (context==NULL) return; /* free read messages */ if (context->ec_res!=NULL) { ldap_msgfree(context->ec_res); context->ec_res=NULL; } /* abandon the search if there were more results to fetch */ if ((context->ec_msgid>-1)&&(do_result_async(context)==NSS_STATUS_SUCCESS)) { ldap_abandon(context->session->ls_conn,context->ec_msgid); context->ec_msgid=-1; } /* clean up cookie */ if (context->ec_cookie!=NULL) { ber_bvfree(context->ec_cookie); context->ec_cookie=NULL; } LS_INIT(context->ec_state); if (_nss_ldap_test_config_flag(NSS_LDAP_FLAGS_CONNECT_POLICY_ONESHOT)) do_close(context->session); } /* * Synchronous search function. Don't call this directly; * always wrap calls to this with do_with_reconnect(), or, * better still, use _nss_ldap_search(). */ static int do_search_sync( MYLDAP_SESSION *session,const char *base,int scope, const char *filter,const char **attrs,int sizelimit, LDAPMessage **res) { int rc; struct timeval tv, *tvp; ldap_set_option(session->ls_conn,LDAP_OPT_SIZELIMIT,(void *)&sizelimit); if (nslcd_cfg->ldc_timelimit==LDAP_NO_LIMIT) tvp=NULL; else { tv.tv_sec=nslcd_cfg->ldc_timelimit; tv.tv_usec=0; tvp=&tv; } rc=ldap_search_st(session->ls_conn,base,scope,filter,(char **)attrs,0,tvp,res); return rc; } /* * Asynchronous search function. Don't call this directly; * always wrap calls to this with do_with_reconnect(), or, * better still, use _nss_ldap_search(). */ static int do_search_async( MYLDAP_SESSION *session,const char *base,int scope, const char *filter,const char **attrs,int sizelimit,int *msgid) { int rc; LDAPControl *serverCtrls[2]; LDAPControl **pServerCtrls; if (nslcd_cfg->ldc_pagesize>0) { rc=ldap_create_page_control(session->ls_conn,nslcd_cfg->ldc_pagesize, NULL,0,&serverCtrls[0]); if (rc!=LDAP_SUCCESS) return rc; serverCtrls[1]=NULL; pServerCtrls=serverCtrls; } else pServerCtrls=NULL; rc=ldap_search_ext(session->ls_conn,base,scope,filter,(char **)attrs, 0,pServerCtrls,NULL,LDAP_NO_LIMIT,sizelimit,msgid); if (pServerCtrls!=NULL) { ldap_control_free(serverCtrls[0]); serverCtrls[0]=NULL; } return rc; } /* * Function to call either do_search_async() or do_search_sync() with * reconnection logic (depending on wheter res or msgid is not NULL). */ static enum nss_status do_with_reconnect( MYLDAP_SESSION *session,const char *base,int scope, const char *filter,const char **attrs,int sizelimit, LDAPMessage **res,int *msgid) { int rc=LDAP_UNAVAILABLE, tries=0, backoff=0; int hard=1, start_uri=0, log=0; enum nss_status stat=NSS_STATUS_UNAVAIL; int maxtries; log_log(LOG_DEBUG,"do_with_reconnect(base=\"%s\", scope=%d, filter=\"%s\")",base,scope,filter); /* get the maximum number of tries */ maxtries=nslcd_cfg->ldc_reconnect_tries; /* keep trying until we have success or a hard failure */ while ((stat==NSS_STATUS_UNAVAIL)&&(hard)&&(tries0) { if (backoff==0) backoff=nslcd_cfg->ldc_reconnect_sleeptime; else if (backoffldc_reconnect_maxsleeptime) backoff*=2; log_log(LOG_INFO,"reconnecting to LDAP server (sleeping %d seconds)...",backoff); (void)sleep(backoff); } /* for each "try", attempt to connect to all specified URIs */ start_uri=session->ls_current_uri; do { /* open a connection and do the search */ if (do_open(session)==0) { if (res!=NULL) { /* we're using the sycnhronous API */ stat=do_map_error(do_search_sync(session,base,scope,filter,attrs,sizelimit,res)); } else { /* we're using the asycnhronous API */ stat=do_map_error(do_search_async(session,base,scope,filter,attrs,sizelimit,msgid)); } /* if we got any feedback from the server, don't try other ones */ if (stat!=NSS_STATUS_UNAVAIL) break; } log++; /* the currently configured uri should exist */ assert(nslcd_cfg->ldc_uris[session->ls_current_uri]!=NULL); /* try the next URI (with wrap-around) */ session->ls_current_uri++; if (nslcd_cfg->ldc_uris[session->ls_current_uri]==NULL) session->ls_current_uri = 0; } while (session->ls_current_uri != start_uri); /* if we had reachability problems with the server close the connection */ /* TODO: we should probably close in the loop above */ if (stat==NSS_STATUS_UNAVAIL) { do_close(session); /* If a soft reconnect policy is specified, then do not * try to reconnect to the LDAP server if it is down. */ if (nslcd_cfg->ldc_reconnect_pol == LP_RECONNECT_SOFT) hard = 0; ++tries; } } switch (stat) { case NSS_STATUS_UNAVAIL: log_log(LOG_ERR,"could not search LDAP server - %s",ldap_err2string(rc)); return NSS_STATUS_UNAVAIL; case NSS_STATUS_TRYAGAIN: log_log(LOG_ERR,"could not %s %sconnect to LDAP server - %s", hard?"hard":"soft", tries?"re":"", ldap_err2string(rc)); return NSS_STATUS_UNAVAIL; case NSS_STATUS_SUCCESS: if (log) { char *uri=nslcd_cfg->ldc_uris[session->ls_current_uri]; if (uri==NULL) uri = "(null)"; if (tries) log_log(LOG_INFO,"reconnected to LDAP server %s after %d attempt%s", uri, tries,(tries == 1) ? "" : "s"); else log_log(LOG_INFO,"reconnected to LDAP server %s", uri); } /* update the last activity on the connection */ time(&session->ls_timestamp); return NSS_STATUS_SUCCESS; case NSS_STATUS_NOTFOUND: case NSS_STATUS_RETURN: default: return stat; } } /* * Parse, fetching reuslts from chain instead of server. */ static enum nss_status do_parse_sync( struct ent_context *context,void *result, char *buffer,size_t buflen,parser_t parser) { enum nss_status parseStat=NSS_STATUS_NOTFOUND; LDAPMessage *e=NULL; log_log(LOG_DEBUG,"==> do_parse_sync"); /* * if ec_state.ls_info.ls_index is non-zero, then we don't collect another * entry off the LDAP chain, and instead refeed the existing result to * the parser. Once the parser has finished with it, it will return * NSS_STATUS_NOTFOUND and reset the index to -1, at which point we'll retrieve * another entry. */ do { if ((context->ec_state.ls_retry==0) && ( (context->ec_state.ls_type==LS_TYPE_KEY) || (context->ec_state.ls_info.ls_index==-1) )) { if (e==NULL) e=ldap_first_entry(context->session->ls_conn,context->ec_res); else e=ldap_next_entry(context->session->ls_conn,e); } if (e==NULL) { /* Could not get a result; bail */ parseStat=NSS_STATUS_NOTFOUND; break; } /* * We have an entry; now, try to parse it. * * If we do not parse the entry because of a schema * violation, the parser should return NSS_STATUS_NOTFOUND. * We'll keep on trying subsequent entries until we * find one which is parseable, or exhaust avialable * entries, whichever is first. */ parseStat=parser(context->session,e,&(context->ec_state),result,buffer,buflen); /* hold onto the state if we're out of memory XXX */ context->ec_state.ls_retry=(parseStat==NSS_STATUS_TRYAGAIN)&&(buffer!=NULL); } while (parseStat==NSS_STATUS_NOTFOUND); log_log(LOG_DEBUG,"<== do_parse_sync"); return parseStat; } /* * Simple wrapper around ldap_get_values(). Requires that * session is already established. */ char **_nss_ldap_get_values(MYLDAP_SESSION *session,LDAPMessage *e, const char *attr) { if (!session->is_connected) return NULL; assert(session->ls_conn!=NULL); return ldap_get_values(session->ls_conn,e,attr); } /* translates a nslcd return code (as defined in nslcd.h) to a nss code (as defined in nss.h) */ /* FIXME: this is a temporary hack, get rid of it */ static int nss2nslcd(enum nss_status code) { switch (code) { case NSS_STATUS_UNAVAIL: return NSLCD_RESULT_UNAVAIL; case NSS_STATUS_NOTFOUND: return NSLCD_RESULT_NOTFOUND; case NSS_STATUS_SUCCESS: return NSLCD_RESULT_SUCCESS; /* case NSS_STATUS_TRYAGAIN: return NSLCD_RS_SMALLBUF; */ default: return NSLCD_RESULT_UNAVAIL; } } /* * Internal entry point for enumeration routines. * This should really use the asynchronous LDAP search API to avoid * pulling down all the entries at once, particularly if the * enumeration is not completed. */ int _nss_ldap_getent( struct ent_context *context,void *result,char *buffer,size_t buflen, const char *base,int scope,const char *filter,const char **attrs, parser_t parser) { enum nss_status stat=NSS_STATUS_SUCCESS; int msgid=-1; log_log(LOG_DEBUG,"_nss_ldap_getent(base=\"%s\", filter=\"%s\")",base,filter); /* if context->ec_msgid < 0, then we haven't searched yet */ if (context->ec_msgid<0) { /* set up a new search */ stat=do_with_reconnect(context->session,base,scope,filter,attrs,LDAP_NO_LIMIT,NULL,&msgid); if (stat != NSS_STATUS_SUCCESS) return nss2nslcd(stat); context->ec_msgid=msgid; } /* try to parse results until we have a final error or ok */ while (1) { /* * Tries parser function "parser" on entries, calling do_result_async() * to retrieve them from the LDAP server until one parses * correctly or there is an exceptional condition. */ stat=NSS_STATUS_NOTFOUND; /* * if ec_state.ls_info.ls_index is non-zero, then we don't collect another * entry off the LDAP chain, and instead refeed the existing result to * the parser. Once the parser has finished with it, it will return * NSS_STATUS_NOTFOUND and reset the index to -1, at which point we'll retrieve * another entry. */ do { enum nss_status resultStat=NSS_STATUS_SUCCESS; /* get an entry from the LDAP server */ if ((context->ec_state.ls_retry==0) && ( (context->ec_state.ls_type==LS_TYPE_KEY) || (context->ec_state.ls_info.ls_index==-1) )) { resultStat=do_result_async(context); if (resultStat!=NSS_STATUS_SUCCESS) { stat=resultStat; break; } } /* * We have an entry; now, try to parse it. * * If we do not parse the entry because of a schema * violation, the parser should return NSS_STATUS_NOTFOUND. * We'll keep on trying subsequent entries until we * find one which is parseable, or exhaust avialable * entries, whichever is first. */ stat=parser(context->session,context->ec_res,&(context->ec_state),result,buffer,buflen); /* hold onto the state if we're out of memory XXX */ context->ec_state.ls_retry=(stat==NSS_STATUS_TRYAGAIN)&&(buffer!=NULL); /* free entry is we're moving on */ if ((context->ec_state.ls_retry==0) && ( (context->ec_state.ls_type==LS_TYPE_KEY) || (context->ec_state.ls_info.ls_index==-1) )) { /* we don't need the result anymore, ditch it. */ ldap_msgfree(context->ec_res); context->ec_res=NULL; } } while (stat==NSS_STATUS_NOTFOUND); /* if this had no more results, try the next page */ if ((stat==NSS_STATUS_NOTFOUND)&&(context->ec_cookie!=NULL)&&(context->ec_cookie->bv_len!=0)) { LDAPControl *serverctrls[2]={ NULL, NULL }; stat=ldap_create_page_control(context->session->ls_conn, nslcd_cfg->ldc_pagesize, context->ec_cookie,0,&serverctrls[0]); if (stat!=LDAP_SUCCESS) return NSS_STATUS_UNAVAIL; stat=ldap_search_ext(context->session->ls_conn, base,scope,filter, (char **)attrs,0,serverctrls,NULL,LDAP_NO_LIMIT, LDAP_NO_LIMIT,&msgid); ldap_control_free(serverctrls[0]); if (msgid<0) return nss2nslcd(NSS_STATUS_UNAVAIL); context->ec_msgid=msgid; } else return nss2nslcd(stat); } } /* * General match function. */ int _nss_ldap_getbyname(MYLDAP_SESSION *session,void *result, char *buffer, size_t buflen, const char *base,int scope,const char *filter,const char **attrs, parser_t parser) { enum nss_status stat = NSS_STATUS_NOTFOUND; struct ent_context context; log_log(LOG_DEBUG,"_nss_ldap_getbyname(base=\"%s\", filter=\"%s\"",base,filter); _nss_ldap_ent_context_init(&context,session); /* synchronous search */ stat=do_with_reconnect(context.session,base,scope,filter,attrs,1,&context.ec_res,NULL); if (stat!=NSS_STATUS_SUCCESS) return nss2nslcd(stat); /* * we pass this along for the benefit of the services parser, * which uses it to figure out which protocol we really wanted. * we only pass the second argument along, as that's what we need * in services. */ LS_INIT(context.ec_state); context.ec_state.ls_type=LS_TYPE_KEY; context.ec_state.ls_info.ls_key=NULL /*was: args->la_arg2.la_string*/; stat=do_parse_sync(&context,result,buffer,buflen,parser); _nss_ldap_ent_context_cleanup(&context); return nss2nslcd(stat); } /* * These functions are called from within the parser, where it is assumed * to be safe to use the connection and the respective message. */ /* * Assign all values, bar omitvalue (if not NULL), to *valptr. */ enum nss_status _nss_ldap_assign_attrvals( MYLDAP_SESSION *session, LDAPMessage *e,const char *attr,const char *omitvalue, char ***valptr,char **pbuffer,size_t *pbuflen,size_t *pvalcount) { char **vals; char **valiter; size_t valcount; char **p=NULL; size_t buflen=*pbuflen; char *buffer=*pbuffer; if (pvalcount!=NULL) *pvalcount=0; if (session->ls_conn==NULL) return NSS_STATUS_UNAVAIL; vals=_nss_ldap_get_values(session,e,attr); valcount=(vals==NULL)?0:ldap_count_values(vals); if (bytesleft(buffer,buflen,char *)<(valcount+1)*sizeof(char *)) { ldap_value_free(vals); return NSS_STATUS_TRYAGAIN; } align(buffer,buflen,char *); p=*valptr=(char **)buffer; buffer+=(valcount+1)*sizeof(char *); buflen-=(valcount+1)*sizeof(char *); if (valcount==0) { *p = NULL; *pbuffer=buffer; *pbuflen=buflen; return NSS_STATUS_SUCCESS; } valiter=vals; while (*valiter!=NULL) { size_t vallen; char *elt = NULL; if ((omitvalue!=NULL)&&(strcmp(*valiter,omitvalue)==0)) valcount--; else { vallen=strlen(*valiter); if (buflen<(vallen+1)) { ldap_value_free(vals); return NSS_STATUS_TRYAGAIN; } /* copy this value into the next block of buffer space */ elt=buffer; buffer+=vallen+1; buflen-=vallen+1; strncpy(elt,*valiter,vallen); elt[vallen]='\0'; *p=elt; p++; } valiter++; } *p=NULL; *pbuffer=buffer; *pbuflen=buflen; if (pvalcount!=NULL) *pvalcount=valcount; ldap_value_free(vals); return NSS_STATUS_SUCCESS; } /* Assign a single value to *valptr. */ enum nss_status _nss_ldap_assign_attrval( MYLDAP_SESSION *session,LDAPMessage *e,const char *attr,char **valptr, char **buffer,size_t *buflen) { char **vals; int vallen; if (session->ls_conn==NULL) return NSS_STATUS_UNAVAIL; vals=_nss_ldap_get_values(session,e,attr); if (vals==NULL) return NSS_STATUS_NOTFOUND; vallen=strlen(*vals); if (*buflen<(size_t)(vallen+1)) { ldap_value_free(vals); return NSS_STATUS_TRYAGAIN; } *valptr=*buffer; strncpy(*valptr,*vals,vallen); (*valptr)[vallen]='\0'; *buffer+=vallen + 1; *buflen-=vallen + 1; ldap_value_free(vals); return NSS_STATUS_SUCCESS; } static const char *_nss_ldap_locate_userpassword(char **vals) { const char *token=NULL; size_t token_length=0; char **valiter; const char *pwd=NULL; if (nslcd_cfg!=NULL) { switch (nslcd_cfg->ldc_password_type) { case LU_RFC2307_USERPASSWORD: token = "{CRYPT}"; token_length = sizeof("{CRYPT}") - 1; break; case LU_RFC3112_AUTHPASSWORD: token = "CRYPT$"; token_length = sizeof("CRYPT$") - 1; break; case LU_OTHER_PASSWORD: default: break; } } if (vals!=NULL) { for (valiter=vals;*valiter!=NULL;valiter++) { if (token_length==0 || strncasecmp(*valiter,token,token_length)==0) { pwd=*valiter; break; } } } if (pwd==NULL) pwd="*"; else pwd+=token_length; return pwd; } /* * Assign a single value to *valptr, after examining userPassword for * a syntactically suitable value. */ enum nss_status _nss_ldap_assign_userpassword( MYLDAP_SESSION *session, LDAPMessage *e,const char *attr,char **valptr, char **buffer,size_t *buflen) { char **vals; const char *pwd; int vallen; log_log(LOG_DEBUG,"==> _nss_ldap_assign_userpassword"); if (session->ls_conn==NULL) return NSS_STATUS_UNAVAIL; vals=_nss_ldap_get_values(session,e,attr); pwd=_nss_ldap_locate_userpassword(vals); vallen=strlen(pwd); if (*buflen<(size_t)(vallen+1)) { if (vals!=NULL) ldap_value_free(vals); log_log(LOG_DEBUG,"<== _nss_ldap_assign_userpassword"); return NSS_STATUS_TRYAGAIN; } *valptr=*buffer; strncpy(*valptr,pwd,vallen); (*valptr)[vallen]='\0'; *buffer+=vallen+1; *buflen-=vallen+1; if (vals!=NULL) ldap_value_free(vals); log_log(LOG_DEBUG,"<== _nss_ldap_assign_userpassword"); return NSS_STATUS_SUCCESS; } int has_objectclass(MYLDAP_SESSION *session,LDAPMessage *entry,const char *objectclass) { char **vals; int i; LDAP *ld; ld=session->ls_conn; if (ld==NULL) return 0; vals=_nss_ldap_get_values(session,entry,"objectClass"); if (vals==NULL) return 0; for (i=0;vals[i]!=NULL;i++) { if (strcasecmp(vals[i],objectclass)==0) { ldap_value_free(vals); return -1; } } ldap_value_free(vals); return 0; } static enum nss_status do_getrdnvalue( const char *dn,const char *rdntype, char **rval,char **buffer,size_t *buflen) { char **exploded_dn; char *rdnvalue=NULL; char rdnava[64]; size_t rdnlen=0,rdnavalen; snprintf(rdnava,sizeof(rdnava),"%s=",rdntype); rdnavalen=strlen(rdnava); exploded_dn=ldap_explode_dn(dn,0); if (exploded_dn!=NULL) { /* * attempt to get the naming attribute's principal * value by parsing the RDN. We need to support * multivalued RDNs (as they're essentially mandated * for services) */ char **p, **exploded_rdn; exploded_rdn=ldap_explode_rdn(*exploded_dn,0); if (exploded_rdn!=NULL) { for (p=exploded_rdn;*p!=NULL;p++) { if (strncasecmp(*p,rdnava,rdnavalen) == 0) { char *r=*p+rdnavalen; rdnlen=strlen(r); if (*buflen<=rdnlen) { ldap_value_free(exploded_rdn); ldap_value_free(exploded_dn); return NSS_STATUS_TRYAGAIN; } rdnvalue=*buffer; strncpy(rdnvalue,r,rdnlen); break; } } ldap_value_free(exploded_rdn); } } if (exploded_dn!=NULL) ldap_value_free (exploded_dn); if (rdnvalue!=NULL) return NSS_STATUS_NOTFOUND; rdnvalue[rdnlen]='\0'; *buffer+=rdnlen+1; *buflen-=rdnlen+1; *rval=rdnvalue; return NSS_STATUS_SUCCESS; } enum nss_status _nss_ldap_getrdnvalue( MYLDAP_SESSION *session,LDAPMessage *entry,const char *rdntype, char **rval,char **buffer,size_t *buflen) { char *dn; enum nss_status status; size_t rdnlen; dn=ldap_get_dn(session->ls_conn,entry); if (dn==NULL) return NSS_STATUS_NOTFOUND; status=do_getrdnvalue(dn,rdntype,rval,buffer,buflen); ldap_memfree(dn); /* * If examining the DN failed, then pick the nominal first * value of cn as the canonical name (recall that attributes * are sets, not sequences) */ if (status==NSS_STATUS_NOTFOUND) { char **vals; vals=_nss_ldap_get_values(session,entry,rdntype); if (vals != NULL) { rdnlen = strlen (*vals); if (*buflen > rdnlen) { char *rdnvalue = *buffer; strncpy (rdnvalue, *vals, rdnlen); rdnvalue[rdnlen] = '\0'; *buffer += rdnlen + 1; *buflen -= rdnlen + 1; *rval = rdnvalue; status = NSS_STATUS_SUCCESS; } else status=NSS_STATUS_TRYAGAIN; ldap_value_free (vals); } } return status; } int myldap_escape(const char *src,char *buffer,size_t buflen) { size_t pos=0; /* go over all characters in source string */ for (;*src!='\0';src++) { /* check if char will fit */ if (pos>=(buflen+4)) return -1; /* do escaping for some characters */ switch (*src) { case '*': strcpy(buffer+pos,"\\2a"); pos+=3; break; case '(': strcpy(buffer+pos,"\\28"); pos+=3; break; case ')': strcpy(buffer+pos,"\\29"); pos+=3; break; case '\\': strcpy(buffer+pos,"\\5c"); pos+=3; break; default: /* just copy character */ buffer[pos++]=*src; break; } } /* terminate destination string */ buffer[pos]='\0'; return 0; }