1 /*
2    pam.c - pam processing routines
3 
4    Copyright (C) 2009 Howard Chu
5    Copyright (C) 2009-2018 Arthur de Jong
6    Copyright (C) 2015 Nokia Solutions and Networks
7 
8    This library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Lesser General Public
10    License as published by the Free Software Foundation; either
11    version 2.1 of the License, or (at your option) any later version.
12 
13    This library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Lesser General Public License for more details.
17 
18    You should have received a copy of the GNU Lesser General Public
19    License along with this library; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301 USA
22 */
23 
24 #include "config.h"
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #ifdef HAVE_STDINT_H
30 #include <stdint.h>
31 #endif /* HAVE_STDINT_H */
32 #include <unistd.h>
33 #include <time.h>
34 
35 #include "common.h"
36 #include "log.h"
37 #include "myldap.h"
38 #include "cfg.h"
39 #include "attmap.h"
40 #include "common/dict.h"
41 #include "common/expr.h"
42 
search_var_add(DICT * dict,const char * name,const char * value)43 static void search_var_add(DICT *dict, const char *name, const char *value)
44 {
45   size_t sz;
46   char *escaped_value;
47   /* allocate memory for escaped string */
48   sz = ((strlen(value) + 8) * 120) / 100;
49   escaped_value = (char *)malloc(sz);
50   if (escaped_value == NULL)
51   {
52     log_log(LOG_CRIT, "search_var_add(): malloc() failed to allocate memory");
53     return;
54   }
55   /* perform escaping of the value */
56   if (myldap_escape(value, escaped_value, sz))
57   {
58     log_log(LOG_ERR, "search_var_add(): escaped_value buffer too small");
59     free(escaped_value);
60     return;
61   }
62   /* add to dict */
63   dict_put(dict, name, escaped_value);
64 }
65 
66 /* build a dictionary with variables that can be used in searches */
search_vars_new(const char * dn,const char * username,const char * service,const char * ruser,const char * rhost,const char * tty)67 static DICT *search_vars_new(const char *dn, const char *username,
68                              const char *service, const char *ruser,
69                              const char *rhost, const char *tty)
70 {
71   char hostname[BUFLEN_HOSTNAME];
72   /* allocating this on the stack is OK because search_var_add()
73      will allocate new memory for the value */
74   const char *fqdn, *found;
75   DICT *dict;
76   dict = dict_new();
77   if (dict == NULL)
78   {
79     log_log(LOG_CRIT, "search_vars_new(): dict_new() failed to allocate memory");
80     return NULL;
81   }
82   /* NOTE: any variables added here also need to be added to
83            cfg.c:check_search_variables() */
84   search_var_add(dict, "username", username);
85   search_var_add(dict, "service", service);
86   search_var_add(dict, "ruser", ruser);
87   search_var_add(dict, "rhost", rhost);
88   search_var_add(dict, "tty", tty);
89   if (gethostname(hostname, sizeof(hostname)) == 0)
90     search_var_add(dict, "hostname", hostname);
91   if ((fqdn = getfqdn()) != NULL)
92   {
93     search_var_add(dict, "fqdn", fqdn);
94     if (((found = strchr(fqdn, '.'))) != NULL && (found[1] != '\0'))
95       search_var_add(dict, "domain", found + 1);
96   }
97   search_var_add(dict, "dn", dn);
98   search_var_add(dict, "uid", username);
99   return dict;
100 }
101 
search_vars_free(DICT * dict)102 static void search_vars_free(DICT *dict)
103 {
104   int i;
105   const char **keys;
106   void *value;
107   /* go over all keys and free all the values
108      (they were allocated in search_var_add) */
109   /* loop over dictionary contents */
110   keys = dict_keys(dict);
111   for (i = 0; keys[i] != NULL; i++)
112   {
113     value = dict_get(dict, keys[i]);
114     if (value)
115       free(value);
116   }
117   free(keys);
118   /* after this values from the dict should obviously no longer be used */
119   dict_free(dict);
120 }
121 
search_var_get(const char * name,void * expander_attr)122 static const char *search_var_get(const char *name, void *expander_attr)
123 {
124   DICT *dict = (DICT *)expander_attr;
125   return (const char *)dict_get(dict, name);
126   /* TODO: if not set use entry to get attribute name (entry can be an
127            element in the dict) */
128 }
129 
130 /* search all search bases using the provided filter */
do_searches(MYLDAP_SESSION * session,const char * option,const char * filter)131 static int do_searches(MYLDAP_SESSION *session, const char *option,
132                        const char *filter)
133 {
134   int i;
135   int rc;
136   const char *base;
137   static const char *attrs[2];
138   MYLDAP_SEARCH *search;
139   MYLDAP_ENTRY *entry;
140   /* prepare the search */
141   attrs[0] = "dn";
142   attrs[1] = NULL;
143   /* perform a search for each search base */
144   log_log(LOG_DEBUG, "trying %s \"%s\"", option, filter);
145   for (i = 0; (base = nslcd_cfg->bases[i]) != NULL; i++)
146   {
147     /* do the LDAP search */
148     search = myldap_search(session, base, LDAP_SCOPE_SUBTREE, filter, attrs, &rc);
149     if (search == NULL)
150     {
151       log_log(LOG_ERR, "%s \"%s\" failed: %s",
152               option, filter, ldap_err2string(rc));
153       return rc;
154     }
155     /* try to get an entry */
156     entry = myldap_get_entry(search, &rc);
157     if (entry != NULL)
158     {
159       log_log(LOG_DEBUG, "%s found \"%s\"", option, myldap_get_dn(entry));
160       return LDAP_SUCCESS;
161     }
162   }
163   log_log(LOG_ERR, "%s \"%s\" found no matches", option, filter);
164   if (rc == LDAP_SUCCESS)
165     rc = LDAP_NO_SUCH_OBJECT;
166   return rc;
167 }
168 
169 /* set up a connection and try to bind with the specified DN and password,
170    returns an LDAP result code */
try_bind(const char * userdn,const char * password,const char * username,const char * service,const char * ruser,const char * rhost,const char * tty,int * authzrc,char * authzmsg,size_t authzmsgsz)171 static int try_bind(const char *userdn, const char *password,
172                     const char *username, const char *service,
173                     const char *ruser, const char *rhost, const char *tty,
174                     int *authzrc, char *authzmsg, size_t authzmsgsz)
175 {
176   MYLDAP_SESSION *session;
177   MYLDAP_SEARCH *search;
178   MYLDAP_ENTRY *entry;
179   static const char *attrs[2];
180   int rc;
181   const char *msg;
182   DICT *dict;
183   char filter[BUFLEN_FILTER];
184   const char *res;
185   /* set up a new connection */
186   session = myldap_create_session();
187   if (session == NULL)
188     return LDAP_UNAVAILABLE;
189   /* perform a BIND operation with user credentials */
190   rc = myldap_bind(session, userdn, password, authzrc, &msg);
191   if (rc == LDAP_SUCCESS)
192   {
193     /* perform a search to trigger the BIND operation */
194     attrs[0] = "dn";
195     attrs[1] = NULL;
196     if (strcasecmp(nslcd_cfg->pam_authc_search, "BASE") == 0)
197     {
198       /* do a simple search to check userdn existence */
199       search = myldap_search(session, userdn, LDAP_SCOPE_BASE,
200                              "(objectClass=*)", attrs, &rc);
201       if ((search == NULL) && (rc == LDAP_SUCCESS))
202         rc = LDAP_LOCAL_ERROR;
203       if (rc == LDAP_SUCCESS)
204       {
205         entry = myldap_get_entry(search, &rc);
206         if ((entry == NULL) && (rc == LDAP_SUCCESS))
207           rc = LDAP_NO_RESULTS_RETURNED;
208       }
209     }
210     else if (strcasecmp(nslcd_cfg->pam_authc_search, "NONE") != 0)
211     {
212       /* build the search filter */
213       dict = search_vars_new(userdn, username, service, ruser, rhost, tty);
214       if (dict == NULL)
215       {
216         myldap_session_close(session);
217         return LDAP_LOCAL_ERROR;
218       }
219       res = expr_parse(nslcd_cfg->pam_authc_search, filter, sizeof(filter),
220                        search_var_get, (void *)dict);
221       if (res == NULL)
222       {
223         search_vars_free(dict);
224         myldap_session_close(session);
225         log_log(LOG_ERR, "invalid pam_authc_search \"%s\"",
226                 nslcd_cfg->pam_authc_search);
227         return LDAP_LOCAL_ERROR;
228       }
229       /* perform a search for each search base */
230       rc = do_searches(session, "pam_authc_search", filter);
231       /* free search variables */
232       search_vars_free(dict);
233     }
234   }
235   /* log any authentication, search or authorisation messages */
236   if (rc != LDAP_SUCCESS)
237     log_log(LOG_WARNING, "%s: %s", userdn, ldap_err2string(rc));
238   if ((msg != NULL) && (msg[0] != '\0'))
239   {
240     mysnprintf(authzmsg, authzmsgsz - 1, "%s", msg);
241     log_log(LOG_WARNING, "%s: %s", userdn, authzmsg);
242   }
243   /* close the session */
244   myldap_session_close(session);
245   /* return results */
246   return rc;
247 }
248 
249 /* ensure that both userdn and username are filled in from the entry,
250    returns an LDAP result code */
validate_user(MYLDAP_SESSION * session,char * username,int * rcp)251 static MYLDAP_ENTRY *validate_user(MYLDAP_SESSION *session,
252                                    char *username, int *rcp)
253 {
254   int rc;
255   MYLDAP_ENTRY *entry = NULL;
256   /* check username for validity */
257   if (!isvalidname(username))
258   {
259     log_log(LOG_WARNING, "request denied by validnames option");
260     *rcp = LDAP_NO_SUCH_OBJECT;
261     return NULL;
262   }
263   /* get the user entry based on the username */
264   entry = uid2entry(session, username, &rc);
265   if (entry == NULL)
266   {
267     if (rc == LDAP_SUCCESS)
268       rc = LDAP_NO_SUCH_OBJECT;
269     log_log(LOG_DEBUG, "\"%s\": user not found: %s", username, ldap_err2string(rc));
270     *rcp = rc;
271   }
272   return entry;
273 }
274 
275 /* update the username value from the entry if needed */
update_username(MYLDAP_ENTRY * entry,char * username,size_t username_len)276 static void update_username(MYLDAP_ENTRY *entry, char *username,
277                             size_t username_len)
278 {
279   const char **values;
280   const char *value;
281   /* get the "real" username */
282   value = myldap_get_rdn_value(entry, attmap_passwd_uid);
283   if (value == NULL)
284   {
285     /* get the username from the uid attribute */
286     values = myldap_get_values(entry, attmap_passwd_uid);
287     if ((values == NULL) || (values[0] == NULL))
288     {
289       log_log(LOG_WARNING, "%s: %s: missing",
290               myldap_get_dn(entry), attmap_passwd_uid);
291       return;
292     }
293     value = values[0];
294   }
295   /* check the username */
296   if ((value == NULL) || !isvalidname(value) || strlen(value) >= username_len)
297   {
298     log_log(LOG_WARNING, "%s: %s: denied by validnames option",
299             myldap_get_dn(entry), attmap_passwd_uid);
300     return;
301   }
302   /* check if the username is different and update it if needed */
303   if (STR_CMP(username, value) != 0)
304   {
305     log_log(LOG_INFO, "username changed from \"%s\" to \"%s\"",
306             username, value);
307     strcpy(username, value);
308   }
309 }
310 
check_shadow(MYLDAP_SESSION * session,const char * username,char * authzmsg,size_t authzmsgsz,int check_maxdays,int check_mindays)311 static int check_shadow(MYLDAP_SESSION *session, const char *username,
312                         char *authzmsg, size_t authzmsgsz,
313                         int check_maxdays, int check_mindays)
314 {
315   MYLDAP_ENTRY *entry = NULL;
316   long today, lastchangedate, mindays, maxdays, warndays, inactdays, expiredate;
317   unsigned long flag;
318   long daysleft, inactleft;
319   /* get the shadow entry */
320   entry = shadow_uid2entry(session, username, NULL);
321   if (entry == NULL)
322     return NSLCD_PAM_SUCCESS; /* no shadow entry found, nothing to check */
323   /* get today's date */
324   today = (long)(time(NULL) / (60 * 60 * 24));
325   /* get shadow information */
326   get_shadow_properties(entry, &lastchangedate, &mindays, &maxdays, &warndays,
327                         &inactdays, &expiredate, &flag);
328   /* check account expiry date */
329   if ((expiredate != -1) && (today >= expiredate))
330   {
331     daysleft = today - expiredate;
332     mysnprintf(authzmsg, authzmsgsz - 1, "Account expired %ld days ago",
333                daysleft);
334     log_log(LOG_WARNING, "%s: %s: %s",
335             myldap_get_dn(entry), attmap_shadow_shadowExpire, authzmsg);
336     return NSLCD_PAM_ACCT_EXPIRED;
337   }
338   /* password expiration isn't interesting at this point because the user
339      may not have authenticated with a password and if he did that would be
340      checked in the authc phase */
341   if (check_maxdays)
342   {
343     /* check lastchanged */
344     if (lastchangedate == 0)
345     {
346       mysnprintf(authzmsg, authzmsgsz - 1, "Need a new password");
347       log_log(LOG_WARNING, "%s: %s: %s",
348               myldap_get_dn(entry), attmap_shadow_shadowLastChange, authzmsg);
349       return NSLCD_PAM_NEW_AUTHTOK_REQD;
350     }
351     else if (today < lastchangedate)
352       log_log(LOG_WARNING, "%s: %s: password changed in the future",
353               myldap_get_dn(entry), attmap_shadow_shadowLastChange);
354     else if (maxdays != -1)
355     {
356       /* check maxdays */
357       daysleft = lastchangedate + maxdays - today;
358       if (daysleft == 0)
359         mysnprintf(authzmsg, authzmsgsz - 1, "Password will expire today");
360       else if (daysleft < 0)
361         mysnprintf(authzmsg, authzmsgsz - 1, "Password expired %ld days ago",
362                    -daysleft);
363       /* check inactdays */
364       if ((daysleft <= 0) && (inactdays != -1))
365       {
366         inactleft = lastchangedate + maxdays + inactdays - today;
367         if (inactleft == 0)
368           mysnprintf(authzmsg + strlen(authzmsg), authzmsgsz - strlen(authzmsg) - 1,
369                      ", account will be locked today");
370         else if (inactleft > 0)
371           mysnprintf(authzmsg + strlen(authzmsg), authzmsgsz - strlen(authzmsg) - 1,
372                      ", account will be locked in %ld days", inactleft);
373         else
374         {
375           mysnprintf(authzmsg + strlen(authzmsg), authzmsgsz - strlen(authzmsg) - 1,
376                      ", account locked %ld days ago", -inactleft);
377           log_log(LOG_WARNING, "%s: %s: %s", myldap_get_dn(entry),
378                   attmap_shadow_shadowInactive, authzmsg);
379           return NSLCD_PAM_AUTHTOK_EXPIRED;
380         }
381       }
382       if (daysleft <= 0)
383       {
384         /* log previously built message */
385         log_log(LOG_WARNING, "%s: %s: %s",
386                 myldap_get_dn(entry), attmap_shadow_shadowMax, authzmsg);
387         return NSLCD_PAM_NEW_AUTHTOK_REQD;
388       }
389       /* check warndays */
390       if ((warndays > 0) && (daysleft <= warndays))
391       {
392         mysnprintf(authzmsg, authzmsgsz - 1,
393                    "Password will expire in %ld days", daysleft);
394         log_log(LOG_WARNING, "%s: %s: %s",
395                 myldap_get_dn(entry), attmap_shadow_shadowWarning, authzmsg);
396       }
397     }
398   }
399   if (check_mindays)
400   {
401     daysleft = lastchangedate + mindays - today;
402     if ((mindays != -1) && (daysleft > 0))
403     {
404       mysnprintf(authzmsg, authzmsgsz - 1,
405                  "Password cannot be changed for another %ld days", daysleft);
406       log_log(LOG_WARNING, "%s: %s: %s",
407               myldap_get_dn(entry), attmap_shadow_shadowMin, authzmsg);
408       return NSLCD_PAM_AUTHTOK_ERR;
409     }
410   }
411   return NSLCD_PAM_SUCCESS;
412 }
413 
414 /* check authentication credentials of the user */
nslcd_pam_authc(TFILE * fp,MYLDAP_SESSION * session,uid_t calleruid)415 int nslcd_pam_authc(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid)
416 {
417   int32_t tmpint32;
418   int rc;
419   char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
420   char password[BUFLEN_PASSWORD];
421   const char *userdn;
422   MYLDAP_ENTRY *entry;
423   int authzrc = NSLCD_PAM_SUCCESS;
424   char authzmsg[BUFLEN_MESSAGE];
425   authzmsg[0] = '\0';
426   /* read request parameters */
427   READ_STRING(fp, username);
428   READ_STRING(fp, service);
429   READ_STRING(fp, ruser);
430   READ_STRING(fp, rhost);
431   READ_STRING(fp, tty);
432   READ_STRING(fp, password);
433   /* log call */
434   log_setrequest("authc=\"%s\"", username);
435   log_log(LOG_DEBUG, "nslcd_pam_authc(\"%s\",\"%s\",\"%s\")",
436           username, service, *password ? "***" : "");
437   /* write the response header */
438   WRITE_INT32(fp, NSLCD_VERSION);
439   WRITE_INT32(fp, NSLCD_ACTION_PAM_AUTHC);
440   /* if the username is blank and rootpwmoddn is configured, try to
441      authenticate as administrator, otherwise validate request as usual */
442   if (*username == '\0')
443   {
444     if (nslcd_cfg->rootpwmoddn == NULL)
445     {
446       log_log(LOG_NOTICE, "rootpwmoddn not configured");
447       /* we break the protocol */
448       memset(password, 0, sizeof(password));
449       return -1;
450     }
451     userdn = nslcd_cfg->rootpwmoddn;
452     /* if the caller is root we will allow the use of the rootpwmodpw option */
453     if ((*password == '\0') && (calleruid == 0) && (nslcd_cfg->rootpwmodpw != NULL))
454     {
455       if (strlen(nslcd_cfg->rootpwmodpw) >= sizeof(password))
456       {
457         log_log(LOG_ERR, "nslcd_pam_authc(): rootpwmodpw will not fit in password");
458         memset(password, 0, sizeof(password));
459         return -1;
460       }
461       strcpy(password, nslcd_cfg->rootpwmodpw);
462     }
463   }
464   else
465   {
466     /* try normal authentication, lookup the user entry */
467     entry = validate_user(session, username, &rc);
468     if (entry == NULL)
469     {
470       /* for user not found we just say no result */
471       if (rc == LDAP_NO_SUCH_OBJECT)
472       {
473         WRITE_INT32(fp, NSLCD_RESULT_END);
474       }
475       memset(password, 0, sizeof(password));
476       return -1;
477     }
478     userdn = myldap_get_dn(entry);
479     update_username(entry, username, sizeof(username));
480   }
481   /* try authentication */
482   rc = try_bind(userdn, password, username, service, ruser, rhost, tty,
483                 &authzrc, authzmsg, sizeof(authzmsg));
484   if (rc == LDAP_SUCCESS)
485     log_log(LOG_DEBUG, "bind successful");
486   /* map result code */
487   switch (rc)
488   {
489     case LDAP_SUCCESS:             rc = NSLCD_PAM_SUCCESS;  break;
490     case LDAP_INVALID_CREDENTIALS: rc = NSLCD_PAM_AUTH_ERR; break;
491     default:                       rc = NSLCD_PAM_AUTH_ERR;
492   }
493   /* perform shadow attribute checks */
494   if ((*username != '\0') && (authzrc == NSLCD_PAM_SUCCESS))
495     authzrc = check_shadow(session, username, authzmsg, sizeof(authzmsg), 1, 0);
496   /* write response */
497   WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
498   WRITE_INT32(fp, rc);
499   WRITE_STRING(fp, username);
500   WRITE_INT32(fp, authzrc);
501   WRITE_STRING(fp, authzmsg);
502   WRITE_INT32(fp, NSLCD_RESULT_END);
503   memset(password, 0, sizeof(password));
504   return 0;
505 }
506 
507 /* perform an authorisation search, returns an LDAP status code */
try_authz_search(MYLDAP_SESSION * session,const char * dn,const char * username,const char * service,const char * ruser,const char * rhost,const char * tty)508 static int try_authz_search(MYLDAP_SESSION *session, const char *dn,
509                           const char *username, const char *service,
510                           const char *ruser, const char *rhost,
511                           const char *tty)
512 {
513   DICT *dict = NULL;
514   char filter[BUFLEN_FILTER];
515   int rc = LDAP_SUCCESS;
516   const char *res;
517   int i;
518   /* go over all pam_authz_search options */
519   for (i = 0; (i < NSS_LDAP_CONFIG_MAX_AUTHZ_SEARCHES) && (nslcd_cfg->pam_authz_searches[i] != NULL); i++)
520   {
521     if (dict == NULL)
522     {
523       dict = search_vars_new(dn, username, service, ruser, rhost, tty);
524       if (dict == NULL)
525         return LDAP_LOCAL_ERROR;
526     }
527     /* build the search filter */
528     res = expr_parse(nslcd_cfg->pam_authz_searches[i],
529                      filter, sizeof(filter),
530                      search_var_get, (void *)dict);
531     if (res == NULL)
532     {
533       search_vars_free(dict);
534       log_log(LOG_ERR, "invalid pam_authz_search \"%s\"",
535               nslcd_cfg->pam_authz_searches[i]);
536       return LDAP_LOCAL_ERROR;
537     }
538     /* perform the actual searches on all bases */
539     rc = do_searches(session, "pam_authz_search", filter);
540     if (rc != LDAP_SUCCESS)
541       break;
542   }
543   /* we went over all pam_authz_search entries */
544   if (dict != NULL)
545     search_vars_free(dict);
546   return rc;
547 }
548 
549 /* check authorisation of the user */
nslcd_pam_authz(TFILE * fp,MYLDAP_SESSION * session)550 int nslcd_pam_authz(TFILE *fp, MYLDAP_SESSION *session)
551 {
552   int32_t tmpint32;
553   int rc;
554   char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
555   MYLDAP_ENTRY *entry;
556   char authzmsg[BUFLEN_MESSAGE];
557   authzmsg[0] = '\0';
558   /* read request parameters */
559   READ_STRING(fp, username);
560   READ_STRING(fp, service);
561   READ_STRING(fp, ruser);
562   READ_STRING(fp, rhost);
563   READ_STRING(fp, tty);
564   /* log call */
565   log_setrequest("authz=\"%s\"", username);
566   log_log(LOG_DEBUG, "nslcd_pam_authz(\"%s\",\"%s\",\"%s\",\"%s\",\"%s\")",
567           username, service, ruser, rhost, tty);
568   /* write the response header */
569   WRITE_INT32(fp, NSLCD_VERSION);
570   WRITE_INT32(fp, NSLCD_ACTION_PAM_AUTHZ);
571   /* validate request */
572   entry = validate_user(session, username, &rc);
573   if (entry == NULL)
574   {
575     /* for user not found we just say no result */
576     if (rc == LDAP_NO_SUCH_OBJECT)
577     {
578       WRITE_INT32(fp, NSLCD_RESULT_END);
579     }
580     return -1;
581   }
582   /* check authorisation search */
583   rc = try_authz_search(session, myldap_get_dn(entry), username, service, ruser,
584                       rhost, tty);
585   if (rc != LDAP_SUCCESS)
586   {
587     WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
588     WRITE_INT32(fp, NSLCD_PAM_PERM_DENIED);
589     WRITE_STRING(fp, "LDAP authorisation check failed");
590     WRITE_INT32(fp, NSLCD_RESULT_END);
591     return 0;
592   }
593   /* perform shadow attribute checks */
594   rc = check_shadow(session, username, authzmsg, sizeof(authzmsg), 0, 0);
595   /* write response */
596   WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
597   WRITE_INT32(fp, rc);
598   WRITE_STRING(fp, authzmsg);
599   WRITE_INT32(fp, NSLCD_RESULT_END);
600   return 0;
601 }
602 
nslcd_pam_sess_o(TFILE * fp,MYLDAP_SESSION UNUSED (* session))603 int nslcd_pam_sess_o(TFILE *fp, MYLDAP_SESSION UNUSED(*session))
604 {
605   int32_t tmpint32;
606   char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
607   char sessionid[25];
608   static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
609                                  "abcdefghijklmnopqrstuvwxyz"
610                                  "01234567890";
611   unsigned int i;
612   /* read request parameters */
613   READ_STRING(fp, username);
614   READ_STRING(fp, service);
615   READ_STRING(fp, ruser);
616   READ_STRING(fp, rhost);
617   READ_STRING(fp, tty);
618   /* generate pseudo-random session id */
619   for (i = 0; i < (sizeof(sessionid) - 1); i++)
620     sessionid[i] = alphabet[rand() % (sizeof(alphabet) - 1)];
621   sessionid[i] = '\0';
622   /* log call */
623   log_setrequest("sess_o=\"%s\"", username);
624   log_log(LOG_DEBUG, "nslcd_pam_sess_o(\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"): %s",
625           username, service, tty, rhost, ruser, sessionid);
626   /* write the response header */
627   WRITE_INT32(fp, NSLCD_VERSION);
628   WRITE_INT32(fp, NSLCD_ACTION_PAM_SESS_O);
629   /* write response */
630   WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
631   WRITE_STRING(fp, sessionid);
632   WRITE_INT32(fp, NSLCD_RESULT_END);
633   return 0;
634 }
635 
nslcd_pam_sess_c(TFILE * fp,MYLDAP_SESSION UNUSED (* session))636 int nslcd_pam_sess_c(TFILE *fp, MYLDAP_SESSION UNUSED(*session))
637 {
638   int32_t tmpint32;
639   char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
640   char sessionid[64];
641   /* read request parameters */
642   READ_STRING(fp, username);
643   READ_STRING(fp, service);
644   READ_STRING(fp, ruser);
645   READ_STRING(fp, rhost);
646   READ_STRING(fp, tty);
647   READ_STRING(fp, sessionid);
648   /* log call */
649   log_setrequest("sess_c=\"%s\"", username);
650   log_log(LOG_DEBUG, "nslcd_pam_sess_c(\"%s\",\"%s\",%s)",
651           username, service, sessionid);
652   /* write the response header */
653   WRITE_INT32(fp, NSLCD_VERSION);
654   WRITE_INT32(fp, NSLCD_ACTION_PAM_SESS_C);
655   /* write response */
656   WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
657   WRITE_INT32(fp, NSLCD_RESULT_END);
658   return 0;
659 }
660 
661 extern const char *shadow_filter;
662 
663 /* try to update the shadowLastChange attribute of the entry if possible */
update_lastchange(MYLDAP_SESSION * session,const char * userdn)664 static int update_lastchange(MYLDAP_SESSION *session, const char *userdn)
665 {
666   MYLDAP_SEARCH *search;
667   MYLDAP_ENTRY *entry;
668   static const char *attrs[3];
669   const char *attr;
670   int rc;
671   const char **values;
672   LDAPMod mod, *mods[2];
673   char buffer[64], *strvals[2];
674   /* find the name of the attribute to use */
675   if ((attmap_shadow_shadowLastChange == NULL) || (attmap_shadow_shadowLastChange[0] == '\0'))
676     return LDAP_LOCAL_ERROR; /* attribute not mapped at all */
677   else if (strcmp(attmap_shadow_shadowLastChange, "\"${shadowLastChange:--1}\"") == 0)
678     attr = "shadowLastChange";
679   else if (attmap_shadow_shadowLastChange[0] == '\"')
680     return LDAP_LOCAL_ERROR; /* other expressions not supported for now */
681   else
682     attr = attmap_shadow_shadowLastChange;
683   /* set up the attributes we need */
684   attrs[0] = attmap_shadow_uid;
685   attrs[1] = attr;
686   attrs[2] = NULL;
687   /* find the entry to see if the attribute is present */
688   search = myldap_search(session, userdn, LDAP_SCOPE_BASE, shadow_filter, attrs, &rc);
689   if (search == NULL)
690     return rc;
691   entry = myldap_get_entry(search, &rc);
692   if (entry == NULL)
693     return rc;
694   values = myldap_get_values(entry, attr);
695   if ((values == NULL) || (values[0] == NULL) || (values[0][0] == '\0'))
696     return LDAP_NO_SUCH_ATTRIBUTE;
697   /* build the value for the new attribute */
698   if (strcasecmp(attr, "pwdLastSet") == 0)
699   {
700     /* for AD we use another timestamp */
701     if (mysnprintf(buffer, sizeof(buffer), "%ld000000000",
702                    ((long int)time(NULL) / 100L + (134774L * 864L))))
703       return LDAP_LOCAL_ERROR;
704   }
705   else
706   {
707     /* time in days since Jan 1, 1970 */
708     if (mysnprintf(buffer, sizeof(buffer), "%ld",
709                    ((long int)(time(NULL) / (long int)(60 * 60 * 24)))))
710       return LDAP_LOCAL_ERROR;
711   }
712   /* update the shadowLastChange attribute */
713   strvals[0] = buffer;
714   strvals[1] = NULL;
715   mod.mod_op = LDAP_MOD_REPLACE;
716   mod.mod_type = (char *)attr;
717   mod.mod_values = strvals;
718   mods[0] = &mod;
719   mods[1] = NULL;
720   rc = myldap_modify(session, userdn, mods);
721   if (rc != LDAP_SUCCESS)
722     log_log(LOG_WARNING, "%s: %s: modification failed: %s",
723             userdn, attr, ldap_err2string(rc));
724   else
725     log_log(LOG_DEBUG, "%s: %s: modification succeeded", userdn, attr);
726   return rc;
727 }
728 
729 /* perform an LDAP password modification, returns an LDAP status code */
try_pwmod(MYLDAP_SESSION * oldsession,const char * binddn,const char * userdn,const char * oldpassword,const char * newpassword,char * authzmsg,size_t authzmsg_len)730 static int try_pwmod(MYLDAP_SESSION *oldsession,
731                      const char *binddn, const char *userdn,
732                      const char *oldpassword, const char *newpassword,
733                      char *authzmsg, size_t authzmsg_len)
734 {
735   MYLDAP_SESSION *session;
736   char buffer[BUFLEN_MESSAGE];
737   int rc;
738   /* set up a new connection */
739   session = myldap_create_session();
740   if (session == NULL)
741     return LDAP_UNAVAILABLE;
742   /* perform a BIND operation */
743   rc = myldap_bind(session, binddn, oldpassword, NULL, NULL);
744   if (rc == LDAP_SUCCESS)
745   {
746     /* if doing password modification as admin, don't pass old password along */
747     if ((nslcd_cfg->rootpwmoddn != NULL) &&
748         (strcmp(binddn, nslcd_cfg->rootpwmoddn) == 0))
749       oldpassword = NULL;
750     /* perform password modification */
751     rc = myldap_passwd(session, userdn, oldpassword, newpassword);
752     if (rc == LDAP_SUCCESS)
753     {
754       /* try to update the shadowLastChange attribute */
755       if (update_lastchange(session, userdn) != LDAP_SUCCESS)
756         /* retry with the normal session */
757         (void)update_lastchange(oldsession, userdn);
758     }
759     else
760     {
761       /* get a diagnostic or error message */
762       if ((myldap_error_message(session, rc, buffer, sizeof(buffer)) == LDAP_SUCCESS) &&
763           (buffer[0] != '\0'))
764         mysnprintf(authzmsg, authzmsg_len - 1, "password change failed: %s",
765                    buffer);
766     }
767   }
768   /* close the session */
769   myldap_session_close(session);
770   /* return */
771   return rc;
772 }
773 
nslcd_pam_pwmod(TFILE * fp,MYLDAP_SESSION * session,uid_t calleruid)774 int nslcd_pam_pwmod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid)
775 {
776   int32_t tmpint32;
777   int rc;
778   char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
779   int asroot;
780   char oldpassword[BUFLEN_PASSWORD];
781   char newpassword[BUFLEN_PASSWORD];
782   const char *binddn = NULL; /* the user performing the modification */
783   MYLDAP_ENTRY *entry;
784   char authzmsg[BUFLEN_MESSAGE];
785   authzmsg[0] = '\0';
786   /* read request parameters */
787   READ_STRING(fp, username);
788   READ_STRING(fp, service);
789   READ_STRING(fp, ruser);
790   READ_STRING(fp, rhost);
791   READ_STRING(fp, tty);
792   READ_INT32(fp, asroot);
793   READ_STRING(fp, oldpassword);
794   READ_STRING(fp, newpassword);
795   /* log call */
796   log_setrequest("pwmod=\"%s\"", username);
797   log_log(LOG_DEBUG, "nslcd_pam_pwmod(\"%s\",%s,\"%s\",\"%s\",\"%s\")",
798           username, asroot ? "asroot" : "asuser", service,
799           *oldpassword ? "***" : "", *newpassword ? "***" : "");
800   /* write the response header */
801   WRITE_INT32(fp, NSLCD_VERSION);
802   WRITE_INT32(fp, NSLCD_ACTION_PAM_PWMOD);
803   /* validate request */
804   entry = validate_user(session, username, &rc);
805   if (entry == NULL)
806   {
807     /* for user not found we just say no result */
808     if (rc == LDAP_NO_SUCH_OBJECT)
809     {
810       WRITE_INT32(fp, NSLCD_RESULT_END);
811     }
812     memset(oldpassword, 0, sizeof(oldpassword));
813     memset(newpassword, 0, sizeof(newpassword));
814     return -1;
815   }
816   /* check if pam_password_prohibit_message is set */
817   if (nslcd_cfg->pam_password_prohibit_message != NULL)
818   {
819     log_log(LOG_NOTICE, "password change prohibited");
820     WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
821     WRITE_INT32(fp, NSLCD_PAM_PERM_DENIED);
822     WRITE_STRING(fp, nslcd_cfg->pam_password_prohibit_message);
823     WRITE_INT32(fp, NSLCD_RESULT_END);
824     memset(oldpassword, 0, sizeof(oldpassword));
825     memset(newpassword, 0, sizeof(newpassword));
826     return 0;
827   }
828   /* check if the the user passed the rootpwmoddn */
829   if (asroot)
830   {
831     binddn = nslcd_cfg->rootpwmoddn;
832     /* check if rootpwmodpw should be used */
833     if ((*oldpassword == '\0') && (calleruid == 0) &&
834         (nslcd_cfg->rootpwmodpw != NULL))
835     {
836       if (strlen(nslcd_cfg->rootpwmodpw) >= sizeof(oldpassword))
837       {
838         log_log(LOG_ERR, "nslcd_pam_pwmod(): rootpwmodpw will not fit in oldpassword");
839         memset(oldpassword, 0, sizeof(oldpassword));
840         memset(newpassword, 0, sizeof(newpassword));
841         return -1;
842       }
843       strcpy(oldpassword, nslcd_cfg->rootpwmodpw);
844     }
845   }
846   else
847   {
848     binddn = myldap_get_dn(entry);
849     /* check whether shadow properties allow password change */
850     rc = check_shadow(session, username, authzmsg, sizeof(authzmsg), 0, 1);
851     if (rc != NSLCD_PAM_SUCCESS)
852     {
853       WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
854       WRITE_INT32(fp, rc);
855       WRITE_STRING(fp, authzmsg);
856       WRITE_INT32(fp, NSLCD_RESULT_END);
857       memset(oldpassword, 0, sizeof(oldpassword));
858       memset(newpassword, 0, sizeof(newpassword));
859       return 0;
860     }
861   }
862   /* perform password modification */
863   rc = try_pwmod(session, binddn, myldap_get_dn(entry), oldpassword, newpassword,
864                  authzmsg, sizeof(authzmsg));
865   if (rc != LDAP_SUCCESS)
866   {
867     if (authzmsg[0] == '\0')
868       mysnprintf(authzmsg, sizeof(authzmsg) - 1, "password change failed: %s",
869                  ldap_err2string(rc));
870     WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
871     WRITE_INT32(fp, NSLCD_PAM_PERM_DENIED);
872     WRITE_STRING(fp, authzmsg);
873     WRITE_INT32(fp, NSLCD_RESULT_END);
874     memset(oldpassword, 0, sizeof(oldpassword));
875     memset(newpassword, 0, sizeof(newpassword));
876     return 0;
877   }
878   /* write response */
879   log_log(LOG_NOTICE, "password changed for %s", myldap_get_dn(entry));
880   WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
881   WRITE_INT32(fp, NSLCD_PAM_SUCCESS);
882   WRITE_STRING(fp, "");
883   WRITE_INT32(fp, NSLCD_RESULT_END);
884   memset(oldpassword, 0, sizeof(oldpassword));
885   memset(newpassword, 0, sizeof(newpassword));
886   return 0;
887 }
888