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