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]

Reply via email to