]>
Commit | Line | Data |
---|---|---|
0854fb22 DM |
1 | package PMG::Service::pmgproxy; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use PVE::SafeSyslog; | |
7 | use PVE::Daemon; | |
8 | use HTTP::Response; | |
9 | use URI; | |
614ee52d DM |
10 | use URI::Escape; |
11 | ||
0854fb22 DM |
12 | use URI::QueryParam; |
13 | use Data::Dumper; | |
14 | ||
15 | use PVE::Tools; | |
16 | use PVE::APIServer::Formatter; | |
17 | use PVE::APIServer::Formatter::Standard; | |
18 | use PVE::APIServer::Formatter::HTML; | |
19 | use PVE::APIServer::AnyEvent; | |
20 | ||
21 | use PMG::HTTPServer; | |
22 | use PMG::API2; | |
23 | ||
803b4163 | 24 | use Template; |
0854fb22 DM |
25 | |
26 | use base qw(PVE::Daemon); | |
27 | ||
28 | my $cmdline = [$0, @ARGV]; | |
29 | ||
30 | my %daemon_options = ( | |
31 | max_workers => 3, | |
32 | restart_on_error => 5, | |
33 | stop_wait_time => 15, | |
34 | leave_children_open_on_reload => 1, | |
35 | setuid => 'www-data', | |
36 | setgid => 'www-data', | |
37 | pidfile => '/var/run/pmgproxy/pmgproxy.pid', | |
38 | ); | |
39 | ||
40 | my $daemon = __PACKAGE__->new('pmgproxy', $cmdline, %daemon_options); | |
41 | ||
42 | sub add_dirs { | |
43 | my ($result_hash, $alias, $subdir) = @_; | |
44 | ||
45 | PVE::APIServer::AnyEvent::add_dirs($result_hash, $alias, $subdir); | |
46 | } | |
47 | ||
803b4163 | 48 | my $gui_base_dir = "/usr/share/javascript/proxmox-mailgateway-gui"; |
61ab886a | 49 | my $fontawesome_dir = "/usr/share/fonts-font-awesome"; |
78c58e58 | 50 | my $novnc_dir = '/usr/share/novnc-pve'; |
803b4163 | 51 | |
0854fb22 DM |
52 | sub init { |
53 | my ($self) = @_; | |
54 | ||
55 | my $accept_lock_fn = "/var/lock/pmgproxy.lck"; | |
56 | ||
57 | my $lockfh = IO::File->new(">>${accept_lock_fn}") || | |
58 | die "unable to open lock file '${accept_lock_fn}' - $!\n"; | |
59 | ||
60 | my $family = PVE::Tools::get_host_address_family($self->{nodename}); | |
61 | my $socket = $self->create_reusable_socket(8006, undef, $family); | |
62 | ||
63 | my $dirs = {}; | |
64 | ||
7e7cdb14 | 65 | add_dirs($dirs, '/pve2/locale/', '/usr/share/pmg-i18n/'); |
0854fb22 | 66 | add_dirs($dirs, '/pve2/ext6/', '/usr/share/javascript/extjs/'); |
803b4163 DM |
67 | add_dirs($dirs, '/pve2/images/' => "$gui_base_dir/images/"); |
68 | add_dirs($dirs, '/pve2/css/' => "$gui_base_dir/css/"); | |
69 | add_dirs($dirs, '/pve2/js/' => "$gui_base_dir/js/"); | |
2ec2c08a DM |
70 | add_dirs($dirs, '/fontawesome/css/' => "$fontawesome_dir/css/"); |
71 | add_dirs($dirs, '/fontawesome/fonts/' => "$fontawesome_dir/fonts/"); | |
302820d1 | 72 | add_dirs($dirs, '/novnc/' => "$novnc_dir/"); |
87546b89 | 73 | add_dirs($dirs, '/pmg-docs/' => '/usr/share/pmg-docs/'); |
0854fb22 DM |
74 | |
75 | $self->{server_config} = { | |
76 | title => 'Proxmox Mail Gateway API', | |
2ec2c08a | 77 | cookie_name => 'PMGAuthCookie', |
0854fb22 DM |
78 | keep_alive => 100, |
79 | max_conn => 500, | |
80 | max_requests => 1000, | |
81 | lockfile => $accept_lock_fn, | |
82 | socket => $socket, | |
83 | lockfh => $lockfh, | |
84 | debug => $self->{debug}, | |
85 | trusted_env => 0, # not trusted, anyone can connect | |
86 | logfile => '/var/log/pmgproxy/pmgproxy.log', | |
87 | ssl => { | |
88 | # Note: older versions are considered insecure, for example | |
89 | # search for "Poodle"-Attac | |
90 | method => 'any', | |
91 | sslv2 => 0, | |
92 | sslv3 => 0, | |
93 | cipher_list => 'HIGH:MEDIUM:!aNULL:!MD5', | |
3278b571 | 94 | cert_file => '/etc/pmg/pmg-api.pem', |
0854fb22 DM |
95 | dh => 'skip2048', |
96 | }, | |
97 | # Note: there is no authentication for those pages and dirs! | |
98 | pages => { | |
99 | '/' => sub { get_index($self->{nodename}, @_) }, | |
614ee52d | 100 | '/quarantine' => sub { get_index($self->{nodename}, @_) }, |
0854fb22 | 101 | # avoid authentication when accessing favicon |
cb159468 | 102 | '/favicon.ico' => { |
ff5a70df | 103 | file => '/usr/share/doc/proxmox-mailgateway/favicon.ico', |
cb159468 | 104 | }, |
bbcc9ce6 DM |
105 | '/proxmoxlib.js' => { |
106 | file => '/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js', | |
107 | }, | |
0854fb22 DM |
108 | }, |
109 | dirs => $dirs, | |
110 | }; | |
111 | } | |
112 | ||
113 | sub run { | |
114 | my ($self) = @_; | |
115 | ||
116 | my $server = PMG::HTTPServer->new(%{$self->{server_config}}); | |
117 | $server->run(); | |
118 | } | |
119 | ||
120 | $daemon->register_start_command(); | |
121 | $daemon->register_restart_command(1); | |
122 | $daemon->register_stop_command(); | |
123 | $daemon->register_status_command(); | |
124 | ||
125 | our $cmddef = { | |
126 | start => [ __PACKAGE__, 'start', []], | |
127 | restart => [ __PACKAGE__, 'restart', []], | |
128 | stop => [ __PACKAGE__, 'stop', []], | |
129 | status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ], | |
130 | }; | |
131 | ||
9ff4a97a DM |
132 | my $template_toolkit; |
133 | ||
134 | sub get_template_toolkit { | |
135 | ||
136 | return $template_toolkit if $template_toolkit; | |
137 | ||
138 | $template_toolkit = Template->new( | |
139 | { INCLUDE_PATH => [$gui_base_dir, $novnc_dir]}); | |
140 | ||
141 | return $template_toolkit; | |
142 | } | |
143 | ||
0854fb22 DM |
144 | sub get_index { |
145 | my ($nodename, $server, $r, $args) = @_; | |
146 | ||
147 | my $lang = 'en'; | |
148 | my $username; | |
149 | my $token = 'null'; | |
150 | ||
151 | if (my $cookie = $r->header('Cookie')) { | |
152 | if (my $newlang = ($cookie =~ /(?:^|\s)PMGLangCookie=([^;]*)/)[0]) { | |
153 | if ($newlang =~ m/^[a-z]{2,3}(_[A-Z]{2,3})?$/) { | |
154 | $lang = $newlang; | |
155 | } | |
156 | } | |
157 | my $ticket = PVE::APIServer::Formatter::extract_auth_cookie($cookie, $server->{cookie_name}); | |
614ee52d DM |
158 | |
159 | if ($ticket =~ m/^PMGQUAR:/) { | |
160 | $username = PMG::Ticket::verify_quarantine_ticket($ticket, 1); | |
161 | } else { | |
162 | $username = PMG::Ticket::verify_ticket($ticket, 1); | |
163 | } | |
164 | } else { | |
165 | if (defined($args->{ticket})) { | |
166 | my $ticket = uri_unescape($args->{ticket}); | |
167 | $username = PMG::Ticket::verify_quarantine_ticket($ticket, 1); | |
0854fb22 DM |
168 | } |
169 | } | |
170 | ||
614ee52d DM |
171 | $token = PMG::Ticket::assemble_csrf_prevention_token($username) |
172 | if defined($username); | |
173 | ||
7e7cdb14 DM |
174 | my $langfile = 0; |
175 | ||
176 | if (-f "/usr/share/pmg-i18n/pmg-lang-$lang.js") { | |
177 | $langfile = 1; | |
178 | } | |
78c58e58 | 179 | |
0854fb22 DM |
180 | $username = '' if !$username; |
181 | ||
803b4163 DM |
182 | my $page = ''; |
183 | ||
78c58e58 DM |
184 | my $vars = { |
185 | lang => $lang, | |
186 | langfile => $langfile, | |
187 | username => $username, | |
188 | token => $token, | |
189 | console => $args->{console}, | |
190 | nodename => $nodename, | |
191 | debug => $args->{debug} || $server->{debug}, | |
192 | }; | |
193 | ||
9ff4a97a DM |
194 | my $template_name; |
195 | if (defined($args->{console}) && $args->{novnc}) { | |
196 | $template_name = "index.html.tpl"; # fixme: use better name | |
197 | } else { | |
198 | $template_name = "pmg-index.html.tt"; | |
199 | } | |
200 | ||
201 | my $tt = get_template_toolkit(); | |
202 | ||
203 | $tt->process($template_name, $vars, \$page) || | |
204 | die $tt->error() . "\n"; | |
803b4163 | 205 | |
0854fb22 DM |
206 | my $headers = HTTP::Headers->new(Content_Type => "text/html; charset=utf-8"); |
207 | my $resp = HTTP::Response->new(200, "OK", $headers, $page); | |
208 | ||
209 | return $resp; | |
210 | } | |
211 | ||
212 | 1; |