4 use User
::Utmp qw
/:constants/;
6 use Sys
::Syslog qw
/openlog syslog/;
8 PAM_SUCCESS
=>0, # Successful function return
9 PAM_OPEN_ERR
=>1, # dlopen() failure when dynamically
10 # loading a service module
11 PAM_SYMBOL_ERR
=>2, # Symbol not found
12 PAM_SERVICE_ERR
=>3, # Error in service module
13 PAM_SYSTEM_ERR
=>4, # System error
14 PAM_BUF_ERR
=>5, # Memory buffer error
15 PAM_PERM_DENIED
=>6, # Permission denied
16 PAM_AUTH_ERR
=>7, # Authentication failure
17 PAM_CRED_INSUFFICIENT
=>8, # Can not access authentication data
18 # due to insufficient credentials
19 PAM_AUTHINFO_UNAVAIL
=>9, # Underlying authentication service
20 # can not retrieve authentication information
21 PAM_USER_UNKNOWN
=>10, # User not known to the underlying
22 # authenticaiton module
23 PAM_MAXTRIES
=>11, # An authentication service has
24 # maintained a retry count which has
25 # been reached. No further retries
27 PAM_NEW_AUTHTOK_REQD
=>12, # New authentication token required.
28 # This is normally returned if the
29 # machine security policies require
30 # that the password should be changed
31 # beccause the password is NULL or it
33 PAM_ACCT_EXPIRED
=>13, # User account has expired
34 PAM_SESSION_ERR
=>14, # Can not make/remove an entry for
35 # the specified session
36 PAM_CRED_UNAVAIL
=>15, # Underlying authentication service
37 # can not retrieve user credentials unavailable
38 PAM_CRED_EXPIRED
=>16, # User credentials expired
39 PAM_CRED_ERR
=>17, # Failure setting user credentials
40 PAM_NO_MODULE_DATA
=>18, # No module specific data is present
41 PAM_CONV_ERR
=>19, # Conversation error
42 PAM_AUTHTOK_ERR
=>20, # Authentication token manipulation error
43 PAM_AUTHTOK_RECOVERY_ERR
=>21, # Authentication information cannot be recovered
44 PAM_AUTHTOK_LOCK_BUSY
=>22, # Authentication token lock busy
45 PAM_AUTHTOK_DISABLE_AGING
=>23, # Authentication token aging disabled
46 PAM_TRY_AGAIN
=>24, # Preliminary check by password service
47 PAM_IGNORE
=>25, # Ignore underlying account module
48 # regardless of whether the control
49 # flag is required, optional, or sufficient
50 PAM_ABORT
=>26, # Critical error (?module fail now request)
51 PAM_AUTHTOK_EXPIRED
=>27, # user's authentication token has expired
52 PAM_MODULE_UNKNOWN
=>28, # module is not known
53 PAM_BAD_ITEM
=>29, # Bad item passed to pam_*_item()
54 PAM_CONV_AGAIN
=>30, # conversation function is event driven and data is not available yet
55 PAM_INCOMPLETE
=>31, # please call this function again to complete authentication stack.
56 # Before calling again, verify that conversation is completed
58 use constant
{STAT_DEVNO
=>0, STAT_INO
=>1, STAT_MODE
=>2, STAT_LINKS
=>3, STAT_UID
=>4, STAT_GID
=>5, STAT_DEVIDENT
=>6, STAT_SIZE
=>7, STAT_ATIME
=>8, STAT_MTIME
=>9, STAT_CTIME
=>10, STAT_PREFBLKSZ
=>11, STAT_BLOCKS
=>12};
64 open my $fh, '<', "/proc/$pid/status";
66 my ($ppid) = <$fh> =~ /PPid:\s*(\d+)/;
71 sub hex_to_dotted_decimal
74 return join '.', reverse map {hex} /(..)/g;
77 @socket_state_name = qw
/0 ESTABLISHED SYN_SENT SYN_RECV FIN_WAIT1 FIN_WAIT2 TIME_WAIT CLOSE CLOSE_WAIT LAST_ACK LISTEN CLOSING/;
82 open my $fh, '<', "/proc/net/tcp";
85 if(/^\s*\d+:\s*(?'hex_local_ip'[[:xdigit:]]+):(?'hex_local_port'[[:xdigit:]]+)\s+(?'hex_remote_ip'[[:xdigit:]]+):(?'hex_remote_port'[[:xdigit:]]+)\s+(?'hex_status'[[:xdigit:]]+)\s+(?'tx_queue'[^:]+):(?'rx_queue'\S+)\s+(?'tr'[^:]+):(?'tm_when'\S+)\s+(?'retrnsmt'\S+)\s+(?'uid'\d+)\s+(?'timeout'\d+)\s+(?'inode'\d+)/)
88 'local_ip' => hex_to_dotted_decimal
($+{'hex_local_ip'}),
89 'local_port' => hex $+{'hex_local_port'},
90 'remote_ip' => hex_to_dotted_decimal
($+{'hex_remote_ip'}),
91 'remote_port' => hex $+{'hex_remote_port'},
92 'status' => $socket_state_name[ hex $+{'hex_status'} ],
94 'inode' => $+{'inode'},
102 sub list_open_inodes_for_pid
104 my $query_pid = shift;
106 my $fd_dir = "/proc/$query_pid/fd";
107 opendir my $dh_fd, $fd_dir;
108 while(my $fd_file = readdir $dh_fd)
110 push @response, (stat "$fd_dir/$fd_file")[STAT_INO
];
116 sub list_sockets_tcp4_with_pid
118 my @sockets = list_sockets_tcp4
();
120 opendir my $dh_proc, '/proc';
121 while(my $_ = readdir $dh_proc)
126 my @inodes = list_open_inodes_for_pid
($this_pid);
127 for my $socket (@sockets)
129 for my $inode (@inodes)
131 if($socket->{'inode'} == $inode)
133 $socket->{'pid'} = $this_pid;
144 sub list_sockets_tcp4_for_pid
146 my $query_pid = shift;
148 my @sockets = list_sockets_tcp4
();
149 my @inodes = list_open_inodes_for_pid
($query_pid);
151 for my $socket (@sockets)
153 for my $inode (@inodes)
155 if($socket->{'inode'} == $inode)
157 $socket->{'pid'} = $query_pid;
158 push @response, $socket;
168 my $utmp_file = $ARGV[0];
170 if ($ENV{'PAM_TYPE'} =~ /^(open|close)_session$/)
172 my $sshd_pid = get_parent_pid
($$);
175 for my $socket (list_sockets_tcp4_for_pid
($sshd_pid)) # TODO IPv6 support
177 if($socket->{'status'} eq 'ESTABLISHED')
179 $ssh_client_port = $socket->{'remote_port'}; # FIXME multiple open connections?
186 if(defined $ssh_client_port)
188 $ut_line = "tcp/$ssh_client_port";
192 if($ENV{'PAM_TYPE'} eq 'open_session')
194 exit PAM_AUTHINFO_UNAVAIL
;
199 open my $fh, '>>', $utmp_file;
200 flock $fh, LOCK_EX
or exit PAM_SYSTEM_ERR
;
201 User
::Utmp
::utmpname
($utmp_file);
205 for my $entry (User
::Utmp
::getut
())
207 $max_ut_id = $entry->{'ut_id'} if $max_ut_id < $entry->{'ut_id'};
209 if($entry->{'ut_type'} == USER_PROCESS
and -d
sprintf "/proc/%d", $entry->{'ut_pid'})
211 if(!defined $ut_line and $entry->{'ut_pid'} eq $sshd_pid)
213 $ut_line_guess = $entry->{'ut_line'};
217 if(not defined $ut_id)
219 $ut_id = $max_ut_id + 1;
221 if(!defined $ut_line and defined $ut_line_guess)
223 $ut_line = $ut_line_guess;
228 'ut_line' => $ut_line || '',
229 'ut_pid' => $sshd_pid,
231 'ut_host' => $ENV{'PAM_RHOST'},
232 'ut_addr' => pack('C4', split /\./, $ENV{'PAM_RHOST'}),
235 if ($ENV{'PAM_TYPE'} eq 'open_session')
237 $ut_ent->{'ut_type'} = USER_PROCESS
;
238 $ut_ent->{'ut_user'} = $ENV{'PAM_USER'};
240 elsif ($ENV{'PAM_TYPE'} eq 'close_session')
242 $ut_ent->{'ut_type'} = DEAD_PROCESS
;
243 $ut_ent->{'ut_user'} = '';
246 User
::Utmp
::pututline
($ut_ent);