r22388: clearer message, thanks David
[kai/samba-autobuild/.git] / source / nsswitch / pam_winbind.c
1 /* pam_winbind module
2
3    Copyright Andrew Tridgell <tridge@samba.org> 2000
4    Copyright Tim Potter <tpot@samba.org> 2000
5    Copyright Andrew Bartlett <abartlet@samba.org> 2002
6    Copyright Guenther Deschner <gd@samba.org> 2005-2007
7
8    largely based on pam_userdb by Cristian Gafton <gafton@redhat.com> 
9    also contains large slabs of code from pam_unix by Elliot Lee <sopwith@redhat.com>
10    (see copyright below for full details)
11 */
12
13 #include "pam_winbind.h"
14
15 #define _PAM_LOG_FUNCTION_ENTER(function, pamh, ctrl, flags) \
16         do { \
17                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "[pamh: 0x%08x] ENTER: " function " (flags: 0x%04x)", (uint32) pamh, flags); \
18                 _pam_log_state(pamh, ctrl); \
19         } while (0)
20
21 #define _PAM_LOG_FUNCTION_LEAVE(function, pamh, ctrl, retval) \
22         do { \
23                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "[pamh: 0x%08x] LEAVE: " function " returning %d", (uint32) pamh, retval); \
24                 _pam_log_state(pamh, ctrl); \
25         } while (0)
26
27 /* data tokens */
28
29 #define MAX_PASSWD_TRIES        3
30
31 /*
32  * Work around the pam API that has functions with void ** as parameters.
33  * These lead to strict aliasing warnings with gcc.
34  */
35 static int _pam_get_item(const pam_handle_t *pamh, int item_type,
36                          const void *_item)
37 {
38         const void **item = (const void **)_item;
39         return pam_get_item(pamh, item_type, item);
40 }
41 static int _pam_get_data(const pam_handle_t *pamh,
42                          const char *module_data_name, const void *_data)
43 {
44         const void **data = (const void **)_data;
45         return pam_get_data(pamh, module_data_name, data);
46 }
47
48 /* some syslogging */
49
50 #ifdef HAVE_PAM_VSYSLOG
51 static void _pam_log_int(const pam_handle_t *pamh, int err, const char *format, va_list args)
52 {
53         pam_vsyslog(pamh, err, format, args);
54 }
55 #else
56 static void _pam_log_int(const pam_handle_t *pamh, int err, const char *format, va_list args)
57 {
58         char *format2 = NULL;
59         const char *service;
60
61         _pam_get_item(pamh, PAM_SERVICE, &service);
62
63         format2 = malloc(strlen(MODULE_NAME)+strlen(format)+strlen(service)+5);
64         if (format2 == NULL) {
65                 /* what else todo ? */
66                 vsyslog(err, format, args);
67                 return;
68         }
69
70         sprintf(format2, "%s(%s): %s", MODULE_NAME, service, format);
71         vsyslog(err, format2, args);
72         SAFE_FREE(format2);
73 }
74 #endif /* HAVE_PAM_VSYSLOG */
75
76 static BOOL _pam_log_is_silent(int ctrl)
77 {
78         return on(ctrl, WINBIND_SILENT);
79 }
80
81 static void _pam_log(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) PRINTF_ATTRIBUTE(4,5);
82 static void _pam_log(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...)
83 {
84         va_list args;
85
86         if (_pam_log_is_silent(ctrl)) {
87                 return;
88         }
89
90         va_start(args, format);
91         _pam_log_int(pamh, err, format, args);
92         va_end(args);
93 }
94
95 static BOOL _pam_log_is_debug_enabled(int ctrl)
96 {
97         if (ctrl == -1) {
98                 return False;
99         }
100
101         if (_pam_log_is_silent(ctrl)) {
102                 return False;
103         }
104
105         if (!(ctrl & WINBIND_DEBUG_ARG)) {
106                 return False;
107         }
108
109         return True;
110 }
111
112 static BOOL _pam_log_is_debug_state_enabled(int ctrl)
113 {
114         if (!(ctrl & WINBIND_DEBUG_STATE)) {
115                 return False;
116         }
117
118         return _pam_log_is_debug_enabled(ctrl);
119 }
120
121 static void _pam_log_debug(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) PRINTF_ATTRIBUTE(4,5);
122 static void _pam_log_debug(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...)
123 {
124         va_list args;
125
126         if (!_pam_log_is_debug_enabled(ctrl)) {
127                 return;
128         }
129
130         va_start(args, format);
131         _pam_log_int(pamh, err, format, args);
132         va_end(args);
133 }
134
135 static void _pam_log_state_datum(const pam_handle_t *pamh, int ctrl, int item_type, const char *key, int is_string)
136 {
137         const void *data = NULL;
138         if (item_type != 0) {
139                 pam_get_item(pamh, item_type, &data);
140         } else {
141                 pam_get_data(pamh, key, &data);
142         }
143         if (data != NULL) {
144                 const char *type = (item_type != 0) ? "ITEM" : "DATA";
145                 if (is_string != 0) {
146                         _pam_log_debug(pamh, ctrl, LOG_DEBUG, "[pamh: 0x%08x] STATE: %s(%s) = \"%s\" (0x%08x)", (uint32) pamh, type, key, (const char *) data, (uint32) data);
147                 } else {
148                         _pam_log_debug(pamh, ctrl, LOG_DEBUG, "[pamh: 0x%08x] STATE: %s(%s) = 0x%08x", (uint32) pamh, type, key, (uint32) data);
149                 }
150         }
151 }
152
153 #define _PAM_LOG_STATE_DATA_POINTER(pamh, ctrl, module_data_name) \
154         _pam_log_state_datum(pamh, ctrl, 0, module_data_name, 0)
155
156 #define _PAM_LOG_STATE_DATA_STRING(pamh, ctrl, module_data_name) \
157         _pam_log_state_datum(pamh, ctrl, 0, module_data_name, 1)
158
159 #define _PAM_LOG_STATE_ITEM_POINTER(pamh, ctrl, item_type) \
160         _pam_log_state_datum(pamh, ctrl, item_type, #item_type, 0)
161
162 #define _PAM_LOG_STATE_ITEM_STRING(pamh, ctrl, item_type) \
163         _pam_log_state_datum(pamh, ctrl, item_type, #item_type, 1)
164
165 #ifdef DEBUG_PASSWORD
166 #define _LOG_PASSWORD_AS_STRING 1
167 #else
168 #define _LOG_PASSWORD_AS_STRING 0
169 #endif
170
171 #define _PAM_LOG_STATE_ITEM_PASSWORD(pamh, ctrl, item_type) \
172         _pam_log_state_datum(pamh, ctrl, item_type, #item_type, _LOG_PASSWORD_AS_STRING)
173
174 static void _pam_log_state(const pam_handle_t *pamh, int ctrl)
175 {
176         if (!_pam_log_is_debug_state_enabled(ctrl)) {
177                 return;
178         }
179
180         _PAM_LOG_STATE_ITEM_STRING(pamh, ctrl, PAM_SERVICE);
181         _PAM_LOG_STATE_ITEM_STRING(pamh, ctrl, PAM_USER);
182         _PAM_LOG_STATE_ITEM_STRING(pamh, ctrl, PAM_TTY);
183         _PAM_LOG_STATE_ITEM_STRING(pamh, ctrl, PAM_RHOST);
184         _PAM_LOG_STATE_ITEM_STRING(pamh, ctrl, PAM_RUSER);
185         _PAM_LOG_STATE_ITEM_PASSWORD(pamh, ctrl, PAM_OLDAUTHTOK);
186         _PAM_LOG_STATE_ITEM_PASSWORD(pamh, ctrl, PAM_AUTHTOK);
187         _PAM_LOG_STATE_ITEM_STRING(pamh, ctrl, PAM_USER_PROMPT);
188         _PAM_LOG_STATE_ITEM_POINTER(pamh, ctrl, PAM_CONV);
189 #ifdef PAM_FAIL_DELAY
190         _PAM_LOG_STATE_ITEM_POINTER(pamh, ctrl, PAM_FAIL_DELAY);
191 #endif
192 #ifdef PAM_REPOSITORY
193         _PAM_LOG_STATE_ITEM_POINTER(pamh, ctrl, PAM_REPOSITORY);
194 #endif
195
196         _PAM_LOG_STATE_DATA_STRING(pamh, ctrl, PAM_WINBIND_HOMEDIR);
197         _PAM_LOG_STATE_DATA_STRING(pamh, ctrl, PAM_WINBIND_LOGONSCRIPT);
198         _PAM_LOG_STATE_DATA_STRING(pamh, ctrl, PAM_WINBIND_LOGONSERVER);
199         _PAM_LOG_STATE_DATA_STRING(pamh, ctrl, PAM_WINBIND_PROFILEPATH);
200         _PAM_LOG_STATE_DATA_STRING(pamh, ctrl, PAM_WINBIND_NEW_AUTHTOK_REQD); /* Use atoi to get PAM result code */
201         _PAM_LOG_STATE_DATA_STRING(pamh, ctrl, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH);
202         _PAM_LOG_STATE_DATA_POINTER(pamh, ctrl, PAM_WINBIND_PWD_LAST_SET);
203 }
204
205 static int _pam_parse(const pam_handle_t *pamh, int flags, int argc, const char **argv, dictionary **result_d)
206 {
207         int ctrl = 0;
208         const char *config_file = NULL;
209         int i;
210         const char **v;
211         dictionary *d = NULL;
212
213         if (flags & PAM_SILENT) {
214                 ctrl |= WINBIND_SILENT;
215         }
216
217         for (i=argc,v=argv; i-- > 0; ++v) {
218                 if (!strncasecmp(*v, "config", strlen("config"))) {
219                         ctrl |= WINBIND_CONFIG_FILE;
220                         config_file = v[i];
221                         break;
222                 }
223         }
224
225         if (config_file == NULL) {
226                 config_file = PAM_WINBIND_CONFIG_FILE;
227         }
228
229         d = iniparser_load(config_file);
230         if (d == NULL) {
231                 goto config_from_pam;
232         }
233
234         if (iniparser_getboolean(d, "global:debug", False)) {
235                 ctrl |= WINBIND_DEBUG_ARG;
236         }
237
238         if (iniparser_getboolean(d, "global:debug_state", False)) {
239                 ctrl |= WINBIND_DEBUG_STATE;
240         }
241
242         if (iniparser_getboolean(d, "global:cached_login", False)) {
243                 ctrl |= WINBIND_CACHED_LOGIN;
244         }
245
246         if (iniparser_getboolean(d, "global:krb5_auth", False)) {
247                 ctrl |= WINBIND_KRB5_AUTH;
248         }
249
250         if (iniparser_getboolean(d, "global:silent", False)) {
251                 ctrl |= WINBIND_SILENT;
252         }
253
254         if (iniparser_getstr(d, "global:krb5_ccache_type") != NULL) {
255                 ctrl |= WINBIND_KRB5_CCACHE_TYPE;
256         }
257
258         if ((iniparser_getstr(d, "global:require-membership-of") != NULL) ||
259             (iniparser_getstr(d, "global:require_membership_of") != NULL)) {
260                 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
261         }
262
263         if (iniparser_getboolean(d, "global:try_first_pass", False)) {
264                 ctrl |= WINBIND_TRY_FIRST_PASS_ARG;
265         }
266
267 config_from_pam:
268         /* step through arguments */
269         for (i=argc,v=argv; i-- > 0; ++v) {
270
271                 /* generic options */
272                 if (!strcmp(*v,"debug"))
273                         ctrl |= WINBIND_DEBUG_ARG;
274                 else if (!strcasecmp(*v, "debug_state"))
275                         ctrl |= WINBIND_DEBUG_STATE;
276                 else if (!strcasecmp(*v, "use_authtok"))
277                         ctrl |= WINBIND_USE_AUTHTOK_ARG;
278                 else if (!strcasecmp(*v, "use_first_pass"))
279                         ctrl |= WINBIND_USE_FIRST_PASS_ARG;
280                 else if (!strcasecmp(*v, "try_first_pass"))
281                         ctrl |= WINBIND_TRY_FIRST_PASS_ARG;
282                 else if (!strcasecmp(*v, "unknown_ok"))
283                         ctrl |= WINBIND_UNKNOWN_OK_ARG;
284                 else if (!strncasecmp(*v, "require_membership_of", strlen("require_membership_of")))
285                         ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
286                 else if (!strncasecmp(*v, "require-membership-of", strlen("require-membership-of")))
287                         ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
288                 else if (!strcasecmp(*v, "krb5_auth"))
289                         ctrl |= WINBIND_KRB5_AUTH;
290                 else if (!strncasecmp(*v, "krb5_ccache_type", strlen("krb5_ccache_type")))
291                         ctrl |= WINBIND_KRB5_CCACHE_TYPE;
292                 else if (!strcasecmp(*v, "cached_login"))
293                         ctrl |= WINBIND_CACHED_LOGIN;
294                 else {
295                         _pam_log(pamh, ctrl, LOG_ERR, "pam_parse: unknown option: %s", *v);
296                         return -1;
297                 }
298
299         }
300
301         if (result_d) {
302                 *result_d = d;
303         } else {
304                 if (d) {
305                         iniparser_freedict(d);
306                 }
307         }
308
309         return ctrl;
310 };
311
312 static void _pam_winbind_cleanup_func(pam_handle_t *pamh, void *data, int error_status)
313 {
314         int ctrl = _pam_parse(pamh, 0, 0, NULL, NULL);
315         if (_pam_log_is_debug_state_enabled(ctrl)) {
316                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "[pamh: 0x%08x] CLEAN: cleaning up PAM data 0x%08x (error_status = %d)", (uint32) pamh, (uint32) data, error_status);
317         }
318         SAFE_FREE(data);
319 }
320
321
322 static const struct ntstatus_errors {
323         const char *ntstatus_string;
324         const char *error_string;
325 } ntstatus_errors[] = {
326         {"NT_STATUS_OK", "Success"},
327         {"NT_STATUS_BACKUP_CONTROLLER", "No primary Domain Controler available"},
328         {"NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND", "No domain controllers found"},
329         {"NT_STATUS_NO_LOGON_SERVERS", "No logon servers"},
330         {"NT_STATUS_PWD_TOO_SHORT", "Password too short"},
331         {"NT_STATUS_PWD_TOO_RECENT", "The password of this user is too recent to change"},
332         {"NT_STATUS_PWD_HISTORY_CONFLICT", "Password is already in password history"},
333         {"NT_STATUS_PASSWORD_EXPIRED", "Your password has expired"},
334         {"NT_STATUS_PASSWORD_MUST_CHANGE", "You need to change your password now"},
335         {"NT_STATUS_INVALID_WORKSTATION", "You are not allowed to logon from this workstation"},
336         {"NT_STATUS_INVALID_LOGON_HOURS", "You are not allowed to logon at this time"},
337         {"NT_STATUS_ACCOUNT_EXPIRED", "Your account has expired. Please contact your System administrator"}, /* SCNR */
338         {"NT_STATUS_ACCOUNT_DISABLED", "Your account is disabled. Please contact your System administrator"}, /* SCNR */
339         {"NT_STATUS_ACCOUNT_LOCKED_OUT", "Your account has been locked. Please contact your System administrator"}, /* SCNR */
340         {"NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT", "Invalid Trust Account"},
341         {"NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT", "Invalid Trust Account"},
342         {"NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT", "Invalid Trust Account"},
343         {"NT_STATUS_ACCESS_DENIED", "Access is denied"},
344         {NULL, NULL}
345 };
346
347 const char *_get_ntstatus_error_string(const char *nt_status_string) 
348 {
349         int i;
350         for (i=0; ntstatus_errors[i].ntstatus_string != NULL; i++) {
351                 if (!strcasecmp(ntstatus_errors[i].ntstatus_string, nt_status_string)) {
352                         return ntstatus_errors[i].error_string;
353                 }
354         }
355         return NULL;
356 }
357
358 /* --- authentication management functions --- */
359
360 /* Attempt a conversation */
361
362 static int converse(pam_handle_t *pamh, int nargs,
363                     struct pam_message **message,
364                     struct pam_response **response)
365 {
366         int retval;
367         struct pam_conv *conv;
368
369         retval = _pam_get_item(pamh, PAM_CONV, &conv );
370         if (retval == PAM_SUCCESS) {
371                 retval = conv->conv(nargs, (const struct pam_message **)message,
372                                     response, conv->appdata_ptr);
373         }
374         
375         return retval; /* propagate error status */
376 }
377
378
379 static int _make_remark(pam_handle_t * pamh, int flags, int type, const char *text)
380 {
381         int retval = PAM_SUCCESS;
382
383         struct pam_message *pmsg[1], msg[1];
384         struct pam_response *resp;
385         
386         if (flags & WINBIND_SILENT) {
387                 return PAM_SUCCESS;
388         }
389
390         pmsg[0] = &msg[0];
391         msg[0].msg = CONST_DISCARD(char *, text);
392         msg[0].msg_style = type;
393         
394         resp = NULL;
395         retval = converse(pamh, 1, pmsg, &resp);
396         
397         if (resp) {
398                 _pam_drop_reply(resp, 1);
399         }
400         return retval;
401 }
402
403 static int _make_remark_v(pam_handle_t * pamh, int flags, int type, const char *format, va_list args)
404 {
405         char *var;
406         int ret;
407
408         ret = vasprintf(&var, format, args);
409         if (ret < 0) {
410                 _pam_log(pamh, 0, LOG_ERR, "memory allocation failure");
411                 return ret;
412         }
413
414         ret = _make_remark(pamh, flags, type, var);
415         SAFE_FREE(var);
416         return ret;
417 }
418
419 static int _make_remark_format(pam_handle_t * pamh, int flags, int type, const char *format, ...) PRINTF_ATTRIBUTE(4,5);
420 static int _make_remark_format(pam_handle_t * pamh, int flags, int type, const char *format, ...)
421 {
422         int ret;
423         va_list args;
424
425         va_start(args, format);
426         ret = _make_remark_v(pamh, flags, type, format, args);
427         va_end(args);
428         return ret;
429 }
430
431 static int pam_winbind_request(pam_handle_t * pamh, int ctrl,
432                                enum winbindd_cmd req_type,
433                                struct winbindd_request *request,
434                                struct winbindd_response *response)
435 {
436         /* Fill in request and send down pipe */
437         init_request(request, req_type);
438         
439         if (write_sock(request, sizeof(*request), 0, 0) == -1) {
440                 _pam_log(pamh, ctrl, LOG_ERR, "pam_winbind_request: write to socket failed!");
441                 close_sock();
442                 return PAM_SERVICE_ERR;
443         }
444         
445         /* Wait for reply */
446         if (read_reply(response) == -1) {
447                 _pam_log(pamh, ctrl, LOG_ERR, "pam_winbind_request: read from socket failed!");
448                 close_sock();
449                 return PAM_SERVICE_ERR;
450         }
451
452         /* We are done with the socket - close it and avoid mischeif */
453         close_sock();
454
455         /* Copy reply data from socket */
456         if (response->result == WINBINDD_OK) {
457                 return PAM_SUCCESS;
458         }
459
460         /* no need to check for pam_error codes for getpwnam() */
461         switch (req_type) {
462
463                 case WINBINDD_GETPWNAM:
464                 case WINBINDD_LOOKUPNAME:
465                         if (strlen(response->data.auth.nt_status_string) > 0) {
466                                 _pam_log(pamh, ctrl, LOG_ERR, "request failed, NT error was %s", 
467                                 response->data.auth.nt_status_string);
468                         } else {
469                                 _pam_log(pamh, ctrl, LOG_ERR, "request failed");
470                         }
471                         return PAM_USER_UNKNOWN;
472                 default:
473                         break;
474         }
475
476         if (response->data.auth.pam_error != PAM_SUCCESS) {
477                 _pam_log(pamh, ctrl, LOG_ERR, "request failed: %s, PAM error was %s (%d), NT error was %s", 
478                          response->data.auth.error_string,
479                          pam_strerror(pamh, response->data.auth.pam_error),
480                          response->data.auth.pam_error,
481                          response->data.auth.nt_status_string);
482                 return response->data.auth.pam_error;
483         } 
484         
485         _pam_log(pamh, ctrl, LOG_ERR, "request failed, but PAM error 0!");
486
487         return PAM_SERVICE_ERR;
488 }
489
490 static int pam_winbind_request_log(pam_handle_t * pamh,
491                                    int ctrl,
492                                    enum winbindd_cmd req_type,
493                                    struct winbindd_request *request,
494                                    struct winbindd_response *response,
495                                    const char *user)
496 {
497         int retval;
498
499         retval = pam_winbind_request(pamh, ctrl, req_type, request, response);
500
501         switch (retval) {
502         case PAM_AUTH_ERR:
503                 /* incorrect password */
504                 _pam_log(pamh, ctrl, LOG_WARNING, "user '%s' denied access (incorrect password or invalid membership)", user);
505                 return retval;
506         case PAM_ACCT_EXPIRED:
507                 /* account expired */
508                 _pam_log(pamh, ctrl, LOG_WARNING, "user '%s' account expired", user);
509                 return retval;
510         case PAM_AUTHTOK_EXPIRED:
511                 /* password expired */
512                 _pam_log(pamh, ctrl, LOG_WARNING, "user '%s' password expired", user);
513                 return retval;
514         case PAM_NEW_AUTHTOK_REQD:
515                 /* new password required */
516                 _pam_log(pamh, ctrl, LOG_WARNING, "user '%s' new password required", user);
517                 return retval;
518         case PAM_USER_UNKNOWN:
519                 /* the user does not exist */
520                 _pam_log_debug(pamh, ctrl, LOG_NOTICE, "user '%s' not found", user);
521                 if (ctrl & WINBIND_UNKNOWN_OK_ARG) {
522                         return PAM_IGNORE;
523                 }        
524                 return retval;
525         case PAM_SUCCESS:
526                 /* Otherwise, the authentication looked good */
527                 switch (req_type) {
528                         case WINBINDD_INFO:
529                                 break;
530                         case WINBINDD_PAM_AUTH:
531                                 _pam_log(pamh, ctrl, LOG_NOTICE, "user '%s' granted access", user);
532                                 break;
533                         case WINBINDD_PAM_CHAUTHTOK:
534                                 _pam_log(pamh, ctrl, LOG_NOTICE, "user '%s' password changed", user);
535                                 break;
536                         default:
537                                 _pam_log(pamh, ctrl, LOG_NOTICE, "user '%s' OK", user);
538                                 break;
539                 }
540         
541                 return retval;
542         default:
543                 /* we don't know anything about this return value */
544                 _pam_log(pamh, ctrl, LOG_ERR, "internal module error (retval = %d, user = '%s')",
545                          retval, user);
546                 return retval;
547         }
548 }
549
550 /**
551  * send a password expiry message if required
552  * 
553  * @param pamh PAM handle
554  * @param ctrl PAM winbind options.
555  * @param next_change expected (calculated) next expiry date.
556  * @param already_expired pointer to a boolean to indicate if the password is
557  *        already expired.
558  *
559  * @return boolean Returns True if message has been sent, False if not.
560  */
561
562 static BOOL _pam_send_password_expiry_message(pam_handle_t *pamh, int ctrl, time_t next_change, time_t now, BOOL *already_expired)
563 {
564         int days = 0;
565         struct tm tm_now, tm_next_change;
566
567         if (already_expired) {
568                 *already_expired = False;
569         }
570
571         if (next_change <= now) {
572                 PAM_WB_REMARK_DIRECT(pamh, ctrl, "NT_STATUS_PASSWORD_EXPIRED");
573                 if (already_expired) {
574                         *already_expired = True;
575                 }
576                 return True;
577         }
578
579         if ((next_change < 0) ||
580             (next_change > now + DAYS_TO_WARN_BEFORE_PWD_EXPIRES * SECONDS_PER_DAY)) {
581                 return False;
582         }
583
584         if ((localtime_r(&now, &tm_now) == NULL) || 
585             (localtime_r(&next_change, &tm_next_change) == NULL)) {
586                 return False;
587         }
588
589         days = (tm_next_change.tm_yday+tm_next_change.tm_year*365) - (tm_now.tm_yday+tm_now.tm_year*365);
590
591         if (days == 0) {
592                 _make_remark(pamh, ctrl, PAM_TEXT_INFO, "Your password expires today");
593                 return True;
594         } 
595         
596         if (days > 0 && days < DAYS_TO_WARN_BEFORE_PWD_EXPIRES) {
597                 _make_remark_format(pamh, ctrl, PAM_TEXT_INFO, "Your password will expire in %d %s", 
598                         days, (days > 1) ? "days":"day");
599                 return True;
600         }
601
602         return False;
603 }
604
605 /**
606  * Send a warning if the password expires in the near future
607  *
608  * @param pamh PAM handle
609  * @param ctrl PAM winbind options.
610  * @param response The full authentication response structure.
611  * @param already_expired boolean, is the pwd already expired?
612  *
613  * @return void.
614  */
615
616 static void _pam_warn_password_expiry(pam_handle_t *pamh, 
617                                       int flags, 
618                                       const struct winbindd_response *response,
619                                       BOOL *already_expired)
620 {
621         time_t now = time(NULL);
622         time_t next_change = 0;
623
624         if (already_expired) {
625                 *already_expired = False;
626         }
627
628         /* accounts with ACB_PWNOEXP set never receive a warning */
629         if (response->data.auth.info3.acct_flags & ACB_PWNOEXP) {
630                 return;
631         }
632
633         /* no point in sending a warning if this is a grace logon */
634         if (PAM_WB_GRACE_LOGON(response->data.auth.info3.user_flgs)) {
635                 return;
636         }
637
638         /* check if the info3 must change timestamp has been set */
639         next_change = response->data.auth.info3.pass_must_change_time;
640
641         if (_pam_send_password_expiry_message(pamh, flags, next_change, now, 
642                                               already_expired)) {
643                 return;
644         }
645
646         /* now check for the global password policy */
647         /* good catch from Ralf Haferkamp: an expiry of "never" is translated
648          * to -1 */
649         if (response->data.auth.policy.expire <= 0) {
650                 return;
651         }
652
653         next_change = response->data.auth.info3.pass_last_set_time + 
654                       response->data.auth.policy.expire;
655
656         if (_pam_send_password_expiry_message(pamh, flags, next_change, now, 
657                                               already_expired)) {
658                 return;
659         }
660
661         /* no warning sent */
662 }
663
664 #define IS_SID_STRING(name) (strncmp("S-", name, 2) == 0)
665
666 static BOOL safe_append_string(char *dest,
667                         const char *src,
668                         int dest_buffer_size)
669 /**
670  * Append a string, making sure not to overflow and to always return a NULL-terminated
671  * string.
672  *
673  * @param dest Destination string buffer (must already be NULL-terminated).
674  * @param src Source string buffer.
675  * @param dest_buffer_size Size of dest buffer in bytes.
676  *
677  * @return False if dest buffer is not big enough (no bytes copied), True on success.
678  */
679 {
680         int dest_length = strlen(dest);
681         int src_length = strlen(src);
682
683         if ( dest_length + src_length + 1 > dest_buffer_size ) {
684                 return False;
685         }
686
687         memcpy(dest + dest_length, src, src_length + 1);
688         return True;
689 }
690
691 static BOOL winbind_name_to_sid_string(pam_handle_t *pamh,
692                                 int ctrl,
693                                 const char *user,
694                                 const char *name,
695                                 char *sid_list_buffer,
696                                 int sid_list_buffer_size)
697 /**
698  * Convert a names into a SID string, appending it to a buffer.
699  *
700  * @param pamh PAM handle
701  * @param ctrl PAM winbind options.
702  * @param user User in PAM request.
703  * @param name Name to convert.
704  * @param sid_list_buffer Where to append the string sid.
705  * @param sid_list_buffer Size of sid_list_buffer (in bytes).
706  *
707  * @return False on failure, True on success.
708  */
709 {
710         const char* sid_string;
711         struct winbindd_response sid_response;
712
713         /* lookup name? */ 
714         if (IS_SID_STRING(name)) {
715                 sid_string = name;
716         } else {
717                 struct winbindd_request sid_request;
718
719                 ZERO_STRUCT(sid_request);
720                 ZERO_STRUCT(sid_response);
721
722                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "no sid given, looking up: %s\n", name);
723
724                 /* fortunatly winbindd can handle non-separated names */
725                 strncpy(sid_request.data.name.name, name,
726                         sizeof(sid_request.data.name.name) - 1);
727
728                 if (pam_winbind_request_log(pamh, ctrl, WINBINDD_LOOKUPNAME, &sid_request, &sid_response, user)) {
729                         _pam_log(pamh, ctrl, LOG_INFO, "could not lookup name: %s\n", name); 
730                         return False;
731                 }
732
733                 sid_string = sid_response.data.sid.sid;
734         }
735
736         if (!safe_append_string(sid_list_buffer, sid_string, sid_list_buffer_size)) {
737                 return False;
738         }
739
740         return True;
741 }
742
743 static BOOL winbind_name_list_to_sid_string_list(pam_handle_t *pamh,
744                                 int ctrl,
745                                 const char *user,
746                                 const char *name_list,
747                                 char *sid_list_buffer,
748                                 int sid_list_buffer_size)
749 /**
750  * Convert a list of names into a list of sids.
751  *
752  * @param pamh PAM handle
753  * @param ctrl PAM winbind options.
754  * @param user User in PAM request.
755  * @param name_list List of names or string sids, separated by commas.
756  * @param sid_list_buffer Where to put the list of string sids.
757  * @param sid_list_buffer Size of sid_list_buffer (in bytes).
758  *
759  * @return False on failure, True on success.
760  */
761 {
762         BOOL result = False;
763         char *current_name = NULL;
764         const char *search_location;
765         const char *comma;
766
767         if ( sid_list_buffer_size > 0 ) {
768                 sid_list_buffer[0] = 0;
769         }
770
771         search_location = name_list;
772         while ( (comma = strstr(search_location, ",")) != NULL ) {
773                 current_name = strndup(search_location, comma - search_location);
774                 if (NULL == current_name) {
775                         goto out;
776                 }
777
778                 if (!winbind_name_to_sid_string(pamh, ctrl, user, current_name, sid_list_buffer, sid_list_buffer_size)) {
779                         goto out;
780                 }
781
782                 SAFE_FREE(current_name);
783
784                 if (!safe_append_string(sid_list_buffer, ",", sid_list_buffer_size)) {
785                         goto out;
786                 }
787
788                 search_location = comma + 1;
789         }
790
791         if (!winbind_name_to_sid_string(pamh, ctrl, user, search_location, sid_list_buffer, sid_list_buffer_size)) {
792                 goto out;
793         }
794
795         result = True;
796
797 out:
798         SAFE_FREE(current_name);
799         return result;
800 }
801
802 /**
803  * put krb5ccname variable into environment
804  *
805  * @param pamh PAM handle
806  * @param ctrl PAM winbind options.
807  * @param krb5ccname env variable retrieved from winbindd.
808  *
809  * @return void.
810  */
811
812 static void _pam_setup_krb5_env(pam_handle_t *pamh, int ctrl, const char *krb5ccname)
813 {
814         char var[PATH_MAX];
815         int ret;
816
817         if (off(ctrl, WINBIND_KRB5_AUTH)) {
818                 return;
819         }
820
821         if (!krb5ccname || (strlen(krb5ccname) == 0)) {
822                 return;
823         }
824
825         _pam_log_debug(pamh, ctrl, LOG_DEBUG, "request returned KRB5CCNAME: %s", krb5ccname);
826         
827         if (snprintf(var, sizeof(var), "KRB5CCNAME=%s", krb5ccname) == -1) {
828                 return;
829         }
830         
831         ret = pam_putenv(pamh, var);
832         if (ret) {
833                 _pam_log(pamh, ctrl, LOG_ERR, "failed to set KRB5CCNAME to %s: %s", 
834                         var, pam_strerror(pamh, ret));
835         }
836 }       
837
838 /**
839  * Set string into the PAM stack.
840  *
841  * @param pamh PAM handle
842  * @param ctrl PAM winbind options.
843  * @param data_name Key name for pam_set_data.
844  * @param value String value.
845  *
846  * @return void.
847  */
848
849 static void _pam_set_data_string(pam_handle_t *pamh, int ctrl, const char *data_name, const char *value)
850 {
851         int ret;
852
853         if ( !data_name || !value || (strlen(data_name) == 0) || (strlen(value) == 0) ) {
854                 return;
855         }
856
857         ret = pam_set_data(pamh, data_name, (void *)strdup(value), _pam_winbind_cleanup_func);
858         if (ret) {
859                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "Could not set data %s: %s\n", 
860                         data_name, pam_strerror(pamh, ret));
861         }
862
863 }
864
865 /**
866  * Set info3 strings into the PAM stack.
867  *
868  * @param pamh PAM handle
869  * @param ctrl PAM winbind options.
870  * @param data_name Key name for pam_set_data.
871  * @param value String value.
872  *
873  * @return void.
874  */
875
876 static void _pam_set_data_info3(pam_handle_t *pamh, int ctrl, struct winbindd_response *response)
877 {
878         _pam_set_data_string(pamh, ctrl, PAM_WINBIND_HOMEDIR, response->data.auth.info3.home_dir);
879         _pam_set_data_string(pamh, ctrl, PAM_WINBIND_LOGONSCRIPT, response->data.auth.info3.logon_script);
880         _pam_set_data_string(pamh, ctrl, PAM_WINBIND_LOGONSERVER, response->data.auth.info3.logon_srv);
881         _pam_set_data_string(pamh, ctrl, PAM_WINBIND_PROFILEPATH, response->data.auth.info3.profile_path);
882 }
883
884 /**
885  * Free info3 strings in the PAM stack.
886  *
887  * @param pamh PAM handle
888  *
889  * @return void.
890  */
891
892 static void _pam_free_data_info3(pam_handle_t *pamh)
893 {
894         pam_set_data(pamh, PAM_WINBIND_HOMEDIR, NULL, NULL);
895         pam_set_data(pamh, PAM_WINBIND_LOGONSCRIPT, NULL, NULL);
896         pam_set_data(pamh, PAM_WINBIND_LOGONSERVER, NULL, NULL);
897         pam_set_data(pamh, PAM_WINBIND_PROFILEPATH, NULL, NULL);
898 }
899
900 /**
901  * Send PAM_ERROR_MSG for cached or grace logons.
902  *
903  * @param pamh PAM handle
904  * @param ctrl PAM winbind options.
905  * @param username User in PAM request.
906  * @param info3_user_flgs Info3 flags containing logon type bits.
907  *
908  * @return void.
909  */
910
911 static void _pam_warn_logon_type(pam_handle_t *pamh, int ctrl, const char *username, uint32 info3_user_flgs)
912 {
913         /* inform about logon type */
914         if (PAM_WB_GRACE_LOGON(info3_user_flgs)) {
915
916                 _make_remark(pamh, ctrl, PAM_ERROR_MSG, 
917                         "Grace login. Please change your password as soon you're online again");
918                 _pam_log_debug(pamh, ctrl, LOG_DEBUG,
919                         "User %s logged on using grace logon\n", username);
920
921         } else if (PAM_WB_CACHED_LOGON(info3_user_flgs)) {
922
923                 _make_remark(pamh, ctrl, PAM_ERROR_MSG, 
924                         "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable");
925                 _pam_log_debug(pamh, ctrl, LOG_DEBUG,
926                         "User %s logged on using cached account\n", username);
927         }
928 }
929
930 /**
931  * Compose Password Restriction String for a PAM_ERROR_MSG conversation.
932  *
933  * @param response The struct winbindd_response.
934  *
935  * @return string (caller needs to free).
936  */
937
938 static char *_pam_compose_pwd_restriction_string(struct winbindd_response *response)
939 {
940         char *str = NULL;
941         size_t offset = 0, ret = 0, str_size = 1024;
942
943         str = (char *)malloc(str_size);
944         if (!str) {
945                 return NULL;
946         }
947
948         memset(str, '\0', str_size);
949
950         offset = snprintf(str, str_size, "Your password ");
951         if (offset == -1) {
952                 goto failed;
953         }
954
955         if (response->data.auth.policy.min_length_password > 0) {
956                 ret = snprintf(str+offset, str_size-offset,
957                              "must be at least %d characters; ",
958                              response->data.auth.policy.min_length_password);
959                 if (ret == -1) {
960                         goto failed;
961                 }
962                 offset += ret;
963         }
964         
965         if (response->data.auth.policy.password_history > 0) {
966                 ret = snprintf(str+offset, str_size-offset,
967                              "cannot repeat any of your previous %d passwords; ",
968                              response->data.auth.policy.password_history);
969                 if (ret == -1) {
970                         goto failed;
971                 }
972                 offset += ret;
973         }
974         
975         if (response->data.auth.policy.password_properties & DOMAIN_PASSWORD_COMPLEX) {
976                 ret = snprintf(str+offset, str_size-offset,
977                              "must contain capitals, numerals or punctuation; "
978                              "and cannot contain your account or full name; ");
979                 if (ret == -1) {
980                         goto failed;
981                 }
982                 offset += ret;
983         }
984
985         ret = snprintf(str+offset, str_size-offset, 
986                      "Please type a different password. "
987                      "Type a password which meets these requirements in both text boxes.");
988         if (ret == -1) {
989                 goto failed;
990         }
991
992         return str;
993
994  failed:
995         SAFE_FREE(str);
996         return NULL;
997 }
998
999 /* talk to winbindd */
1000 static int winbind_auth_request(pam_handle_t * pamh,
1001                                 int ctrl, 
1002                                 const char *user, 
1003                                 const char *pass, 
1004                                 const char *member, 
1005                                 const char *cctype,
1006                                 struct winbindd_response *p_response,
1007                                 time_t *pwd_last_set,
1008                                 char **user_ret)
1009 {
1010         struct winbindd_request request;
1011         struct winbindd_response response;
1012         int ret;
1013         BOOL already_expired = False;
1014
1015         ZERO_STRUCT(request);
1016         ZERO_STRUCT(response);
1017
1018         if (pwd_last_set) {
1019                 *pwd_last_set = 0;
1020         }
1021
1022         strncpy(request.data.auth.user, user, 
1023                 sizeof(request.data.auth.user)-1);
1024
1025         strncpy(request.data.auth.pass, pass, 
1026                 sizeof(request.data.auth.pass)-1);
1027
1028         request.data.auth.krb5_cc_type[0] = '\0';
1029         request.data.auth.uid = -1;
1030         
1031         request.flags = WBFLAG_PAM_INFO3_TEXT | WBFLAG_PAM_CONTACT_TRUSTDOM;
1032
1033         if (ctrl & (WINBIND_KRB5_AUTH|WINBIND_CACHED_LOGIN)) {
1034                 struct passwd *pwd = NULL;
1035
1036                 pwd = getpwnam(user);
1037                 if (pwd == NULL) {
1038                         return PAM_USER_UNKNOWN;
1039                 }
1040                 request.data.auth.uid = pwd->pw_uid;
1041         }
1042
1043         if (ctrl & WINBIND_KRB5_AUTH) {
1044
1045                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "enabling krb5 login flag\n"); 
1046
1047                 request.flags |= WBFLAG_PAM_KRB5 | WBFLAG_PAM_FALLBACK_AFTER_KRB5;
1048         }
1049
1050         if (ctrl & WINBIND_CACHED_LOGIN) {
1051                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "enabling cached login flag\n"); 
1052                 request.flags |= WBFLAG_PAM_CACHED_LOGIN;
1053         }
1054
1055         if (user_ret) {
1056                 *user_ret = NULL;
1057                 request.flags |= WBFLAG_PAM_UNIX_NAME;
1058         }
1059
1060         if (cctype != NULL) {
1061                 strncpy(request.data.auth.krb5_cc_type, cctype, 
1062                         sizeof(request.data.auth.krb5_cc_type) - 1);
1063                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "enabling request for a %s krb5 ccache\n", cctype); 
1064         }
1065
1066         request.data.auth.require_membership_of_sid[0] = '\0';
1067
1068         if (member != NULL) {
1069
1070                 if (!winbind_name_list_to_sid_string_list(pamh, ctrl, user, member,
1071                         request.data.auth.require_membership_of_sid,
1072                         sizeof(request.data.auth.require_membership_of_sid))) {
1073
1074                         _pam_log_debug(pamh, ctrl, LOG_ERR, "failed to serialize membership of sid \"%s\"\n", member);
1075                         return PAM_AUTH_ERR;
1076                 }
1077         }
1078
1079         ret = pam_winbind_request_log(pamh, ctrl, WINBINDD_PAM_AUTH, &request, &response, user);
1080
1081         if (pwd_last_set) {
1082                 *pwd_last_set = response.data.auth.info3.pass_last_set_time;
1083         }
1084
1085         if (p_response) {
1086                 /* We want to process the response in the caller. */
1087                 *p_response = response;
1088                 return ret;
1089         }
1090
1091         if (ret) {
1092                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_PASSWORD_EXPIRED");
1093                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_PASSWORD_MUST_CHANGE");
1094                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_INVALID_WORKSTATION");
1095                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_INVALID_LOGON_HOURS");
1096                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_ACCOUNT_EXPIRED");
1097                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_ACCOUNT_DISABLED");
1098                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_ACCOUNT_LOCKED_OUT");
1099                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT");
1100                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT");
1101                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT");
1102                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
1103                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_NO_LOGON_SERVERS");
1104                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_WRONG_PASSWORD");
1105                 PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_ACCESS_DENIED");
1106         }
1107
1108         if (ret == PAM_SUCCESS) {
1109
1110                 /* warn a user if the password is about to expire soon */
1111                 _pam_warn_password_expiry(pamh, ctrl, &response, &already_expired);
1112
1113                 if (already_expired == True) {
1114                         _pam_log_debug(pamh, ctrl, LOG_DEBUG, "Password has expired "
1115                                        "(Password was last set: %lld, the policy says "
1116                                        "it should expire here %lld (now it's: %lu))\n",
1117                                        response.data.auth.info3.pass_last_set_time, 
1118                                        response.data.auth.info3.pass_last_set_time +
1119                                        response.data.auth.policy.expire,
1120                                        time(NULL));
1121
1122                         return PAM_AUTHTOK_EXPIRED;
1123                 }
1124
1125                 /* inform about logon type */
1126                 _pam_warn_logon_type(pamh, ctrl, user, response.data.auth.info3.user_flgs);
1127
1128                 /* set some info3 info for other modules in the stack */
1129                 _pam_set_data_info3(pamh, ctrl, &response);
1130
1131                 /* put krb5ccname into env */
1132                 _pam_setup_krb5_env(pamh, ctrl, response.data.auth.krb5ccname);
1133
1134                 /* If winbindd returned a username, return the pointer to it here. */
1135                 if (user_ret && response.extra_data.data) {
1136                         /* We have to trust it's a null terminated string. */
1137                         *user_ret = (char *)response.extra_data.data;
1138                 }
1139         }
1140
1141         return ret;
1142 }
1143
1144 /* talk to winbindd */
1145 static int winbind_chauthtok_request(pam_handle_t * pamh,
1146                                      int ctrl,
1147                                      const char *user, 
1148                                      const char *oldpass,
1149                                      const char *newpass,
1150                                      time_t pwd_last_set) 
1151 {
1152         struct winbindd_request request;
1153         struct winbindd_response response;
1154         int ret;
1155
1156         ZERO_STRUCT(request);
1157         ZERO_STRUCT(response);
1158
1159         if (request.data.chauthtok.user == NULL) return -2;
1160
1161         strncpy(request.data.chauthtok.user, user, 
1162                 sizeof(request.data.chauthtok.user) - 1);
1163
1164         if (oldpass != NULL) {
1165                 strncpy(request.data.chauthtok.oldpass, oldpass, 
1166                         sizeof(request.data.chauthtok.oldpass) - 1);
1167         } else {
1168                 request.data.chauthtok.oldpass[0] = '\0';
1169         }
1170         
1171         if (newpass != NULL) {
1172                 strncpy(request.data.chauthtok.newpass, newpass, 
1173                         sizeof(request.data.chauthtok.newpass) - 1);
1174         } else {
1175                 request.data.chauthtok.newpass[0] = '\0';
1176         }
1177
1178         if (ctrl & WINBIND_KRB5_AUTH) {
1179                 request.flags = WBFLAG_PAM_KRB5 | WBFLAG_PAM_CONTACT_TRUSTDOM;
1180         }
1181
1182         ret = pam_winbind_request_log(pamh, ctrl, WINBINDD_PAM_CHAUTHTOK, &request, &response, user);
1183
1184         if (ret == PAM_SUCCESS) {
1185                 return ret;
1186         }
1187
1188         PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_BACKUP_CONTROLLER");
1189         PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
1190         PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_NO_LOGON_SERVERS");
1191         PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_ACCESS_DENIED");
1192
1193         /* TODO: tell the min pwd length ? */
1194         PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_PWD_TOO_SHORT");
1195
1196         /* TODO: tell the minage ? */
1197         PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_PWD_TOO_RECENT");
1198
1199         /* TODO: tell the history length ? */
1200         PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, ctrl, response, "NT_STATUS_PWD_HISTORY_CONFLICT");
1201
1202         if (!strcasecmp(response.data.auth.nt_status_string, "NT_STATUS_PASSWORD_RESTRICTION")) {
1203
1204                 char *pwd_restriction_string = NULL;
1205
1206                 /* FIXME: avoid to send multiple PAM messages after another */
1207                 switch (response.data.auth.reject_reason) {
1208                         case -1:
1209                                 break;
1210                         case REJECT_REASON_OTHER:
1211                                 if ((response.data.auth.policy.min_passwordage > 0) &&
1212                                     (pwd_last_set + response.data.auth.policy.min_passwordage > time(NULL))) {
1213                                         PAM_WB_REMARK_DIRECT(pamh, ctrl, "NT_STATUS_PWD_TOO_RECENT");
1214                                 }
1215                                 break;
1216                         case REJECT_REASON_TOO_SHORT:
1217                                 PAM_WB_REMARK_DIRECT(pamh, ctrl, "NT_STATUS_PWD_TOO_SHORT");
1218                                 break;
1219                         case REJECT_REASON_IN_HISTORY:
1220                                 PAM_WB_REMARK_DIRECT(pamh, ctrl, "NT_STATUS_PWD_HISTORY_CONFLICT");
1221                                 break;
1222                         case REJECT_REASON_NOT_COMPLEX:
1223                                 _make_remark(pamh, ctrl, PAM_ERROR_MSG, "Password does not meet complexity requirements");
1224                                 break;
1225                         default:
1226                                 _pam_log_debug(pamh, ctrl, LOG_DEBUG,
1227                                                "unknown password change reject reason: %d", 
1228                                                response.data.auth.reject_reason);
1229                                 break;
1230                 }
1231
1232                 pwd_restriction_string = _pam_compose_pwd_restriction_string(&response);
1233                 if (pwd_restriction_string) {
1234                         _make_remark(pamh, ctrl, PAM_ERROR_MSG, pwd_restriction_string);
1235                         SAFE_FREE(pwd_restriction_string);
1236                 }
1237         }
1238
1239         return ret;
1240 }
1241
1242 /*
1243  * Checks if a user has an account
1244  *
1245  * return values:
1246  *       1  = User not found
1247  *       0  = OK
1248  *      -1  = System error
1249  */
1250 static int valid_user(pam_handle_t *pamh, int ctrl, const char *user)
1251 {
1252         /* check not only if the user is available over NSS calls, also make
1253          * sure it's really a winbind user, this is important when stacking PAM
1254          * modules in the 'account' or 'password' facility. */
1255
1256         struct passwd *pwd = NULL;
1257         struct winbindd_request request;
1258         struct winbindd_response response;
1259         int ret;
1260
1261         ZERO_STRUCT(request);
1262         ZERO_STRUCT(response);
1263
1264         pwd = getpwnam(user);
1265         if (pwd == NULL) {
1266                 return 1;
1267         }
1268
1269         strncpy(request.data.username, user,
1270                 sizeof(request.data.username) - 1);
1271
1272         ret = pam_winbind_request_log(pamh, ctrl, WINBINDD_GETPWNAM, &request, &response, user);
1273
1274         switch (ret) {
1275                 case PAM_USER_UNKNOWN:
1276                         return 1;
1277                 case PAM_SUCCESS:
1278                         return 0;
1279                 default:
1280                         break;
1281         }
1282         return -1;
1283 }
1284
1285 static char *_pam_delete(register char *xx)
1286 {
1287         _pam_overwrite(xx);
1288         _pam_drop(xx);
1289         return NULL;
1290 }
1291
1292 /*
1293  * obtain a password from the user
1294  */
1295
1296 static int _winbind_read_password(pam_handle_t * pamh,
1297                                   unsigned int ctrl,
1298                                   const char *comment,
1299                                   const char *prompt1,
1300                                   const char *prompt2,
1301                                   const char **pass)
1302 {
1303         int authtok_flag;
1304         int retval;
1305         const char *item;
1306         char *token;
1307
1308         _pam_log(pamh, ctrl, LOG_DEBUG, "getting password (0x%08x)", ctrl);
1309
1310         /*
1311          * make sure nothing inappropriate gets returned
1312          */
1313
1314         *pass = token = NULL;
1315
1316         /*
1317          * which authentication token are we getting?
1318          */
1319
1320         authtok_flag = on(WINBIND__OLD_PASSWORD, ctrl) ? PAM_OLDAUTHTOK : PAM_AUTHTOK;
1321
1322         /*
1323          * should we obtain the password from a PAM item ?
1324          */
1325
1326         if (on(WINBIND_TRY_FIRST_PASS_ARG, ctrl) || on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
1327                 retval = _pam_get_item(pamh, authtok_flag, &item);
1328                 if (retval != PAM_SUCCESS) {
1329                         /* very strange. */
1330                         _pam_log(pamh, ctrl, LOG_ALERT, 
1331                                  "pam_get_item returned error to unix-read-password"
1332                             );
1333                         return retval;
1334                 } else if (item != NULL) {      /* we have a password! */
1335                         *pass = item;
1336                         item = NULL;
1337                         _pam_log(pamh, ctrl, LOG_DEBUG, 
1338                                  "pam_get_item returned a password");
1339                         return PAM_SUCCESS;
1340                 } else if (on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
1341                         return PAM_AUTHTOK_RECOVER_ERR;         /* didn't work */
1342                 } else if (on(WINBIND_USE_AUTHTOK_ARG, ctrl)
1343                            && off(WINBIND__OLD_PASSWORD, ctrl)) {
1344                         return PAM_AUTHTOK_RECOVER_ERR;
1345                 }
1346         }
1347         /*
1348          * getting here implies we will have to get the password from the
1349          * user directly.
1350          */
1351
1352         {
1353                 struct pam_message msg[3], *pmsg[3];
1354                 struct pam_response *resp;
1355                 int i, replies;
1356
1357                 /* prepare to converse */
1358
1359                 if (comment != NULL && off(ctrl, WINBIND_SILENT)) {
1360                         pmsg[0] = &msg[0];
1361                         msg[0].msg_style = PAM_TEXT_INFO;
1362                         msg[0].msg = CONST_DISCARD(char *, comment);
1363                         i = 1;
1364                 } else {
1365                         i = 0;
1366                 }
1367
1368                 pmsg[i] = &msg[i];
1369                 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
1370                 msg[i++].msg = CONST_DISCARD(char *, prompt1);
1371                 replies = 1;
1372
1373                 if (prompt2 != NULL) {
1374                         pmsg[i] = &msg[i];
1375                         msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
1376                         msg[i++].msg = CONST_DISCARD(char *, prompt2);
1377                         ++replies;
1378                 }
1379                 /* so call the conversation expecting i responses */
1380                 resp = NULL;
1381                 retval = converse(pamh, i, pmsg, &resp);
1382
1383                 if (resp != NULL) {
1384
1385                         /* interpret the response */
1386
1387                         if (retval == PAM_SUCCESS) {    /* a good conversation */
1388
1389                                 token = x_strdup(resp[i - replies].resp);
1390                                 if (token != NULL) {
1391                                         if (replies == 2) {
1392                                                 /* verify that password entered correctly */
1393                                                 if (!resp[i - 1].resp
1394                                                     || strcmp(token, resp[i - 1].resp)) {
1395                                                         _pam_delete(token);     /* mistyped */
1396                                                         retval = PAM_AUTHTOK_RECOVER_ERR;
1397                                                         _make_remark(pamh, ctrl, PAM_ERROR_MSG, MISTYPED_PASS);
1398                                                 }
1399                                         }
1400                                 } else {
1401                                         _pam_log(pamh, ctrl, LOG_NOTICE, "could not recover authentication token");
1402                                         retval = PAM_AUTHTOK_RECOVER_ERR;
1403                                 }
1404
1405                         }
1406                         /*
1407                          * tidy up the conversation (resp_retcode) is ignored
1408                          * -- what is it for anyway? AGM
1409                          */
1410
1411                         _pam_drop_reply(resp, i);
1412
1413                 } else {
1414                         retval = (retval == PAM_SUCCESS)
1415                             ? PAM_AUTHTOK_RECOVER_ERR : retval;
1416                 }
1417         }
1418
1419         if (retval != PAM_SUCCESS) {
1420                 _pam_log_debug(pamh, ctrl, LOG_DEBUG,
1421                                  "unable to obtain a password");
1422                 return retval;
1423         }
1424         /* 'token' is the entered password */
1425
1426         /* we store this password as an item */
1427         
1428         retval = pam_set_item(pamh, authtok_flag, token);
1429         _pam_delete(token);     /* clean it up */
1430         if (retval != PAM_SUCCESS || 
1431             (retval = _pam_get_item(pamh, authtok_flag, &item)) != PAM_SUCCESS) {
1432                 
1433                 _pam_log(pamh, ctrl, LOG_CRIT, "error manipulating password");
1434                 return retval;
1435                 
1436         }
1437
1438         *pass = item;
1439         item = NULL;            /* break link to password */
1440
1441         return PAM_SUCCESS;
1442 }
1443
1444 const char *get_conf_item_string(const pam_handle_t *pamh,
1445                                  int argc, 
1446                                  const char **argv, 
1447                                  int ctrl,
1448                                  dictionary *d,
1449                                  const char *item, 
1450                                  int config_flag)
1451 {
1452         int i = 0;
1453         const char *parm_opt = NULL;
1454         char *key = NULL;
1455
1456         if (!(ctrl & config_flag)) {
1457                 goto out;
1458         }
1459
1460         /* let the pam opt take precedence over the pam_winbind.conf option */
1461
1462         if (d != NULL) {
1463
1464                 if (!asprintf(&key, "global:%s", item)) {
1465                         goto out;
1466                 }
1467
1468                 parm_opt = iniparser_getstr(d, key);
1469                 SAFE_FREE(key);
1470         }
1471
1472         for ( i=0; i<argc; i++ ) {
1473
1474                 if ((strncmp(argv[i], item, strlen(item)) == 0)) {
1475                         char *p;
1476
1477                         if ( (p = strchr( argv[i], '=' )) == NULL) {
1478                                 _pam_log(pamh, ctrl, LOG_INFO, "no \"=\" delimiter for \"%s\" found\n", item);
1479                                 goto out;
1480                         }
1481                         _pam_log_debug(pamh, ctrl, LOG_INFO, "PAM config: %s '%s'\n", item, p+1);
1482                         return p + 1;
1483                 }
1484         }
1485
1486         if (d != NULL) {
1487                 _pam_log_debug(pamh, ctrl, LOG_INFO, "CONFIG file: %s '%s'\n", item, parm_opt);
1488         }
1489 out:
1490         return parm_opt;
1491 }
1492
1493 const char *get_krb5_cc_type_from_config(const pam_handle_t *pamh, int argc, const char **argv, int ctrl, dictionary *d)
1494 {
1495         return get_conf_item_string(pamh, argc, argv, ctrl, d, "krb5_ccache_type", WINBIND_KRB5_CCACHE_TYPE);
1496 }
1497
1498 const char *get_member_from_config(const pam_handle_t *pamh, int argc, const char **argv, int ctrl, dictionary *d)
1499 {
1500         const char *ret = NULL;
1501         ret = get_conf_item_string(pamh, argc, argv, ctrl, d, "require_membership_of", WINBIND_REQUIRED_MEMBERSHIP);
1502         if (ret) {
1503                 return ret;
1504         }
1505         return get_conf_item_string(pamh, argc, argv, ctrl, d, "require-membership-of", WINBIND_REQUIRED_MEMBERSHIP);
1506 }
1507
1508 PAM_EXTERN
1509 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
1510                         int argc, const char **argv)
1511 {
1512         const char *username;
1513         const char *password;
1514         const char *member = NULL;
1515         const char *cctype = NULL;
1516         int retval = PAM_AUTH_ERR;
1517         dictionary *d = NULL;
1518         char *username_ret = NULL;
1519         char *new_authtok_required = NULL;
1520         const char *real_username = NULL;
1521
1522         /* parse arguments */
1523         int ctrl = _pam_parse(pamh, flags, argc, argv, &d);
1524         if (ctrl == -1) {
1525                 retval = PAM_SYSTEM_ERR;
1526                 goto out;
1527         }
1528
1529         _PAM_LOG_FUNCTION_ENTER("pam_sm_authenticate", pamh, ctrl, flags);
1530
1531         /* Get the username */
1532         retval = pam_get_user(pamh, &username, NULL);
1533         if ((retval != PAM_SUCCESS) || (!username)) {
1534                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "can not get the username");
1535                 retval = PAM_SERVICE_ERR;
1536                 goto out;
1537         }
1538
1539 #if defined(AIX)
1540         /* Decode the user name since AIX does not support logn user
1541            names by default.  The name is encoded as _#uid.  */
1542
1543         if ( username[0] == '_' ) {
1544                 uid_t id = atoi( &username[1] );
1545                 struct passwd *pw = NULL;               
1546
1547                 if ( (id!=0) && ((pw = getpwuid( id )) != NULL) ) {
1548                         real_username = strdup( pw->pw_name );
1549                 }
1550         }
1551 #endif
1552
1553         if ( !real_username ) {
1554                 /* Just making a copy of the username we got from PAM */
1555                 if ( (real_username = strdup( username )) == NULL ) {
1556                         _pam_log_debug(pamh, ctrl, LOG_DEBUG, 
1557                                        "memory allocation failure when copying username");
1558                         retval = PAM_SERVICE_ERR;
1559                         goto out;
1560                 }
1561         }       
1562
1563         retval = _winbind_read_password(pamh, ctrl, NULL, 
1564                                         "Password: ", NULL,
1565                                         &password);
1566
1567         if (retval != PAM_SUCCESS) {
1568                 _pam_log(pamh, ctrl, LOG_ERR, "Could not retrieve user's password");
1569                 retval = PAM_AUTHTOK_ERR;
1570                 goto out;
1571         }
1572
1573         /* Let's not give too much away in the log file */
1574
1575 #ifdef DEBUG_PASSWORD
1576         _pam_log_debug(pamh, ctrl, LOG_INFO, "Verify user '%s' with password '%s'", 
1577                        real_username, password);
1578 #else
1579         _pam_log_debug(pamh, ctrl, LOG_INFO, "Verify user '%s'", real_username);
1580 #endif
1581
1582         member = get_member_from_config(pamh, argc, argv, ctrl, d);
1583
1584         cctype = get_krb5_cc_type_from_config(pamh, argc, argv, ctrl, d);
1585
1586         /* Now use the username to look up password */
1587         retval = winbind_auth_request(pamh, ctrl, username, password, member,
1588                                       cctype, NULL, NULL, &username_ret);
1589
1590         if (retval == PAM_NEW_AUTHTOK_REQD ||
1591             retval == PAM_AUTHTOK_EXPIRED) {
1592
1593                 char *new_authtok_required_during_auth = NULL;
1594
1595                 if (!asprintf(&new_authtok_required, "%d", retval)) {
1596                         retval = PAM_BUF_ERR;
1597                         goto out;
1598                 }
1599
1600                 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, new_authtok_required, _pam_winbind_cleanup_func);
1601
1602                 retval = PAM_SUCCESS;
1603
1604                 if (!asprintf(&new_authtok_required_during_auth, "%d", True)) {
1605                         retval = PAM_BUF_ERR;
1606                         goto out;
1607                 }
1608
1609                 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH, 
1610                              new_authtok_required_during_auth, _pam_winbind_cleanup_func);
1611
1612                 goto out;
1613         }
1614
1615 out:
1616         if (username_ret) {
1617                 pam_set_item (pamh, PAM_USER, username_ret);
1618                 _pam_log_debug(pamh, ctrl, LOG_INFO, "Returned user was '%s'", username_ret);
1619                 free(username_ret);
1620         }
1621
1622         if ( real_username ) {          
1623                 free( real_username );
1624         }       
1625                         
1626         if (d) {
1627                 iniparser_freedict(d);
1628         }
1629
1630         if (!new_authtok_required) {
1631                 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, NULL, NULL);
1632         }
1633
1634         if (retval != PAM_SUCCESS) {
1635                 _pam_free_data_info3(pamh);
1636         }
1637
1638         _PAM_LOG_FUNCTION_LEAVE("pam_sm_authenticate", pamh, ctrl, retval);
1639
1640         return retval;
1641 }
1642
1643 PAM_EXTERN
1644 int pam_sm_setcred(pam_handle_t *pamh, int flags,
1645                    int argc, const char **argv)
1646 {
1647         int ret = PAM_SYSTEM_ERR;
1648         dictionary *d = NULL;
1649
1650         /* parse arguments */
1651         int ctrl = _pam_parse(pamh, flags, argc, argv, &d);
1652         if (ctrl == -1) {
1653                 ret = PAM_SYSTEM_ERR;
1654                 goto out;
1655         }
1656
1657         _PAM_LOG_FUNCTION_ENTER("pam_sm_setcred", pamh, ctrl, flags);
1658
1659         switch (flags & ~PAM_SILENT) {
1660
1661                 case PAM_DELETE_CRED:
1662                         ret = pam_sm_close_session(pamh, flags, argc, argv);
1663                         break;
1664                 case PAM_REFRESH_CRED:
1665                         _pam_log_debug(pamh, ctrl, LOG_WARNING, "PAM_REFRESH_CRED not implemented");
1666                         ret = PAM_SUCCESS;
1667                         break;
1668                 case PAM_REINITIALIZE_CRED:
1669                         _pam_log_debug(pamh, ctrl, LOG_WARNING, "PAM_REINITIALIZE_CRED not implemented");
1670                         ret = PAM_SUCCESS;
1671                         break;
1672                 case PAM_ESTABLISH_CRED:
1673                         _pam_log_debug(pamh, ctrl, LOG_WARNING, "PAM_ESTABLISH_CRED not implemented");
1674                         ret = PAM_SUCCESS;
1675                         break;
1676                 default:
1677                         ret = PAM_SYSTEM_ERR;
1678                         break;
1679         }
1680
1681  out:
1682         if (d) {
1683                 iniparser_freedict(d);
1684         }
1685
1686         _PAM_LOG_FUNCTION_LEAVE("pam_sm_setcred", pamh, ctrl, ret);
1687         
1688         return ret;
1689 }
1690
1691 /*
1692  * Account management. We want to verify that the account exists 
1693  * before returning PAM_SUCCESS
1694  */
1695 PAM_EXTERN
1696 int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
1697                    int argc, const char **argv)
1698 {
1699         const char *username;
1700         int ret = PAM_USER_UNKNOWN;
1701         void *tmp = NULL;
1702         dictionary *d = NULL;
1703
1704         /* parse arguments */
1705         int ctrl = _pam_parse(pamh, flags, argc, argv, &d);
1706         if (ctrl == -1) {
1707                 return PAM_SYSTEM_ERR;
1708         }
1709
1710         _PAM_LOG_FUNCTION_ENTER("pam_sm_acct_mgmt", pamh, ctrl, flags);
1711
1712
1713         /* Get the username */
1714         ret = pam_get_user(pamh, &username, NULL);
1715         if ((ret != PAM_SUCCESS) || (!username)) {
1716                 _pam_log_debug(pamh, ctrl, LOG_DEBUG,"can not get the username");
1717                 ret = PAM_SERVICE_ERR;
1718                 goto out;
1719         }
1720
1721         /* Verify the username */
1722         ret = valid_user(pamh, ctrl, username);
1723         switch (ret) {
1724         case -1:
1725                 /* some sort of system error. The log was already printed */
1726                 ret = PAM_SERVICE_ERR;
1727                 goto out;
1728         case 1:
1729                 /* the user does not exist */
1730                 _pam_log_debug(pamh, ctrl, LOG_NOTICE, "user '%s' not found", username);
1731                 if (ctrl & WINBIND_UNKNOWN_OK_ARG) {
1732                         ret = PAM_IGNORE;
1733                         goto out;
1734                 }
1735                 ret = PAM_USER_UNKNOWN;
1736                 goto out;
1737         case 0:
1738                 pam_get_data( pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, (const void **)&tmp);
1739                 if (tmp != NULL) {
1740                         ret = atoi((const char *)tmp);
1741                         switch (ret) {
1742                         case PAM_AUTHTOK_EXPIRED:
1743                                 /* fall through, since new token is required in this case */
1744                         case PAM_NEW_AUTHTOK_REQD:
1745                                 _pam_log(pamh, ctrl, LOG_WARNING, "pam_sm_acct_mgmt success but %s is set", 
1746                                          PAM_WINBIND_NEW_AUTHTOK_REQD);
1747                                 _pam_log(pamh, ctrl, LOG_NOTICE, "user '%s' needs new password", username);
1748                                 /* PAM_AUTHTOKEN_REQD does not exist, but is documented in the manpage */
1749                                 ret = PAM_NEW_AUTHTOK_REQD;
1750                                 goto out;
1751                         default:
1752                                 _pam_log(pamh, ctrl, LOG_WARNING, "pam_sm_acct_mgmt success");
1753                                 _pam_log(pamh, ctrl, LOG_NOTICE, "user '%s' granted access", username);
1754                                 ret = PAM_SUCCESS;
1755                                 goto out;
1756                         }
1757                 }
1758
1759                 /* Otherwise, the authentication looked good */
1760                 _pam_log(pamh, ctrl, LOG_NOTICE, "user '%s' granted access", username);
1761                 ret = PAM_SUCCESS;
1762                 goto out;
1763         default:
1764                 /* we don't know anything about this return value */
1765                 _pam_log(pamh, ctrl, LOG_ERR, "internal module error (ret = %d, user = '%s')", 
1766                          ret, username);
1767                 ret = PAM_SERVICE_ERR;
1768                 goto out;
1769         }
1770
1771         /* should not be reached */
1772         ret = PAM_IGNORE;
1773
1774  out:
1775
1776         if (d) {
1777                 iniparser_freedict(d);
1778         }
1779
1780         _PAM_LOG_FUNCTION_LEAVE("pam_sm_acct_mgmt", pamh, ctrl, ret);
1781         
1782         return ret;
1783 }
1784
1785 PAM_EXTERN
1786 int pam_sm_open_session(pam_handle_t *pamh, int flags,
1787                         int argc, const char **argv)
1788 {
1789         int ret = PAM_SYSTEM_ERR;
1790         dictionary *d = NULL;
1791
1792         /* parse arguments */
1793         int ctrl = _pam_parse(pamh, flags, argc, argv, &d);
1794         if (ctrl == -1) {
1795                 ret = PAM_SYSTEM_ERR;
1796                 goto out;
1797         }
1798
1799         _PAM_LOG_FUNCTION_ENTER("pam_sm_open_session", pamh, ctrl, flags);
1800
1801         ret = PAM_SUCCESS;
1802
1803  out:
1804         if (d) {
1805                 iniparser_freedict(d);
1806         }
1807
1808         _PAM_LOG_FUNCTION_LEAVE("pam_sm_open_session", pamh, ctrl, ret);
1809         
1810         return ret;
1811 }
1812
1813 PAM_EXTERN
1814 int pam_sm_close_session(pam_handle_t *pamh, int flags,
1815                          int argc, const char **argv)
1816 {
1817         dictionary *d = NULL;
1818         int retval = PAM_SUCCESS;
1819
1820         /* parse arguments */
1821         int ctrl = _pam_parse(pamh, flags, argc, argv, &d);
1822         if (ctrl == -1) {
1823                 retval = PAM_SYSTEM_ERR;
1824                 goto out;
1825         }
1826
1827         _PAM_LOG_FUNCTION_ENTER("pam_sm_close_session", pamh, ctrl, flags);
1828
1829         if (!(flags & PAM_DELETE_CRED)) {
1830                 retval = PAM_SUCCESS;
1831                 goto out;
1832         }
1833
1834         if (ctrl & WINBIND_KRB5_AUTH) {
1835
1836                 /* destroy the ccache here */
1837                 struct winbindd_request request;
1838                 struct winbindd_response response;
1839                 const char *user;
1840                 const char *ccname = NULL;
1841                 struct passwd *pwd = NULL;
1842
1843                 ZERO_STRUCT(request);
1844                 ZERO_STRUCT(response);
1845
1846                 retval = pam_get_user(pamh, &user, "Username: ");
1847                 if (retval) {
1848                         _pam_log(pamh, ctrl, LOG_ERR, "could not identify user");
1849                         goto out;
1850                 }
1851
1852                 if (user == NULL) {
1853                         _pam_log(pamh, ctrl, LOG_ERR, "username was NULL!");
1854                         retval = PAM_USER_UNKNOWN;
1855                         goto out;
1856                 }
1857
1858                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "username [%s] obtained", user);
1859
1860                 ccname = pam_getenv(pamh, "KRB5CCNAME");
1861                 if (ccname == NULL) {
1862                         _pam_log_debug(pamh, ctrl, LOG_DEBUG, "user has no KRB5CCNAME environment");
1863                 }
1864
1865                 strncpy(request.data.logoff.user, user,
1866                         sizeof(request.data.logoff.user) - 1);
1867
1868                 if (ccname) {
1869                         strncpy(request.data.logoff.krb5ccname, ccname,
1870                                 sizeof(request.data.logoff.krb5ccname) - 1);
1871                 }
1872
1873                 pwd = getpwnam(user);
1874                 if (pwd == NULL) {
1875                         retval = PAM_USER_UNKNOWN;
1876                         goto out;
1877                 }
1878                 request.data.logoff.uid = pwd->pw_uid;
1879
1880                 request.flags = WBFLAG_PAM_KRB5 | WBFLAG_PAM_CONTACT_TRUSTDOM;
1881
1882                 retval = pam_winbind_request_log(pamh, ctrl, WINBINDD_PAM_LOGOFF, &request, &response, user);
1883         }
1884
1885 out:
1886         if (d) {
1887                 iniparser_freedict(d);
1888         }
1889
1890         _PAM_LOG_FUNCTION_LEAVE("pam_sm_close_session", pamh, ctrl, retval);
1891         
1892         return retval;
1893 }
1894
1895 /**
1896  * evaluate whether we need to re-authenticate with kerberos after a password change
1897  * 
1898  * @param pamh PAM handle
1899  * @param ctrl PAM winbind options.
1900  * @param user The username
1901  *
1902  * @return boolean Returns True if required, False if not.
1903  */
1904
1905 static BOOL _pam_require_krb5_auth_after_chauthtok(pam_handle_t *pamh, int ctrl, const char *user)
1906 {
1907
1908         /* Make sure that we only do this if 
1909          * a) the chauthtok got initiated during a logon attempt (authenticate->acct_mgmt->chauthtok)
1910          * b) any later password change via the "passwd" command if done by the user itself 
1911          */
1912                 
1913         char *new_authtok_reqd_during_auth = NULL;
1914         struct passwd *pwd = NULL;
1915
1916         if (!(ctrl & WINBIND_KRB5_AUTH)) {
1917                 return False;
1918         }
1919
1920         _pam_get_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH, &new_authtok_reqd_during_auth);
1921         pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH, NULL, NULL);
1922
1923         if (new_authtok_reqd_during_auth) {
1924                 return True;
1925         }
1926
1927         pwd = getpwnam(user);
1928         if (!pwd) {
1929                 return False;
1930         }
1931
1932         if (getuid() == pwd->pw_uid) {
1933                 return True;
1934         }
1935
1936         return False;
1937 }
1938
1939
1940 PAM_EXTERN 
1941 int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
1942                      int argc, const char **argv)
1943 {
1944         unsigned int lctrl;
1945         int ret;
1946         unsigned int ctrl;
1947
1948         /* <DO NOT free() THESE> */
1949         const char *user;
1950         char *pass_old, *pass_new;
1951         /* </DO NOT free() THESE> */
1952
1953         char *Announce;
1954         
1955         int retry = 0;
1956         dictionary *d = NULL;
1957         char *username_ret = NULL;
1958         struct winbindd_response response;
1959
1960         ZERO_STRUCT(response);
1961
1962         ctrl = _pam_parse(pamh, flags, argc, argv, &d);
1963         if (ctrl == -1) {
1964                 ret = PAM_SYSTEM_ERR;
1965                 goto out;
1966         }
1967
1968         _PAM_LOG_FUNCTION_ENTER("pam_sm_chauthtok", pamh, ctrl, flags);
1969
1970         /* clearing offline bit for the auth in the password change */
1971         ctrl &= ~WINBIND_CACHED_LOGIN;
1972
1973         /*
1974          * First get the name of a user
1975          */
1976         ret = pam_get_user(pamh, &user, "Username: ");
1977         if (ret) {
1978                 _pam_log(pamh, ctrl, LOG_ERR,
1979                          "password - could not identify user");
1980                 goto out;
1981         }
1982
1983         if (user == NULL) {
1984                 _pam_log(pamh, ctrl, LOG_ERR, "username was NULL!");
1985                 ret = PAM_USER_UNKNOWN;
1986                 goto out;
1987         }
1988
1989         _pam_log_debug(pamh, ctrl, LOG_DEBUG, "username [%s] obtained", user);
1990
1991         /* check if this is really a user in winbindd, not only in NSS */
1992         ret = valid_user(pamh, ctrl, user);
1993         switch (ret) {
1994                 case 1:
1995                         ret = PAM_USER_UNKNOWN;
1996                         goto out;
1997                 case -1:
1998                         ret = PAM_SYSTEM_ERR;
1999                         goto out;
2000                 default:
2001                         break;
2002         }
2003                 
2004         /*
2005          * obtain and verify the current password (OLDAUTHTOK) for
2006          * the user.
2007          */
2008
2009         if (flags & PAM_PRELIM_CHECK) {
2010                 time_t pwdlastset_prelim = 0;
2011                 
2012                 /* instruct user what is happening */
2013 #define greeting "Changing password for "
2014                 Announce = (char *) malloc(sizeof(greeting) + strlen(user));
2015                 if (Announce == NULL) {
2016                         _pam_log(pamh, ctrl, LOG_CRIT, "password - out of memory");
2017                         ret = PAM_BUF_ERR;
2018                         goto out;
2019                 }
2020                 (void) strcpy(Announce, greeting);
2021                 (void) strcpy(Announce + sizeof(greeting) - 1, user);
2022 #undef greeting
2023                 
2024                 lctrl = ctrl | WINBIND__OLD_PASSWORD;
2025                 ret = _winbind_read_password(pamh, lctrl,
2026                                                 Announce,
2027                                                 "(current) NT password: ",
2028                                                 NULL,
2029                                                 (const char **) &pass_old);
2030                 if (ret != PAM_SUCCESS) {
2031                         _pam_log(pamh, ctrl, LOG_NOTICE, "password - (old) token not obtained");
2032                         goto out;
2033                 }
2034
2035                 /* verify that this is the password for this user */
2036                 
2037                 ret = winbind_auth_request(pamh, ctrl, user, pass_old,
2038                                         NULL, NULL, &response, &pwdlastset_prelim, NULL);
2039
2040                 if (ret != PAM_ACCT_EXPIRED && 
2041                     ret != PAM_AUTHTOK_EXPIRED &&
2042                     ret != PAM_NEW_AUTHTOK_REQD &&
2043                     ret != PAM_SUCCESS) {
2044                         pass_old = NULL;
2045                         goto out;
2046                 }
2047                 
2048                 pam_set_data(pamh, PAM_WINBIND_PWD_LAST_SET, (void *)pwdlastset_prelim, NULL);
2049
2050                 ret = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
2051                 pass_old = NULL;
2052                 if (ret != PAM_SUCCESS) {
2053                         _pam_log(pamh, ctrl, LOG_CRIT, "failed to set PAM_OLDAUTHTOK");
2054                 }
2055         } else if (flags & PAM_UPDATE_AUTHTOK) {
2056         
2057                 time_t pwdlastset_update = 0;
2058                 
2059                 /*
2060                  * obtain the proposed password
2061                  */
2062                 
2063                 /*
2064                  * get the old token back. 
2065                  */
2066                 
2067                 ret = _pam_get_item(pamh, PAM_OLDAUTHTOK, &pass_old);
2068                 
2069                 if (ret != PAM_SUCCESS) {
2070                         _pam_log(pamh, ctrl, LOG_NOTICE, "user not authenticated");
2071                         goto out;
2072                 }
2073                 
2074                 lctrl = ctrl & ~WINBIND_TRY_FIRST_PASS_ARG;
2075                 
2076                 if (on(WINBIND_USE_AUTHTOK_ARG, lctrl)) {
2077                         lctrl |= WINBIND_USE_FIRST_PASS_ARG;
2078                 }
2079                 retry = 0;
2080                 ret = PAM_AUTHTOK_ERR;
2081                 while ((ret != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
2082                         /*
2083                          * use_authtok is to force the use of a previously entered
2084                          * password -- needed for pluggable password strength checking
2085                          */
2086                         
2087                         ret = _winbind_read_password(pamh, lctrl,
2088                                                         NULL,
2089                                                         "Enter new NT password: ",
2090                                                         "Retype new NT password: ",
2091                                                         (const char **) &pass_new);
2092                         
2093                         if (ret != PAM_SUCCESS) {
2094                                 _pam_log_debug(pamh, ctrl, LOG_ALERT
2095                                          ,"password - new password not obtained");
2096                                 pass_old = NULL;/* tidy up */
2097                                 goto out;
2098                         }
2099
2100                         /*
2101                          * At this point we know who the user is and what they
2102                          * propose as their new password. Verify that the new
2103                          * password is acceptable.
2104                          */
2105                         
2106                         if (pass_new[0] == '\0') {/* "\0" password = NULL */
2107                                 pass_new = NULL;
2108                         }
2109                 }
2110                 
2111                 /*
2112                  * By reaching here we have approved the passwords and must now
2113                  * rebuild the password database file.
2114                  */
2115                 _pam_get_data( pamh, PAM_WINBIND_PWD_LAST_SET,
2116                                &pwdlastset_update);
2117
2118                 ret = winbind_chauthtok_request(pamh, ctrl, user, pass_old, pass_new, pwdlastset_update);
2119                 if (ret) {
2120                         _pam_overwrite(pass_new);
2121                         _pam_overwrite(pass_old);
2122                         pass_old = pass_new = NULL;
2123                         goto out;
2124                 }
2125
2126                 if (_pam_require_krb5_auth_after_chauthtok(pamh, ctrl, user)) {
2127
2128                         const char *member = get_member_from_config(pamh, argc, argv, ctrl, d);
2129                         const char *cctype = get_krb5_cc_type_from_config(pamh, argc, argv, ctrl, d);
2130
2131                         ret = winbind_auth_request(pamh, ctrl, user, pass_new,
2132                                                         member, cctype, &response, NULL, &username_ret);
2133                         _pam_overwrite(pass_new);
2134                         _pam_overwrite(pass_old);
2135                         pass_old = pass_new = NULL;
2136
2137                         if (ret == PAM_SUCCESS) {
2138                         
2139                                 /* warn a user if the password is about to expire soon */
2140                                 _pam_warn_password_expiry(pamh, ctrl, &response, NULL);
2141
2142                                 /* set some info3 info for other modules in the stack */
2143                                 _pam_set_data_info3(pamh, ctrl, &response);
2144
2145                                 /* put krb5ccname into env */
2146                                 _pam_setup_krb5_env(pamh, ctrl, response.data.auth.krb5ccname);
2147
2148                                 if (username_ret) {
2149                                         pam_set_item (pamh, PAM_USER, username_ret);
2150                                         _pam_log_debug(pamh, ctrl, LOG_INFO, "Returned user was '%s'", username_ret);
2151                                         free(username_ret);
2152                                 }
2153                         }
2154
2155                         goto out;
2156                 }
2157         } else {
2158                 ret = PAM_SERVICE_ERR;
2159         }
2160
2161 out:
2162         if (d) {
2163                 iniparser_freedict(d);
2164         }
2165
2166         /* Deal with offline errors. */
2167         PAM_WB_REMARK_CHECK_RESPONSE(pamh, ctrl, response, "NT_STATUS_NO_LOGON_SERVERS");
2168         PAM_WB_REMARK_CHECK_RESPONSE(pamh, ctrl, response, "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
2169         PAM_WB_REMARK_CHECK_RESPONSE(pamh, ctrl, response, "NT_STATUS_ACCESS_DENIED");
2170
2171         _PAM_LOG_FUNCTION_LEAVE("pam_sm_chauthtok", pamh, ctrl, ret);
2172         
2173         return ret;
2174 }
2175
2176 #ifdef PAM_STATIC
2177
2178 /* static module data */
2179
2180 struct pam_module _pam_winbind_modstruct = {
2181         MODULE_NAME,
2182         pam_sm_authenticate,
2183         pam_sm_setcred,
2184         pam_sm_acct_mgmt,
2185         pam_sm_open_session,
2186         pam_sm_close_session,
2187         pam_sm_chauthtok
2188 };
2189
2190 #endif
2191
2192 /*
2193  * Copyright (c) Andrew Tridgell  <tridge@samba.org>   2000
2194  * Copyright (c) Tim Potter       <tpot@samba.org>     2000
2195  * Copyright (c) Andrew Bartlettt <abartlet@samba.org> 2002
2196  * Copyright (c) Guenther Deschner <gd@samba.org>      2005-2007
2197  * Copyright (c) Jan Rêkorajski 1999.
2198  * Copyright (c) Andrew G. Morgan 1996-8.
2199  * Copyright (c) Alex O. Yuriev, 1996.
2200  * Copyright (c) Cristian Gafton 1996.
2201  * Copyright (C) Elliot Lee <sopwith@redhat.com> 1996, Red Hat Software. 
2202  *
2203  * Redistribution and use in source and binary forms, with or without
2204  * modification, are permitted provided that the following conditions
2205  * are met:
2206  * 1. Redistributions of source code must retain the above copyright
2207  *    notice, and the entire permission notice in its entirety,
2208  *    including the disclaimer of warranties.
2209  * 2. Redistributions in binary form must reproduce the above copyright
2210  *    notice, this list of conditions and the following disclaimer in the
2211  *    documentation and/or other materials provided with the distribution.
2212  * 3. The name of the author may not be used to endorse or promote
2213  *    products derived from this software without specific prior
2214  *    written permission.
2215  *
2216  * ALTERNATIVELY, this product may be distributed under the terms of
2217  * the GNU Public License, in which case the provisions of the GPL are
2218  * required INSTEAD OF the above restrictions.  (This clause is
2219  * necessary due to a potential bad interaction between the GPL and
2220  * the restrictions contained in a BSD-style copyright.)
2221  *
2222  * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
2223  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2224  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2225  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
2226  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2227  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2228  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2229  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
2230  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2231  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2232  * OF THE POSSIBILITY OF SUCH DAMAGE.
2233  */