Ticket #1358: proxysrc.cgi

File proxysrc.cgi, 4.0 KB (added by jimc, 2 years ago)

CGI to return the content of a port-share origin file.

Line 
1#!/usr/local/sbin/perl-suid -Tw /home/httpd/htdocs/proxysrc.cgi
2# proxysrc.cgi -- Retrieves the origin of an OpenVPN proxy connection.
3# Copyright (c) 2020 by James F. Carter.  2020-11-27, Perl-5.32.0
4
5# For evading criminal stupidity and net censorship, I run OpenVPN on Surya
6# port 443/tcp.  It has this configuration command:
7#   port_share claude.cft.ca.us 443 /var/lib/wwwrun/openvpn-ps
8# The directory (optional) is pre-created with owner wwwrun:nogroup mode 700. 
9# When it detects a non-OpenVPN protocol (HTTPS), it does the reverse proxy
10# thing to the host and port specified, and it creates in the dir a file
11# telling who the upstream client is.  The file is removed when the connection
12# is closed.  (To do this, OpenVPN has to run as root and not be in a chroot
13# jail.)  For example, suppose:
14#   Remote client 172.16.0.1 or fd18:dead:beef::c8 port 34567 connects
15#       to OpenVPN. 
16#   It's proxied to claude from 192.168.0.185 or 2600:3c01::e6
17#       port 65432
18#   OpenVPN creates filename "[AF_INET]192.168.0.185:65432"
19#       or "[AF_INET6]2600:3c01::e6:65432" (last colon separated unit is
20#       the port in decimal).
21#   Its content is "[AF_INET]172.16.0.1:34567" or
22#       "[AF_INET6]fd18:dead:beef::c8:34567"
23
24# Nasty quirk: This script is executed by wwwrun and the dir and files are
25# owned by openvpn.  Recently (around 2020-07-xx I think) in OpenSuSE
26# Tumbleweed, filesystem namespaces for non-root users are segregated.  The
27# symptom here is that the target file cannot be opened by name, and when the
28# directory is read, only the test file created by root is returned.  This
29# script has to be setUID root; then it will open or enumerate the target file.
30
31# An interested script on Claude, specifically whatismyip.cgi, should look in
32# environment variables REMOTE_ADDR (should be Surya's) and REMOTE_PORT, and
33# should put together a query to
34# http://surya.cft.ca.us:80/proxysrc.cgi?192.168.0.185:65432 (IP colon port).
35# This program will give a 1-line reply (text/plain) containing
36# [AF_INET]172.16.0.1:34567 (the original client's IP and port) followed by
37# debug information.  Append ';debug' to get the debug info.  If the requested
38# connection info is not available, the first line will be empty (just a
39# newline).
40
41# Test command line: On Surya,
42#   echo "[AF_INET]192.168.1.2:65000" > \
43#       "/var/lib/wwwrun/openvpn-ps/[AF_INET]192.9.200.185:33000"
44# Or [AF_INET6]; IPv6 addresses are not bracketed.  Then execute
45#   QUERY_STRING="[AF_INET]192.9.200.185:33000;debug" proxysrc.cgi
46# And/or test from another host with
47#   curl "http://surya/proxysrc.cgi?[AF_INET]192.9.200.185:33000;debug"
48# Remember to delete the test file afterward. 
49
50use IO::File;                   # Also exports O_xxx from Fcntl
51
52# Environment cleanup for setUID operation.  The path is
53# _PATH_STDPATH in /usr/include/paths.h .
54delete @ENV{qw(IFS CDPATH ENV BASH_ENV LD_LIBRARY_PATH LD_RUN_PATH)};
55$ENV{PATH} = "/usr/bin:/bin:/usr/sbin:/sbin";
56
57# These options are not really settable from the command line. 
58# Directory where OpenVPN puts its connection info files. 
59our $opt_d = "/var/lib/wwwrun/openvpn-ps";
60chdir $opt_d or do {
61    print "\nCan't chdir $opt_d: $!\n";
62    exit 0;
63};
64
65                # Sanitize the query string: just IP adrs, ports, and "debug".
66($ENV{QUERY_STRING} // '') =~ /([\w.:;]*)/;
67my %qs; @qs{qw(conn debug)} = (split(';', ($1 // '')), qw(missing1 missing2));
68
69my $result = "\n";                      # This is returned if no conn info.
70my $i = rindex($qs{conn}, ':');
71my $fname = ((index($qs{conn}, '.') > 0) ? '[AF_INET]' : '[AF_INET6]')
72        . $qs{conn};
73my $FH = IO::File->new($fname);
74if ($FH) {
75    $result = <$FH>;
76    $result .= "\n" unless $result =~ /\n$/s;
77}
78
79if ($qs{debug} eq 'debug') {
80    my %filemap;                        # Key = filename, value = 1st/only line
81    $filemap{extra} = "target filename = $fname";
82    for $fname (glob("*")) {    # Bypass files not starting with family
83        $FH = IO::File->new($fname) or next;
84        chomp($filemap{$fname} = <$FH>);
85    }
86    $result .= join("", map {"$_\t= $filemap{$_}\n"} sort keys %filemap);
87}
88
89print "Content-type: text/plain; charset=utf-8\n\n$result";
90exit 0;