25f7540e06681056b839a37ff71031682aedc6e0
[tprouty/samba.git] / source / nsswitch / pam_winbind.c
1 /* pam_winbind module
2
3    Copyright Andrew Tridgell <tridge@samba.org> 2000
4    Copyright Tim Potter <tpot@samba.org> 2000
5    Copyright Andrew Bartlett <abartlet@samba.org> 2002
6    Copyright Guenther Deschner <gd@samba.org> 2005-2007
7
8    largely based on pam_userdb by Cristian Gafton <gafton@redhat.com> 
9    also contains large slabs of code from pam_unix by Elliot Lee <sopwith@redhat.com>
10    (see copyright below for full details)
11 */
12
13 #include "pam_winbind.h"
14
15 #define _PAM_LOG_FUNCTION_ENTER(function, pamh, ctrl, flags) \
16         do { \
17                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "[pamh: %p] ENTER: " function " (flags: 0x%04x)", 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: %p] LEAVE: " function " returning %d", 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: %p] STATE: %s(%s) = \"%s\" (%p)", pamh, type, key, (const char *) data, data);
147                 } else {
148                         _pam_log_debug(pamh, ctrl, LOG_DEBUG, "[pamh: %p] STATE: %s(%s) = %p", pamh, type, key, 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: %p] CLEAN: cleaning up PAM data %p (error_status = %d)", pamh, 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 /**
1618  * Retrieve the winbind separator.
1619  *
1620  * @param pamh PAM handle
1621  * @param ctrl PAM winbind options.
1622  *
1623  * @return string separator character. NULL on failure.
1624  */
1625
1626 static char winbind_get_separator(pam_handle_t *pamh, int ctrl)
1627 {
1628         struct winbindd_request request;
1629         struct winbindd_response response;
1630
1631         ZERO_STRUCT(request);
1632         ZERO_STRUCT(response);
1633
1634         if (pam_winbind_request_log(pamh, ctrl, WINBINDD_INFO, &request, &response, NULL)) {
1635                 return '\0';
1636         }
1637
1638         return response.data.info.winbind_separator;
1639 }
1640
1641 /**
1642  * Convert a upn to a name.
1643  *
1644  * @param pamh PAM handle
1645  * @param ctrl PAM winbind options.
1646  * @param upn  USer UPN to be trabslated.
1647  *
1648  * @return converted name. NULL pointer on failure. Caller needs to free.
1649  */
1650
1651 static char* winbind_upn_to_username(pam_handle_t *pamh, int ctrl, const char *upn)
1652 {
1653         struct winbindd_request req;
1654         struct winbindd_response resp;
1655         int retval;     
1656         char *account_name;     
1657         int account_name_len;
1658         char sep;       
1659
1660         /* This cannot work when the winbind separator = @ */
1661
1662         sep = winbind_get_separator(pamh, ctrl);
1663         if (!sep || sep == '@') {
1664                 return NULL;
1665         }
1666         
1667         /* Convert the UPN to a SID */
1668
1669         ZERO_STRUCT(req);
1670         ZERO_STRUCT(resp);
1671
1672         strncpy(req.data.name.dom_name, "",
1673                 sizeof(req.data.name.dom_name) - 1);
1674         strncpy(req.data.name.name, upn,
1675                 sizeof(req.data.name.name) - 1);
1676         retval = pam_winbind_request_log(pamh, ctrl, WINBINDD_LOOKUPNAME, 
1677                                          &req, &resp, upn);
1678         if ( retval != PAM_SUCCESS ) {          
1679                 return NULL;
1680         }
1681         
1682         /* Convert the the SID back to the sAMAccountName */
1683         
1684         ZERO_STRUCT(req);
1685         strncpy(req.data.sid, resp.data.sid.sid, sizeof(req.data.sid)-1);
1686         ZERO_STRUCT(resp);
1687         retval =  pam_winbind_request_log(pamh, ctrl, WINBINDD_LOOKUPSID, 
1688                                           &req, &resp, upn);
1689         if ( retval != PAM_SUCCESS ) {          
1690                 return NULL;
1691         }
1692         
1693         account_name_len = asprintf(&account_name, "%s\\%s", 
1694                                     resp.data.name.dom_name,
1695                                     resp.data.name.name);
1696
1697         return account_name;
1698 }
1699
1700 PAM_EXTERN
1701 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
1702                         int argc, const char **argv)
1703 {
1704         const char *username;
1705         const char *password;
1706         const char *member = NULL;
1707         const char *cctype = NULL;
1708         int warn_pwd_expire;
1709         int retval = PAM_AUTH_ERR;
1710         dictionary *d = NULL;
1711         char *username_ret = NULL;
1712         char *new_authtok_required = NULL;
1713         char *real_username = NULL;
1714
1715         /* parse arguments */
1716         int ctrl = _pam_parse(pamh, flags, argc, argv, &d);
1717         if (ctrl == -1) {
1718                 retval = PAM_SYSTEM_ERR;
1719                 goto out;
1720         }
1721
1722         _PAM_LOG_FUNCTION_ENTER("pam_sm_authenticate", pamh, ctrl, flags);
1723
1724         /* Get the username */
1725         retval = pam_get_user(pamh, &username, NULL);
1726         if ((retval != PAM_SUCCESS) || (!username)) {
1727                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "can not get the username");
1728                 retval = PAM_SERVICE_ERR;
1729                 goto out;
1730         }
1731
1732
1733 #if defined(AIX)
1734         /* Decode the user name since AIX does not support logn user
1735            names by default.  The name is encoded as _#uid.  */
1736
1737         if ( username[0] == '_' ) {
1738                 uid_t id = atoi( &username[1] );
1739                 struct passwd *pw = NULL;               
1740
1741                 if ( (id!=0) && ((pw = getpwuid( id )) != NULL) ) {
1742                         real_username = strdup( pw->pw_name );
1743                 }
1744         }
1745 #endif
1746
1747         if ( !real_username ) {
1748                 /* Just making a copy of the username we got from PAM */
1749                 if ( (real_username = strdup( username )) == NULL ) {
1750                         _pam_log_debug(pamh, ctrl, LOG_DEBUG, 
1751                                        "memory allocation failure when copying username");
1752                         retval = PAM_SERVICE_ERR;
1753                         goto out;
1754                 }
1755         }       
1756
1757         /* Maybe this was a UPN */
1758
1759         if (strchr(real_username, '@') != NULL) {
1760                 char *samaccountname = NULL;
1761                 
1762                 samaccountname = winbind_upn_to_username(pamh, ctrl, 
1763                                                          real_username);
1764                 if (samaccountname) {
1765                         free(real_username);
1766                         real_username = samaccountname;
1767                 }
1768         }
1769
1770         retval = _winbind_read_password(pamh, ctrl, NULL, 
1771                                         "Password: ", NULL,
1772                                         &password);
1773
1774         if (retval != PAM_SUCCESS) {
1775                 _pam_log(pamh, ctrl, LOG_ERR, "Could not retrieve user's password");
1776                 retval = PAM_AUTHTOK_ERR;
1777                 goto out;
1778         }
1779
1780         /* Let's not give too much away in the log file */
1781
1782 #ifdef DEBUG_PASSWORD
1783         _pam_log_debug(pamh, ctrl, LOG_INFO, "Verify user '%s' with password '%s'", 
1784                        real_username, password);
1785 #else
1786         _pam_log_debug(pamh, ctrl, LOG_INFO, "Verify user '%s'", real_username);
1787 #endif
1788
1789         member = get_member_from_config(pamh, argc, argv, ctrl, d);
1790
1791         cctype = get_krb5_cc_type_from_config(pamh, argc, argv, ctrl, d);
1792
1793         warn_pwd_expire = get_warn_pwd_expire_from_config(pamh, argc, argv,
1794                                                           ctrl, d);
1795
1796         /* Now use the username to look up password */
1797         retval = winbind_auth_request(pamh, ctrl, real_username, password, member,
1798                                       cctype, warn_pwd_expire, NULL, NULL, 
1799                                       &username_ret);
1800
1801         if (retval == PAM_NEW_AUTHTOK_REQD ||
1802             retval == PAM_AUTHTOK_EXPIRED) {
1803
1804                 char *new_authtok_required_during_auth = NULL;
1805
1806                 if (!asprintf(&new_authtok_required, "%d", retval)) {
1807                         retval = PAM_BUF_ERR;
1808                         goto out;
1809                 }
1810
1811                 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, new_authtok_required, _pam_winbind_cleanup_func);
1812
1813                 retval = PAM_SUCCESS;
1814
1815                 if (!asprintf(&new_authtok_required_during_auth, "%d", True)) {
1816                         retval = PAM_BUF_ERR;
1817                         goto out;
1818                 }
1819
1820                 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH, 
1821                              new_authtok_required_during_auth, _pam_winbind_cleanup_func);
1822
1823                 goto out;
1824         }
1825
1826 out:
1827         if (username_ret) {
1828                 pam_set_item (pamh, PAM_USER, username_ret);
1829                 _pam_log_debug(pamh, ctrl, LOG_INFO, "Returned user was '%s'", username_ret);
1830                 free(username_ret);
1831         }
1832
1833         if ( real_username ) {          
1834                 free( real_username );
1835         }       
1836                         
1837         if (d) {
1838                 iniparser_freedict(d);
1839         }
1840
1841         if (!new_authtok_required) {
1842                 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, NULL, NULL);
1843         }
1844
1845         if (retval != PAM_SUCCESS) {
1846                 _pam_free_data_info3(pamh);
1847         }
1848
1849         _PAM_LOG_FUNCTION_LEAVE("pam_sm_authenticate", pamh, ctrl, retval);
1850
1851         return retval;
1852 }
1853
1854 PAM_EXTERN
1855 int pam_sm_setcred(pam_handle_t *pamh, int flags,
1856                    int argc, const char **argv)
1857 {
1858         int ret = PAM_SYSTEM_ERR;
1859         dictionary *d = NULL;
1860
1861         /* parse arguments */
1862         int ctrl = _pam_parse(pamh, flags, argc, argv, &d);
1863         if (ctrl == -1) {
1864                 ret = PAM_SYSTEM_ERR;
1865                 goto out;
1866         }
1867
1868         _PAM_LOG_FUNCTION_ENTER("pam_sm_setcred", pamh, ctrl, flags);
1869
1870         switch (flags & ~PAM_SILENT) {
1871
1872                 case PAM_DELETE_CRED:
1873                         ret = pam_sm_close_session(pamh, flags, argc, argv);
1874                         break;
1875                 case PAM_REFRESH_CRED:
1876                         _pam_log_debug(pamh, ctrl, LOG_WARNING, "PAM_REFRESH_CRED not implemented");
1877                         ret = PAM_SUCCESS;
1878                         break;
1879                 case PAM_REINITIALIZE_CRED:
1880                         _pam_log_debug(pamh, ctrl, LOG_WARNING, "PAM_REINITIALIZE_CRED not implemented");
1881                         ret = PAM_SUCCESS;
1882                         break;
1883                 case PAM_ESTABLISH_CRED:
1884                         _pam_log_debug(pamh, ctrl, LOG_WARNING, "PAM_ESTABLISH_CRED not implemented");
1885                         ret = PAM_SUCCESS;
1886                         break;
1887                 default:
1888                         ret = PAM_SYSTEM_ERR;
1889                         break;
1890         }
1891
1892  out:
1893         if (d) {
1894                 iniparser_freedict(d);
1895         }
1896
1897         _PAM_LOG_FUNCTION_LEAVE("pam_sm_setcred", pamh, ctrl, ret);
1898         
1899         return ret;
1900 }
1901
1902 /*
1903  * Account management. We want to verify that the account exists 
1904  * before returning PAM_SUCCESS
1905  */
1906 PAM_EXTERN
1907 int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
1908                    int argc, const char **argv)
1909 {
1910         const char *username;
1911         int ret = PAM_USER_UNKNOWN;
1912         void *tmp = NULL;
1913         dictionary *d = NULL;
1914
1915         /* parse arguments */
1916         int ctrl = _pam_parse(pamh, flags, argc, argv, &d);
1917         if (ctrl == -1) {
1918                 return PAM_SYSTEM_ERR;
1919         }
1920
1921         _PAM_LOG_FUNCTION_ENTER("pam_sm_acct_mgmt", pamh, ctrl, flags);
1922
1923
1924         /* Get the username */
1925         ret = pam_get_user(pamh, &username, NULL);
1926         if ((ret != PAM_SUCCESS) || (!username)) {
1927                 _pam_log_debug(pamh, ctrl, LOG_DEBUG,"can not get the username");
1928                 ret = PAM_SERVICE_ERR;
1929                 goto out;
1930         }
1931
1932         /* Verify the username */
1933         ret = valid_user(pamh, ctrl, username);
1934         switch (ret) {
1935         case -1:
1936                 /* some sort of system error. The log was already printed */
1937                 ret = PAM_SERVICE_ERR;
1938                 goto out;
1939         case 1:
1940                 /* the user does not exist */
1941                 _pam_log_debug(pamh, ctrl, LOG_NOTICE, "user '%s' not found", username);
1942                 if (ctrl & WINBIND_UNKNOWN_OK_ARG) {
1943                         ret = PAM_IGNORE;
1944                         goto out;
1945                 }
1946                 ret = PAM_USER_UNKNOWN;
1947                 goto out;
1948         case 0:
1949                 pam_get_data( pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, (const void **)&tmp);
1950                 if (tmp != NULL) {
1951                         ret = atoi((const char *)tmp);
1952                         switch (ret) {
1953                         case PAM_AUTHTOK_EXPIRED:
1954                                 /* fall through, since new token is required in this case */
1955                         case PAM_NEW_AUTHTOK_REQD:
1956                                 _pam_log(pamh, ctrl, LOG_WARNING, "pam_sm_acct_mgmt success but %s is set", 
1957                                          PAM_WINBIND_NEW_AUTHTOK_REQD);
1958                                 _pam_log(pamh, ctrl, LOG_NOTICE, "user '%s' needs new password", username);
1959                                 /* PAM_AUTHTOKEN_REQD does not exist, but is documented in the manpage */
1960                                 ret = PAM_NEW_AUTHTOK_REQD;
1961                                 goto out;
1962                         default:
1963                                 _pam_log(pamh, ctrl, LOG_WARNING, "pam_sm_acct_mgmt success");
1964                                 _pam_log(pamh, ctrl, LOG_NOTICE, "user '%s' granted access", username);
1965                                 ret = PAM_SUCCESS;
1966                                 goto out;
1967                         }
1968                 }
1969
1970                 /* Otherwise, the authentication looked good */
1971                 _pam_log(pamh, ctrl, LOG_NOTICE, "user '%s' granted access", username);
1972                 ret = PAM_SUCCESS;
1973                 goto out;
1974         default:
1975                 /* we don't know anything about this return value */
1976                 _pam_log(pamh, ctrl, LOG_ERR, "internal module error (ret = %d, user = '%s')", 
1977                          ret, username);
1978                 ret = PAM_SERVICE_ERR;
1979                 goto out;
1980         }
1981
1982         /* should not be reached */
1983         ret = PAM_IGNORE;
1984
1985  out:
1986
1987         if (d) {
1988                 iniparser_freedict(d);
1989         }
1990
1991         _PAM_LOG_FUNCTION_LEAVE("pam_sm_acct_mgmt", pamh, ctrl, ret);
1992         
1993         return ret;
1994 }
1995
1996 PAM_EXTERN
1997 int pam_sm_open_session(pam_handle_t *pamh, int flags,
1998                         int argc, const char **argv)
1999 {
2000         int ret = PAM_SYSTEM_ERR;
2001         dictionary *d = NULL;
2002
2003         /* parse arguments */
2004         int ctrl = _pam_parse(pamh, flags, argc, argv, &d);
2005         if (ctrl == -1) {
2006                 ret = PAM_SYSTEM_ERR;
2007                 goto out;
2008         }
2009
2010         _PAM_LOG_FUNCTION_ENTER("pam_sm_open_session", pamh, ctrl, flags);
2011
2012         ret = PAM_SUCCESS;
2013
2014  out:
2015         if (d) {
2016                 iniparser_freedict(d);
2017         }
2018
2019         _PAM_LOG_FUNCTION_LEAVE("pam_sm_open_session", pamh, ctrl, ret);
2020         
2021         return ret;
2022 }
2023
2024 PAM_EXTERN
2025 int pam_sm_close_session(pam_handle_t *pamh, int flags,
2026                          int argc, const char **argv)
2027 {
2028         dictionary *d = NULL;
2029         int retval = PAM_SUCCESS;
2030
2031         /* parse arguments */
2032         int ctrl = _pam_parse(pamh, flags, argc, argv, &d);
2033         if (ctrl == -1) {
2034                 retval = PAM_SYSTEM_ERR;
2035                 goto out;
2036         }
2037
2038         _PAM_LOG_FUNCTION_ENTER("pam_sm_close_session", pamh, ctrl, flags);
2039
2040         if (!(flags & PAM_DELETE_CRED)) {
2041                 retval = PAM_SUCCESS;
2042                 goto out;
2043         }
2044
2045         if (ctrl & WINBIND_KRB5_AUTH) {
2046
2047                 /* destroy the ccache here */
2048                 struct winbindd_request request;
2049                 struct winbindd_response response;
2050                 const char *user;
2051                 const char *ccname = NULL;
2052                 struct passwd *pwd = NULL;
2053
2054                 ZERO_STRUCT(request);
2055                 ZERO_STRUCT(response);
2056
2057                 retval = pam_get_user(pamh, &user, "Username: ");
2058                 if (retval) {
2059                         _pam_log(pamh, ctrl, LOG_ERR, "could not identify user");
2060                         goto out;
2061                 }
2062
2063                 if (user == NULL) {
2064                         _pam_log(pamh, ctrl, LOG_ERR, "username was NULL!");
2065                         retval = PAM_USER_UNKNOWN;
2066                         goto out;
2067                 }
2068
2069                 _pam_log_debug(pamh, ctrl, LOG_DEBUG, "username [%s] obtained", user);
2070
2071                 ccname = pam_getenv(pamh, "KRB5CCNAME");
2072                 if (ccname == NULL) {
2073                         _pam_log_debug(pamh, ctrl, LOG_DEBUG, "user has no KRB5CCNAME environment");
2074                 }
2075
2076                 strncpy(request.data.logoff.user, user,
2077                         sizeof(request.data.logoff.user) - 1);
2078
2079                 if (ccname) {
2080                         strncpy(request.data.logoff.krb5ccname, ccname,
2081                                 sizeof(request.data.logoff.krb5ccname) - 1);
2082                 }
2083
2084                 pwd = getpwnam(user);
2085                 if (pwd == NULL) {
2086                         retval = PAM_USER_UNKNOWN;
2087                         goto out;
2088                 }
2089                 request.data.logoff.uid = pwd->pw_uid;
2090
2091                 request.flags = WBFLAG_PAM_KRB5 | WBFLAG_PAM_CONTACT_TRUSTDOM;
2092
2093                 retval = pam_winbind_request_log(pamh, ctrl, WINBINDD_PAM_LOGOFF, &request, &response, user);
2094         }
2095
2096 out:
2097         if (d) {
2098                 iniparser_freedict(d);
2099         }
2100
2101         _PAM_LOG_FUNCTION_LEAVE("pam_sm_close_session", pamh, ctrl, retval);
2102         
2103         return retval;
2104 }
2105
2106 /**
2107  * evaluate whether we need to re-authenticate with kerberos after a password change
2108  * 
2109  * @param pamh PAM handle
2110  * @param ctrl PAM winbind options.
2111  * @param user The username
2112  *
2113  * @return boolean Returns True if required, False if not.
2114  */
2115
2116 static BOOL _pam_require_krb5_auth_after_chauthtok(pam_handle_t *pamh, int ctrl, const char *user)
2117 {
2118
2119         /* Make sure that we only do this if 
2120          * a) the chauthtok got initiated during a logon attempt (authenticate->acct_mgmt->chauthtok)
2121          * b) any later password change via the "passwd" command if done by the user itself 
2122          */
2123                 
2124         char *new_authtok_reqd_during_auth = NULL;
2125         struct passwd *pwd = NULL;
2126
2127         if (!(ctrl & WINBIND_KRB5_AUTH)) {
2128                 return False;
2129         }
2130
2131         _pam_get_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH, &new_authtok_reqd_during_auth);
2132         pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH, NULL, NULL);
2133
2134         if (new_authtok_reqd_during_auth) {
2135                 return True;
2136         }
2137
2138         pwd = getpwnam(user);
2139         if (!pwd) {
2140                 return False;
2141         }
2142
2143         if (getuid() == pwd->pw_uid) {
2144                 return True;
2145         }
2146
2147         return False;
2148 }
2149
2150
2151 PAM_EXTERN 
2152 int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
2153                      int argc, const char **argv)
2154 {
2155         unsigned int lctrl;
2156         int ret;
2157         unsigned int ctrl;
2158
2159         /* <DO NOT free() THESE> */
2160         const char *user;
2161         char *pass_old, *pass_new;
2162         /* </DO NOT free() THESE> */
2163
2164         char *Announce;
2165         
2166         int retry = 0;
2167         dictionary *d = NULL;
2168         char *username_ret = NULL;
2169         struct winbindd_response response;
2170
2171         ZERO_STRUCT(response);
2172
2173         ctrl = _pam_parse(pamh, flags, argc, argv, &d);
2174         if (ctrl == -1) {
2175                 ret = PAM_SYSTEM_ERR;
2176                 goto out;
2177         }
2178
2179         _PAM_LOG_FUNCTION_ENTER("pam_sm_chauthtok", pamh, ctrl, flags);
2180
2181         /* clearing offline bit for the auth in the password change */
2182         ctrl &= ~WINBIND_CACHED_LOGIN;
2183
2184         /*
2185          * First get the name of a user
2186          */
2187         ret = pam_get_user(pamh, &user, "Username: ");
2188         if (ret) {
2189                 _pam_log(pamh, ctrl, LOG_ERR,
2190                          "password - could not identify user");
2191                 goto out;
2192         }
2193
2194         if (user == NULL) {
2195                 _pam_log(pamh, ctrl, LOG_ERR, "username was NULL!");
2196                 ret = PAM_USER_UNKNOWN;
2197                 goto out;
2198         }
2199
2200         _pam_log_debug(pamh, ctrl, LOG_DEBUG, "username [%s] obtained", user);
2201
2202         /* check if this is really a user in winbindd, not only in NSS */
2203         ret = valid_user(pamh, ctrl, user);
2204         switch (ret) {
2205                 case 1:
2206                         ret = PAM_USER_UNKNOWN;
2207                         goto out;
2208                 case -1:
2209                         ret = PAM_SYSTEM_ERR;
2210                         goto out;
2211                 default:
2212                         break;
2213         }
2214                 
2215         /*
2216          * obtain and verify the current password (OLDAUTHTOK) for
2217          * the user.
2218          */
2219
2220         if (flags & PAM_PRELIM_CHECK) {
2221                 time_t pwdlastset_prelim = 0;
2222                 
2223                 /* instruct user what is happening */
2224 #define greeting "Changing password for "
2225                 Announce = (char *) malloc(sizeof(greeting) + strlen(user));
2226                 if (Announce == NULL) {
2227                         _pam_log(pamh, ctrl, LOG_CRIT, "password - out of memory");
2228                         ret = PAM_BUF_ERR;
2229                         goto out;
2230                 }
2231                 (void) strcpy(Announce, greeting);
2232                 (void) strcpy(Announce + sizeof(greeting) - 1, user);
2233 #undef greeting
2234                 
2235                 lctrl = ctrl | WINBIND__OLD_PASSWORD;
2236                 ret = _winbind_read_password(pamh, lctrl,
2237                                                 Announce,
2238                                                 "(current) NT password: ",
2239                                                 NULL,
2240                                                 (const char **) &pass_old);
2241                 if (ret != PAM_SUCCESS) {
2242                         _pam_log(pamh, ctrl, LOG_NOTICE, "password - (old) token not obtained");
2243                         goto out;
2244                 }
2245
2246                 /* verify that this is the password for this user */
2247                 
2248                 ret = winbind_auth_request(pamh, ctrl, user, pass_old,
2249                                            NULL, NULL, 0, &response,
2250                                            &pwdlastset_prelim, NULL);
2251
2252                 if (ret != PAM_ACCT_EXPIRED && 
2253                     ret != PAM_AUTHTOK_EXPIRED &&
2254                     ret != PAM_NEW_AUTHTOK_REQD &&
2255                     ret != PAM_SUCCESS) {
2256                         pass_old = NULL;
2257                         goto out;
2258                 }
2259                 
2260                 pam_set_data(pamh, PAM_WINBIND_PWD_LAST_SET, (void *)pwdlastset_prelim, NULL);
2261
2262                 ret = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
2263                 pass_old = NULL;
2264                 if (ret != PAM_SUCCESS) {
2265                         _pam_log(pamh, ctrl, LOG_CRIT, "failed to set PAM_OLDAUTHTOK");
2266                 }
2267         } else if (flags & PAM_UPDATE_AUTHTOK) {
2268         
2269                 time_t pwdlastset_update = 0;
2270                 
2271                 /*
2272                  * obtain the proposed password
2273                  */
2274                 
2275                 /*
2276                  * get the old token back. 
2277                  */
2278                 
2279                 ret = _pam_get_item(pamh, PAM_OLDAUTHTOK, &pass_old);
2280                 
2281                 if (ret != PAM_SUCCESS) {
2282                         _pam_log(pamh, ctrl, LOG_NOTICE, "user not authenticated");
2283                         goto out;
2284                 }
2285                 
2286                 lctrl = ctrl & ~WINBIND_TRY_FIRST_PASS_ARG;
2287                 
2288                 if (on(WINBIND_USE_AUTHTOK_ARG, lctrl)) {
2289                         lctrl |= WINBIND_USE_FIRST_PASS_ARG;
2290                 }
2291                 retry = 0;
2292                 ret = PAM_AUTHTOK_ERR;
2293                 while ((ret != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
2294                         /*
2295                          * use_authtok is to force the use of a previously entered
2296                          * password -- needed for pluggable password strength checking
2297                          */
2298                         
2299                         ret = _winbind_read_password(pamh, lctrl,
2300                                                         NULL,
2301                                                         "Enter new NT password: ",
2302                                                         "Retype new NT password: ",
2303                                                         (const char **) &pass_new);
2304                         
2305                         if (ret != PAM_SUCCESS) {
2306                                 _pam_log_debug(pamh, ctrl, LOG_ALERT
2307                                          ,"password - new password not obtained");
2308                                 pass_old = NULL;/* tidy up */
2309                                 goto out;
2310                         }
2311
2312                         /*
2313                          * At this point we know who the user is and what they
2314                          * propose as their new password. Verify that the new
2315                          * password is acceptable.
2316                          */
2317                         
2318                         if (pass_new[0] == '\0') {/* "\0" password = NULL */
2319                                 pass_new = NULL;
2320                         }
2321                 }
2322                 
2323                 /*
2324                  * By reaching here we have approved the passwords and must now
2325                  * rebuild the password database file.
2326                  */
2327                 _pam_get_data( pamh, PAM_WINBIND_PWD_LAST_SET,
2328                                &pwdlastset_update);
2329
2330                 ret = winbind_chauthtok_request(pamh, ctrl, user, pass_old, pass_new, pwdlastset_update);
2331                 if (ret) {
2332                         _pam_overwrite(pass_new);
2333                         _pam_overwrite(pass_old);
2334                         pass_old = pass_new = NULL;
2335                         goto out;
2336                 }
2337
2338                 if (_pam_require_krb5_auth_after_chauthtok(pamh, ctrl, user)) {
2339
2340                         const char *member = get_member_from_config(pamh, argc, argv, ctrl, d);
2341                         const char *cctype = get_krb5_cc_type_from_config(pamh, argc, argv, ctrl, d);
2342                         const int warn_pwd_expire =
2343                          get_warn_pwd_expire_from_config(pamh, argc, argv, ctrl,
2344                                                          d);
2345
2346                         ret = winbind_auth_request(pamh, ctrl, user, pass_new,
2347                                                    member, cctype, 0, &response,
2348                                                    NULL, &username_ret);
2349                         _pam_overwrite(pass_new);
2350                         _pam_overwrite(pass_old);
2351                         pass_old = pass_new = NULL;
2352
2353                         if (ret == PAM_SUCCESS) {
2354                         
2355                                 /* warn a user if the password is about to expire soon */
2356                                 _pam_warn_password_expiry(pamh, ctrl, &response,
2357                                                           warn_pwd_expire , NULL);
2358
2359                                 /* set some info3 info for other modules in the stack */
2360                                 _pam_set_data_info3(pamh, ctrl, &response);
2361
2362                                 /* put krb5ccname into env */
2363                                 _pam_setup_krb5_env(pamh, ctrl, response.data.auth.krb5ccname);
2364
2365                                 if (username_ret) {
2366                                         pam_set_item (pamh, PAM_USER, username_ret);
2367                                         _pam_log_debug(pamh, ctrl, LOG_INFO, "Returned user was '%s'", username_ret);
2368                                         free(username_ret);
2369                                 }
2370                         }
2371
2372                         goto out;
2373                 }
2374         } else {
2375                 ret = PAM_SERVICE_ERR;
2376         }
2377
2378 out:
2379         if (d) {
2380                 iniparser_freedict(d);
2381         }
2382
2383         /* Deal with offline errors. */
2384         PAM_WB_REMARK_CHECK_RESPONSE(pamh, ctrl, response, "NT_STATUS_NO_LOGON_SERVERS");
2385         PAM_WB_REMARK_CHECK_RESPONSE(pamh, ctrl, response, "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
2386         PAM_WB_REMARK_CHECK_RESPONSE(pamh, ctrl, response, "NT_STATUS_ACCESS_DENIED");
2387
2388         _PAM_LOG_FUNCTION_LEAVE("pam_sm_chauthtok", pamh, ctrl, ret);
2389         
2390         return ret;
2391 }
2392
2393 #ifdef PAM_STATIC
2394
2395 /* static module data */
2396
2397 struct pam_module _pam_winbind_modstruct = {
2398         MODULE_NAME,
2399         pam_sm_authenticate,
2400         pam_sm_setcred,
2401         pam_sm_acct_mgmt,
2402         pam_sm_open_session,
2403         pam_sm_close_session,
2404         pam_sm_chauthtok
2405 };
2406
2407 #endif
2408
2409 /*
2410  * Copyright (c) Andrew Tridgell  <tridge@samba.org>   2000
2411  * Copyright (c) Tim Potter       <tpot@samba.org>     2000
2412  * Copyright (c) Andrew Bartlettt <abartlet@samba.org> 2002
2413  * Copyright (c) Guenther Deschner <gd@samba.org>      2005-2007
2414  * Copyright (c) Jan Rêkorajski 1999.
2415  * Copyright (c) Andrew G. Morgan 1996-8.
2416  * Copyright (c) Alex O. Yuriev, 1996.
2417  * Copyright (c) Cristian Gafton 1996.
2418  * Copyright (C) Elliot Lee <sopwith@redhat.com> 1996, Red Hat Software. 
2419  *
2420  * Redistribution and use in source and binary forms, with or without
2421  * modification, are permitted provided that the following conditions
2422  * are met:
2423  * 1. Redistributions of source code must retain the above copyright
2424  *    notice, and the entire permission notice in its entirety,
2425  *    including the disclaimer of warranties.
2426  * 2. Redistributions in binary form must reproduce the above copyright
2427  *    notice, this list of conditions and the following disclaimer in the
2428  *    documentation and/or other materials provided with the distribution.
2429  * 3. The name of the author may not be used to endorse or promote
2430  *    products derived from this software without specific prior
2431  *    written permission.
2432  *
2433  * ALTERNATIVELY, this product may be distributed under the terms of
2434  * the GNU Public License, in which case the provisions of the GPL are
2435  * required INSTEAD OF the above restrictions.  (This clause is
2436  * necessary due to a potential bad interaction between the GPL and
2437  * the restrictions contained in a BSD-style copyright.)
2438  *
2439  * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
2440  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2441  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2442  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
2443  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2444  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2445  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2446  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
2447  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2448  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2449  * OF THE POSSIBILITY OF SUCH DAMAGE.
2450  */