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 | |
---|
50 | use 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 . |
---|
54 | delete @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. |
---|
59 | our $opt_d = "/var/lib/wwwrun/openvpn-ps"; |
---|
60 | chdir $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.:;]*)/; |
---|
67 | my %qs; @qs{qw(conn debug)} = (split(';', ($1 // '')), qw(missing1 missing2)); |
---|
68 | |
---|
69 | my $result = "\n"; # This is returned if no conn info. |
---|
70 | my $i = rindex($qs{conn}, ':'); |
---|
71 | my $fname = ((index($qs{conn}, '.') > 0) ? '[AF_INET]' : '[AF_INET6]') |
---|
72 | . $qs{conn}; |
---|
73 | my $FH = IO::File->new($fname); |
---|
74 | if ($FH) { |
---|
75 | $result = <$FH>; |
---|
76 | $result .= "\n" unless $result =~ /\n$/s; |
---|
77 | } |
---|
78 | |
---|
79 | if ($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 | |
---|
89 | print "Content-type: text/plain; charset=utf-8\n\n$result"; |
---|
90 | exit 0; |
---|