File Coverage

blib/lib/Authen/SimplePam.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Authen::SimplePam;
2              
3 1     1   9885 use Authen::PAM '0.13' ;
  0            
  0            
4              
5             use strict;
6             use warnings;
7              
8             our $VERSION = '0.1.24';
9             our $DEBUG = 0;
10              
11             #------------------------------------------------------------
12             #sometimes, we need to know what pam
13             #really wants.
14             #These lists pam's hardcoded messages.
15             #Different modules might add new messages,
16             #in this case, we need to expand these
17             #This might be the case even for
18             #internationalization
19              
20             #known messages and meanings.
21             #0 => asking for the current password
22             #1 => asking for the new password
23             #2 => askng for the new password, but as a confirmation
24              
25             #messages to ask for the current password.
26             our $PAM_MESSAGES = {
27             "(current) UNIX password: " => 0,
28             "New UNIX password: " => 1,
29             "Retype new UNIX password: " => 2,
30             "Password: " => 0,
31             };
32              
33             our $PAM_ERROR_MESSAGES = {
34             "it's WAY too short" => 1,
35             "it is too short" => 2,
36             "it does not contain enough DIFFERENT characters" => 3,
37             "it is too simplistic/systematic" => 4,
38             "is too similar to the old one" => 5,
39             "is too simple" => 6,
40             "Password unchanged" => 7,
41             };
42              
43             #==============================================================
44              
45             #---------------------------------------------------
46             # PAM_CONSTANTS
47             #
48             # These PAM Constants are not
49             # defined by Authen::PAM (at least 0.13),
50             # so we define them here.
51             # These values were taken from a linux system with
52             # pam 0.75 from /usr/include/security/_pam_types.h
53             # note that pam is highly patched so, this might
54             # be different in your system
55              
56             sub PAM_BINARY_PROMPT { return 7 }
57             sub _PAM_AUTHTOK_RECOVER_ERR { return 21 }
58              
59             #
60             #============================
61              
62              
63             #OO interface
64             sub new {
65             my ($proto, %args) = @_;
66             my $class = ref $proto || $proto;
67              
68             my $username = _get_username();
69             my $obj ={
70             #have we used the old password?
71             used_old_password => 0,
72              
73             # our code if we get an error message
74             pam_error_message => undef,
75              
76             # if we get an error message from pam
77             # do we abort?
78             # 0 => no
79             # 1 => yes
80             _abort_on_error => 1,
81              
82             #conv has failed?
83             conv_failure => 0,
84              
85             #the pam error message
86             #valid only if conv_failure == 1
87             pam_error_message => undef,
88             #our error code for this message
89             error_code => undef,
90              
91             #data used to talk to pam
92             username => $username,
93             password => undef,
94             new_password => undef,
95             service => undef,
96             _call_type => undef,
97             _pam_result => undef,
98             _module_result => undef,
99             %args,
100             };
101              
102             bless ($obj, $class);
103             return $obj;
104             }
105              
106              
107             #sets abort_on_error
108             #or return its value
109             sub _abort_on_error {
110             my ($self, $abort) = @_ ;
111             $self->{_abort_on_error} = $abort
112             if (defined $abort);
113             return $self->{_abort_on_error};
114             }
115              
116              
117             #sets the username
118             sub username {
119             my ($self, $user) = @_;
120             $self->{username} = $user
121             if (defined $user);
122             return $self->{username};
123             }
124              
125             #alias for username
126             sub user {
127             return username(@_);
128             }
129              
130             sub name {
131             return username(@_);
132             }
133              
134             #sets the current password
135             sub current_password {
136             my ($self, $password) = @_;
137             $self->{password} = $password
138             if (defined $password);
139             return $self->{password};
140             }
141              
142             #password is an alias for current_password
143             sub password {
144             return current_password(@_);
145             }
146              
147             #same for old_password
148             sub old_password {
149             return current_password(@_);
150             }
151              
152             #sets the new password
153             sub new_password {
154             my ($self, $new_password) = @_;
155             $self->{new_password} = $new_password
156             if (defined $new_password);
157             return $self->{new_password};
158             }
159              
160             #sets the service to user
161             sub service {
162             my ($self, $service) = @_;
163             $self->{service} = $service
164             if (defined ($service));
165              
166             return $self->{service};
167             }
168              
169             #sets the type of coonvertion function to be used
170             sub _call_type {
171             my ($self, $call_type) = @_;
172             $self->{_call_type} = $call_type
173             if (defined $call_type);
174             return $self->{_call_type};
175             }
176              
177             sub pam_result {
178             my ($self) = @_;
179             return $self->{_pam_result};
180             }
181              
182             sub error_code {
183             my ($self) = @_;
184             return $self->{error_code};
185             }
186              
187             sub error_message {
188             my ($self) = @_;
189             return $self->{pam_error_message};
190             }
191              
192             sub auth_user {
193             my ($self, $user, $password, $service) = @_;
194             my ($pam, $pam_result);
195              
196             if (defined ($service))
197             {
198             $self->service($service);
199             }
200              
201             unless ($self->service)
202             {
203             $self->service('login');
204             }
205              
206             if (defined ($user))
207             {
208             $self->username($user);
209             }
210              
211             if (defined ($password))
212             {
213             $self->password($password);
214             }
215              
216             $self->_abort_on_error(1);
217             $self->_call_type("authenticate");
218              
219             $self->{conv_failure} = 0;
220             $pam = new Authen::PAM ($self->service,
221             $self->username,
222             sub {
223             return $self->_general_pam_conv ( @_ );
224             }
225             );
226              
227             # $pam should always return an object even if
228             # the information is wrong (e.g. service)
229             return 0
230             unless ref($pam);
231              
232             $pam_result = $pam->pam_authenticate();
233             $self->{_pam_result} = $pam_result;
234             print "DEBUG: RESULT is $pam_result\n" if $DEBUG;
235              
236             $self->{_module_result} = _pam2result($pam_result);
237             return $self->{_module_result};
238             }
239              
240             sub change_password {
241             my ($self, $user, $old_password, $new_password, $service) = @_;
242             my ($pam, $pam_result);
243              
244             if (defined ($service))
245             {
246             $self->service($service);
247             }
248              
249             unless ($self->service)
250             {
251             $self->service('passwd');
252             }
253              
254             if (defined ($user))
255             {
256             $self->username($user);
257             }
258              
259             if (defined ($old_password))
260             {
261             $self->password($old_password);
262             }
263              
264             if (defined ($new_password))
265             {
266             $self->new_password($new_password);
267             }
268              
269             unless ($self->service)
270             {
271             $self->service('passwd');
272             }
273              
274             $self->{used_old_password} = 0;
275             $self->_abort_on_error(0);
276             $self->_call_type("change_password");
277             $self->{conv_failure} = 0;
278              
279             print "DEBUG: change_password:\n" .
280             "username: " . $self->username . ", old password: " . $self->password if $DEBUG;
281             print " new password: " . $self->new_password if $DEBUG;
282             print " service: " . $self->service . "\n" if $DEBUG;
283              
284             $pam = new Authen::PAM ($self->service,
285             $self->username,
286             sub {
287             return $self->_general_pam_conv ( @_ );
288             }
289             );
290             return 0
291             unless ref($pam);
292              
293             $pam_result = $pam->pam_chauthtok();
294              
295             $self->{_pam_result} = $pam_result;
296              
297             print "DEBUG: RESULT is $pam_result\n" if $DEBUG;
298              
299             $self->{_module_result} = _pam2result($pam_result);
300             return $self->{_module_result};
301             }
302              
303             sub result2string {
304             my ($self, $result) = @_;
305             $result = $self->{_module_result}
306             unless (defined ($result));
307              
308             if ( $result == 0 ) { return "Authen::PAM error" }
309             elsif ( $result == 1 ) { return "success" }
310             elsif ( $result == 2 ) { return "failure" }
311             elsif ( $result == 3 ) { return "insuficient credentials" }
312             elsif ( $result == 4 ) { return "authentication information unavailable" }
313             elsif ( $result == 5 ) { return "user unknown" }
314             elsif ( $result == 6 ) { return "maximum tries" }
315             elsif ( $result == 7 ) { return "unknown error" }
316             elsif ( $result == 8 ) { return "authentication error" }
317             elsif ( $result == 9 ) { return "authentication information cannot be recovered" }
318             elsif ( $result == 10 ) { return "authentication locked busy" }
319             elsif ( $result == 11 ) { return "authentication aging disable" }
320             elsif ( $result == 12 ) { return "permission denied" }
321             elsif ( $result == 13 ) { return "try again" }
322             elsif ( $result == 14 ) { return "dlopen error" }
323             elsif ( $result == 15 ) { return "symbol not found" }
324             elsif ( $result == 16 ) { return "memory buffer error" }
325             elsif ( $result == 17 ) { return "the password should be changed" }
326             elsif ( $result == 18 ) { return "user account has expired" }
327             elsif ( $result == 19 ) { return "cannot make/remove an entry for the specified session" }
328             elsif ( $result == 20 ) { return "cannot retrieve users credentials" }
329             elsif ( $result == 21 ) { return "user credentials expired" }
330             elsif ( $result == 22 ) { return "no pam module specific data is present" }
331             elsif ( $result == 23 ) { return "conversation error" }
332             elsif ( $result == 24 ) { return "ignore underlying account module" }
333             elsif ( $result == 25 ) { return "critical error" }
334             elsif ( $result == 26 ) { return "user authentication has expired" }
335             elsif ( $result == 27 ) { return "pam module is unknown" }
336             elsif ( $result == 28 ) { return "bad item passed to pam" }
337             elsif ( $result == 29 ) { return "conversation function is event driven and data is not available yet" }
338             elsif ( $result == 30 ) { return "call this function again to complete authentication stack" }
339             elsif ( $result == 31 ) { return "error in service module" }
340             elsif ( $result == 32 ) { return "system error" }
341             elsif ( $result == 33 ) { return "failure setting user credential" }
342             else { return "invalid result number: $result" }
343             }
344              
345              
346             #returns the EUID that is running this module
347             sub _get_username {
348             #we use the EFECTIVE USER ID (EUID),
349             #not the REAL USER ID ( UID )
350             my $name = getpwuid($<);
351             return $name;
352             }
353              
354             #checks the meaning os a message
355             #Returns the state of a message:
356             #undef means a unknown message.
357             #0: old password
358             #1: new password (1st time)
359             #2: new password (2nsd time)
360             sub _check_msg {
361             my ($message) = @_;
362              
363             unless (defined ($PAM_MESSAGES->{$message})) {
364             warn __PACKAGE__ . " warning!\n";
365             warn "Unclassified message: '$message' .\n";
366             warn "Please contact the author at in order to improve SimplePam.\n";
367             warn "Version used: $VERSION .\n";
368             return undef;
369             }
370             return $PAM_MESSAGES->{$message};
371             }
372              
373              
374             #Converts a error message to its code.
375             sub _check_error_msg {
376             my ($message) = @_;
377              
378             $message =~ s/^BAD PASSWORD: //;
379              
380             unless (defined ($PAM_ERROR_MESSAGES->{$message})) {
381             warn __PACKAGE__ . " warning!\n";
382             warn "Unclassified error message: '$message' .\n";
383             warn "Please contact the author at in order to improve this module.\n";
384             warn "Version used: $VERSION .\n";
385             return undef;
386             }
387             return $PAM_ERROR_MESSAGES->{$message};
388             }
389              
390              
391             #converts pam result codes to
392             #our own result codes
393             #(source is _pam_types.h)
394             #attention, some PAM constants are commented out, because
395             #they are not present in Authen::PAM module
396             #latest tested version: 0.11
397              
398             sub _pam2result {
399             my ($pam_result) = @_;
400             my $result;
401              
402             if ($pam_result == PAM_SUCCESS ) { $result = 1; }
403             elsif ($pam_result == PAM_AUTH_ERR ) { $result = 2; }
404             elsif ($pam_result == PAM_CRED_INSUFFICIENT ) { $result = 3; }
405             elsif ($pam_result == PAM_AUTHINFO_UNAVAIL ) { $result = 4; }
406             elsif ($pam_result == PAM_USER_UNKNOWN ) { $result = 5; }
407             elsif ($pam_result == PAM_MAXTRIES ) { $result = 6; }
408             elsif ($pam_result == PAM_AUTHTOK_ERR ) { $result = 8; }
409             elsif ($pam_result == _PAM_AUTHTOK_RECOVER_ERR ) { $result = 9; }
410             elsif ($pam_result == PAM_AUTHTOK_LOCK_BUSY ) { $result = 10;}
411             elsif ($pam_result == PAM_AUTHTOK_DISABLE_AGING) { $result = 11;}
412             elsif ($pam_result == PAM_PERM_DENIED ) { $result = 12;}
413             elsif ($pam_result == PAM_TRY_AGAIN ) { $result = 13;}
414             elsif ($pam_result == PAM_OPEN_ERR ) { $result = 14;}
415             elsif ($pam_result == PAM_SYMBOL_ERR ) { $result = 15;}
416             elsif ($pam_result == PAM_BUF_ERR ) { $result = 16;}
417             elsif ($pam_result == PAM_NEW_AUTHTOK_REQD ) { $result = 17;}
418             elsif ($pam_result == PAM_ACCT_EXPIRED ) { $result = 18;}
419             elsif ($pam_result == PAM_SESSION_ERR ) { $result = 19;}
420             elsif ($pam_result == PAM_CRED_UNAVAIL ) { $result = 20;}
421             elsif ($pam_result == PAM_CRED_EXPIRED ) { $result = 21;}
422             elsif ($pam_result == PAM_NO_MODULE_DATA ) { $result = 22;}
423             elsif ($pam_result == PAM_CONV_ERR ) { $result = 23;}
424             elsif ($pam_result == PAM_IGNORE ) { $result = 24;}
425             elsif ($pam_result == PAM_ABORT ) { $result = 25;}
426             elsif ($pam_result == PAM_AUTHTOK_EXPIRED ) { $result = 26;}
427             elsif ($pam_result == PAM_MODULE_UNKNOWN ) { $result = 27;}
428             elsif ($pam_result == PAM_BAD_ITEM ) { $result = 28;}
429             elsif ($pam_result == PAM_CONV_AGAIN ) { $result = 29;}
430             elsif ($pam_result == PAM_INCOMPLETE ) { $result = 30;}
431             elsif ($pam_result == PAM_SERVICE_ERR ) { $result = 31;}
432             elsif ($pam_result == PAM_SYSTEM_ERR ) { $result = 32;}
433             elsif ($pam_result == PAM_CRED_ERR ) { $result = 33;}
434             else { $result = 7; }
435              
436             return $result;
437             }
438              
439              
440             sub _general_pam_conv {
441             my $self = shift;
442             my ($user, $old_password, $new_password);
443              
444             #determines if something failed.
445             my $failure = $self->{conv_failure};
446              
447             # call_type => The type of calling is this (required)
448             # types are:
449             # authenticate => authenticates the user,
450             # password and username required
451             # change_password => Changes the user password
452             # username, old_password and new_password required
453             # root_change_password => root is changing password
454             # username, new_password required
455             # username => The username to be used
456             # old_password => user's old password
457             # new_password => user's new password
458             # password => the user's password
459              
460             unless (defined($self->_call_type)) {
461             warn "\n\nATTENTION 0!!!!\n\n" . __PACKAGE__ . "::_general_pam_conv() called wrongly.\nSomething will break!\n\n";
462             return (PAM_CONV_ERR, "", PAM_CONV_ERR);
463             }
464              
465             #checks what we have here
466             if ($self->_call_type eq "authenticate") {
467             unless (
468             (defined ($self->username)) &&
469             (defined ($self->current_password))
470             ) {
471             warn "\n\nATTENTION 1!!!!\n\n" . __PACKAGE__ . "::_general_pam_conv() called wrongly.\nSomething will break!\n\n";
472             return (PAM_CONV_ERR, "", PAM_CONV_ERR);
473             }else{
474             $user = $self->username;
475             $old_password = $self->current_password;
476             }
477             }
478             elsif ($self->_call_type eq "change_password") {
479             unless (
480             (defined ($self->username)) &&
481             (defined ($self->password)) &&
482             (defined ($self->new_password))
483             ) {
484             warn "\n\nATTENTION!!!!\n\n" . __PACKAGE__ . "::_general_pam_conv() called wrongly.\nSomething will break!\n\n";
485             return (PAM_CONV_ERR, "", PAM_CONV_ERR);
486             }else {
487             $user = $self->username;
488             $old_password = $self->password;
489             $new_password = $self->new_password;
490             }
491             }
492             elsif ($self->_call_type eq "root_change_password") {
493             unless (
494             (defined ($self->username)) &&
495             (defined ($self->new_password))
496             ) {
497             warn "\n\nATTENTION!!!!\n\n" . __PACKAGE__ . "::_general_pam_conv() called wrongly.\nSomething will break!\n\n";
498             return (PAM_CONV_ERR, "", PAM_CONV_ERR);
499             } else {
500             $user = $self->username;
501             $new_password = $self->new_password;
502             }
503             }
504             else {
505             warn "\n\nATTENTION!!!!\n\n" . __PACKAGE__ . "::_general_pam_conv() called wrongly.\nSomething will break!\n\n";
506             return (PAM_CONV_ERR, "", PAM_CONV_ERR);
507             }
508              
509              
510             my @response;
511              
512             #state controls what to do:
513             # 0 => send old password
514             # 1 => send new password
515             # 2 => send new password (as a confirmation)
516             my $state = 0;
517              
518             #done controls what stage have we done already
519             #its function is to try to go blindly when
520             #something goes wrong
521             # 0 => nothing done yet.
522             # 1 => sent old password
523             # 2 => sent new password (once)
524             # 3 => sent new password (twice)
525             # 4 => sent new passowrd. This time we abort because there is
526             # something wrong.
527             my $done = 0;
528              
529             #pass counter
530             my $pass = 0;
531              
532             while ( @_ ) {
533             #pam_code is the type of action PAM is asking us to do.
534             my $pam_code = shift;
535             #pam_message is the prompt to show the user.
536             my $pam_message = shift;
537              
538             my $answer = "";
539             $pass++;
540              
541             print "\n\nDEBUG: pass: $pass\n" if $DEBUG;
542             print "DEBUG: code is $pam_code, PAM_MESSAGE is '$pam_message'\n" if $DEBUG;
543              
544             #we just continue if no failure has happen
545             unless ($failure)
546             {
547              
548             #Checks what type of code, pam replyed.
549             #PAM_PROMPT_ECHO_ON,usually is the user name
550             if ( $pam_code == PAM_PROMPT_ECHO_ON )
551             {
552             #note that right now there is no database of setences used by
553             #PAM_PROMPT_ECHO_ON
554             #so we always assume it wants the user name.
555             #also note that the username was already given during Authen::PAM::new
556              
557             print "DEBUG: PAM_PROMPT_ECHO_ON message '$pam_message'\n" if $DEBUG;
558             print "DEBUG: Sending the user name: $user\n" if $DEBUG;
559             $answer = $user;
560             }
561              
562             #PAM_PROMPT_ECHO_OFF usually is the new or old password.
563             elsif ($pam_code == PAM_PROMPT_ECHO_OFF )
564             {
565              
566             print "DEBUG: PAM_PROMPT_ECHO_OFF message '$pam_message'\n" if $DEBUG;
567              
568             #we try to verify what it wants accordinly with $pam_message
569              
570             if (defined ($state = _check_msg($pam_message)))
571             {
572             print "DEBUG: PAM_PROMPT_ECHO_OFF: state: $state\n" if $DEBUG;
573             #state == 0 is the old_password
574             if ($state == 0)
575             {
576             print "DEBUG: sending the old password.\n" if $DEBUG;
577             $answer = $old_password;
578             $self->{used_old_password} = 1;
579             $done = 1;
580             }
581              
582             #state == 1 or 2 is the new_password
583             elsif ($state == 1 || $state == 2)
584             {
585             print "DEBUG: sending the new pasword.\n" if $DEBUG;
586             $answer = $new_password;
587              
588             if (! $self->{used_old_password} && $done < 1)
589             {
590             print "DEBUG: The old password was not asked for (before)\n" if $DEBUG;
591             $done = 1;
592             }
593              
594             $done++;
595             }
596             else
597             {
598             #we got an unknown state
599             #if this happens it is our fault
600             warn __PACKAGE__ . ": You seen to have found a bug in _general_pam_conv().\n";
601             warn __PACKAGE__ . ": state is $state and this is invalid.\n";
602             warn __PACKAGE__ . ": Please fill a bug report to relate this.\n";
603             warn __PACKAGE__ . ": I will try to continue, but it might not work.\n";
604              
605             $answer = $new_password;
606              
607             if (! $self->{used_old_password} && $done < 1)
608             {
609             print "DEBUG: The old password was not asked for\n" if $DEBUG;
610             $done = 1;
611             }
612             $done++;
613             }
614             }
615             else
616             {
617             # $state not defined
618             # This means that we got an unknow message.
619             # guessing blindly
620             warn "Don't know what to do about '$pam_message' .\n";
621             print "DEBUG: 'done' guess flag is $done\n";
622              
623             if ($done == 0)
624             {
625             $answer = $old_password;
626             warn "Trying to give the OLD password.\n";
627             $done ++;
628             }
629             elsif ($done > 0 && $done < 4)
630             {
631             $answer = $new_password;
632              
633             $done++;
634             warn "trying to give the NEW password.\n";
635             }
636             else
637             {
638             warn "Giving up.\n";
639             }
640             }
641             print "DEBUG: end of state comparation \n" if $DEBUG;
642             }
643              
644             #PAM_ERROR_MSG is an error whichh we got.
645             elsif ($pam_code == PAM_ERROR_MSG)
646             {
647             # we got some kind of error.
648             my $error_message = _check_error_msg ($pam_message);
649              
650             #save the error messag
651             $self->{pam_error_message} = $pam_message;
652             $self->{error_code} = $error_message;
653              
654             if ($self->_abort_on_error)
655             {
656             print "DEBUG: PAM_ERROR_MSG Aborting\n" if $DEBUG;
657             #advise pam about the error
658             $self->{conv_failure} = 1;
659             $failure = 1;
660             #note that this will cause PAM_CONV_ERR to be returned to the pam_function
661             }
662             else
663             {
664             print "DEBUG: PAM_ERROR_MSG: Ignoring the error.\n" if $DEBUG;
665             }
666             }
667              
668             elsif ($pam_code == PAM_TEXT_INFO) {
669             #Pam sent a informative message
670             #for now this messages are hardcoded here
671             print "DEBUG: PAM_TEXT_INFO: $pam_message\n" if $DEBUG;
672              
673             if ($pam_message =~ /^Changing password for (.*)$/)
674             {
675             if (($1 ne $user) && ($self->_call_type eq "change_password"))
676             {
677             warn "Something bad is about to happen, I am trying to change";
678             warn " password for $user, howerver, the system expects $1\n";
679             }
680             else
681             {
682             print "DEBUG: PAM_TEXT_INFO: So far so good.\n" if $DEBUG;
683             }
684             }
685             }
686              
687             #PAM_RADIO_TYPE, multiple choose selection
688             #like (yes, no,maybe). Never seem this is use.
689             elsif ($pam_code == PAM_RADIO_TYPE)
690             {
691             #FIX-ME
692             #don't know how to deal with this
693             warn __PACKAGE__ . "::change_password::conv(): Got PAM_RADIO_TYPE.\n";
694             warn "Don't know what to do!\n";
695             warn "Please contact the module's author to explain him how did you got";
696             warn "this situation.\n";
697             }
698              
699             #PAM_BINARY_PROMPT is not commonly used
700             elsif ($pam_code == PAM_BINARY_PROMPT)
701             {
702             #FIX-ME
703             #don't know how to deal with this
704             warn __PACKAGE__ . "::change_password::conv(): Got PAM_BINARY_PROMPT.\n";
705             warn "Don't know what to do!\n";
706             warn "Please contact the module's author to explain him how did you got";
707             warn "this situation.\n";
708             }
709              
710             else
711             {
712             #got an unspecified PAM CODE
713             warn __PACKAGE__ . "::change_password::conv(): Got an unexpected PAM CODE: $pam_code.\n";
714             warn "Don't know what to do!\n";
715             }
716             push (@response, (PAM_SUCCESS, $answer));
717             }
718             else
719             {
720             push (@response, (PAM_CONV_ERR, ""));
721             }
722             }
723              
724             if ($failure)
725             {
726             push (@response, (PAM_CONV_ERR));
727             }
728             else
729             {
730             push (@response, (PAM_SUCCESS));
731             }
732              
733             return @response;
734             }
735              
736              
737             1;
738             __END__