4 # pam_exec.so [debug] [expose_authtok] [seteuid] [quiet] [log=file] /usr/local/libexec/pam_multipasswd [pam_service]
6 # /etc/security/pam_multipasswd.conf
7 # /etc/security/pam_multipasswd.hook (script)
8 # Environment variables:
9 # PAM_RHOST, PAM_RUSER, PAM_SERVICE, PAM_TTY, PAM_USER, PAM_TYPE, PROGNAME
11 # pure-ftpd auth required pam_exec_wrap.so expose_authtok /usr/local/libexec/pam_multipasswd
12 # Hook script outputs lines like:
13 # set <variable> <value>
14 # Variables recognized: shadow_home rhost tty pam_servicenick
15 # So you can override these internal variables dynamically call-by-call
20 use Authen
::Simple
::PAM
;
23 SUCCESS
=>0, # Successful function return
24 OPEN_ERR
=>1, # dlopen() failure when dynamically
25 # loading a service module
26 SYMBOL_ERR
=>2, # Symbol not found
27 SERVICE_ERR
=>3, # Error in service module
28 SYSTEM_ERR
=>4, # System error
29 BUF_ERR
=>5, # Memory buffer error
30 PERM_DENIED
=>6, # Permission denied
31 AUTH_ERR
=>7, # Authentication failure
32 CRED_INSUFFICIENT
=>8, # Can not access authentication data
33 # due to insufficient credentials
34 AUTHINFO_UNAVAIL
=>9, # Underlying authentication service
35 # can not retrieve authentication information
36 USER_UNKNOWN
=>10, # User not known to the underlying
37 # authenticaiton module
38 MAXTRIES
=>11, # An authentication service has
39 # maintained a retry count which has
40 # been reached. No further retries
42 NEW_AUTHTOK_REQD
=>12, # New authentication token required.
43 # This is normally returned if the
44 # machine security policies require
45 # that the password should be changed
46 # beccause the password is NULL or it
48 ACCT_EXPIRED
=>13, # User account has expired
49 SESSION_ERR
=>14, # Can not make/remove an entry for
50 # the specified session
51 CRED_UNAVAIL
=>15, # Underlying authentication service
52 # can not retrieve user credentials unavailable
53 CRED_EXPIRED
=>16, # User credentials expired
54 CRED_ERR
=>17, # Failure setting user credentials
55 NO_MODULE_DATA
=>18, # No module specific data is present
56 CONV_ERR
=>19, # Conversation error
57 AUTHTOK_ERR
=>20, # Authentication token manipulation error
58 AUTHTOK_RECOVERY_ERR
=>21, # Authentication information cannot be recovered
59 AUTHTOK_LOCK_BUSY
=>22, # Authentication token lock busy
60 AUTHTOK_DISABLE_AGING
=>23, # Authentication token aging disabled
61 TRY_AGAIN
=>24, # Preliminary check by password service
62 IGNORE
=>25, # Ignore underlying account module
63 # regardless of whether the control
64 # flag is required, optional, or sufficient
65 ABORT
=>26, # Critical error (?module fail now request)
66 AUTHTOK_EXPIRED
=>27, # user's authentication token has expired
67 MODULE_UNKNOWN
=>28, # module is not known
68 BAD_ITEM
=>29, # Bad item passed to pam_*_item()
69 CONV_AGAIN
=>30, # conversation function is event driven and data is not available yet
70 INCOMPLETE
=>31, # please call this function again to complete authentication stack.
71 # Before calling again, verify that conversation is completed
73 %PAM_STATUS_TEXT = reverse %PAM_STATUS;
74 use Sys
::Syslog qw
/openlog syslog/;
75 use List
::MoreUtils qw
/any/;
81 my $pam_result = shift;
82 openlog
$ENV{'PROGNAME'}, '', Sys
::Syslog
::LOG_AUTH
;
83 syslog Sys
::Syslog
::LOG_INFO
, sprintf "pam_multipasswd(%s:%s): code %d, %s, logname=%s uid=%d euid=%d tty=%s ruser=%s rhost=%s user=%s shadow_home=%s",
84 $pam_service, $ENV{'PAM_TYPE'}, $pam_result, ($PAM_STATUS_TEXT{$pam_result} || "unknown status code"), $ENV{'LOGNAME'}||(getpwuid$<)[0], $<, $>, $ENV{'PAM_TTY'}, $ENV{'PAM_RUSER'}, $ENV{'PAM_RHOST'}, $ENV{'PAM_USER'}, $shadow_home;
88 sub check_password
($$$)
95 if(substr($stored, 0, 1) eq '$')
97 my ($none, $hashtype, $salt, $hash) = split(/\$/, $stored, 4);
98 if(grep {$hashtype eq $_} qw
/1 5 6/)
100 # md5, sha-256 and sha-512 formats respectively
101 $cmpstr = crypt($given, '$'.$hashtype.'$'.$salt);
103 elsif($stored_type == 'apr1')
105 $cmpstr = apache_md5_crypt
($given, $salt);
108 elsif(substr($stored, 0, 1) eq '@')
110 my $pam_svc = substr($stored, 1);
111 my $pam = Authen
::Simple
::PAM
->new( service
=> $pam_svc );
112 if($pam->authenticate($user, $given))
114 return $PAM_STATUS{SUCCESS
};
117 elsif(substr($stored, 0, 1) eq '=')
119 $cmpstr = '=' . encode_base64
($given, '');
121 elsif(length $stored == 13)
123 # crypt(3) format (password length max 8 char)
124 my $salt = substr($stored, 0, 2);
125 $cmpstr = crypt($given, $salt);
127 elsif($stored eq '*')
129 return $PAM_STATUS{SUCCESS
};
133 # assuming $stored is a cleartext password
137 if(defined $cmpstr and $stored eq $cmpstr)
139 return $PAM_STATUS{SUCCESS
};
141 return $PAM_STATUS{PERM_DENIED
};
144 sub in_subnet_ip4
($$)
146 my ($cidr, $ip) = @_;
147 # if no netmask given then IP must equal to CIDR
148 return ($cidr eq $ip) unless $cidr =~ /^(.*)\/(.*)$/;
149 my ($subnet, $mask) = ($1, $2);
150 # the /0 mask matches to the whole world
151 return 1 if $mask == 0;
152 # mask IPv4 addresses
153 return (((unpack('N', pack('C4', split(/\./, $ip))) ^ unpack('N', pack('C4', split(/\./, $subnet)))) & (0xFFFFFFFF << (32-$mask))) == 0);
158 $configfile = "/etc/security/pam_multipasswd.conf";
159 $pam_service = defined $ARGV[0] ?
$ARGV[0] : $ENV{'PAM_SERVICE'};
160 $pam_servicenick = $pam_service;
162 pam_return
($PAM_STATUS{IGNORE
}) if $ENV{'PAM_TYPE'} ne 'auth';
164 $givenpass = <STDIN
>;
165 $givenpass =~ s/[\r\n\x00]*$//;
167 $shadow_home = (getpwnam $ENV{'PAM_USER'})[7];
169 open my $fh, '<', $configfile;
173 if(/^\s*service\s*map\s*(\S+)\s+(\S+)\s*$/)
175 if($pam_service eq $1)
177 $pam_servicenick = $2;
182 print STDERR
"unknown config in $configfile line $.\n";
187 my $rhost = defined $ENV{'PAM_RHOST'} ?
$ENV{'PAM_RHOST'} : "unknown";
188 my $tty = defined $ENV{'PAM_TTY'} ?
$ENV{'PAM_TTY'} : "unknown";
191 open my $ph, '-|', "/etc/security/pam_multipasswd.hook";
194 if(my ($variable, $value) = /^set (shadow_home|rhost|tty|pam_servicenick) (.*)$/)
196 eval "\$$variable = \$value";
200 print STDERR
"pam_multipasswd: unrecognized hook script output: $_";
205 pam_return
($PAM_STATUS{USER_UNKNOWN
}) if not defined $shadow_home;
208 "$pam_servicenick.d/rhost/$rhost",
209 "$pam_servicenick.d/tty/$tty",
214 for my $pwdfile (map {"$shadow_home/.shadow.d/$_"} @precedence)
218 if(open my $fh, '<', $pwdfile)
224 my ($uuser, $upass, $ucidr) = split /:/;
225 if($uuser eq '*' or $uuser eq $ENV{'PAM_USER'})
227 if(!defined $ucidr or any
{in_subnet_ip4
($_, $rhost)} split /,/, $ucidr)
229 $pam_result = check_password
($uuser, $givenpass, $upass);
230 if($pam_result eq $PAM_STATUS{SUCCESS
})
238 if(defined $pam_result)
245 print STDERR
"$pwdfile: $!\n";
246 $pam_result = $PAM_STATUS{SYSTEM_ERR
};
253 $pam_result = $PAM_STATUS{AUTHINFO_UNAVAIL
} if not defined $pam_result;
254 pam_return
($pam_result);