added systemd service file
[safeq] / safeq-pGina-proxy.pl
1 #!/usr/bin/perl
2 #
3 # Peteris Krumins (peter@catonmat.net)
4 # http://www.catonmat.net  --  good coders code, great reuse
5 #
6 # A simple TCP proxy that implements IP-based access control
7 # Currently the ports are hard-coded, and it proxies
8 # 0.0.0.0:1080 to localhost:55555.
9 #
10 # Written for the article "Turn any Linux computer into SOCKS5
11 # proxy in one command," which can be read here:
12 #
13 # http://www.catonmat.net/blog/linux-socks5-proxy
14 # https://github.com/pkrumins/perl-tcp-proxy/raw/master/tcp-proxy.pl
15
16 use warnings;
17 use strict;
18
19 my ($from,$to) = @ARGV;
20 die "usage: $0 10.60.3.9:9100 10.60.3.10:9100\n" unless defined $from && defined $to && $from =~ m/:/ && $to =~ m/:/;
21
22 use IO::Socket;
23 use IO::Select;
24
25 my @allowed_ips; # = ('127.0.0.1'); FIXME -- disabled IP check
26 my $ioset = IO::Select->new;
27 my %socket_map;
28
29 my $debug = $ENV{DEBUG} || 0;
30
31 use DBI;
32
33 my $database = $ENV{PGINA_DB}   || 'pGinaDB';
34 my $hostname = $ENV{PGINA_HOST} || '10.60.4.9';
35 my $port     = $ENV{PGINA_PORT} || 3306;
36 my $user     = $ENV{PGINA_USER} || 'pGina';
37 my $password = $ENV{PGINA_PASS} || 'secret';
38
39 my $dsn = "DBI:mysql:database=$database;host=$hostname;port=$port";
40 my $dbh = DBI->connect($dsn, $user, $password);
41
42
43 sub new_conn {
44     my ($host, $port) = @_;
45     return IO::Socket::INET->new(
46         PeerAddr => $host,
47         PeerPort => $port
48     ) || die "Unable to connect to $host:$port: $!";
49 }
50
51 sub new_server {
52     my ($host, $port) = @_;
53     my $server = IO::Socket::INET->new(
54         LocalAddr => $host,
55         LocalPort => $port,
56         ReuseAddr => 1,
57         Listen    => 100
58     ) || die "Unable to listen on $host:$port: $!";
59 }
60
61 sub new_connection {
62     my $server = shift;
63     my $client = $server->accept;
64     my $client_ip = client_ip($client);
65
66     unless (client_allowed($client)) {
67         print "Connection from $client_ip denied.\n" if $debug;
68         $client->close;
69         return;
70     }
71     print "Connection from $client_ip accepted.\n" if $debug;
72
73     my $remote = new_conn(split(/:/,$to));
74     $ioset->add($client);
75     $ioset->add($remote);
76
77     $socket_map{$client} = $remote;
78     $socket_map{$remote} = $client;
79 }
80
81 sub close_connection {
82     my $client = shift;
83     my $client_ip = client_ip($client);
84     my $remote = $socket_map{$client};
85     
86     $ioset->remove($client);
87     $ioset->remove($remote);
88
89     delete $socket_map{$client};
90     delete $socket_map{$remote};
91
92     $client->close;
93     $remote->close;
94
95     print "Connection from $client_ip closed.\n" if $debug;
96 }
97
98 sub client_ip {
99     my $client = shift;
100     return inet_ntoa($client->sockaddr);
101 }
102
103 sub client_allowed {
104         return 1 unless @allowed_ips;
105     my $client = shift;
106     my $client_ip = client_ip($client);
107     return grep { $_ eq $client_ip } @allowed_ips;
108 }
109
110 print "Starting a server on $from -> $to\n";
111 my $server = new_server(split(/:/,$from));
112 $ioset->add($server);
113
114 use Data::Dump qw(dump);
115
116 while (1) {
117     for my $socket ($ioset->can_read) {
118         if ($socket == $server) {
119             new_connection($server);
120         }
121         else {
122             next unless exists $socket_map{$socket};
123             my $remote = $socket_map{$socket};
124             my $buffer;
125             my $read = $socket->sysread($buffer, 4096);
126             if ($read) {
127                 warn "# ", inet_ntoa($socket->peeraddr), " buffer=", dump($buffer) if $debug;
128                 if ( $buffer =~ m/\n%%%SmartQ-For: (\w+)\n/s ) {
129                         my $win_username = $1;
130                         my $ip = inet_ntoa($socket->peeraddr);
131
132                         my $sth = $dbh->prepare(qq{
133                                 select * from pGinaSession where ipaddress = ? and logoutstamp is null order by loginstamp desc
134                         }) or die "prepare statement failed: $dbh->errstr()";
135                         $sth->execute($ip) or die "execution failed: $dbh->errstr()";
136                         if ( $sth->rows < 1 ) {
137                                 die "can't find IP for job";
138                         } elsif ( $sth->rows > 1 ) {
139                                 warn "ERROR: found $sth->rows() rows usng first one\n";
140                         }
141                         my $row = $sth->fetchrow_hashref();
142                         warn "## row = ",dump($row) if $debug;
143
144                         $sth->finish;
145
146                         my $username = $row->{username} || die "no username in row = ",dump($row);
147                         $username =~ s/\@ffzg.hr$//; # strip domain, same as pGina
148                         $username .= '@ffzg.hr';
149
150                         $buffer =~ s/(\n%%%SmartQ-For: )(\w+)(\n)/$1$username$3/s || die "can't replace user with $username";
151
152                         warn "%%%SmartQ-For: $win_username FROM $ip is $username";
153                 }
154                 $remote->syswrite($buffer);
155             }
156             else {
157                 close_connection($socket);
158             }
159         }
160     }
161 }
162