make getpeername() return the original socket address which before it was intercepted
[hband-tools.git] / pam / pam_sshd_updwtmp
blobe21e3a475ec8377bfaae92826729477df6ca0072
1 #!/usr/bin/env perl
3 use Data::Dumper;
4 use User::Utmp qw/:constants/;
5 use Fcntl qw/:flock/;
6 use Sys::Syslog qw/openlog syslog/;
7 use constant {
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
26 # should be attempted
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
32 # has aged
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};
61 sub get_parent_pid
63 my $pid = shift;
64 open my $fh, '<', "/proc/$pid/status";
65 local $/ = undef;
66 my ($ppid) = <$fh> =~ /PPid:\s*(\d+)/;
67 close $fh;
68 return $ppid;
71 sub hex_to_dotted_decimal
73 my $_ = shift;
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/;
79 sub list_sockets_tcp4
81 my @sockets;
82 open my $fh, '<', "/proc/net/tcp";
83 while(<$fh>)
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+)/)
87 push @sockets, {
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'} ],
93 'uid' => $+{'uid'},
94 'inode' => $+{'inode'},
98 close $fh;
99 return @sockets;
102 sub list_open_inodes_for_pid
104 my $query_pid = shift;
105 my @response;
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];
112 closedir $dh_fd;
113 return @response;
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)
123 if(/^(\d+)$/)
125 my $this_pid = $1;
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;
139 closedir $dh_proc;
141 return @sockets;
144 sub list_sockets_tcp4_for_pid
146 my $query_pid = shift;
147 my @response;
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;
163 return @response;
168 my $utmp_file = $ARGV[0];
170 if ($ENV{'PAM_TYPE'} =~ /^(open|close)_session$/)
172 my $sshd_pid = get_parent_pid($$);
173 my $ssh_client_port;
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?
180 last;
184 my $ut_line;
185 my $ut_line_guess;
186 if(defined $ssh_client_port)
188 $ut_line = "tcp/$ssh_client_port";
190 else
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);
203 my $ut_id;
204 my $max_ut_id = 0;
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;
226 $ut_ent = {
227 'ut_id' => $ut_id,
228 'ut_line' => $ut_line || '',
229 'ut_pid' => $sshd_pid,
230 'ut_time' => time,
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);
248 close $fh;
249 exit PAM_SUCCESS;
252 exit PAM_IGNORE;