This commit was manufactured by cvs2svn to create branch 'SAMBA_3_0'.
[samba.git] / source / pam_smbpass / support.c
1 /* Unix NT password database implementation, version 0.6.
2  *
3  * This program is free software; you can redistribute it and/or modify it under
4  * the terms of the GNU General Public License as published by the Free
5  * Software Foundation; either version 2 of the License, or (at your option)
6  * any later version.
7  *
8  * This program is distributed in the hope that it will be useful, but WITHOUT
9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
11  * more details.
12  *
13  * You should have received a copy of the GNU General Public License along with
14  * this program; if not, write to the Free Software Foundation, Inc., 675
15  * Mass Ave, Cambridge, MA 02139, USA.
16  */
17
18 #include "includes.h"
19 #include "general.h"
20
21 #include "support.h"
22
23
24 #define _pam_overwrite(x)        \
25 do {                             \
26      register char *__xx__;      \
27      if ((__xx__=(x)))           \
28           while (*__xx__)        \
29                *__xx__++ = '\0'; \
30 } while (0)
31
32 /*
33  * Don't just free it, forget it too.
34  */
35
36 #define _pam_drop(X) \
37 do {                 \
38     if (X) {         \
39         free(X);     \
40         X=NULL;      \
41     }                \
42 } while (0)
43
44 #define _pam_drop_reply(/* struct pam_response * */ reply, /* int */ replies) \
45 do {                                              \
46     int reply_i;                                  \
47                                                   \
48     for (reply_i=0; reply_i<replies; ++reply_i) { \
49         if (reply[reply_i].resp) {                \
50             _pam_overwrite(reply[reply_i].resp);  \
51             free(reply[reply_i].resp);            \
52         }                                         \
53     }                                             \
54     if (reply)                                    \
55         free(reply);                              \
56 } while (0)
57
58
59 int converse(pam_handle_t *, int, int, struct pam_message **,
60                          struct pam_response **);
61 int make_remark(pam_handle_t *, unsigned int, int, const char *);
62 void _cleanup(pam_handle_t *, void *, int);
63 char *_pam_delete(register char *);
64
65 /* syslogging function for errors and other information */
66
67 void _log_err( int err, const char *format, ... )
68 {
69     va_list args;
70
71     va_start( args, format );
72     openlog( "PAM_smbpass", LOG_CONS | LOG_PID, LOG_AUTH );
73     vsyslog( err, format, args );
74     va_end( args );
75     closelog();
76 }
77
78 /* this is a front-end for module-application conversations */
79
80 int converse( pam_handle_t * pamh, int ctrl, int nargs
81               , struct pam_message **message
82               , struct pam_response **response )
83 {
84         int retval;
85         struct pam_conv *conv;
86
87         retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
88         if (retval == PAM_SUCCESS) {
89
90                 retval = conv->conv(nargs, (const struct pam_message **) message
91                                                         ,response, conv->appdata_ptr);
92
93                 if (retval != PAM_SUCCESS && on(SMB_DEBUG, ctrl)) {
94                         _log_err(LOG_DEBUG, "conversation failure [%s]"
95                                          ,pam_strerror(pamh, retval));
96                 }
97         } else {
98                 _log_err(LOG_ERR, "couldn't obtain coversation function [%s]"
99                                  ,pam_strerror(pamh, retval));
100         }
101
102         return retval;                          /* propagate error status */
103 }
104
105 int make_remark( pam_handle_t * pamh, unsigned int ctrl
106                  , int type, const char *text )
107 {
108         if (off(SMB__QUIET, ctrl)) {
109                 struct pam_message *pmsg[1], msg[1];
110                 struct pam_response *resp;
111
112                 pmsg[0] = &msg[0];
113                 msg[0].msg = text;
114                 msg[0].msg_style = type;
115                 resp = NULL;
116
117                 return converse(pamh, ctrl, 1, pmsg, &resp);
118         }
119         return PAM_SUCCESS;
120 }
121
122
123 /* set the control flags for the SMB module. */
124
125 int set_ctrl( int flags, int argc, const char **argv )
126 {
127     int i = 0;
128     static pstring servicesf = CONFIGFILE;
129     const char *service_file = servicesf;
130     unsigned int ctrl;
131
132     ctrl = SMB_DEFAULTS;        /* the default selection of options */
133
134     /* set some flags manually */
135
136     /* A good, sane default (matches Samba's behavior). */
137     set( SMB__NONULL, ctrl );
138
139     if (flags & PAM_SILENT) {
140         set( SMB__QUIET, ctrl );
141     }
142
143     /* Run through the arguments once, looking for an alternate smb config
144        file location */
145     while (i < argc) {
146         int j;
147
148         for (j = 0; j < SMB_CTRLS_; ++j) {
149             if (smb_args[j].token
150                 && !strncmp(argv[i], smb_args[j].token, strlen(smb_args[j].token)))
151             {
152                 break;
153             }
154         }
155
156         if (j == SMB_CONF_FILE) {
157             service_file = argv[i] + 8;
158         }
159         i++;
160     }
161
162     /* Read some options from the Samba config. Can be overridden by
163        the PAM config. */
164     if(lp_load(service_file,True,False,False) == False) {
165         _log_err( LOG_ERR, "Error loading service file %s", service_file );
166     }
167
168     if (lp_null_passwords()) {
169         set( SMB__NULLOK, ctrl );
170     }
171
172     /* now parse the rest of the arguments to this module */
173
174     while (argc-- > 0) {
175         int j;
176
177         for (j = 0; j < SMB_CTRLS_; ++j) {
178             if (smb_args[j].token
179                 && !strncmp(*argv, smb_args[j].token, strlen(smb_args[j].token)))
180             {
181                 break;
182             }
183         }
184
185         if (j >= SMB_CTRLS_) {
186             _log_err( LOG_ERR, "unrecognized option [%s]", *argv );
187         } else {
188             ctrl &= smb_args[j].mask;   /* for turning things off */
189             ctrl |= smb_args[j].flag;   /* for turning things on  */
190         }
191
192         ++argv;                         /* step to next argument */
193     }
194
195     /* auditing is a more sensitive version of debug */
196
197     if (on( SMB_AUDIT, ctrl )) {
198         set( SMB_DEBUG, ctrl );
199     }
200     /* return the set of flags */
201
202     return ctrl;
203 }
204
205 /* use this to free strings. ESPECIALLY password strings */
206
207 char * _pam_delete( register char *xx )
208 {
209     _pam_overwrite( xx );
210     _pam_drop( xx );
211     return NULL;
212 }
213
214 void _cleanup( pam_handle_t * pamh, void *x, int error_status )
215 {
216     x = _pam_delete( (char *) x );
217 }
218
219 /* JHT
220  *
221  * Safe duplication of character strings. "Paranoid"; don't leave
222  * evidence of old token around for later stack analysis.
223  *
224  */
225 char * smbpXstrDup( const char *x )
226 {
227     register char *new = NULL;
228
229     if (x != NULL) {
230         register int i;
231
232         for (i = 0; x[i]; ++i); /* length of string */
233         if ((new = malloc(++i)) == NULL) {
234             i = 0;
235             _log_err( LOG_CRIT, "out of memory in smbpXstrDup" );
236         } else {
237             while (i-- > 0) {
238                 new[i] = x[i];
239             }
240         }
241         x = NULL;
242     }
243     return new;                 /* return the duplicate or NULL on error */
244 }
245
246 /* ************************************************************** *
247  * Useful non-trivial functions                                   *
248  * ************************************************************** */
249
250 void _cleanup_failures( pam_handle_t * pamh, void *fl, int err )
251 {
252     int quiet;
253     const char *service = NULL;
254     struct _pam_failed_auth *failure;
255
256 #ifdef PAM_DATA_SILENT
257     quiet = err & PAM_DATA_SILENT;      /* should we log something? */
258 #else
259     quiet = 0;
260 #endif
261 #ifdef PAM_DATA_REPLACE
262     err &= PAM_DATA_REPLACE;    /* are we just replacing data? */
263 #endif
264     failure = (struct _pam_failed_auth *) fl;
265
266     if (failure != NULL) {
267
268 #ifdef PAM_DATA_SILENT
269         if (!quiet && !err) {   /* under advisement from Sun,may go away */
270 #else
271         if (!quiet) {   /* under advisement from Sun,may go away */
272 #endif
273
274             /* log the number of authentication failures */
275             if (failure->count != 0) {
276                 pam_get_item( pamh, PAM_SERVICE, (const void **) &service );
277                 _log_err( LOG_NOTICE
278                           , "%d authentication %s "
279                             "from %s for service %s as %s(%d)"
280                           , failure->count
281                           , failure->count == 1 ? "failure" : "failures"
282                           , failure->agent
283                           , service == NULL ? "**unknown**" : service 
284                           , failure->user, failure->id );
285                 if (failure->count > SMB_MAX_RETRIES) {
286                     _log_err( LOG_ALERT
287                               , "service(%s) ignoring max retries; %d > %d"
288                               , service == NULL ? "**unknown**" : service
289                               , failure->count
290                               , SMB_MAX_RETRIES );
291                 }
292             }
293         }
294         _pam_delete( failure->agent );  /* tidy up */
295         _pam_delete( failure->user );   /* tidy up */
296         SAFE_FREE( failure );
297     }
298 }
299
300 int _smb_verify_password( pam_handle_t * pamh, SAM_ACCOUNT *sampass,
301                           const char *p, unsigned int ctrl )
302 {
303     uchar hash_pass[16];
304     uchar lm_pw[16];
305     uchar nt_pw[16];
306     int retval;
307     char *data_name;
308     const char *name;
309
310     if (!sampass)
311         return PAM_ABORT;
312
313     name = pdb_get_username(sampass);
314
315 #ifdef HAVE_PAM_FAIL_DELAY
316     if (off( SMB_NODELAY, ctrl )) {
317         (void) pam_fail_delay( pamh, 1000000 ); /* 1 sec delay for on failure */
318     }
319 #endif
320
321     if (!pdb_get_lanman_passwd(sampass))
322     {
323         _log_err( LOG_DEBUG, "user %s has null SMB password"
324                   , name );
325
326         if (off( SMB__NONULL, ctrl )
327             && (pdb_get_acct_ctrl(sampass) & ACB_PWNOTREQ))
328         { /* this means we've succeeded */
329             return PAM_SUCCESS;
330         } else {
331             const char *service;
332
333             pam_get_item( pamh, PAM_SERVICE, (const void **)&service );
334             _log_err( LOG_NOTICE
335                       , "failed auth request by %s for service %s as %s(%d)"
336                       , uidtoname( getuid() )
337                       , service ? service : "**unknown**", name
338                       , pdb_get_uid(sampass) );
339             return PAM_AUTH_ERR;
340         }
341     }
342
343     data_name = (char *) malloc( sizeof(FAIL_PREFIX) + strlen( name ));
344     if (data_name == NULL) {
345         _log_err( LOG_CRIT, "no memory for data-name" );
346     }
347     strncpy( data_name, FAIL_PREFIX, sizeof(FAIL_PREFIX) );
348     strncpy( data_name + sizeof(FAIL_PREFIX) - 1, name, strlen( name ) + 1 );
349
350     /* First we check whether we've been given the password in already
351        encrypted form. */
352     if (strlen( p ) == 16 || (strlen( p ) == 32
353          && pdb_gethexpwd( p, (char *) hash_pass ))) {
354
355         if (!memcmp( hash_pass, pdb_get_lanman_passwd(sampass), 16 )
356             || (pdb_get_nt_passwd(sampass)
357                 && !memcmp( hash_pass, pdb_get_nt_passwd(sampass), 16 )))
358         {
359             retval = PAM_SUCCESS;
360             if (data_name) {    /* reset failures */
361                 pam_set_data( pamh, data_name, NULL, _cleanup_failures );
362             }
363             _pam_delete( data_name );
364             memset( hash_pass, '\0', 16 );
365             return retval;
366         }
367     }
368
369     /*
370      * The password we were given wasn't an encrypted password, or it
371      * didn't match the one we have.  We encrypt the password now and try
372      * again.
373      */
374
375     nt_lm_owf_gen(p, nt_pw, lm_pw);
376
377     /* the moment of truth -- do we agree with the password? */
378
379     if (!memcmp( nt_pw, pdb_get_nt_passwd(sampass), 16 )) {
380
381         retval = PAM_SUCCESS;
382         if (data_name) {                /* reset failures */
383             pam_set_data(pamh, data_name, NULL, _cleanup_failures);
384         }
385     } else {
386
387         const char *service;
388
389         pam_get_item( pamh, PAM_SERVICE, (const void **)&service );
390
391         if (data_name != NULL) {
392             struct _pam_failed_auth *new = NULL;
393             const struct _pam_failed_auth *old = NULL;
394
395             /* get a failure recorder */
396
397             new = (struct _pam_failed_auth *)
398                       malloc( sizeof(struct _pam_failed_auth) );
399
400             if (new != NULL) {
401
402                 /* any previous failures for this user ? */
403                 pam_get_data(pamh, data_name, (const void **) &old);
404
405                 if (old != NULL) {
406                     new->count = old->count + 1;
407                     if (new->count >= SMB_MAX_RETRIES) {
408                         retval = PAM_MAXTRIES;
409                     }
410                 } else {
411                     _log_err( LOG_NOTICE
412                       , "failed auth request by %s for service %s as %s(%d)"
413                       , uidtoname( getuid() )
414                       , service ? service : "**unknown**", name
415                       , pdb_get_uid(sampass) );
416                     new->count = 1;
417                 }
418                 new->user = smbpXstrDup( name );
419                 new->id = pdb_get_uid(sampass);
420                 new->agent = smbpXstrDup( uidtoname( getuid() ) );
421                 pam_set_data( pamh, data_name, new, _cleanup_failures );
422
423             } else {
424                 _log_err( LOG_CRIT, "no memory for failure recorder" );
425                 _log_err( LOG_NOTICE
426                       , "failed auth request by %s for service %s as %s(%d)"
427                       , uidtoname( getuid() )
428                       , service ? service : "**unknown**", name
429                       , pdb_get_uid(sampass) );
430             }
431         } else {
432             _log_err( LOG_NOTICE
433                       , "failed auth request by %s for service %s as %s(%d)"
434                       , uidtoname( getuid() )
435                       , service ? service : "**unknown**", name
436                       , pdb_get_uid(sampass) );
437             retval = PAM_AUTH_ERR;
438         }
439     }
440
441     _pam_delete( data_name );
442     
443     return retval;
444 }
445
446
447 /*
448  * _smb_blankpasswd() is a quick check for a blank password
449  *
450  * returns TRUE if user does not have a password
451  * - to avoid prompting for one in such cases (CG)
452  */
453
454 int _smb_blankpasswd( unsigned int ctrl, SAM_ACCOUNT *sampass )
455 {
456         int retval;
457
458         /*
459          * This function does not have to be too smart if something goes
460          * wrong, return FALSE and let this case to be treated somewhere
461          * else (CG)
462          */
463
464         if (on( SMB__NONULL, ctrl ))
465                 return 0;               /* will fail but don't let on yet */
466
467         if (pdb_get_lanman_passwd(sampass) == NULL)
468                 retval = 1;
469         else
470                 retval = 0;
471
472         return retval;
473 }
474
475 /*
476  * obtain a password from the user
477  */
478
479 int _smb_read_password( pam_handle_t * pamh, unsigned int ctrl,
480                         const char *comment, const char *prompt1,
481                         const char *prompt2, const char *data_name, char **pass )
482 {
483     int authtok_flag;
484     int retval;
485     const char *item = NULL;
486     char *token;
487
488     struct pam_message msg[3], *pmsg[3];
489     struct pam_response *resp;
490     int i, expect;
491
492
493     /* make sure nothing inappropriate gets returned */
494
495     *pass = token = NULL;
496
497     /* which authentication token are we getting? */
498
499     authtok_flag = on(SMB__OLD_PASSWD, ctrl) ? PAM_OLDAUTHTOK : PAM_AUTHTOK;
500
501     /* should we obtain the password from a PAM item ? */
502
503     if (on(SMB_TRY_FIRST_PASS, ctrl) || on(SMB_USE_FIRST_PASS, ctrl)) {
504         retval = pam_get_item( pamh, authtok_flag, (const void **) &item );
505         if (retval != PAM_SUCCESS) {
506             /* very strange. */
507             _log_err( LOG_ALERT
508                       , "pam_get_item returned error to smb_read_password" );
509             return retval;
510         } else if (item != NULL) {      /* we have a password! */
511             *pass = item;
512             item = NULL;
513             return PAM_SUCCESS;
514         } else if (on( SMB_USE_FIRST_PASS, ctrl )) {
515             return PAM_AUTHTOK_RECOVER_ERR;             /* didn't work */
516         } else if (on( SMB_USE_AUTHTOK, ctrl )
517                    && off( SMB__OLD_PASSWD, ctrl ))
518         {
519             return PAM_AUTHTOK_RECOVER_ERR;
520         }
521     }
522
523     /*
524      * getting here implies we will have to get the password from the
525      * user directly.
526      */
527
528     /* prepare to converse */
529     if (comment != NULL && off(SMB__QUIET, ctrl)) {
530         pmsg[0] = &msg[0];
531         msg[0].msg_style = PAM_TEXT_INFO;
532         msg[0].msg = comment;
533         i = 1;
534     } else {
535         i = 0;
536     }
537
538     pmsg[i] = &msg[i];
539     msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
540     msg[i++].msg = prompt1;
541
542     if (prompt2 != NULL) {
543         pmsg[i] = &msg[i];
544         msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
545         msg[i++].msg = prompt2;
546         expect = 2;
547     } else
548         expect = 1;
549
550     resp = NULL;
551
552     retval = converse( pamh, ctrl, i, pmsg, &resp );
553
554     if (resp != NULL) {
555         int j = comment ? 1 : 0;
556         /* interpret the response */
557
558         if (retval == PAM_SUCCESS) {    /* a good conversation */
559
560             token = smbpXstrDup(resp[j++].resp);
561             if (token != NULL) {
562                 if (expect == 2) {
563                     /* verify that password entered correctly */
564                     if (!resp[j].resp || strcmp( token, resp[j].resp )) {
565                         _pam_delete( token );
566                         retval = PAM_AUTHTOK_RECOVER_ERR;
567                         make_remark( pamh, ctrl, PAM_ERROR_MSG
568                                      , MISTYPED_PASS );
569                     }
570                 }
571             } else {
572                 _log_err(LOG_NOTICE, "could not recover authentication token");
573             }
574         }
575
576         /* tidy up */
577         _pam_drop_reply( resp, expect );
578
579     } else {
580         retval = (retval == PAM_SUCCESS) ? PAM_AUTHTOK_RECOVER_ERR : retval;
581     }
582
583     if (retval != PAM_SUCCESS) {
584         if (on( SMB_DEBUG, ctrl ))
585             _log_err( LOG_DEBUG, "unable to obtain a password" );
586         return retval;
587     }
588     /* 'token' is the entered password */
589
590     if (off( SMB_NOT_SET_PASS, ctrl )) {
591
592         /* we store this password as an item */
593
594         retval = pam_set_item( pamh, authtok_flag, (const void *)token );
595         _pam_delete( token );           /* clean it up */
596         if (retval != PAM_SUCCESS
597             || (retval = pam_get_item( pamh, authtok_flag
598                             ,(const void **)&item )) != PAM_SUCCESS)
599         {
600             _log_err( LOG_CRIT, "error manipulating password" );
601             return retval;
602         }
603     } else {
604         /*
605          * then store it as data specific to this module. pam_end()
606          * will arrange to clean it up.
607          */
608
609         retval = pam_set_data( pamh, data_name, (void *) token, _cleanup );
610         if (retval != PAM_SUCCESS
611             || (retval = pam_get_data( pamh, data_name, (const void **)&item ))
612                              != PAM_SUCCESS)
613         {
614             _log_err( LOG_CRIT, "error manipulating password data [%s]"
615                       , pam_strerror( pamh, retval ));
616             _pam_delete( token );
617             item = NULL;
618             return retval;
619         }
620         token = NULL;                   /* break link to password */
621     }
622
623     *pass = item;
624     item = NULL;                        /* break link to password */
625
626     return PAM_SUCCESS;
627 }
628
629 int _pam_smb_approve_pass(pam_handle_t * pamh,
630                 unsigned int ctrl,
631                 const char *pass_old,
632                 const char *pass_new )
633 {
634
635     /* Further checks should be handled through module stacking. -SRL */
636     if (pass_new == NULL || (pass_old && !strcmp( pass_old, pass_new )))
637     {
638         if (on(SMB_DEBUG, ctrl)) {
639             _log_err( LOG_DEBUG,
640                       "passwd: bad authentication token (null or unchanged)" );
641         }
642         make_remark( pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ?
643                                 "No password supplied" : "Password unchanged" );
644         return PAM_AUTHTOK_ERR;
645     }
646
647     return PAM_SUCCESS;
648 }