1 /*
2    myldap.c - simple interface to do LDAP requests
3    Parts of this file were part of the nss_ldap library (as ldap-nss.c)
4    which has been forked into the nss-pam-ldapd library.
5 
6    Copyright (C) 1997-2006 Luke Howard
7    Copyright (C) 2006-2007 West Consulting
8    Copyright (C) 2006-2017 Arthur de Jong
9 
10    This library is free software; you can redistribute it and/or
11    modify it under the terms of the GNU Lesser General Public
12    License as published by the Free Software Foundation; either
13    version 2.1 of the License, or (at your option) any later version.
14 
15    This library is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18    Lesser General Public License for more details.
19 
20    You should have received a copy of the GNU Lesser General Public
21    License along with this library; if not, write to the Free Software
22    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23    02110-1301 USA
24 */
25 
26 /*
27    This library expects to use an LDAP library to provide the real
28    functionality and only provides a convenient wrapper.
29    Some pointers for more information on the LDAP API:
30      http://tools.ietf.org/id/draft-ietf-ldapext-ldap-c-api-05.txt
31      http://www.mozilla.org/directory/csdk-docs/function.htm
32      http://publib.boulder.ibm.com/infocenter/iseries/v5r3/topic/apis/dirserv1.htm
33      http://www.openldap.org/software/man.cgi?query=ldap
34 */
35 
36 #include "config.h"
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <unistd.h>
41 #include <string.h>
42 #include <strings.h>
43 #include <sys/time.h>
44 #include <time.h>
45 #include <sys/types.h>
46 #include <sys/socket.h>
47 #include <errno.h>
48 #include <lber.h>
49 #include <ldap.h>
50 #ifdef HAVE_LDAP_SSL_H
51 #include <ldap_ssl.h>
52 #endif
53 #ifdef HAVE_GSSLDAP_H
54 #include <gssldap.h>
55 #endif
56 #ifdef HAVE_GSSSASL_H
57 #include <gsssasl.h>
58 #endif
59 #ifdef HAVE_SASL_SASL_H
60 #include <sasl/sasl.h>
61 #endif
62 #ifdef HAVE_SASL_H
63 #include <sasl.h>
64 #endif
65 #include <ctype.h>
66 #include <pthread.h>
67 #include <stdarg.h>
68 
69 #include "myldap.h"
70 #include "common.h"
71 #include "log.h"
72 #include "cfg.h"
73 #include "common/set.h"
74 #include "compat/ldap_compat.h"
75 #include "attmap.h"
76 
77 /* the maximum number of searches per session */
78 #define MAX_SEARCHES_IN_SESSION 4
79 
80 /* the maximum number of dn's to log to the debug log for each search */
81 #define MAX_DEBUG_LOG_DNS 10
82 
83 /* a fake scope that is used to not perform an actual search but only
84    simulate the handling of the search (used for authentication) */
85 #define MYLDAP_SCOPE_BINDONLY 0x1972  /* magic number: should never be a real scope */
86 
87 /* This refers to a current LDAP session that contains the connection
88    information. */
89 struct ldap_session {
90   /* the connection */
91   LDAP *ld;
92   /* timestamp of last activity */
93   time_t lastactivity;
94   /* index into uris: currently connected LDAP uri */
95   int current_uri;
96   /* a list of searches registered with this session */
97   struct myldap_search *searches[MAX_SEARCHES_IN_SESSION];
98   /* the username to bind with */
99   char binddn[BUFLEN_DN];
100   /* the password to bind with if any */
101   char bindpw[BUFLEN_PASSWORD];
102   /* the authentication result (NSLCD_PAM_* code) */
103   int policy_response;
104   /* the authentication message */
105   char policy_message[BUFLEN_MESSAGE];
106 };
107 
108 /* A search description set as returned by myldap_search(). */
109 struct myldap_search {
110   /* reference to the session */
111   MYLDAP_SESSION *session;
112   /* indicator that the search is still valid */
113   int valid;
114   /* the parameters describing the search */
115   const char *base;
116   int scope;
117   const char *filter;
118   char **attrs;
119   /* a pointer to the current result entry, used for
120      freeing resource allocated with that entry */
121   MYLDAP_ENTRY *entry;
122   /* LDAP message id for the search, -1 indicates absence of an active search */
123   int msgid;
124   /* the last result that was returned by ldap_result() */
125   LDAPMessage *msg;
126   /* cookie for paged searches */
127   struct berval *cookie;
128   /* to indicate that we can retry the search from myldap_get_entry() */
129   int may_retry_search;
130   /* the number of results returned so far */
131   int count;
132 };
133 
134 /* The maximum number of calls to myldap_get_values() that may be
135    done per returned entry. */
136 #define MAX_ATTRIBUTES_PER_ENTRY 16
137 
138 /* The maximum number of buffers (used for ranged attribute values and
139    values returned by bervalues_to_values()) that may be stored per entry. */
140 #define MAX_BUFFERS_PER_ENTRY 8
141 
142 /* A single entry from the LDAP database as returned by
143    myldap_get_entry(). */
144 struct myldap_entry {
145   /* reference to the search to be used to get parameters
146      (e.g. LDAP connection) for other calls */
147   MYLDAP_SEARCH *search;
148   /* the DN */
149   const char *dn;
150   /* a cached version of the exploded rdn */
151   char **exploded_rdn;
152   /* a cache of attribute to value list */
153   char **attributevalues[MAX_ATTRIBUTES_PER_ENTRY];
154   /* a reference to buffers so we can free() them later on */
155   char **buffers[MAX_BUFFERS_PER_ENTRY];
156 };
157 
158 /* Flag to record first search operation */
159 int first_search = 1;
160 
myldap_err(int pri,LDAP * ld,int rc,const char * format,...)161 static void myldap_err(int pri, LDAP *ld, int rc, const char *format, ...)
162 {
163   char message[BUFLEN_MESSAGE];
164   char *msg_ldap = NULL;
165   char *msg_diag = NULL;
166   char *msg_errno = NULL;
167   va_list ap;
168   /* make the message */
169   va_start(ap, format);
170   vsnprintf(message, sizeof(message), format, ap);
171   message[sizeof(message) - 1] = '\0';
172   va_end(ap);
173   /* get the various error message */
174   if (rc != LDAP_SUCCESS)
175   {
176     msg_ldap = ldap_err2string(rc);
177     /* get the diagnostic information */
178 #ifdef LDAP_OPT_DIAGNOSTIC_MESSAGE
179     if (ld != NULL)
180       ldap_get_option(ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, &msg_diag);
181 #endif /* LDAP_OPT_DIAGNOSTIC_MESSAGE */
182   }
183   if (errno != 0)
184     msg_errno = strerror(errno);
185   /* log the message */
186   log_log(pri, "%s%s%s%s%s%s%s", message,
187           (msg_ldap == NULL) ? "" : ": ", (msg_ldap == NULL) ? "" : msg_ldap,
188           (msg_diag == NULL) ? "" : ": ", (msg_diag == NULL) ? "" : msg_diag,
189           (msg_errno == NULL) ? "" : ": ", (msg_errno == NULL) ? "" : msg_errno);
190   /* free diagnostic message */
191   if (msg_diag != NULL)
192     ldap_memfree(msg_diag);
193 }
194 
myldap_entry_new(MYLDAP_SEARCH * search)195 static MYLDAP_ENTRY *myldap_entry_new(MYLDAP_SEARCH *search)
196 {
197   MYLDAP_ENTRY *entry;
198   int i;
199   /* Note: as an alternative we could embed the myldap_entry into the
200      myldap_search struct to save on malloc() and free() calls. */
201   /* allocate new entry */
202   entry = (MYLDAP_ENTRY *)malloc(sizeof(struct myldap_entry));
203   if (entry == NULL)
204   {
205     log_log(LOG_CRIT, "myldap_entry_new(): malloc() failed to allocate memory");
206     exit(EXIT_FAILURE);
207   }
208   /* fill in fields */
209   entry->search = search;
210   entry->dn = NULL;
211   entry->exploded_rdn = NULL;
212   for (i = 0; i < MAX_ATTRIBUTES_PER_ENTRY; i++)
213     entry->attributevalues[i] = NULL;
214   for (i = 0; i < MAX_BUFFERS_PER_ENTRY; i++)
215     entry->buffers[i] = NULL;
216   /* return the fresh entry */
217   return entry;
218 }
219 
myldap_entry_free(MYLDAP_ENTRY * entry)220 static void myldap_entry_free(MYLDAP_ENTRY *entry)
221 {
222   int i;
223   /* free the DN */
224   if (entry->dn != NULL)
225     ldap_memfree((char *)entry->dn);
226   /* free the exploded RDN */
227   if (entry->exploded_rdn != NULL)
228     ldap_value_free(entry->exploded_rdn);
229   /* free all attribute values */
230   for (i = 0; i < MAX_ATTRIBUTES_PER_ENTRY; i++)
231     if (entry->attributevalues[i] != NULL)
232       ldap_value_free(entry->attributevalues[i]);
233   /* free all buffers */
234   for (i = 0; i < MAX_BUFFERS_PER_ENTRY; i++)
235     if (entry->buffers[i] != NULL)
236       free(entry->buffers[i]);
237   /* we don't need the result anymore, ditch it. */
238   ldap_msgfree(entry->search->msg);
239   entry->search->msg = NULL;
240   /* free the actual memory for the struct */
241   free(entry);
242 }
243 
myldap_search_new(MYLDAP_SESSION * session,const char * base,int scope,const char * filter,const char ** attrs)244 static MYLDAP_SEARCH *myldap_search_new(MYLDAP_SESSION *session,
245                                         const char *base, int scope,
246                                         const char *filter,
247                                         const char **attrs)
248 {
249   char *buffer;
250   MYLDAP_SEARCH *search;
251   int i;
252   size_t sz;
253   /* figure out size for new memory block to allocate
254      this has the advantage that we can free the whole lot with one call */
255   sz = sizeof(struct myldap_search);
256   sz += strlen(base) + 1 + strlen(filter) + 1;
257   for (i = 0; attrs[i] != NULL; i++)
258     sz += strlen(attrs[i]) + 1;
259   sz += (i + 1) * sizeof(char *);
260   /* allocate new results memory region */
261   buffer = (char *)malloc(sz);
262   if (buffer == NULL)
263   {
264     log_log(LOG_CRIT, "myldap_search_new(): malloc() failed to allocate memory");
265     exit(EXIT_FAILURE);
266   }
267   /* initialize struct */
268   search = (MYLDAP_SEARCH *)(void *)(buffer);
269   buffer += sizeof(struct myldap_search);
270   /* save pointer to session */
271   search->session = session;
272   /* flag as valid search */
273   search->valid = 1;
274   /* initialize array of attributes */
275   search->attrs = (char **)(void *)buffer;
276   buffer += (i + 1) * sizeof(char *);
277   /* copy base */
278   strcpy(buffer, base);
279   search->base = buffer;
280   buffer += strlen(base) + 1;
281   /* just plainly store scope */
282   search->scope = scope;
283   /* copy filter */
284   strcpy(buffer, filter);
285   search->filter = buffer;
286   buffer += strlen(filter) + 1;
287   /* copy attributes themselves */
288   for (i = 0; attrs[i] != NULL; i++)
289   {
290     strcpy(buffer, attrs[i]);
291     search->attrs[i] = buffer;
292     buffer += strlen(attrs[i]) + 1;
293   }
294   search->attrs[i] = NULL;
295   /* initialize context */
296   search->cookie = NULL;
297   search->msg = NULL;
298   search->msgid = -1;
299   search->may_retry_search = 1;
300   /* clear result entry */
301   search->entry = NULL;
302   search->count = 0;
303   /* return the new search struct */
304   return search;
305 }
306 
myldap_session_new(void)307 static MYLDAP_SESSION *myldap_session_new(void)
308 {
309   MYLDAP_SESSION *session;
310   int i;
311   /* allocate memory for the session storage */
312   session = (struct ldap_session *)malloc(sizeof(struct ldap_session));
313   if (session == NULL)
314   {
315     log_log(LOG_CRIT, "myldap_session_new(): malloc() failed to allocate memory");
316     exit(EXIT_FAILURE);
317   }
318   /* initialize the session */
319   session->ld = NULL;
320   session->lastactivity = 0;
321   session->current_uri = 0;
322   for (i = 0; i < MAX_SEARCHES_IN_SESSION; i++)
323     session->searches[i] = NULL;
324   session->binddn[0] = '\0';
325   memset(session->bindpw, 0, sizeof(session->bindpw));
326   session->bindpw[0] = '\0';
327   session->policy_response = NSLCD_PAM_SUCCESS;
328   session->policy_message[0] = '\0';
329   /* return the new session */
330   return session;
331 }
332 
is_valid_entry(MYLDAP_ENTRY * entry)333 PURE static inline int is_valid_entry(MYLDAP_ENTRY *entry)
334 {
335  return (entry != NULL) && (entry->search != NULL) &&
336         (entry->search->session != NULL) && (entry->search->session->ld != NULL) &&
337         (entry->search->msg != NULL);
338 }
339 
340 #ifdef HAVE_SASL_INTERACT_T
341 /* this is registered with ldap_sasl_interactive_bind_s() in do_bind() */
do_sasl_interact(LDAP UNUSED (* ld),unsigned UNUSED (flags),void * defaults,void * _interact)342 static int do_sasl_interact(LDAP UNUSED(*ld), unsigned UNUSED(flags),
343                             void *defaults, void *_interact)
344 {
345   struct ldap_config *cfg = defaults;
346   sasl_interact_t *interact = _interact;
347   while (interact->id != SASL_CB_LIST_END)
348   {
349     switch (interact->id)
350     {
351       case SASL_CB_GETREALM:
352         if (cfg->sasl_realm)
353         {
354           log_log(LOG_DEBUG, "do_sasl_interact(): returning sasl_realm \"%s\"",
355                   cfg->sasl_realm);
356           interact->result = cfg->sasl_realm;
357           interact->len = strlen(cfg->sasl_realm);
358         }
359         else
360           log_log(LOG_DEBUG, "do_sasl_interact(): were asked for sasl_realm but we don't have any");
361         break;
362       case SASL_CB_AUTHNAME:
363         if (cfg->sasl_authcid)
364         {
365           log_log(LOG_DEBUG, "do_sasl_interact(): returning sasl_authcid \"%s\"",
366                   cfg->sasl_authcid);
367           interact->result = cfg->sasl_authcid;
368           interact->len = strlen(cfg->sasl_authcid);
369         }
370         else
371           log_log(LOG_DEBUG, "do_sasl_interact(): were asked for sasl_authcid but we don't have any");
372         break;
373       case SASL_CB_USER:
374         if (cfg->sasl_authzid)
375         {
376           log_log(LOG_DEBUG, "do_sasl_interact(): returning sasl_authzid \"%s\"",
377                   cfg->sasl_authzid);
378           interact->result = cfg->sasl_authzid;
379           interact->len = strlen(cfg->sasl_authzid);
380         }
381         else
382           log_log(LOG_DEBUG, "do_sasl_interact(): were asked for sasl_authzid but we don't have any");
383         break;
384       case SASL_CB_PASS:
385         if (cfg->bindpw)
386         {
387           log_log(LOG_DEBUG, "do_sasl_interact(): returning bindpw \"***\"");
388           interact->result = cfg->bindpw;
389           interact->len = strlen(cfg->bindpw);
390         }
391         else
392           log_log(LOG_DEBUG, "do_sasl_interact(): were asked for bindpw but we don't have any");
393         break;
394       default:
395         /* just ignore */
396         break;
397     }
398     interact++;
399   }
400   return LDAP_SUCCESS;
401 }
402 #endif /* HAVE_SASL_INTERACT_T */
403 
404 #define LDAP_SET_OPTION(ld, option, invalue)                                \
405   rc = ldap_set_option(ld, option, invalue);                                \
406   if (rc != LDAP_SUCCESS)                                                   \
407   {                                                                         \
408     myldap_err(LOG_ERR, ld, rc, "ldap_set_option(" #option ") failed");     \
409     return rc;                                                              \
410   }
411 
412 #if defined(HAVE_LDAP_SASL_BIND) && defined(LDAP_SASL_SIMPLE)
print_ppolicy_expiry(MYLDAP_SESSION * session,unsigned int sec)413 static void print_ppolicy_expiry(MYLDAP_SESSION *session, unsigned int sec)
414 {
415   unsigned int days = 0;
416   unsigned int hours = 0;
417   unsigned int minutes = 0;
418   /* return this warning so PAM can present it to the user */
419   if (strlen(session->policy_message) != 0)
420     return;
421   if (sec > 24 * 3600)
422   {
423     days = sec / (24 * 3600);
424     sec -= days * 24 * 3600;
425   }
426   if (sec > 3600)
427   {
428     hours = sec / 3600;
429     sec -= (hours * 3600);
430   }
431   if (sec > 60)
432   {
433     minutes = sec / 60;
434     sec -= minutes * 60;
435   }
436   if (days > 1)
437     mysnprintf(session->policy_message, sizeof(session->policy_message),
438                "Password will expire in %u days", days);
439   else if (days > 0)
440     mysnprintf(session->policy_message, sizeof(session->policy_message),
441                "Password will expire in %u hours", hours + 24);
442   else if (hours > 1)
443   {
444     if (minutes > 1)
445       mysnprintf(session->policy_message, sizeof(session->policy_message),
446                  "Password will expire in %u hours and %u minutes",
447                  hours, minutes);
448     else
449       mysnprintf(session->policy_message, sizeof(session->policy_message),
450                  "Password will expire in %u hours", hours);
451   }
452   else if (hours > 0)
453     mysnprintf(session->policy_message, sizeof(session->policy_message),
454                "Password will expire in %u minutes", minutes + 60);
455   else if (minutes > 1)
456   {
457     if (sec > 1)
458       mysnprintf(session->policy_message, sizeof(session->policy_message),
459                  "Password will expire in %u minutes and %u seconds",
460                  minutes, sec);
461     else
462       mysnprintf(session->policy_message, sizeof(session->policy_message),
463                  "Password will expire in %u minutes", minutes);
464   }
465   else
466     mysnprintf(session->policy_message, sizeof(session->policy_message),
467                "Password will expire in %u seconds", sec);
468 }
469 
handle_ppolicy_controls(MYLDAP_SESSION * session,LDAP * ld,LDAPControl ** ctrls)470 static void handle_ppolicy_controls(MYLDAP_SESSION *session, LDAP *ld, LDAPControl **ctrls)
471 {
472   int i;
473   int rc;
474   /* clear policy response information in session */
475   session->policy_response = NSLCD_PAM_SUCCESS;
476   strncpy(session->policy_message, "", sizeof(session->policy_message));
477   for (i = 0; ctrls[i] != NULL; i++)
478   {
479     if (strcmp(ctrls[i]->ldctl_oid, LDAP_CONTROL_PWEXPIRED) == 0)
480     {
481       /* check for expired control: force the user to change their password */
482       log_log(LOG_DEBUG, "got LDAP_CONTROL_PWEXPIRED (password expired, user should change)");
483       if (session->policy_response == NSLCD_PAM_SUCCESS)
484         session->policy_response = NSLCD_PAM_NEW_AUTHTOK_REQD;
485     }
486     else if (strcmp(ctrls[i]->ldctl_oid, LDAP_CONTROL_PWEXPIRING) == 0)
487     {
488       /* check for password expiration warning control: the password is about
489          to expire (returns the number of seconds remaining until the password
490          expires) */
491       char seconds[32];
492       long int sec;
493       mysnprintf(seconds, sizeof(seconds), "%.*s", (int)ctrls[i]->ldctl_value.bv_len,
494                  ctrls[i]->ldctl_value.bv_val);
495       sec = atol(seconds);
496       log_log(LOG_DEBUG, "got LDAP_CONTROL_PWEXPIRING (password will expire in %ld seconds)",
497               sec);
498       print_ppolicy_expiry(session, (unsigned int)sec);
499     }
500     else if (strcmp(ctrls[i]->ldctl_oid, LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0)
501     {
502       /* check for password policy control */
503       int expire = 0, grace = 0;
504       LDAPPasswordPolicyError error = -1;
505       rc = ldap_parse_passwordpolicy_control(ld, ctrls[i], &expire, &grace, &error);
506       if (rc != LDAP_SUCCESS)
507         myldap_err(LOG_WARNING, ld, rc, "ldap_parse_passwordpolicy_control() failed (ignored)");
508       else
509       {
510         /* log returned control information */
511         log_log(LOG_DEBUG, "got LDAP_CONTROL_PASSWORDPOLICYRESPONSE (%s)",
512                 ldap_passwordpolicy_err2txt(error));
513         if (expire >= 0)
514           log_log(LOG_DEBUG, "got LDAP_CONTROL_PASSWORDPOLICYRESPONSE (password will expire in %d seconds)",
515                   expire);
516         if (grace >= 0)
517           log_log(LOG_DEBUG, "got LDAP_CONTROL_PASSWORDPOLICYRESPONSE (%d grace logins left)",
518                   grace);
519         /* return this information to PAM */
520         if ((error == PP_passwordExpired) &&
521             ((session->policy_response == NSLCD_PAM_SUCCESS) ||
522              (session->policy_response == NSLCD_PAM_NEW_AUTHTOK_REQD)))
523         {
524           /* this means that the password has expired and must be reset */
525           session->policy_response = NSLCD_PAM_NEW_AUTHTOK_REQD;
526           mysnprintf(session->policy_message, sizeof(session->policy_message),
527                      "%s", ldap_passwordpolicy_err2txt(error));
528         }
529         else if ((error == PP_accountLocked) &&
530                  ((session->policy_response == NSLCD_PAM_SUCCESS) ||
531                   (session->policy_response == NSLCD_PAM_NEW_AUTHTOK_REQD)))
532         {
533           /* this means that the account is locked and the user cannot log
534              in (the bind probably failed already) */
535           session->policy_response = NSLCD_PAM_ACCT_EXPIRED;
536           mysnprintf(session->policy_message, sizeof(session->policy_message),
537                      "%s", ldap_passwordpolicy_err2txt(error));
538         }
539         else if ((error == PP_changeAfterReset) &&
540                  (session->policy_response == NSLCD_PAM_SUCCESS))
541         {
542           /* this indicates that the password must be changed before the
543              user is allowed to perform any other operation */
544           session->policy_response = NSLCD_PAM_NEW_AUTHTOK_REQD;
545           mysnprintf(session->policy_message, sizeof(session->policy_message),
546                      "%s", ldap_passwordpolicy_err2txt(error));
547         }
548         else if ((error != PP_noError) &&
549                  ((session->policy_response == NSLCD_PAM_SUCCESS) ||
550                   (session->policy_response == NSLCD_PAM_NEW_AUTHTOK_REQD)))
551         {
552           /* any other error is assumed to mean that the operation failed */
553           session->policy_response = NSLCD_PAM_PERM_DENIED;
554           mysnprintf(session->policy_message, sizeof(session->policy_message),
555                      "%s", ldap_passwordpolicy_err2txt(error));
556         }
557         /* both expire and grace should just be warnings to the user */
558         if ((expire >= 0) && (strlen(session->policy_message) == 0))
559         {
560           /* if no other error has happened, this indicates that the password
561              will soon expire (number of seconds) */
562           print_ppolicy_expiry(session, (unsigned int)expire);
563         }
564         else if ((grace >= 0) && (strlen(session->policy_message) == 0))
565         {
566           /* this indicates the number of grace logins that are left before
567              no further login attempts will be allowed */
568           mysnprintf(session->policy_message, sizeof(session->policy_message),
569                      "Password expired, %d grace logins left", grace);
570         }
571       }
572     }
573     /* ignore any other controls */
574   }
575 }
576 
do_ppolicy_bind(MYLDAP_SESSION * session,LDAP * ld,const char * uri)577 static int do_ppolicy_bind(MYLDAP_SESSION *session, LDAP *ld, const char *uri)
578 {
579   int rc, parserc;
580   struct berval cred;
581   LDAPControl passwd_policy_req;
582   LDAPControl *requestctrls[2];
583   LDAPControl **responsectrls;
584   int msgid;
585   struct timeval timeout;
586   LDAPMessage *result;
587   /* build policy request if pam_authc_ppolicy is set */
588   if (nslcd_cfg->pam_authc_ppolicy)
589   {
590     passwd_policy_req.ldctl_oid = LDAP_CONTROL_PASSWORDPOLICYREQUEST;
591     passwd_policy_req.ldctl_value.bv_val = NULL; /* none */
592     passwd_policy_req.ldctl_value.bv_len = 0;
593     passwd_policy_req.ldctl_iscritical = 0; /* not critical */
594     requestctrls[0] = &passwd_policy_req;
595   }
596   else
597     requestctrls[0] = NULL;
598   requestctrls[1] = NULL;
599   /* build password berval */
600   cred.bv_val = (char *)session->bindpw;
601   cred.bv_len = strlen(session->bindpw);
602   /* do a SASL simple bind with the binddn and bindpw */
603   log_log(LOG_DEBUG, "ldap_sasl_bind(\"%s\",%s) (uri=\"%s\") (ppolicy=%s)",
604           session->binddn, (session->bindpw[0] != '\0') ? "\"***\"" : "\"\"",
605           uri, (requestctrls[0] == NULL) ? "no" : "yes");
606   rc = ldap_sasl_bind(ld, session->binddn, LDAP_SASL_SIMPLE, &cred, requestctrls, NULL, &msgid);
607   if (rc != LDAP_SUCCESS)
608     return rc;
609   if (msgid == -1)
610   {
611     myldap_err(LOG_WARNING, ld, rc,"ldap_sasl_bind() failed (msgid=-1, uri=%s)", uri);
612     return LDAP_OPERATIONS_ERROR;
613   }
614   /* get the result from the bind operation */
615   timeout.tv_sec = nslcd_cfg->bind_timelimit;
616   timeout.tv_usec = 0;
617   result = NULL;
618   rc = ldap_result(ld, msgid, LDAP_MSG_ALL, &timeout, &result);
619   if (rc == -1) /* some error */
620   {
621     if (ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_SUCCESS)
622       rc = LDAP_UNAVAILABLE;
623     myldap_err(LOG_ERR, ld, rc, "ldap_result() failed");
624     if (result != NULL)
625       ldap_msgfree(result);
626     return LDAP_LOCAL_ERROR;
627   }
628   if (rc == 0) /* the timeout expired */
629   {
630     log_log(LOG_ERR, "ldap_result() timed out");
631     if (result != NULL)
632       ldap_msgfree(result);
633     return LDAP_TIMEOUT;
634   }
635   responsectrls = NULL;
636   /* ignore any response controls unless we're interested in ppolicy */
637   if (nslcd_cfg->pam_authc_ppolicy)
638   {
639     /* parse the result from the bind operation (frees result, gets controls) */
640     parserc = ldap_parse_result(ld, result, &rc, NULL, NULL, NULL, &responsectrls, 1);
641     if (parserc != LDAP_SUCCESS)
642     {
643       myldap_err(LOG_ERR, ld, parserc, "ldap_parse_result() failed");
644       if (responsectrls != NULL)
645         ldap_controls_free(responsectrls);
646       return parserc;
647     }
648     /* handle any returned controls */
649     if (responsectrls != NULL)
650     {
651       handle_ppolicy_controls(session, ld, responsectrls);
652       ldap_controls_free(responsectrls);
653     }
654   }
655   /* return the result of the BIND operation */
656   if (rc != LDAP_SUCCESS)
657   {
658     myldap_err(LOG_DEBUG, ld, rc, "ldap_parse_result() result");
659     return rc;
660   }
661   /* check the returned controls */
662   return LDAP_SUCCESS;
663 }
664 #endif /* no SASL, so no ppolicy */
665 
666 /* This function performs the authentication phase of opening a connection.
667    The binddn and bindpw parameters may be used to override the authentication
668    mechanism defined in the configuration.  This returns an LDAP result
669    code. */
do_bind(MYLDAP_SESSION * session,LDAP * ld,const char * uri)670 static int do_bind(MYLDAP_SESSION *session, LDAP *ld, const char *uri)
671 {
672   int rc;
673 #ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S
674 #ifndef HAVE_SASL_INTERACT_T
675   struct berval cred;
676 #endif /* not HAVE_SASL_INTERACT_T */
677 #endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */
678 #ifdef LDAP_OPT_X_TLS
679   /* check if StartTLS is requested */
680   if (nslcd_cfg->ssl == SSL_START_TLS)
681   {
682     log_log(LOG_DEBUG, "ldap_start_tls_s()");
683     errno = 0;
684     rc = ldap_start_tls_s(ld, NULL, NULL);
685     if (rc != LDAP_SUCCESS)
686     {
687       myldap_err(LOG_WARNING, ld, rc, "ldap_start_tls_s() failed (uri=%s)",
688                  uri);
689       return rc;
690     }
691   }
692 #endif /* LDAP_OPT_X_TLS */
693   /* check if the binddn and bindpw are overwritten in the session */
694   if (session->binddn[0] != '\0')
695   {
696 #if defined(HAVE_LDAP_SASL_BIND) && defined(LDAP_SASL_SIMPLE)
697     return do_ppolicy_bind(session, ld, uri);
698 #else /* no SASL, so no ppolicy */
699     /* do a simple bind */
700     log_log(LOG_DEBUG, "ldap_simple_bind_s(\"%s\",%s) (uri=\"%s\")",
701             session->binddn,
702             (session->bindpw[0] != '\0') ? "\"***\"" : "\"\"",
703             uri);
704     return ldap_simple_bind_s(ld, session->binddn, session->bindpw);
705 #endif
706   }
707   /* perform SASL bind if requested and available on platform */
708 #ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S
709   /* TODO: store this information in the session */
710   if (nslcd_cfg->sasl_mech != NULL)
711   {
712     /* do a SASL bind */
713     if (nslcd_cfg->sasl_secprops != NULL)
714     {
715       log_log(LOG_DEBUG, "ldap_set_option(LDAP_OPT_X_SASL_SECPROPS,\"%s\")",
716               nslcd_cfg->sasl_secprops);
717       LDAP_SET_OPTION(ld, LDAP_OPT_X_SASL_SECPROPS, (void *)nslcd_cfg->sasl_secprops);
718     }
719 #ifdef HAVE_SASL_INTERACT_T
720     if (nslcd_cfg->binddn != NULL)
721       log_log(LOG_DEBUG, "ldap_sasl_interactive_bind_s(\"%s\",\"%s\") (uri=\"%s\")",
722               nslcd_cfg->binddn, nslcd_cfg->sasl_mech, uri);
723     else
724       log_log(LOG_DEBUG, "ldap_sasl_interactive_bind_s(NULL,\"%s\") (uri=\"%s\")",
725               nslcd_cfg->sasl_mech, uri);
726     return ldap_sasl_interactive_bind_s(ld, nslcd_cfg->binddn,
727                                         nslcd_cfg->sasl_mech, NULL, NULL,
728                                         LDAP_SASL_QUIET, do_sasl_interact,
729                                         (void *)nslcd_cfg);
730 #else /* HAVE_SASL_INTERACT_T */
731     if (nslcd_cfg->bindpw != NULL)
732     {
733       cred.bv_val = nslcd_cfg->bindpw;
734       cred.bv_len = strlen(nslcd_cfg->bindpw);
735     }
736     else
737     {
738       cred.bv_val = "";
739       cred.bv_len = 0;
740     }
741     if (nslcd_cfg->binddn != NULL)
742       log_log(LOG_DEBUG, "ldap_sasl_bind_s(\"%s\",\"%s\",%s) (uri=\"%s\")",
743               nslcd_cfg->binddn, nslcd_cfg->sasl_mech,
744               nslcd_cfg->bindpw ? "\"***\"" : "NULL", uri);
745     else
746       log_log(LOG_DEBUG, "ldap_sasl_bind_s(NULL,\"%s\",%s) (uri=\"%s\")",
747               nslcd_cfg->sasl_mech,
748               nslcd_cfg->bindpw ? "\"***\"" : "NULL", uri);
749     return ldap_sasl_bind_s(ld, nslcd_cfg->binddn,
750                             nslcd_cfg->sasl_mech, &cred, NULL, NULL, NULL);
751 #endif /* not HAVE_SASL_INTERACT_T */
752   }
753 #endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */
754   /* do a simple bind */
755   if (nslcd_cfg->binddn)
756     log_log(LOG_DEBUG, "ldap_simple_bind_s(\"%s\",%s) (uri=\"%s\")",
757             nslcd_cfg->binddn, nslcd_cfg->bindpw ? "\"***\"" : "NULL",
758             uri);
759   else
760     log_log(LOG_DEBUG, "ldap_simple_bind_s(NULL,%s) (uri=\"%s\")",
761             nslcd_cfg->bindpw ? "\"***\"" : "NULL", uri);
762   return ldap_simple_bind_s(ld, nslcd_cfg->binddn, nslcd_cfg->bindpw);
763 }
764 
765 #ifdef HAVE_LDAP_SET_REBIND_PROC
766 /* This function is called by the LDAP library when chasing referrals.
767    It is configured with the ldap_set_rebind_proc() below. */
768 #if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
do_rebind(LDAP * ld,LDAP_CONST char * url,ber_tag_t UNUSED (request),ber_int_t UNUSED (msgid),void * arg)769 static int do_rebind(LDAP *ld, LDAP_CONST char *url,
770                      ber_tag_t UNUSED(request),
771                      ber_int_t UNUSED(msgid), void *arg)
772 {
773   MYLDAP_SESSION *session = (MYLDAP_SESSION *)arg;
774   log_log(LOG_DEBUG, "rebinding to %s", url);
775   return do_bind(session, ld, url);
776 }
777 #else /* not recent OpenLDAP */
do_rebind(LDAP * ld,char ** dnp,char ** passwdp,int * authmethodp,int freeit,void * arg)778 static int do_rebind(LDAP *ld, char **dnp, char **passwdp, int *authmethodp,
779                      int freeit, void *arg)
780 {
781   MYLDAP_SESSION *session = (MYLDAP_SESSION *)arg;
782   if (freeit)
783   {
784     free(*dnp);
785     memset(*passwdp, 0, strlen(*passwdp));
786     free(*passwdp);
787   }
788   else
789   {
790     log_log(LOG_DEBUG, "rebinding");
791     *dnp = strdup(session->binddn);
792     *passwdp = strdup(session->bindpw);
793     *authmethodp = LDAP_AUTH_SIMPLE;
794     if ((*dnp == NULL) || (*passwdp == NULL))
795     {
796       if (*dnp != NULL)
797         free(*dnp);
798       log_log(LOG_CRIT, "do_rebind(): strdup() failed to allocate memory");
799       return LDAP_NO_MEMORY;
800     }
801   }
802   return LDAP_SUCCESS;
803 }
804 #endif /* not recent OpenLDAP */
805 #endif /* HAVE_LDAP_SET_REBIND_PROC */
806 
807 /* set a recieve and send timeout on a socket */
set_socket_timeout(LDAP * ld,time_t sec,suseconds_t usec)808 static int set_socket_timeout(LDAP *ld, time_t sec, suseconds_t usec)
809 {
810   struct timeval tv;
811   int rc = LDAP_SUCCESS;
812   int sd;
813   log_log(LOG_DEBUG, "set_socket_timeout(%lu,%lu)",
814           (unsigned long)sec, (unsigned long)usec);
815   /* get the socket */
816   if ((rc = ldap_get_option(ld, LDAP_OPT_DESC, &sd)) != LDAP_SUCCESS)
817   {
818     myldap_err(LOG_ERR, ld, rc, "ldap_get_option(LDAP_OPT_DESC) failed");
819     return rc;
820   }
821   /* ignore invalid (probably closed) file descriptors */
822   if (sd <= 0)
823     return LDAP_SUCCESS;
824   /* set timeouts */
825   memset(&tv, 0, sizeof(tv));
826   tv.tv_sec = sec;
827   tv.tv_usec = usec;
828   if (setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(tv)))
829   {
830     log_log(LOG_ERR, "setsockopt(%d,SO_RCVTIMEO) failed: %s",
831             sd, strerror(errno));
832     rc = LDAP_LOCAL_ERROR;
833   }
834   if (setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO, (void *)&tv, sizeof(tv)))
835   {
836     log_log(LOG_ERR, "setsockopt(%d,SO_RCVTIMEO) failed: %s",
837             sd, strerror(errno));
838     rc = LDAP_LOCAL_ERROR;
839   }
840   return rc;
841 }
842 
843 #ifdef LDAP_OPT_CONNECT_CB
844 /* This function is called by the LDAP library once a connection was made to the server. We
845    set a timeout on the socket here, to catch network timeouts during the ssl
846    handshake phase. It is configured with LDAP_OPT_CONNECT_CB. */
connect_cb(LDAP * ld,Sockbuf UNUSED (* sb),LDAPURLDesc UNUSED (* srv),struct sockaddr UNUSED (* addr),struct ldap_conncb UNUSED (* ctx))847 static int connect_cb(LDAP *ld, Sockbuf UNUSED(*sb),
848                       LDAPURLDesc UNUSED(*srv), struct sockaddr UNUSED(*addr),
849                       struct ldap_conncb UNUSED(*ctx))
850 {
851   /* set timeout options on socket to avoid hang in some cases (a little
852      more than the normal timeout so this should only be triggered in cases
853      where the library behaves incorrectly) */
854   if (nslcd_cfg->timelimit)
855     set_socket_timeout(ld, nslcd_cfg->timelimit, 500000);
856   return LDAP_SUCCESS;
857 }
858 
859 /* We have an empty disconnect callback because LDAP_OPT_CONNECT_CB expects
860    both functions to be available. */
disconnect_cb(LDAP UNUSED (* ld),Sockbuf UNUSED (* sb),struct ldap_conncb UNUSED (* ctx))861 static void disconnect_cb(LDAP UNUSED(*ld), Sockbuf UNUSED(*sb),
862                           struct ldap_conncb UNUSED(*ctx))
863 {
864 }
865 #endif /* LDAP_OPT_CONNECT_CB */
866 
867 /* This function sets a number of properties on the connection, based
868    what is configured in the configfile. This function returns an
869    LDAP status code. */
do_set_options(MYLDAP_SESSION * session)870 static int do_set_options(MYLDAP_SESSION *session)
871 {
872   int rc;
873   struct timeval tv;
874 #ifdef LDAP_OPT_CONNECT_CB
875   /* make this static because OpenLDAP doesn't make its own copy */
876   static struct ldap_conncb cb;
877 #endif /* LDAP_OPT_CONNECT_CB */
878 #ifdef LDAP_OPT_X_TLS
879   int i;
880 #endif /* LDAP_OPT_X_TLS */
881 #ifdef HAVE_LDAP_SET_REBIND_PROC
882   /* the rebind function that is called when chasing referrals, see
883      http://publib.boulder.ibm.com/infocenter/iseries/v5r3/topic/apis/ldap_set_rebind_proc.htm
884      http://www.openldap.org/software/man.cgi?query=ldap_set_rebind_proc&manpath=OpenLDAP+2.4-Release */
885   /* TODO: probably only set this if we should chase referrals */
886   log_log(LOG_DEBUG, "ldap_set_rebind_proc()");
887 #ifndef LDAP_SET_REBIND_PROC_RETURNS_VOID /* it returns int */
888   rc = ldap_set_rebind_proc(session->ld, do_rebind, session);
889   if (rc != LDAP_SUCCESS)
890   {
891     myldap_err(LOG_ERR, session->ld, rc, "ldap_set_rebind_proc() failed");
892     return rc;
893   }
894 #else /* ldap_set_rebind_proc() returns void */
895   ldap_set_rebind_proc(session->ld, do_rebind, session);
896 #endif
897 #endif /* HAVE_LDAP_SET_REBIND_PROC */
898   /* set the protocol version to use */
899   log_log(LOG_DEBUG, "ldap_set_option(LDAP_OPT_PROTOCOL_VERSION,%d)",
900           nslcd_cfg->ldap_version);
901   LDAP_SET_OPTION(session->ld, LDAP_OPT_PROTOCOL_VERSION,
902                   &nslcd_cfg->ldap_version);
903   /* set some other options */
904   log_log(LOG_DEBUG, "ldap_set_option(LDAP_OPT_DEREF,%d)",
905           nslcd_cfg->deref);
906   LDAP_SET_OPTION(session->ld, LDAP_OPT_DEREF, &nslcd_cfg->deref);
907   log_log(LOG_DEBUG, "ldap_set_option(LDAP_OPT_TIMELIMIT,%d)",
908           nslcd_cfg->timelimit);
909   LDAP_SET_OPTION(session->ld, LDAP_OPT_TIMELIMIT, &nslcd_cfg->timelimit);
910   tv.tv_sec = nslcd_cfg->bind_timelimit;
911   tv.tv_usec = 0;
912 #ifdef LDAP_OPT_TIMEOUT
913   log_log(LOG_DEBUG, "ldap_set_option(LDAP_OPT_TIMEOUT,%d)",
914           nslcd_cfg->timelimit);
915   LDAP_SET_OPTION(session->ld, LDAP_OPT_TIMEOUT, &tv);
916 #endif /* LDAP_OPT_TIMEOUT */
917 #ifdef LDAP_OPT_NETWORK_TIMEOUT
918   log_log(LOG_DEBUG, "ldap_set_option(LDAP_OPT_NETWORK_TIMEOUT,%d)",
919           nslcd_cfg->timelimit);
920   LDAP_SET_OPTION(session->ld, LDAP_OPT_NETWORK_TIMEOUT, &tv);
921 #endif /* LDAP_OPT_NETWORK_TIMEOUT */
922 #ifdef LDAP_X_OPT_CONNECT_TIMEOUT
923   log_log(LOG_DEBUG, "ldap_set_option(LDAP_X_OPT_CONNECT_TIMEOUT,%d)",
924           nslcd_cfg->timelimit);
925   LDAP_SET_OPTION(session->ld, LDAP_X_OPT_CONNECT_TIMEOUT, &tv);
926 #endif /* LDAP_X_OPT_CONNECT_TIMEOUT */
927   log_log(LOG_DEBUG, "ldap_set_option(LDAP_OPT_REFERRALS,%s)",
928           nslcd_cfg->referrals ? "LDAP_OPT_ON" : "LDAP_OPT_OFF");
929   LDAP_SET_OPTION(session->ld, LDAP_OPT_REFERRALS,
930                   nslcd_cfg->referrals ? LDAP_OPT_ON : LDAP_OPT_OFF);
931   log_log(LOG_DEBUG, "ldap_set_option(LDAP_OPT_RESTART,LDAP_OPT_ON)");
932   LDAP_SET_OPTION(session->ld, LDAP_OPT_RESTART, LDAP_OPT_ON);
933 #ifdef LDAP_OPT_CONNECT_CB
934   /* register a connection callback */
935   cb.lc_add = connect_cb;
936   cb.lc_del = disconnect_cb;
937   cb.lc_arg = NULL;
938   LDAP_SET_OPTION(session->ld, LDAP_OPT_CONNECT_CB, (void *)&cb);
939 #endif /* LDAP_OPT_CONNECT_CB */
940 #ifdef LDAP_OPT_X_TLS
941   /* if SSL is desired, then enable it */
942   if ((nslcd_cfg->ssl == SSL_LDAPS) ||
943       (strncasecmp(nslcd_cfg->uris[session->current_uri].uri, "ldaps://", 8) == 0))
944   {
945     /* use tls */
946     i = LDAP_OPT_X_TLS_HARD;
947     log_log(LOG_DEBUG, "ldap_set_option(LDAP_OPT_X_TLS,LDAP_OPT_X_TLS_HARD)");
948     LDAP_SET_OPTION(session->ld, LDAP_OPT_X_TLS, &i);
949   }
950 #endif /* LDAP_OPT_X_TLS */
951 #ifdef LDAP_OPT_X_SASL_NOCANON
952   if (nslcd_cfg->sasl_canonicalize >= 0)
953   {
954     log_log(LOG_DEBUG, "ldap_set_option(LDAP_OPT_X_SASL_NOCANON,%s)",
955             nslcd_cfg->sasl_canonicalize ? "LDAP_OPT_OFF" : "LDAP_OPT_ON");
956     LDAP_SET_OPTION(session->ld, LDAP_OPT_X_SASL_NOCANON,
957                     nslcd_cfg->sasl_canonicalize ? LDAP_OPT_OFF : LDAP_OPT_ON);
958   }
959 #endif /* LDAP_OPT_X_SASL_NOCANON */
960   /* if nothing above failed, everything should be fine */
961   return LDAP_SUCCESS;
962 }
963 
964 /* close the connection to the server and invalidate any running searches */
do_close(MYLDAP_SESSION * session)965 static void do_close(MYLDAP_SESSION *session)
966 {
967   int i;
968   int rc;
969   time_t sec;
970   /* if we had reachability problems with the server close the connection */
971   if (session->ld != NULL)
972   {
973     /* set timeout options on socket to avoid hang in some cases
974        (we set a short timeout because we don't care too much about properly
975        shutting down the connection) */
976     if (nslcd_cfg->timelimit)
977     {
978       sec = nslcd_cfg->timelimit / 2;
979       if (!sec)
980         sec = 1;
981       set_socket_timeout(session->ld, sec, 0);
982     }
983     /* go over the other searches and partially close them */
984     for (i = 0; i < MAX_SEARCHES_IN_SESSION; i++)
985     {
986       if (session->searches[i] != NULL)
987       {
988         /* free any messages (because later ld is no longer valid) */
989         if (session->searches[i]->msg != NULL)
990         {
991           ldap_msgfree(session->searches[i]->msg);
992           session->searches[i]->msg = NULL;
993         }
994         /* abandon the search if there were more results to fetch */
995         if (session->searches[i]->msgid != -1)
996         {
997           log_log(LOG_DEBUG, "ldap_abandon()");
998           if (ldap_abandon(session->searches[i]->session->ld, session->searches[i]->msgid))
999           {
1000             if (ldap_get_option(session->ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_SUCCESS)
1001               rc = LDAP_OTHER;
1002             myldap_err(LOG_WARNING, session->ld, rc,
1003                        "ldap_abandon() failed to abandon search");
1004           }
1005           session->searches[i]->msgid = -1;
1006         }
1007         /* flag the search as invalid */
1008         session->searches[i]->valid = 0;
1009       }
1010     }
1011     /* close the connection to the server */
1012     log_log(LOG_DEBUG, "ldap_unbind()");
1013     rc = ldap_unbind(session->ld);
1014     session->ld = NULL;
1015     if (rc != LDAP_SUCCESS)
1016       myldap_err(LOG_WARNING, session->ld, rc, "ldap_unbind() failed");
1017   }
1018 }
1019 
myldap_session_check(MYLDAP_SESSION * session)1020 void myldap_session_check(MYLDAP_SESSION *session)
1021 {
1022   int i;
1023   time_t current_time;
1024   int sd;
1025   int rc;
1026   struct sockaddr sa;
1027   socklen_t salen = sizeof(sa);
1028   /* check parameters */
1029   if (session == NULL)
1030   {
1031     log_log(LOG_ERR, "myldap_session_check(): invalid parameter passed");
1032     errno = EINVAL;
1033     return;
1034   }
1035   if (session->ld != NULL)
1036   {
1037     rc = ldap_get_option(session->ld, LDAP_OPT_DESC, &sd);
1038     if (rc != LDAP_SUCCESS)
1039     {
1040       myldap_err(LOG_WARNING, session->ld, rc,
1041                  "ldap_get_option(LDAP_OPT_DESC) failed (ignored)");
1042     }
1043     else
1044     {
1045       /* check if the connection was closed by the peer */
1046       if (getpeername(sd, &sa, &salen) == -1)
1047       {
1048         if (errno == ENOTCONN)
1049         {
1050           log_log(LOG_DEBUG, "myldap_session_check(): connection reset by peer");
1051           do_close(session);
1052           return;
1053         }
1054       }
1055     }
1056     /* check if we should time out the connection */
1057     if (nslcd_cfg->idle_timelimit > 0)
1058     {
1059       /* if we have any running searches, don't time out */
1060       for (i = 0; i < MAX_SEARCHES_IN_SESSION; i++)
1061         if ((session->searches[i] != NULL) && (session->searches[i]->valid))
1062           return;
1063       /* consider timeout (there are no running searches) */
1064       time(&current_time);
1065       if ((session->lastactivity + nslcd_cfg->idle_timelimit) < current_time)
1066       {
1067         log_log(LOG_DEBUG, "myldap_session_check(): idle_timelimit reached");
1068         do_close(session);
1069       }
1070     }
1071   }
1072 }
1073 
1074 /* This opens connection to an LDAP server, sets all connection options
1075    and binds to the server. This returns an LDAP status code. */
do_open(MYLDAP_SESSION * session)1076 static int do_open(MYLDAP_SESSION *session)
1077 {
1078   int rc;
1079   /* if the connection is still there (ie. ldap_unbind() wasn't
1080      called) then we can return the cached connection */
1081   if (session->ld != NULL)
1082     return LDAP_SUCCESS;
1083   /* we should build a new session now */
1084   session->ld = NULL;
1085   session->lastactivity = 0;
1086   /* open the connection */
1087   log_log(LOG_DEBUG, "ldap_initialize(%s)",
1088           nslcd_cfg->uris[session->current_uri].uri);
1089   errno = 0;
1090   rc = ldap_initialize(&(session->ld), nslcd_cfg->uris[session->current_uri].uri);
1091   if (rc != LDAP_SUCCESS)
1092   {
1093     myldap_err(LOG_WARNING, session->ld, rc, "ldap_initialize(%s) failed",
1094                nslcd_cfg->uris[session->current_uri].uri);
1095     if (session->ld != NULL)
1096       do_close(session);
1097     return rc;
1098   }
1099   else if (session->ld == NULL)
1100   {
1101     log_log(LOG_WARNING, "ldap_initialize() returned NULL");
1102     return LDAP_LOCAL_ERROR;
1103   }
1104   /* set the options for the connection */
1105   rc = do_set_options(session);
1106   if (rc != LDAP_SUCCESS)
1107   {
1108     do_close(session);
1109     return rc;
1110   }
1111   /* bind to the server */
1112   errno = 0;
1113   rc = do_bind(session, session->ld, nslcd_cfg->uris[session->current_uri].uri);
1114   if (rc != LDAP_SUCCESS)
1115   {
1116     /* log actual LDAP error code */
1117     myldap_err((session->binddn[0] == '\0') ? LOG_WARNING : LOG_DEBUG,
1118                session->ld, rc, "failed to bind to LDAP server %s",
1119                nslcd_cfg->uris[session->current_uri].uri);
1120     do_close(session);
1121     return rc;
1122   }
1123   /* update last activity and finish off state */
1124   time(&(session->lastactivity));
1125   return LDAP_SUCCESS;
1126 }
1127 
1128 /* Perform a simple bind operation and return the ppolicy results. */
myldap_bind(MYLDAP_SESSION * session,const char * dn,const char * password,int * response,const char ** message)1129 int myldap_bind(MYLDAP_SESSION *session, const char *dn, const char *password,
1130                 int *response, const char **message)
1131 {
1132   MYLDAP_SEARCH *search;
1133   static const char *attrs[2];
1134   int rc;
1135   /* error out when buffers are too small */
1136   if (strlen(dn) >= sizeof(session->binddn))
1137   {
1138     log_log(LOG_ERR, "myldap_bind(): binddn buffer too small (%lu required)",
1139             (unsigned long) strlen(dn));
1140     return LDAP_LOCAL_ERROR;
1141   }
1142   if (strlen(password) >= sizeof(session->bindpw))
1143   {
1144     log_log(LOG_ERR, "myldap_bind(): bindpw buffer too small (%lu required)",
1145             (unsigned long) strlen(password));
1146     return LDAP_LOCAL_ERROR;
1147   }
1148   /* copy dn and password into session */
1149   strncpy(session->binddn, dn, sizeof(session->binddn));
1150   session->binddn[sizeof(session->binddn) - 1] = '\0';
1151   strncpy(session->bindpw, password, sizeof(session->bindpw));
1152   session->bindpw[sizeof(session->bindpw) - 1] = '\0';
1153   /* construct a fake search to trigger the BIND operation */
1154   attrs[0] = "dn";
1155   attrs[1] = NULL;
1156   search = myldap_search(session, session->binddn, MYLDAP_SCOPE_BINDONLY,
1157                          "(objectClass=*)", attrs, &rc);
1158   if (search != NULL)
1159     myldap_search_close(search);
1160   /* return ppolicy results */
1161   if (response != NULL)
1162     *response = session->policy_response;
1163   if (message != NULL)
1164     *message = session->policy_message;
1165   return rc;
1166 }
1167 
1168 /* perform a search operation, the connection is assumed to be open */
do_try_search(MYLDAP_SEARCH * search)1169 static int do_try_search(MYLDAP_SEARCH *search)
1170 {
1171   int ctrlidx = 0;
1172   int rc;
1173   LDAPControl *serverctrls[3];
1174 #ifdef HAVE_LDAP_CREATE_DEREF_CONTROL
1175   int i;
1176   struct LDAPDerefSpec ds[2];
1177   char *deref_attrs[2];
1178 #endif /* HAVE_LDAP_CREATE_DEREF_CONTROL */
1179   int msgid;
1180   /* if we're using paging, build a page control */
1181   if ((nslcd_cfg->pagesize > 0) && (search->scope != LDAP_SCOPE_BASE))
1182   {
1183     rc = ldap_create_page_control(search->session->ld, nslcd_cfg->pagesize,
1184                                   search->cookie, 0, &serverctrls[ctrlidx]);
1185     if (rc == LDAP_SUCCESS)
1186       ctrlidx++;
1187     else
1188     {
1189       myldap_err(LOG_WARNING, search->session->ld, rc,
1190                  "ldap_create_page_control() failed");
1191       serverctrls[ctrlidx] = NULL;
1192       /* if we were paging, failure building the second control is fatal */
1193       if (search->cookie != NULL)
1194         return rc;
1195     }
1196   }
1197 #ifdef HAVE_LDAP_CREATE_DEREF_CONTROL
1198   /* if doing group searches, add deref control to search request
1199      (this is currently a bit of a hack and hard-coded for group searches
1200      which are detected by requesting the attmap_group_member member
1201      attribute) */
1202   for (i = 0; search->attrs[i] != NULL; i++)
1203     if (strcasecmp(search->attrs[i], attmap_group_member) == 0)
1204     {
1205       /* attributes from dereff'd entries */
1206       deref_attrs[0] = (void *)attmap_passwd_uid;
1207       deref_attrs[1] = NULL;
1208       /* build deref control */
1209       ds[0].derefAttr = (void *)attmap_group_member;
1210       ds[0].attributes = deref_attrs;
1211       ds[1].derefAttr = NULL;
1212       ds[1].attributes = NULL;
1213       rc = ldap_create_deref_control(search->session->ld, ds, 0, &serverctrls[ctrlidx]);
1214       if (rc == LDAP_SUCCESS)
1215         ctrlidx++;
1216       else
1217       {
1218         myldap_err(LOG_WARNING, search->session->ld, rc,
1219                    "ldap_create_deref_control() failed");
1220         serverctrls[ctrlidx] = NULL;
1221       }
1222     }
1223 #endif /* HAVE_LDAP_CREATE_DEREF_CONTROL */
1224   /* NULL terminate control list */
1225   serverctrls[ctrlidx] = NULL;
1226   /* clear error flag (perhaps control setting failed) */
1227   if (ctrlidx > 0)
1228   {
1229     rc = LDAP_SUCCESS;
1230     if (ldap_set_option(search->session->ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_SUCCESS)
1231       log_log(LOG_WARNING, "failed to clear the error flag");
1232   }
1233   /* perform the search */
1234   rc = ldap_search_ext(search->session->ld, search->base, search->scope,
1235                        search->filter, (char **)(search->attrs),
1236                        0, serverctrls[0] == NULL ? NULL : serverctrls,
1237                        NULL, NULL, LDAP_NO_LIMIT, &msgid);
1238   /* free the controls if we had them */
1239   for (ctrlidx = 0; serverctrls[ctrlidx] != NULL; ctrlidx++)
1240     ldap_control_free(serverctrls[ctrlidx]);
1241   /* handle errors */
1242   if (rc != LDAP_SUCCESS)
1243   {
1244     myldap_err(LOG_WARNING, search->session->ld, rc, "ldap_search_ext() failed");
1245     return rc;
1246   }
1247   /* update the last activity on the connection */
1248   time(&(search->session->lastactivity));
1249   /* save msgid */
1250   search->msgid = msgid;
1251   /* return the new search */
1252   return LDAP_SUCCESS;
1253 }
1254 
myldap_create_session(void)1255 MYLDAP_SESSION *myldap_create_session(void)
1256 {
1257   return myldap_session_new();
1258 }
1259 
myldap_session_cleanup(MYLDAP_SESSION * session)1260 void myldap_session_cleanup(MYLDAP_SESSION *session)
1261 {
1262   int i;
1263   /* check parameter */
1264   if (session == NULL)
1265   {
1266     log_log(LOG_ERR, "myldap_session_cleanup(): invalid session passed");
1267     return;
1268   }
1269   /* go over all searches in the session and close them */
1270   for (i = 0; i < MAX_SEARCHES_IN_SESSION; i++)
1271   {
1272     if (session->searches[i] != NULL)
1273     {
1274       myldap_search_close(session->searches[i]);
1275       session->searches[i] = NULL;
1276     }
1277   }
1278 }
1279 
myldap_session_close(MYLDAP_SESSION * session)1280 void myldap_session_close(MYLDAP_SESSION *session)
1281 {
1282   /* check parameter */
1283   if (session == NULL)
1284   {
1285     log_log(LOG_ERR, "myldap_session_cleanup(): invalid session passed");
1286     return;
1287   }
1288   /* close pending searches */
1289   myldap_session_cleanup(session);
1290   /* close any open connections */
1291   do_close(session);
1292   /* free allocated memory */
1293   memset(session->bindpw, 0, sizeof(session->bindpw));
1294   free(session);
1295 }
1296 
1297 /* mutex for updating the times in the uri */
1298 pthread_mutex_t uris_mutex = PTHREAD_MUTEX_INITIALIZER;
1299 
do_retry_search(MYLDAP_SEARCH * search)1300 static int do_retry_search(MYLDAP_SEARCH *search)
1301 {
1302   int sleeptime = 0;
1303   int start_uri;
1304   time_t endtime;
1305   time_t nexttry;
1306   time_t t;
1307   int rc = LDAP_UNAVAILABLE;
1308   struct myldap_uri *current_uri;
1309   int dotry[NSS_LDAP_CONFIG_MAX_URIS];
1310   int do_invalidate = 0;
1311   /* clear time stamps */
1312   for (start_uri = 0; start_uri < NSS_LDAP_CONFIG_MAX_URIS; start_uri++)
1313     dotry[start_uri] = 1;
1314   /* keep trying until we time out */
1315   endtime = time(NULL) + nslcd_cfg->reconnect_retrytime;
1316   while (1)
1317   {
1318     nexttry = endtime;
1319     /* try each configured URL once */
1320     pthread_mutex_lock(&uris_mutex);
1321     start_uri = search->session->current_uri;
1322     do
1323     {
1324       current_uri = &(nslcd_cfg->uris[search->session->current_uri]);
1325       /* only try this URI if we should */
1326       if (!dotry[search->session->current_uri])
1327       { /* skip this URI */ }
1328       else if ((current_uri->lastfail > (current_uri->firstfail + nslcd_cfg->reconnect_retrytime)) &&
1329                ((t = time(NULL)) < (current_uri->lastfail + nslcd_cfg->reconnect_retrytime)))
1330       {
1331         /* we are in a hard fail state and have retried not long ago */
1332         log_log(LOG_DEBUG, "not retrying server %s which failed just %d second(s) ago and has been failing for %d seconds",
1333                 current_uri->uri, (int)(t - current_uri->lastfail),
1334                 (int)(t - current_uri->firstfail));
1335         dotry[search->session->current_uri] = 0;
1336       }
1337       else
1338       {
1339         /* try to start the search */
1340         pthread_mutex_unlock(&uris_mutex);
1341         /* ensure that we have an open connection and start a search */
1342         rc = do_open(search->session);
1343         /* perform the actual search, unless we were only binding */
1344         if ((rc == LDAP_SUCCESS) && (search->scope != MYLDAP_SCOPE_BINDONLY))
1345           rc = do_try_search(search);
1346         /* if we are authenticating a user and get an error regarding failed
1347            password we should error out instead of trying all servers */
1348         if ((search->session->binddn[0] != '\0') && (rc == LDAP_INVALID_CREDENTIALS))
1349         {
1350           do_close(search->session);
1351           return rc;
1352         }
1353         if (rc == LDAP_SUCCESS)
1354         {
1355           pthread_mutex_lock(&uris_mutex);
1356           /* check if we are coming back from an error */
1357           if ((current_uri->lastfail > 0) || (search->session->current_uri != start_uri))
1358           {
1359             log_log(LOG_INFO, "connected to LDAP server %s", current_uri->uri);
1360             do_invalidate = 1;
1361           }
1362           if (first_search)
1363           {
1364             do_invalidate = 1;
1365             first_search = 0;
1366           }
1367           /* update ok time */
1368           current_uri->firstfail = 0;
1369           current_uri->lastfail = 0;
1370           pthread_mutex_unlock(&uris_mutex);
1371           /* flag the search as valid */
1372           search->valid = 1;
1373           /* signal external invalidation of configured caches */
1374           if (do_invalidate)
1375             invalidator_do(LM_NONE);
1376           return LDAP_SUCCESS;
1377         }
1378         /* close the current connection */
1379         do_close(search->session);
1380         /* update time of failure and figure out when we should retry */
1381         pthread_mutex_lock(&uris_mutex);
1382         t = time(NULL);
1383         /* update timestamps unless we are doing an authentication search */
1384         if (search->session->binddn[0] == '\0')
1385         {
1386           if (current_uri->firstfail == 0)
1387             current_uri->firstfail = t;
1388           current_uri->lastfail = t;
1389         }
1390         /* if it is one of these, retrying this URI is not going to help */
1391         if ((rc == LDAP_INVALID_CREDENTIALS) || (rc == LDAP_INSUFFICIENT_ACCESS) ||
1392             (rc == LDAP_AUTH_METHOD_NOT_SUPPORTED))
1393           dotry[search->session->current_uri] = 0;
1394         /* check when we should try this URI again */
1395         else if (t <= (current_uri->firstfail + nslcd_cfg->reconnect_retrytime))
1396         {
1397           t += nslcd_cfg->reconnect_sleeptime;
1398           if (t < nexttry)
1399             nexttry = t;
1400         }
1401       }
1402       /* try the next URI (with wrap-around) */
1403       search->session->current_uri++;
1404       if (nslcd_cfg->uris[search->session->current_uri].uri == NULL)
1405         search->session->current_uri = 0;
1406     }
1407     while (search->session->current_uri != start_uri);
1408     pthread_mutex_unlock(&uris_mutex);
1409     /* see if it is any use sleeping */
1410     if (nexttry >= endtime)
1411     {
1412       if (search->session->binddn[0] == '\0')
1413         myldap_err(LOG_ERR, search->session->ld, rc, "no available LDAP server found");
1414       return rc;
1415     }
1416     /* sleep between tries */
1417     sleeptime = nexttry - time(NULL);
1418     if (sleeptime > 0)
1419     {
1420       log_log(LOG_WARNING, "no available LDAP server found, sleeping %d seconds",
1421               sleeptime);
1422       (void)sleep(sleeptime);
1423     }
1424   }
1425 }
1426 
1427 /* force quick retries of all failing LDAP servers */
myldap_immediate_reconnect(void)1428 void myldap_immediate_reconnect(void)
1429 {
1430   int i;
1431   time_t t;
1432   t = time(NULL) - nslcd_cfg->reconnect_retrytime;
1433   pthread_mutex_lock(&uris_mutex);
1434   for (i = 0; i < (NSS_LDAP_CONFIG_MAX_URIS + 1); i++)
1435   {
1436     /* only adjust failing connections that are in a hard fail state */
1437     if ((nslcd_cfg->uris[i].lastfail > t) &&
1438         (nslcd_cfg->uris[i].lastfail > (nslcd_cfg->uris[i].firstfail + nslcd_cfg->reconnect_retrytime)))
1439     {
1440       /* move lastfail back to ensure quick retry */
1441       log_log(LOG_DEBUG, "moving lastfail of %s %d second(s) back to force retry",
1442               nslcd_cfg->uris[i].uri, (int)(nslcd_cfg->uris[i].lastfail - t));
1443       nslcd_cfg->uris[i].lastfail = t;
1444     }
1445   }
1446   pthread_mutex_unlock(&uris_mutex);
1447 }
1448 
myldap_search(MYLDAP_SESSION * session,const char * base,int scope,const char * filter,const char ** attrs,int * rcp)1449 MYLDAP_SEARCH *myldap_search(MYLDAP_SESSION *session,
1450                              const char *base, int scope, const char *filter,
1451                              const char **attrs, int *rcp)
1452 {
1453   MYLDAP_SEARCH *search;
1454   int i;
1455   int rc;
1456   /* check parameters */
1457   if ((session == NULL) || (base == NULL) || (filter == NULL) || (attrs == NULL))
1458   {
1459     log_log(LOG_ERR, "myldap_search(): invalid parameter passed");
1460     errno = EINVAL;
1461     if (rcp != NULL)
1462       *rcp = LDAP_OPERATIONS_ERROR;
1463     return NULL;
1464   }
1465   /* log the call */
1466   log_log(LOG_DEBUG, "myldap_search(base=\"%s\", filter=\"%s\")",
1467           base, filter);
1468   /* check if the idle time for the connection has expired */
1469   myldap_session_check(session);
1470   /* allocate a new search entry */
1471   search = myldap_search_new(session, base, scope, filter, attrs);
1472   /* find a place in the session where we can register our search */
1473   for (i = 0; (i < MAX_SEARCHES_IN_SESSION) && (session->searches[i] != NULL); i++)
1474     /* nothing */ ;
1475   if (i >= MAX_SEARCHES_IN_SESSION)
1476   {
1477     log_log(LOG_ERR, "myldap_search(): too many searches registered with session (max %d)",
1478             MAX_SEARCHES_IN_SESSION);
1479     myldap_search_close(search);
1480     if (rcp != NULL)
1481       *rcp = LDAP_OPERATIONS_ERROR;
1482     return NULL;
1483   }
1484   /* register search with the session so we can free it later on */
1485   session->searches[i] = search;
1486   /* do the search with retries to all configured servers */
1487   rc = do_retry_search(search);
1488   if (rc != LDAP_SUCCESS)
1489   {
1490     myldap_search_close(search);
1491     if (rcp != NULL)
1492       *rcp = rc;
1493     return NULL;
1494   }
1495   if (rcp != NULL)
1496     *rcp = LDAP_SUCCESS;
1497   return search;
1498 }
1499 
myldap_search_close(MYLDAP_SEARCH * search)1500 void myldap_search_close(MYLDAP_SEARCH *search)
1501 {
1502   int i;
1503   if (search == NULL)
1504     return;
1505   /* free any messages */
1506   if (search->msg != NULL)
1507   {
1508     ldap_msgfree(search->msg);
1509     search->msg = NULL;
1510   }
1511   /* abandon the search if there were more results to fetch */
1512   if ((search->session->ld != NULL) && (search->msgid != -1))
1513   {
1514     ldap_abandon(search->session->ld, search->msgid);
1515     search->msgid = -1;
1516   }
1517   /* find the reference to this search in the session */
1518   for (i = 0; i < MAX_SEARCHES_IN_SESSION; i++)
1519   {
1520     if (search->session->searches[i] == search)
1521       search->session->searches[i] = NULL;
1522   }
1523   /* free any search entries */
1524   if (search->entry != NULL)
1525     myldap_entry_free(search->entry);
1526   /* clean up cookie */
1527   if (search->cookie != NULL)
1528     ber_bvfree(search->cookie);
1529   /* free read messages */
1530   if (search->msg != NULL)
1531     ldap_msgfree(search->msg);
1532   /* free the storage we allocated */
1533   free(search);
1534 }
1535 
myldap_get_entry(MYLDAP_SEARCH * search,int * rcp)1536 MYLDAP_ENTRY *myldap_get_entry(MYLDAP_SEARCH *search, int *rcp)
1537 {
1538   int rc;
1539   int parserc;
1540   struct timeval tv, *tvp;
1541   LDAPControl **resultcontrols;
1542   ber_int_t count;
1543   /* check parameters */
1544   if ((search == NULL) || (search->session == NULL) || (search->session->ld == NULL))
1545   {
1546     log_log(LOG_ERR, "myldap_get_entry(): invalid search passed");
1547     errno = EINVAL;
1548     if (rcp != NULL)
1549       *rcp = LDAP_OPERATIONS_ERROR;
1550     return NULL;
1551   }
1552   /* check if the connection wasn't closed in another search */
1553   if (!search->valid)
1554   {
1555     log_log(LOG_WARNING, "myldap_get_entry(): connection was closed");
1556     /* retry the search */
1557     if (search->may_retry_search)
1558     {
1559       log_log(LOG_DEBUG, "myldap_get_entry(): retry search");
1560       search->may_retry_search = 0;
1561       if (do_retry_search(search) == LDAP_SUCCESS)
1562         return myldap_get_entry(search, rcp);
1563     }
1564     myldap_search_close(search);
1565     if (rcp != NULL)
1566       *rcp = LDAP_SERVER_DOWN;
1567     return NULL;
1568   }
1569   /* set up a timelimit value for operations */
1570   if (nslcd_cfg->timelimit == LDAP_NO_LIMIT)
1571     tvp = NULL;
1572   else
1573   {
1574     tv.tv_sec = nslcd_cfg->timelimit;
1575     tv.tv_usec = 0;
1576     tvp = &tv;
1577   }
1578   /* if we have an existing result entry, free it */
1579   if (search->entry != NULL)
1580   {
1581     myldap_entry_free(search->entry);
1582     search->entry = NULL;
1583   }
1584   /* try to parse results until we have a final error or ok */
1585   while (1)
1586   {
1587     /* free the previous message if there was any */
1588     if (search->msg != NULL)
1589     {
1590       ldap_msgfree(search->msg);
1591       search->msg = NULL;
1592     }
1593     /* get the next result */
1594     rc = ldap_result(search->session->ld, search->msgid, LDAP_MSG_ONE, tvp,
1595                      &(search->msg));
1596     /* handle result */
1597     switch (rc)
1598     {
1599       case LDAP_RES_SEARCH_ENTRY:
1600         /* we have a normal search entry, update timestamp and return result */
1601         time(&(search->session->lastactivity));
1602         search->entry = myldap_entry_new(search);
1603         if (rcp != NULL)
1604           *rcp = LDAP_SUCCESS;
1605         /* log the first couple of dns in the result (but not all, to
1606            prevent swamping the log) */
1607         if (search->count < MAX_DEBUG_LOG_DNS)
1608           log_log(LOG_DEBUG, "ldap_result(): %s", myldap_get_dn(search->entry));
1609         search->count++;
1610         search->may_retry_search = 0;
1611         return search->entry;
1612       case LDAP_RES_SEARCH_RESULT:
1613         /* we have a search result, parse it */
1614         resultcontrols = NULL;
1615         if (search->cookie != NULL)
1616         {
1617           ber_bvfree(search->cookie);
1618           search->cookie = NULL;
1619         }
1620         /* NB: this frees search->msg */
1621         parserc = ldap_parse_result(search->session->ld, search->msg, &rc,
1622                                     NULL, NULL, NULL, &resultcontrols, 1);
1623         search->msg = NULL;
1624         /* check for errors during parsing */
1625         if ((parserc != LDAP_SUCCESS) && (parserc != LDAP_MORE_RESULTS_TO_RETURN))
1626         {
1627           if (resultcontrols != NULL)
1628             ldap_controls_free(resultcontrols);
1629           myldap_err(LOG_ERR, search->session->ld, parserc, "ldap_parse_result() failed");
1630           myldap_search_close(search);
1631           if (rcp != NULL)
1632             *rcp = parserc;
1633           return NULL;
1634         }
1635         /* check for errors in message */
1636         if ((rc != LDAP_SUCCESS) && (rc != LDAP_MORE_RESULTS_TO_RETURN))
1637         {
1638           if (resultcontrols != NULL)
1639             ldap_controls_free(resultcontrols);
1640           myldap_err(LOG_ERR, search->session->ld, rc, "ldap_result() failed");
1641           /* close connection on connection problems */
1642           if ((rc == LDAP_UNAVAILABLE) || (rc == LDAP_SERVER_DOWN))
1643             do_close(search->session);
1644           myldap_search_close(search);
1645           if (rcp != NULL)
1646             *rcp = rc;
1647           return NULL;
1648         }
1649         /* handle result controls */
1650         if (resultcontrols != NULL)
1651         {
1652           /* see if there are any more pages to come */
1653           rc = ldap_parse_page_control(search->session->ld, resultcontrols,
1654                                        &count, &(search->cookie));
1655           if (rc != LDAP_SUCCESS)
1656           {
1657             if (rc != LDAP_CONTROL_NOT_FOUND)
1658               myldap_err(LOG_WARNING, search->session->ld, rc, "ldap_parse_page_control() failed");
1659             /* clear error flag */
1660             rc = LDAP_SUCCESS;
1661             if (ldap_set_option(search->session->ld, LDAP_OPT_ERROR_NUMBER,
1662                                 &rc) != LDAP_SUCCESS)
1663               log_log(LOG_WARNING, "failed to clear the error flag");
1664           }
1665           /* TODO: handle the above return code?? */
1666           ldap_controls_free(resultcontrols);
1667         }
1668         search->msgid = -1;
1669         /* check if there are more pages to come */
1670         if ((search->cookie == NULL) || (search->cookie->bv_len == 0))
1671         {
1672           if (search->count > MAX_DEBUG_LOG_DNS)
1673             log_log(LOG_DEBUG, "ldap_result(): ... %d more results",
1674                     search->count - MAX_DEBUG_LOG_DNS);
1675           log_log(LOG_DEBUG, "ldap_result(): end of results (%d total)",
1676                   search->count);
1677           /* we are at the end of the search, no more results */
1678           myldap_search_close(search);
1679           if (rcp != NULL)
1680             *rcp = LDAP_SUCCESS;
1681           return NULL;
1682         }
1683         /* try the next page */
1684         rc = do_try_search(search);
1685         if (rc != LDAP_SUCCESS)
1686         {
1687           /* close connection on connection problems */
1688           if ((rc == LDAP_UNAVAILABLE) || (rc == LDAP_SERVER_DOWN))
1689             do_close(search->session);
1690           myldap_search_close(search);
1691           if (rcp != NULL)
1692             *rcp = rc;
1693           return NULL;
1694         }
1695         /* we continue with another pass */
1696         break;
1697       case LDAP_RES_SEARCH_REFERENCE:
1698         break; /* just ignore search references */
1699       default:
1700         /* we have some error condition, find out which */
1701         switch (rc)
1702         {
1703           case -1:
1704             /* try to get error code */
1705             if (ldap_get_option(search->session->ld, LDAP_OPT_ERROR_NUMBER,
1706                                 &rc) != LDAP_SUCCESS)
1707               rc = LDAP_UNAVAILABLE;
1708             myldap_err(LOG_ERR, search->session->ld, rc, "ldap_result() failed");
1709             break;
1710           case 0:
1711             /* the timeout expired */
1712             log_log(LOG_ERR, "ldap_result() timed out");
1713             rc = LDAP_TIMELIMIT_EXCEEDED;
1714             break;
1715           default:
1716             /* unknown code */
1717             log_log(LOG_WARNING, "ldap_result() returned unexpected result type");
1718             rc = LDAP_PROTOCOL_ERROR;
1719         }
1720         /* close connection on some connection problems */
1721         if ((rc == LDAP_UNAVAILABLE) || (rc == LDAP_SERVER_DOWN) ||
1722             (rc == LDAP_SUCCESS) || (rc == LDAP_TIMELIMIT_EXCEEDED) ||
1723             (rc == LDAP_OPERATIONS_ERROR) || (rc == LDAP_PROTOCOL_ERROR) ||
1724             (rc == LDAP_BUSY) || (rc == LDAP_UNWILLING_TO_PERFORM) ||
1725             (rc == LDAP_TIMEOUT) || (rc == LDAP_CONNECT_ERROR) ||
1726             (rc == LDAP_NOT_SUPPORTED))
1727         {
1728           do_close(search->session);
1729           /* retry once if no data has been received yet */
1730           if (search->may_retry_search)
1731           {
1732             log_log(LOG_DEBUG, "myldap_get_entry(): retry search");
1733             search->may_retry_search = 0;
1734             if (do_retry_search(search) == LDAP_SUCCESS)
1735               return myldap_get_entry(search, rcp);
1736           }
1737         }
1738         /* close search */
1739         myldap_search_close(search);
1740         if (rcp != NULL)
1741           *rcp = rc;
1742         return NULL;
1743     }
1744   }
1745 }
1746 
1747 /* Get the DN from the entry. This function only returns NULL (and sets
1748    errno) if an incorrect entry is passed. If the DN value cannot be
1749    retrieved "unknown" is returned instead. */
myldap_get_dn(MYLDAP_ENTRY * entry)1750 const char *myldap_get_dn(MYLDAP_ENTRY *entry)
1751 {
1752   int rc;
1753   /* check parameters */
1754   if (!is_valid_entry(entry))
1755   {
1756     log_log(LOG_ERR, "myldap_get_dn(): invalid result entry passed");
1757     errno = EINVAL;
1758     return "unknown";
1759   }
1760   /* if we don't have it yet, retrieve it */
1761   if ((entry->dn == NULL) && (entry->search->valid))
1762   {
1763     entry->dn = ldap_get_dn(entry->search->session->ld, entry->search->msg);
1764     if (entry->dn == NULL)
1765     {
1766       if (ldap_get_option(entry->search->session->ld, LDAP_OPT_ERROR_NUMBER,
1767                           &rc) != LDAP_SUCCESS)
1768         rc = LDAP_UNAVAILABLE;
1769       myldap_err(LOG_WARNING, entry->search->session->ld, rc, "ldap_get_dn() returned NULL");
1770       /* close connection on connection problems */
1771       if ((rc == LDAP_UNAVAILABLE) || (rc == LDAP_SERVER_DOWN))
1772         do_close(entry->search->session);
1773     }
1774   }
1775   /* if we still don't have it, return unknown */
1776   if (entry->dn == NULL)
1777     return "unknown";
1778   /* return it */
1779   return entry->dn;
1780 }
1781 
myldap_cpy_dn(MYLDAP_ENTRY * entry,char * buf,size_t buflen)1782 char *myldap_cpy_dn(MYLDAP_ENTRY *entry, char *buf, size_t buflen)
1783 {
1784   const char *dn;
1785   /* get the dn */
1786   dn = myldap_get_dn(entry);
1787   /* copy into buffer */
1788   if (strlen(dn) < buflen)
1789     strcpy(buf, dn);
1790   else
1791     buf = NULL;
1792   return buf;
1793 }
1794 
1795 /* Perform ranged retrieval of attributes.
1796    http://msdn.microsoft.com/en-us/library/aa367017(vs.85).aspx
1797    http://www.tkk.fi/cc/docs/kerberos/draft-kashi-incremental-00.txt */
myldap_get_ranged_values(MYLDAP_ENTRY * entry,const char * attr)1798 static char **myldap_get_ranged_values(MYLDAP_ENTRY *entry, const char *attr)
1799 {
1800   char **values;
1801   char *attn;
1802   const char *attrs[2];
1803   BerElement *ber;
1804   int i;
1805   int startat = 0, nxt = 0;
1806   char attbuf[80];
1807   const char *dn = myldap_get_dn(entry);
1808   MYLDAP_SESSION *session = entry->search->session;
1809   MYLDAP_SEARCH *search = NULL;
1810   SET *set = NULL;
1811   /* build the attribute name to find */
1812   if (mysnprintf(attbuf, sizeof(attbuf), "%s;range=0-*", attr))
1813   {
1814     log_log(LOG_ERR, "myldap_get_ranged_values(): attbuf buffer too small (%lu required)",
1815             (unsigned long) strlen(attr) + 10);
1816     return NULL;
1817   }
1818   /* keep doing lookups untul we can't get any more results */
1819   while (1)
1820   {
1821     /* go over all attributes to find the ranged attribute */
1822     ber = NULL;
1823     attn = ldap_first_attribute(entry->search->session->ld, entry->search->msg, &ber);
1824     values = NULL;
1825     while (attn != NULL)
1826     {
1827       if (strncasecmp(attn, attbuf, strlen(attbuf) - 1) == 0)
1828       {
1829         log_log(LOG_DEBUG, "found ranged results %s", attn);
1830         nxt = atoi(attn + strlen(attbuf) - 1) + 1;
1831         values = ldap_get_values(entry->search->session->ld, entry->search->msg, attn);
1832         ldap_memfree(attn);
1833         break;
1834       }
1835       /* free old attribute name and get next one */
1836       ldap_memfree(attn);
1837       attn = ldap_next_attribute(entry->search->session->ld, entry->search->msg, ber);
1838     }
1839     ber_free(ber, 0);
1840     /* see if we found any values */
1841     if ((values == NULL) || (*values == NULL))
1842       break;
1843     /* allocate memory */
1844     if (set == NULL)
1845     {
1846       set = set_new();
1847       if (set == NULL)
1848       {
1849         ldap_value_free(values);
1850         log_log(LOG_CRIT, "myldap_get_ranged_values(): set_new() failed to allocate memory");
1851         return NULL;
1852       }
1853     }
1854     /* add to the set */
1855     for (i = 0; values[i] != NULL; i++)
1856       set_add(set, values[i]);
1857     /* free results */
1858     ldap_value_free(values);
1859     /* check if we should start a new search */
1860     if (nxt <= startat)
1861       break;
1862     startat = nxt;
1863     /* build attributes for a new search */
1864     if (mysnprintf(attbuf, sizeof(attbuf), "%s;range=%d-*", attr, startat))
1865     {
1866       log_log(LOG_ERR, "myldap_get_ranged_values(): attbuf buffer too small");
1867       break;
1868     }
1869     attrs[0] = attbuf;
1870     attrs[1] = NULL;
1871     /* close the previous search, if any */
1872     if (search != NULL)
1873       myldap_search_close(search);
1874     /* start the new search */
1875     search = myldap_search(session, dn, LDAP_SCOPE_BASE, "(objectClass=*)", attrs, NULL);
1876     if (search == NULL)
1877       break;
1878     entry = myldap_get_entry(search, NULL);
1879     if (entry == NULL)
1880       break;
1881   }
1882   /* close any started searches */
1883   if (search != NULL)
1884     myldap_search_close(search);
1885   /* return the contents of the set as a list */
1886   if (set == NULL)
1887     return NULL;
1888   values = (char **)set_tolist(set);
1889   set_free(set);
1890   if (values == NULL)
1891     log_log(LOG_CRIT, "myldap_get_ranged_values(): malloc() failed to allocate memory");
1892   return values;
1893 }
1894 
1895 /* Simple wrapper around ldap_get_values(). */
myldap_get_values(MYLDAP_ENTRY * entry,const char * attr)1896 const char **myldap_get_values(MYLDAP_ENTRY *entry, const char *attr)
1897 {
1898   char **values;
1899   int rc;
1900   int i;
1901   /* check parameters */
1902   if (!is_valid_entry(entry))
1903   {
1904     log_log(LOG_ERR, "myldap_get_values(): invalid result entry passed");
1905     errno = EINVAL;
1906     return NULL;
1907   }
1908   else if (attr == NULL)
1909   {
1910     log_log(LOG_ERR, "myldap_get_values(): invalid attribute name passed");
1911     errno = EINVAL;
1912     return NULL;
1913   }
1914   if (!entry->search->valid)
1915     return NULL; /* search has been stopped */
1916   /* get from LDAP */
1917   values = ldap_get_values(entry->search->session->ld, entry->search->msg, attr);
1918   if (values == NULL)
1919   {
1920     if (ldap_get_option(entry->search->session->ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_SUCCESS)
1921       rc = LDAP_UNAVAILABLE;
1922     /* ignore decoding errors as they are just non-existing attribute values */
1923     if (rc == LDAP_DECODING_ERROR)
1924     {
1925       rc = LDAP_SUCCESS;
1926       if (ldap_set_option(entry->search->session->ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_SUCCESS)
1927         log_log(LOG_WARNING, "failed to clear the error flag");
1928     }
1929     else if (rc == LDAP_SUCCESS)
1930     {
1931       /* we have a success code but no values, let's try to get ranged
1932          values */
1933       values = myldap_get_ranged_values(entry, attr);
1934       if (values == NULL)
1935         return NULL;
1936       /* store values entry so we can free it later on */
1937       for (i = 0; i < MAX_BUFFERS_PER_ENTRY; i++)
1938         if (entry->buffers[i] == NULL)
1939         {
1940           entry->buffers[i] = values;
1941           return (const char **)entry->buffers[i];
1942         }
1943       /* we found no room to store the values */
1944       log_log(LOG_ERR, "ldap_get_values() couldn't store results, increase MAX_BUFFERS_PER_ENTRY");
1945       free(values);
1946       return NULL;
1947     }
1948     else
1949       myldap_err(LOG_WARNING, entry->search->session->ld, rc,
1950                  "ldap_get_values() of attribute \"%s\" on entry \"%s\" returned NULL",
1951                  attr, myldap_get_dn(entry));
1952     return NULL;
1953   }
1954   /* store values entry so we can free it later on */
1955   for (i = 0; i < MAX_ATTRIBUTES_PER_ENTRY; i++)
1956     if (entry->attributevalues[i] == NULL)
1957     {
1958       entry->attributevalues[i] = values;
1959       return (const char **)values;
1960     }
1961   /* we found no room to store the entry */
1962   log_log(LOG_ERR, "ldap_get_values() couldn't store results, increase MAX_ATTRIBUTES_PER_ENTRY");
1963   ldap_value_free(values);
1964   return NULL;
1965 }
1966 
1967 /* Convert the bervalues to a simple list of strings that can be freed
1968    with one call to free(). */
bervalues_to_values(struct berval ** bvalues)1969 static const char **bervalues_to_values(struct berval **bvalues)
1970 {
1971   int num_values;
1972   int i;
1973   size_t sz;
1974   char *buf;
1975   char **values;
1976   /* figure out how much memory to allocate */
1977   num_values = ldap_count_values_len(bvalues);
1978   sz = (num_values + 1) * sizeof(char *);
1979   for (i = 0; i < num_values; i++)
1980     sz += bvalues[i]->bv_len + 1;
1981   /* allocate the needed memory */
1982   values = (char **)malloc(sz);
1983   if (values == NULL)
1984   {
1985     log_log(LOG_CRIT, "bervalues_to_values(): malloc() failed to allocate memory");
1986     return NULL;
1987   }
1988   buf = (char *)values;
1989   buf += (num_values + 1) * sizeof(char *);
1990   /* copy from bvalues */
1991   for (i = 0; i < num_values; i++)
1992   {
1993     values[i] = buf;
1994     memcpy(values[i], bvalues[i]->bv_val, bvalues[i]->bv_len);
1995     values[i][bvalues[i]->bv_len] = '\0';
1996     buf += bvalues[i]->bv_len + 1;
1997   }
1998   values[i] = NULL;
1999   return (const char **)values;
2000 }
2001 
2002 /* Simple wrapper around ldap_get_values(). */
myldap_get_values_len(MYLDAP_ENTRY * entry,const char * attr)2003 const char **myldap_get_values_len(MYLDAP_ENTRY *entry, const char *attr)
2004 {
2005   const char **values;
2006   struct berval **bvalues;
2007   int rc;
2008   int i;
2009   /* check parameters */
2010   if (!is_valid_entry(entry))
2011   {
2012     log_log(LOG_ERR, "myldap_get_values_len(): invalid result entry passed");
2013     errno = EINVAL;
2014     return NULL;
2015   }
2016   else if (attr == NULL)
2017   {
2018     log_log(LOG_ERR, "myldap_get_values_len(): invalid attribute name passed");
2019     errno = EINVAL;
2020     return NULL;
2021   }
2022   if (!entry->search->valid)
2023     return NULL; /* search has been stopped */
2024   /* get from LDAP */
2025   bvalues = ldap_get_values_len(entry->search->session->ld, entry->search->msg, attr);
2026   if (bvalues == NULL)
2027   {
2028     if (ldap_get_option(entry->search->session->ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_SUCCESS)
2029       rc = LDAP_UNAVAILABLE;
2030     /* ignore decoding errors as they are just non-existing attribute values */
2031     if (rc == LDAP_DECODING_ERROR)
2032     {
2033       rc = LDAP_SUCCESS;
2034       if (ldap_set_option(entry->search->session->ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_SUCCESS)
2035         log_log(LOG_WARNING, "failed to clear the error flag");
2036       return NULL;
2037     }
2038     else if (rc == LDAP_SUCCESS)
2039     {
2040       /* we have a success code but no values, let's try to get ranged
2041          values */
2042       values = (const char **)myldap_get_ranged_values(entry, attr);
2043     }
2044     else
2045     {
2046       myldap_err(LOG_WARNING, entry->search->session->ld, rc,
2047                  "myldap_get_values_len() of attribute \"%s\" on entry \"%s\" returned NULL",
2048                  attr, myldap_get_dn(entry));
2049       return NULL;
2050     }
2051   }
2052   else
2053   {
2054     values = bervalues_to_values(bvalues);
2055     ldap_value_free_len(bvalues);
2056   }
2057   /* check if we got allocated memory */
2058   if (values == NULL)
2059     return NULL;
2060   /* store values entry so we can free it later on */
2061   for (i = 0; i < MAX_BUFFERS_PER_ENTRY; i++)
2062     if (entry->buffers[i] == NULL)
2063     {
2064       entry->buffers[i] = (char **)values;
2065       return values;
2066     }
2067   /* we found no room to store the values */
2068   log_log(LOG_ERR, "myldap_get_values_len() couldn't store results, increase MAX_BUFFERS_PER_ENTRY");
2069   free(values);
2070   return NULL;
2071 }
2072 
2073 /* Go over the entries in exploded_rdn and see if any start with
2074    the requested attribute. Return a reference to the value part of
2075    the DN (does not modify exploded_rdn). */
find_rdn_value(char ** exploded_rdn,const char * attr)2076 static const char *find_rdn_value(char **exploded_rdn, const char *attr)
2077 {
2078   int i, j;
2079   int l;
2080   if (exploded_rdn == NULL)
2081     return NULL;
2082   /* go over all RDNs */
2083   l = strlen(attr);
2084   for (i = 0; exploded_rdn[i] != NULL; i++)
2085   {
2086     /* check that RDN starts with attr */
2087     if (strncasecmp(exploded_rdn[i], attr, l) != 0)
2088       continue;
2089     j = l;
2090     /* skip spaces */
2091     while (isspace(exploded_rdn[i][j]))
2092       j++;
2093     /* ensure that we found an equals sign now */
2094     if (exploded_rdn[i][j] != '=')
2095       continue;
2096     j++;
2097     /* skip more spaces */
2098     while (isspace(exploded_rdn[i][j]))
2099       j++;
2100     /* ensure that we're not at the end of the string */
2101     if (exploded_rdn[i][j] == '\0')
2102       continue;
2103     /* we found our value */
2104     return exploded_rdn[i] + j;
2105   }
2106   /* fail */
2107   return NULL;
2108 }
2109 
2110 /* explode the first part of DN into parts
2111    (e.g. "cn=Test", "uid=test")
2112    The returned value should be freed with ldap_value_free(). */
get_exploded_rdn(const char * dn)2113 static char **get_exploded_rdn(const char *dn)
2114 {
2115   char **exploded_dn;
2116   char **exploded_rdn;
2117   /* check if we have a DN */
2118   if ((dn == NULL) || (strcasecmp(dn, "unknown") == 0))
2119     return NULL;
2120   /* explode dn into { "uid=test", "ou=people", ..., NULL } */
2121   exploded_dn = ldap_explode_dn(dn, 0);
2122   if ((exploded_dn == NULL) || (exploded_dn[0] == NULL))
2123   {
2124     log_log(LOG_WARNING, "ldap_explode_dn(%s) returned NULL: %s",
2125             dn, strerror(errno));
2126     return NULL;
2127   }
2128   /* explode rdn (first part of exploded_dn),
2129      e.g. "cn=Test User+uid=testusr" into
2130      { "cn=Test User", "uid=testusr", NULL } */
2131   errno = 0;
2132   exploded_rdn = ldap_explode_rdn(exploded_dn[0], 0);
2133   if ((exploded_rdn == NULL) || (exploded_rdn[0] == NULL))
2134   {
2135     log_log(LOG_WARNING, "ldap_explode_rdn(%s) returned NULL: %s",
2136             exploded_dn[0], strerror(errno));
2137     if (exploded_rdn != NULL)
2138       ldap_value_free(exploded_rdn);
2139     ldap_value_free(exploded_dn);
2140     return NULL;
2141   }
2142   ldap_value_free(exploded_dn);
2143   return exploded_rdn;
2144 }
2145 
myldap_get_rdn_value(MYLDAP_ENTRY * entry,const char * attr)2146 const char *myldap_get_rdn_value(MYLDAP_ENTRY *entry, const char *attr)
2147 {
2148   /* check parameters */
2149   if (!is_valid_entry(entry))
2150   {
2151     log_log(LOG_ERR, "myldap_get_rdn_value(): invalid result entry passed");
2152     errno = EINVAL;
2153     return NULL;
2154   }
2155   else if (attr == NULL)
2156   {
2157     log_log(LOG_ERR, "myldap_get_rdn_value(): invalid attribute name passed");
2158     errno = EINVAL;
2159     return NULL;
2160   }
2161   /* check if entry contains exploded_rdn */
2162   if (entry->exploded_rdn == NULL)
2163   {
2164     entry->exploded_rdn = get_exploded_rdn(myldap_get_dn(entry));
2165     if (entry->exploded_rdn == NULL)
2166       return NULL;
2167   }
2168   /* find rnd value */
2169   return find_rdn_value(entry->exploded_rdn, attr);
2170 }
2171 
myldap_cpy_rdn_value(const char * dn,const char * attr,char * buf,size_t buflen)2172 const char *myldap_cpy_rdn_value(const char *dn, const char *attr,
2173                                  char *buf, size_t buflen)
2174 {
2175   char **exploded_rdn;
2176   const char *value;
2177   /* explode dn into { "cn=Test", "uid=test", NULL } */
2178   exploded_rdn = get_exploded_rdn(dn);
2179   if (exploded_rdn == NULL)
2180     return NULL;
2181   /* see if we have a match */
2182   value = find_rdn_value(exploded_rdn, attr);
2183   /* if we have something store it in the buffer */
2184   if ((value != NULL) && (strlen(value) < buflen))
2185     strcpy(buf, value);
2186   else
2187     value = NULL;
2188   /* free allocated stuff */
2189   ldap_value_free(exploded_rdn);
2190   /* check if we have something to return */
2191   return (value != NULL) ? buf : NULL;
2192 }
2193 
myldap_has_objectclass(MYLDAP_ENTRY * entry,const char * objectclass)2194 int myldap_has_objectclass(MYLDAP_ENTRY *entry, const char *objectclass)
2195 {
2196   const char **values;
2197   int i;
2198   if ((!is_valid_entry(entry)) || (objectclass == NULL))
2199   {
2200     log_log(LOG_ERR, "myldap_has_objectclass(): invalid argument passed");
2201     errno = EINVAL;
2202     return 0;
2203   }
2204   values = myldap_get_values(entry, "objectClass");
2205   if (values == NULL)
2206     return 0;
2207   for (i = 0; values[i] != NULL; i++)
2208   {
2209     if (strcasecmp(values[i], objectclass) == 0)
2210       return -1;
2211   }
2212   return 0;
2213 }
2214 
2215 #ifdef HAVE_LDAP_PARSE_DEREF_CONTROL
myldap_get_deref_values(MYLDAP_ENTRY * entry,const char * derefattr,const char * getattr)2216 const char ***myldap_get_deref_values(MYLDAP_ENTRY *entry,
2217                 const char *derefattr, const char *getattr)
2218 {
2219   LDAPControl **entryctrls;
2220   LDAPDerefRes *deref, *d;
2221   LDAPDerefVal *a;
2222   int i, pass;
2223   int rc;
2224   int found;
2225   int counts[2];
2226   size_t sizes[2], size;
2227   char *buffer = NULL;
2228   char ***results = NULL;
2229   rc = ldap_get_entry_controls(entry->search->session->ld, entry->search->msg,
2230                                 &entryctrls);
2231   if (rc != LDAP_SUCCESS)
2232   {
2233     myldap_err(LOG_WARNING, entry->search->session->ld, rc,
2234                "ldap_get_entry_controls() failed");
2235     return NULL;
2236   }
2237   if (entryctrls == NULL)
2238     return NULL;
2239   /* see if we can find a deref control */
2240   rc = ldap_parse_deref_control(entry->search->session->ld, entryctrls,
2241                                 &deref);
2242   if ((rc != LDAP_SUCCESS) || (deref == NULL))
2243   {
2244     if ((rc != LDAP_SUCCESS) && (rc != LDAP_CONTROL_NOT_FOUND))
2245       myldap_err(LOG_WARNING, entry->search->session->ld, rc,
2246                  "ldap_parse_deref_control() failed");
2247     /* clear error flag */
2248     rc = LDAP_SUCCESS;
2249     if (ldap_set_option(entry->search->session->ld, LDAP_OPT_ERROR_NUMBER,
2250                         &rc) != LDAP_SUCCESS)
2251       log_log(LOG_WARNING, "failed to clear the error flag");
2252     ldap_controls_free(entryctrls);
2253     return NULL;
2254   }
2255   /* two passes: one to calculate size, one to store data */
2256   for (pass=0; pass < 2; pass++)
2257   {
2258     /* reset counters and size */
2259     for (i = 0; i < 2; i++)
2260     {
2261       counts[i] = 0;
2262       sizes[i] = 0;
2263     }
2264     /* go over all deref'd attributes and find the one we're looking for */
2265     for (d = deref; d != NULL; d = d->next)
2266       if ((d->derefAttr != NULL) && (d->derefVal.bv_val != NULL) &&
2267           (strcasecmp(derefattr, d->derefAttr) == 0))
2268       {
2269         /* we should have one d per original attribute value */
2270         found = 0;
2271         /* go over deref'd attribute values to find the ones we're looking for */
2272         for (a = d->attrVals; a != NULL; a = a->next)
2273           if ((a->type != NULL) && (a->vals != NULL) &&
2274               (strcasecmp(getattr, a->type) == 0))
2275             for (i=0; a->vals[i].bv_val != NULL; i++)
2276             {
2277               found = 1;
2278               if (results == NULL)
2279               {
2280                 log_log(LOG_DEBUG, "deref %s %s=%s -> %s=%s",
2281                         myldap_get_dn(entry),  d->derefAttr, d->derefVal.bv_val,
2282                         a->type, a->vals[i].bv_val);
2283                 counts[0]++;
2284                 sizes[0] += strlen(a->vals[i].bv_val) + 1;
2285               }
2286               else
2287               {
2288                 strcpy(buffer, a->vals[i].bv_val);
2289                 results[0][counts[0]++] = buffer;
2290                 buffer += strlen(buffer) + 1;
2291               }
2292             }
2293         if (!found)
2294         {
2295           if (results == NULL)
2296           {
2297             log_log(LOG_DEBUG, "no %s deref %s %s=%s", getattr,
2298                     myldap_get_dn(entry),  d->derefAttr, d->derefVal.bv_val);
2299             counts[1]++;
2300             sizes[1] += strlen(d->derefVal.bv_val) + 1;
2301           }
2302           else
2303           {
2304             strcpy(buffer, d->derefVal.bv_val);
2305             results[1][counts[1]++] = buffer;
2306             buffer += strlen(buffer) + 1;
2307           }
2308         }
2309       }
2310     /* allocate memory after first pass */
2311     if (results == NULL)
2312     {
2313       size = sizeof(char **) * 3;
2314       for (i = 0; i < 2; i++)
2315         size += sizeof(char *) * (counts[i] + 1);
2316       for (i = 0; i < 2; i++)
2317         size += sizeof(char) * sizes[i];
2318       buffer = (char *)malloc(size);
2319       if (buffer == NULL)
2320       {
2321         log_log(LOG_CRIT, "myldap_get_deref_values(): malloc() failed to allocate memory");
2322         return NULL;
2323       }
2324       /* allocate the list of lists */
2325       results = (void *)buffer;
2326       buffer += sizeof(char **) * 3;
2327       /* allocate the lists */
2328       for (i = 0; i < 2; i++)
2329       {
2330         results[i] = (char **)buffer;
2331         buffer += sizeof(char *) * (counts[i] + 1);
2332       }
2333       results[i] = NULL;
2334     }
2335   }
2336   /* NULL terminate the lists */
2337   results[0][counts[0]] = NULL;
2338   results[1][counts[1]] = NULL;
2339   /* free control data */
2340   ldap_derefresponse_free(deref);
2341   ldap_controls_free(entryctrls);
2342   /* store results so we can free it later on */
2343   for (i = 0; i < MAX_BUFFERS_PER_ENTRY; i++)
2344     if (entry->buffers[i] == NULL)
2345     {
2346       entry->buffers[i] = (void *)results;
2347       return (const char ***)results;
2348     }
2349   /* we found no room to store the values */
2350   log_log(LOG_ERR, "myldap_get_deref_values() couldn't store results, "
2351           "increase MAX_BUFFERS_PER_ENTRY");
2352   free(results);
2353   return NULL;
2354 }
2355 #else /* not HAVE_LDAP_PARSE_DEREF_CONTROL */
myldap_get_deref_values(MYLDAP_ENTRY UNUSED (* entry),const char UNUSED (* derefattr),const char UNUSED (* getattr))2356 const char ***myldap_get_deref_values(MYLDAP_ENTRY UNUSED(*entry),
2357                 const char UNUSED(*derefattr), const char UNUSED(*getattr))
2358 {
2359   return NULL;
2360 }
2361 #endif /* not HAVE_LDAP_PARSE_DEREF_CONTROL */
2362 
myldap_escape(const char * src,char * buffer,size_t buflen)2363 int myldap_escape(const char *src, char *buffer, size_t buflen)
2364 {
2365   size_t pos = 0;
2366   /* go over all characters in source string */
2367   for (; *src != '\0'; src++)
2368   {
2369     /* check if char will fit */
2370     if ((pos + 4) >= buflen)
2371       return -1;
2372     /* do escaping for some characters */
2373     switch (*src)
2374     {
2375       case '*':
2376         strcpy(buffer + pos, "\\2a");
2377         pos += 3;
2378         break;
2379       case '(':
2380         strcpy(buffer + pos, "\\28");
2381         pos += 3;
2382         break;
2383       case ')':
2384         strcpy(buffer + pos, "\\29");
2385         pos += 3;
2386         break;
2387       case '\\':
2388         strcpy(buffer + pos, "\\5c");
2389         pos += 3;
2390         break;
2391       default:
2392         /* just copy character */
2393         buffer[pos++] = *src;
2394         break;
2395     }
2396   }
2397   /* terminate destination string */
2398   buffer[pos] = '\0';
2399   return 0;
2400 }
2401 
myldap_set_debuglevel(int level)2402 int myldap_set_debuglevel(int level)
2403 {
2404   int i;
2405   int rc;
2406   /* turn on debugging */
2407   if (level > 1)
2408   {
2409 #ifdef LBER_OPT_LOG_PRINT_FILE
2410     log_log(LOG_DEBUG, "ber_set_option(LBER_OPT_LOG_PRINT_FILE)");
2411     rc = ber_set_option(NULL, LBER_OPT_LOG_PRINT_FILE, stderr);
2412     if (rc != LDAP_SUCCESS)
2413     {
2414       myldap_err(LOG_ERR, NULL, rc, "ber_set_option(LBER_OPT_LOG_PRINT_FILE) failed");
2415       return rc;
2416     }
2417 #endif /* LBER_OPT_LOG_PRINT_FILE */
2418 #ifdef LBER_OPT_DEBUG_LEVEL
2419     if (level > 2)
2420     {
2421       i = -1;
2422       log_log(LOG_DEBUG, "ber_set_option(LBER_OPT_DEBUG_LEVEL,-1)");
2423       rc = ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &i);
2424       if (rc != LDAP_SUCCESS)
2425       {
2426         myldap_err(LOG_ERR, NULL, rc, "ber_set_option(LBER_OPT_DEBUG_LEVEL) failed");
2427         return rc;
2428       }
2429     }
2430 #endif /* LBER_OPT_DEBUG_LEVEL */
2431 #ifdef LDAP_OPT_DEBUG_LEVEL
2432     i = -1;
2433     log_log(LOG_DEBUG, "ldap_set_option(LDAP_OPT_DEBUG_LEVEL,-1)");
2434     rc = ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &i);
2435     if (rc != LDAP_SUCCESS)
2436     {
2437       myldap_err(LOG_ERR, NULL, rc, "ldap_set_option(LDAP_OPT_DEBUG_LEVEL) failed");
2438       return rc;
2439     }
2440 #endif /* LDAP_OPT_DEBUG_LEVEL */
2441   }
2442   return LDAP_SUCCESS;
2443 }
2444 
myldap_passwd(MYLDAP_SESSION * session,const char * userdn,const char * oldpassword,const char * newpasswd)2445 int myldap_passwd(MYLDAP_SESSION *session,
2446                   const char *userdn, const char *oldpassword,
2447                   const char *newpasswd)
2448 {
2449   int rc;
2450   struct berval ber_userdn, ber_oldpassword, ber_newpassword, ber_retpassword;
2451   /* check parameters */
2452   if ((session == NULL) || (userdn == NULL) || (newpasswd == NULL))
2453   {
2454     log_log(LOG_ERR, "myldap_passwd(): invalid parameter passed");
2455     errno = EINVAL;
2456     return LDAP_OTHER;
2457   }
2458   /* log the call */
2459   log_log(LOG_DEBUG, "myldap_passwd(userdn=\"%s\",oldpasswd=%s,newpasswd=\"***\")",
2460           userdn, oldpassword ? "\"***\"" : "NULL");
2461   /* translate to ber stuff */
2462   ber_userdn.bv_val = (char *)userdn;
2463   ber_userdn.bv_len = strlen(userdn);
2464   ber_newpassword.bv_val = (char *)newpasswd;
2465   ber_newpassword.bv_len = strlen(newpasswd);
2466   ber_retpassword.bv_val = NULL;
2467   ber_retpassword.bv_len = 0;
2468   /* perform request */
2469   log_log(LOG_DEBUG, "myldap_passwd(): try ldap_passwd_s() without old password");
2470   rc = ldap_passwd_s(session->ld, &ber_userdn, NULL, &ber_newpassword,
2471                      &ber_retpassword, NULL, NULL);
2472   if (rc != LDAP_SUCCESS)
2473     myldap_err(LOG_ERR, session->ld, rc, "ldap_passwd_s() without old password failed");
2474   /* free returned data if needed */
2475   if (ber_retpassword.bv_val != NULL)
2476     ldap_memfree(ber_retpassword.bv_val);
2477   if ((rc != LDAP_SUCCESS) && (oldpassword != NULL))
2478   {
2479     /* retry with old password */
2480     log_log(LOG_DEBUG, "myldap_passwd(): try ldap_passwd_s() with old password");
2481     ber_oldpassword.bv_val = (char *)oldpassword;
2482     ber_oldpassword.bv_len = strlen(oldpassword);
2483     /* perform request */
2484     rc = ldap_passwd_s(session->ld, &ber_userdn, &ber_oldpassword,
2485                        &ber_newpassword, &ber_retpassword, NULL, NULL);
2486     if (rc != LDAP_SUCCESS)
2487       myldap_err(LOG_ERR, session->ld, rc, "ldap_passwd_s() with old password failed");
2488     /* free returned data if needed */
2489     if (ber_retpassword.bv_val != NULL)
2490       ldap_memfree(ber_retpassword.bv_val);
2491   }
2492   return rc;
2493 }
2494 
myldap_modify(MYLDAP_SESSION * session,const char * dn,LDAPMod * mods[])2495 int myldap_modify(MYLDAP_SESSION *session, const char *dn, LDAPMod * mods[])
2496 {
2497   if ((session == NULL) || (dn == NULL))
2498   {
2499     log_log(LOG_ERR, "myldap_passwd(): invalid parameter passed");
2500     errno = EINVAL;
2501     return LDAP_OTHER;
2502   }
2503   return ldap_modify_ext_s(session->ld, dn, mods, NULL, NULL);
2504 }
2505 
myldap_error_message(MYLDAP_SESSION * session,int rc,char * buffer,size_t buflen)2506 int myldap_error_message(MYLDAP_SESSION *session, int rc,
2507                          char *buffer, size_t buflen)
2508 {
2509   char *msg_diag = NULL;
2510   if ((session == NULL) || (buffer == NULL) || (buflen <= 0))
2511   {
2512     log_log(LOG_ERR, "myldap_error_message(): invalid parameter passed");
2513     errno = EINVAL;
2514     return LDAP_OTHER;
2515   }
2516   /* clear buffer */
2517   buffer[0] = '\0';
2518 #ifdef LDAP_OPT_DIAGNOSTIC_MESSAGE
2519   if (session->ld != NULL)
2520     ldap_get_option(session->ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, &msg_diag);
2521 #endif /* LDAP_OPT_DIAGNOSTIC_MESSAGE */
2522   /* return msg_diag or generic error message */
2523   mysnprintf(buffer, buflen - 1, "%s",
2524              ((msg_diag != NULL) && (msg_diag[0]!='\0')) ?
2525              msg_diag : ldap_err2string(rc));
2526   /* free diagnostic message */
2527   if (msg_diag != NULL)
2528     ldap_memfree(msg_diag);
2529   return LDAP_SUCCESS;
2530 }
2531