Here is a lsof-based approach which doesn't rely on setproctitle_enable,
but instead relies on vsftpd's process parent/child structure. It shows
pending and completed logins, and if run as root, the client IP address.
Dependencies are perl-base and lsof, though it could be modified to use
ps instead at the expense of not showing the client IP.
If 'nopriv_user=root' is set, it will misidentify established sessions
as waiting for login, but if that's the case, ftpwho is the least of my
concerns.
Tested on vsftpd 2.0.5-2 (etch) and 2.0.6-1.1 (sid).
#!/usr/bin/perl
# This script implements an 'ftpwho' command for vsftpd using 'lsof'.
#
# A running vsftpd has the following process structure:
# vsftpd (root) Socket listener
# \_ vsftpd (vsftpd) Login rights manager
# | \_ vsftpd (user1) User access handler
# \_ vsftpd (vsftpd)
# | \_ vsftpd (user2)
# \_ vsftpd (root)
# \_ vsftpd (vsftpd) Waiting for login
#
# So to find all open FTP sessions, we find all the vsftpd processes
# with no children. We can then tell a pending login from an
# established session by checking whether its parent process is running
# as root.
#
# The control socket is open on the child's stdin/stdout/stderr, so if
# we're root we can have lsof grab the client IP address too.
use strict; use warnings;
use List::Util qw[max];
# Redirect stderr to avoid lsof complaining if we're not root
open my $stderr_bk, '>&', STDERR;
close STDERR;
open STDERR, '>', '/dev/null';
open my $lsof_pipe, '-|', qw(lsof -F pRLufn -a -c vsftpd)
or my $dead=$!;
close STDERR;
open STDERR, '>&', $stderr_bk;
die "Can't run 'lsof': $dead -- quit" if $dead;
my (%uid_of, %user_of, %parent_of, %net_of, %has_childs, $pid, $fd);
while (<$lsof_pipe>) {
chomp;
/^p(\d+)$/ and $pid = $1, next;
/^R(\d+)$/ and $parent_of{$pid} = $1, $has_childs{$1} = 1, next;
/^L(.+)$/ and $user_of{$pid} = $1, next;
/^u(.+)$/ and $uid_of{$pid} = $1, next;
/^f(\d+)$/ and $fd = $1, next;
/^n.+->(.+)$/ and $fd eq '0' and $net_of{$pid} = $1, next;
}
close $lsof_pipe or die "Error running 'lsof'";
my $maxuserlen = max map {length $_} (keys %user_of, '[login]');
for my $pid (sort keys %user_of) {
my $uppid = $uid_of{$parent_of{$pid}};
if (!$has_childs{$pid} and defined $uppid) {
$net_of{$pid} ||= '';
printf "%2s %-${maxuserlen}s %s\n",
$uppid == 0
? (' ', '[login]')
: ('->', $user_of{$pid}),
$net_of{$pid};
}
}
--
To UNSUBSCRIBE, email to [EMAIL PROTECTED]
with a subject of "unsubscribe". Trouble? Contact [EMAIL PROTECTED]