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