added details about requesting processes, on systems where we can find such informati...
authorDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Tue, 12 Oct 2010 03:11:32 +0000 (23:11 -0400)
committerDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Tue, 12 Oct 2010 03:11:32 +0000 (23:11 -0400)
Changelog
Crypt/Monkeysphere/MSVA.pm
Crypt/Monkeysphere/MSVA/MarginalUI.pm

index cbbebb22dbd6dc26f4fcfb149c1d98c9827cec6f..478d8e5419446529ec1732bf8b0f0b40c00df997 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -2,6 +2,8 @@ msva-perl (0.5~pre) unstable; urgency=low
 
   * If ${MSVA_KEYSERVER} is unset or blank, default to using keyserver
     from ${GNUPGHOME}/gpg.conf if that file exists. (addresses MS #2080)
+  * Under Linux, report details about the requesting process if we can
+    learn them from /proc (closes MS #2005)
 
  -- Daniel Kahn Gillmor <dkg@fifthhorseman.net>  Mon, 11 Oct 2010 16:02:22 -0400
 
index 8a02c8da83c58e1cd3e99c2adff6bd53afaf02ff..7612ebdc2360c40ebdee4a2db25ae768d9ecdd9e 100755 (executable)
     }
 
     $self->{allowed_uids} = {};
-    if (exists $ENV{MSVA_ALLOWED_USERS}) {
+    if (exists $ENV{MSVA_ALLOWED_USERS} and $ENV{MSVA_ALLOWED_USERS} ne '') {
       msvalog('verbose', "MSVA_ALLOWED_USERS environment variable is set.\nLimiting access to specified users.\n");
       foreach my $user (split(/ +/, $ENV{MSVA_ALLOWED_USERS})) {
         my ($name, $passwd, $uid);
     return @out;
   }
 
-  # return the numeric ID of the peer on the other end of $socket,
-  # returning undef if unknown.
-  sub get_remote_peer_id {
+
+  # return an arrayref of processes which we can detect that have the
+  # given socket open (the socket is specified with its inode)
+  sub getpidswithsocketinode {
+    my $sockid = shift;
+
+    # this appears to be how Linux symlinks open sockets in /proc/*/fd,
+    # as of at least 2.6.26:
+    my $socktarget = sprintf('socket:[%d]', $sockid);
+    my @pids;
+
+    my $procfs;
+    if (opendir($procfs, '/proc')) {
+      foreach my $pid (grep { /^\d+$/ } readdir($procfs)) {
+        my $procdir = sprintf('/proc/%d', $pid);
+        if (-d $procdir) {
+          my $procfds;
+          if (opendir($procfds, sprintf('/proc/%d/fd', $pid))) {
+            foreach my $procfd (grep { /^\d+$/ } readdir($procfds)) {
+              my $fd = sprintf('/proc/%d/fd/%d', $pid, $procfd);
+              if (-l $fd) {
+                #my ($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($fd);
+                my $targ = readlink($fd);
+                push @pids, $pid
+                  if ($targ eq $socktarget);
+              }
+            }
+            closedir($procfds);
+          }
+        }
+      }
+      closedir($procfs);
+    }
+
+    # FIXME: this whole business is very linux-specific, i think.  i
+    # wonder how to get this info in other OSes?
+
+    return \@pids;
+  }
+
+  # return {uid => X, inode => Y}, meaning the numeric ID of the peer
+  # on the other end of $socket, "socket inode" identifying the peer's
+  # open network socket.  each value could be undef if unknown.
+  sub get_client_info {
     my $socket = shift;
 
     my $sock = IO::Socket->new_from_fd($socket, 'r');
     # might not be able to support SO_PEERCRED (even on the loopback),
     # though apparently some kernels (Solaris?) are able to.
 
-    my $remotepeerid;
+    my $clientid;
+    my $remotesocketinode;
     my $socktype = $sock->sockopt(SO_TYPE) or die "could not get SO_TYPE info";
     if (defined $socktype) {
       msvalog('debug', "sockopt(SO_TYPE) = %d\n", $socktype);
     }
 
     my $peercred = $sock->sockopt(SO_PEERCRED) or die "could not get SO_PEERCRED info";
-    my $remotepeer = $sock->peername();
-    my $family = sockaddr_family($remotepeer); # should be AF_UNIX (a.k.a. AF_LOCAL) or AF_INET
+    my $client = $sock->peername();
+    my $family = sockaddr_family($client); # should be AF_UNIX (a.k.a. AF_LOCAL) or AF_INET
 
     msvalog('verbose', "socket family: %d\nsocket type: %d\n", $family, $socktype);
 
               $pid, $uid, $gid,
              );
       if ($pid != 0 && $uid != 0) { # then we can accept it:
-        $remotepeerid = $uid;
+        $clientid = $uid;
       }
+      # FIXME: can we get the socket inode as well this way?
     }
 
     # another option in Linux would be to parse the contents of
     # /proc/net/tcp to find the uid of the peer process based on that
     # information.
-    if (! defined $remotepeerid) {
+    if (! defined $clientid) {
+      msvalog('verbose', "SO_PEERCRED failed, digging around in /proc/net/tcp\n");
       my $proto;
       if ($family == AF_INET) {
         $proto = '';
           undef $proto;
         }
         if (defined $proto) {
-          my ($port, $iaddr) = unpack_sockaddr_in($remotepeer);
+          my ($port, $iaddr) = unpack_sockaddr_in($client);
           my $iaddrstring = unpack("H*", reverse($iaddr));
           msvalog('verbose', "Port: %04x\nAddr: %s\n", $port, $iaddrstring);
           my $remmatch = lc(sprintf("%s:%04x", $iaddrstring, $port));
           my $f = new IO::File;
           if ( $f->open('< '.$infofile)) {
             my @header = split(/ +/, <$f>);
-            my ($localaddrix, $uidix);
+            my ($localaddrix, $uidix, $inodeix);
             my $ix = 0;
             my $skipcount = 0;
             while ($ix <= $#header) {
               $localaddrix = $ix - $skipcount if (lc($header[$ix]) eq 'local_address');
               $uidix = $ix - $skipcount if (lc($header[$ix]) eq 'uid');
+              $inodeix = $ix - $skipcount if (lc($header[$ix]) eq 'inode');
               $skipcount++ if (lc($header[$ix]) eq 'tx_queue') or (lc($header[$ix]) eq 'tr'); # these headers don't actually result in a new column during the data rows
               $ix++;
             }
             } elsif (!defined $uidix) {
               msvalog('info', "Could not find uid field in %s; unable to determine peer UID\n",
                       $infofile);
+            } elsif (!defined $inodeix) {
+              msvalog('info', "Could not find inode field in %s; unable to determine peer network socket inode\n",
+                      $infofile);
             } else {
               msvalog('debug', "local_address: %d; uid: %d\n", $localaddrix,$uidix);
               while (my @line = split(/ +/,<$f>)) {
                 if (lc($line[$localaddrix]) eq $remmatch) {
-                  if (defined $remotepeerid) {
-                    msvalog('error', "Warning! found more than one remote uid! (%s and %s\n", $remotepeerid, $line[$uidix]);
+                  if (defined $clientid) {
+                    msvalog('error', "Warning! found more than one remote uid! (%s and %s\n", $clientid, $line[$uidix]);
                   } else {
-                    $remotepeerid = $line[$uidix];
-                    msvalog('info', "remote peer is uid %d\n",
-                            $remotepeerid);
+                    $clientid = $line[$uidix];
+                    $remotesocketinode = $line[$inodeix];
+                    msvalog('info', "remote peer is uid %d (inode %d)\n",
+                            $clientid, $remotesocketinode);
                   }
                 }
               }
-            msvalog('error', "Warning! could not find peer information in %s.  Not verifying.\n", $infofile) unless defined $remotepeerid;
+            msvalog('error', "Warning! could not find peer information in %s.  Not verifying.\n", $infofile) unless defined $clientid;
             }
           } else { # FIXME: we couldn't read the file.  what should we
                    # do besides warning?
         }
       }
     }
-    return $remotepeerid;
+    return { 'uid' => $clientid,
+             'inode' => $remotesocketinode };
   }
 
   sub handle_request {
     my $self = shift;
     my $cgi  = shift;
 
-    my $remotepeerid =  get_remote_peer_id(select);
+    my $clientinfo = get_client_info(select);
+    my $clientuid = $clientinfo->{uid};
 
-    if (defined $remotepeerid) {
+    if (defined $clientuid) {
       # test that this is an allowed user:
-      if (exists $self->{allowed_uids}->{$remotepeerid}) {
-        msvalog('verbose', "Allowing access from uid %d (%s)\n", $remotepeerid, $self->{allowed_uids}->{$remotepeerid});
+      if (exists $self->{allowed_uids}->{$clientuid}) {
+        msvalog('verbose', "Allowing access from uid %d (%s)\n", $clientuid, $self->{allowed_uids}->{$clientuid});
       } else {
-        msvalog('error', "MSVA client connection from uid %d, forbidden.\n", $remotepeerid);
+        msvalog('error', "MSVA client connection from uid %d, forbidden.\n", $clientuid);
         printf("HTTP/1.0 403 Forbidden -- peer does not match local user ID\r\nContent-Type: text/plain\r\nDate: %s\r\n\r\nHTTP/1.1 403 Not Found -- peer does not match the local user ID.  Are you sure the agent is running as the same user?\r\n",
                strftime("%a, %d %b %Y %H:%M:%S %z", localtime(time())),);
         return;
           }
         };
 
-        my ($status, $object) = $handler->{handler}($data);
+        my ($status, $object) = $handler->{handler}($data, $clientinfo);
         my $ret = to_json($object);
         msvalog('info', "returning: %s\n", $ret);
         printf("HTTP/1.0 %s\r\nDate: %s\r\nContent-Type: application/json\r\n\r\n%s",
       msvalog('error', "Not a valid keyserver (from MSVA_KEYSERVER):\n  %s\n", $ENV{MSVA_KEYSERVER});
     }
 
-    # FIXME: some msva.conf file (system and user?)
+    # FIXME: some msva.conf or monkeysphere.conf file (system and user?)
 
     # or else read from the relevant gnupg.conf:
     my $gpghome;
 
   sub reviewcert {
     my $data  = shift;
+    my $clientinfo  = shift;
     return if !ref $data;
 
     my $status = '200 OK';
 
           my $resp = Crypt::Monkeysphere::MSVA::MarginalUI->ask_the_user($gnupg,
                                                                          $uid,
-                                                                         \@subvalid_key_fprs);
+                                                                         \@subvalid_key_fprs,
+                                                                         getpidswithsocketinode($clientinfo->{inode}));
           msvalog('info', "response: %s\n", $resp);
           if ($resp) {
             $ret->{valid} = JSON::true;
index 7677cd1bc70f5dce74bafec4035e106d60a7775b..f49d58a93395e8627688a9d066bee9a8adf2cf87 100755 (executable)
 
   use Gtk2;
   use Crypt::Monkeysphere::MSVA qw( msvalog );
+  use IO::File;
 
   sub ask_the_user {
     my $self = shift;
     my $gnupg = shift;
     my $uid = shift;
     my $fprs = shift;
+    my $clientpids = shift;
     my @subvalid_key_fprs = @{$fprs};
 
     msvalog('debug', "%d subvalid_key_fprs\n", $#subvalid_key_fprs+1);
@@ -109,7 +111,7 @@ The certificate is certified by:
 
 Would you like to temporarily accept this certificate for this peer?",
                               $uid,
-                              ('m' == $keyfpr->{val} ? 'fully ' : ''),
+                              ('m' eq $keyfpr->{val} ? 'fully ' : ''),
                               $certifier_list,
                              );
             my $tip = sprintf("Peer's User ID: %s
@@ -122,8 +124,28 @@ GnuPG calculated validity for the peer: %s",
             # FIXME: what about revoked certifications?
             # FIXME: what about expired certifications?
             # FIXME: what about certifications ostensibly made in the future?
+
+            my @clienttext;
+            foreach my $clientpid (@{$clientpids}) {
+              my $cmd = '<unknown>';
+              # FIXME: not very portable
+              my $procfh;
+              $procfh = new IO::File(sprintf('/proc/%d/cmdline', $clientpid));
+              if (defined $procfh) {
+                $cmd = <$procfh>;
+                $procfh->close;
+                # FIXME: maybe there's a better way to display this textually
+                # that doesn't conflate spaces with argument delimiters?
+                $cmd = join(' ', split(/\0/, $cmd));
+              }
+              push @clienttext, sprintf("Process %d (%s)", $clientpid, $cmd);
+            }
+            if ($#clienttext >= 0) {
+              $tip = sprintf("%s\n\nRequested by:\n%s\n", $tip, join("\n", @clienttext));
+            }
             msvalog('info', "%s\n", $msg);
             msvalog('verbose', "%s\n", $tip);
+
             my $resp = prompt($uid, $msg, $tip);
             if ($resp) {
               return $resp;