]>
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; | |
e617af3f | 20 | use PVE::APIServer::Utils; |
0854fb22 DM |
21 | |
22 | use PMG::HTTPServer; | |
23 | use PMG::API2; | |
f9fb1781 | 24 | use PMG::Config; |
0854fb22 | 25 | |
803b4163 | 26 | use Template; |
0854fb22 DM |
27 | |
28 | use base qw(PVE::Daemon); | |
29 | ||
30 | my $cmdline = [$0, @ARGV]; | |
31 | ||
32 | my %daemon_options = ( | |
33 | max_workers => 3, | |
34 | restart_on_error => 5, | |
35 | stop_wait_time => 15, | |
36 | leave_children_open_on_reload => 1, | |
37 | setuid => 'www-data', | |
38 | setgid => 'www-data', | |
83e9f427 | 39 | pidfile => '/run/pmgproxy/pmgproxy.pid', |
0854fb22 DM |
40 | ); |
41 | ||
42 | my $daemon = __PACKAGE__->new('pmgproxy', $cmdline, %daemon_options); | |
43 | ||
44 | sub add_dirs { | |
45 | my ($result_hash, $alias, $subdir) = @_; | |
46 | ||
47 | PVE::APIServer::AnyEvent::add_dirs($result_hash, $alias, $subdir); | |
48 | } | |
49 | ||
572ff727 | 50 | my $gui_base_dir = "/usr/share/javascript/pmg-gui"; |
6fcf7afb | 51 | my $extjs_dir = "/usr/share/javascript/extjs/"; |
61ab886a | 52 | my $fontawesome_dir = "/usr/share/fonts-font-awesome"; |
6466cf6d | 53 | my $xtermjs_dir = '/usr/share/pve-xtermjs'; |
9adf84b5 | 54 | my $framework7_dir = '/usr/share/javascript/framework7'; |
a0de78bd | 55 | my $widgettoolkit_dir = '/usr/share/javascript/proxmox-widget-toolkit'; |
803b4163 | 56 | |
0854fb22 DM |
57 | sub init { |
58 | my ($self) = @_; | |
59 | ||
e617af3f SI |
60 | my $proxyconf = PVE::APIServer::Utils::read_proxy_config($self->{name}); |
61 | ||
0854fb22 DM |
62 | my $accept_lock_fn = "/var/lock/pmgproxy.lck"; |
63 | ||
64 | my $lockfh = IO::File->new(">>${accept_lock_fn}") || | |
65 | die "unable to open lock file '${accept_lock_fn}' - $!\n"; | |
66 | ||
cc7fa1b8 OB |
67 | my $listen_ip = $proxyconf->{LISTEN_IP}; |
68 | my $socket = $self->create_reusable_socket(8006, $listen_ip); | |
0854fb22 DM |
69 | |
70 | my $dirs = {}; | |
71 | ||
7e7cdb14 | 72 | add_dirs($dirs, '/pve2/locale/', '/usr/share/pmg-i18n/'); |
6fcf7afb | 73 | add_dirs($dirs, '/pve2/ext6/', $extjs_dir); |
803b4163 DM |
74 | add_dirs($dirs, '/pve2/images/' => "$gui_base_dir/images/"); |
75 | add_dirs($dirs, '/pve2/css/' => "$gui_base_dir/css/"); | |
76 | add_dirs($dirs, '/pve2/js/' => "$gui_base_dir/js/"); | |
2ec2c08a DM |
77 | add_dirs($dirs, '/fontawesome/css/' => "$fontawesome_dir/css/"); |
78 | add_dirs($dirs, '/fontawesome/fonts/' => "$fontawesome_dir/fonts/"); | |
9adf84b5 DC |
79 | add_dirs($dirs, '/framework7/fonts/' => "$framework7_dir/fonts/"); |
80 | add_dirs($dirs, '/framework7/css/' => "$framework7_dir/css/"); | |
81 | add_dirs($dirs, '/framework7/js/' => "$framework7_dir/js/"); | |
6466cf6d | 82 | add_dirs($dirs, '/xtermjs/' => "$xtermjs_dir/"); |
87546b89 | 83 | add_dirs($dirs, '/pmg-docs/' => '/usr/share/pmg-docs/'); |
6fcf7afb | 84 | add_dirs($dirs, '/pmg-docs/api-viewer/extjs/' => $extjs_dir); |
a0de78bd | 85 | add_dirs($dirs, '/pwt/css/' => "$widgettoolkit_dir/css/"); |
47b5dcf8 | 86 | add_dirs($dirs, '/pwt/images/' => "$widgettoolkit_dir/images/"); |
0854fb22 DM |
87 | |
88 | $self->{server_config} = { | |
89 | title => 'Proxmox Mail Gateway API', | |
2ec2c08a | 90 | cookie_name => 'PMGAuthCookie', |
0854fb22 DM |
91 | keep_alive => 100, |
92 | max_conn => 500, | |
93 | max_requests => 1000, | |
94 | lockfile => $accept_lock_fn, | |
95 | socket => $socket, | |
96 | lockfh => $lockfh, | |
97 | debug => $self->{debug}, | |
98 | trusted_env => 0, # not trusted, anyone can connect | |
99 | logfile => '/var/log/pmgproxy/pmgproxy.log', | |
e617af3f SI |
100 | allow_from => $proxyconf->{ALLOW_FROM}, |
101 | deny_from => $proxyconf->{DENY_FROM}, | |
102 | policy => $proxyconf->{POLICY}, | |
0854fb22 | 103 | ssl => { |
3278b571 | 104 | cert_file => '/etc/pmg/pmg-api.pem', |
0854fb22 | 105 | dh => 'skip2048', |
e617af3f | 106 | cipher_list => $proxyconf->{CIPHERS}, |
387cc196 | 107 | ciphersuites => $proxyconf->{CIPHERSUITES}, |
e617af3f | 108 | honor_cipher_order => $proxyconf->{HONOR_CIPHER_ORDER}, |
0854fb22 | 109 | }, |
e617af3f | 110 | compression => $proxyconf->{COMPRESSION}, |
0854fb22 DM |
111 | # Note: there is no authentication for those pages and dirs! |
112 | pages => { | |
113 | '/' => sub { get_index($self->{nodename}, @_) }, | |
614ee52d | 114 | '/quarantine' => sub { get_index($self->{nodename}, @_) }, |
0854fb22 | 115 | # avoid authentication when accessing favicon |
cb159468 | 116 | '/favicon.ico' => { |
572ff727 | 117 | file => '/usr/share/doc/pmg-api/favicon.ico', |
cb159468 | 118 | }, |
bbcc9ce6 DM |
119 | '/proxmoxlib.js' => { |
120 | file => '/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js', | |
121 | }, | |
0a462c62 WB |
122 | '/qrcode.min.js' => { |
123 | file => '/usr/share/javascript/qrcodejs/qrcode.min.js', | |
124 | }, | |
0854fb22 DM |
125 | }, |
126 | dirs => $dirs, | |
127 | }; | |
e617af3f SI |
128 | |
129 | if (defined($proxyconf->{DHPARAMS})) { | |
130 | $self->{server_config}->{ssl}->{dh_file} = $proxyconf->{DHPARAMS}; | |
131 | } | |
0854fb22 DM |
132 | } |
133 | ||
134 | sub run { | |
135 | my ($self) = @_; | |
136 | ||
137 | my $server = PMG::HTTPServer->new(%{$self->{server_config}}); | |
138 | $server->run(); | |
139 | } | |
140 | ||
141 | $daemon->register_start_command(); | |
142 | $daemon->register_restart_command(1); | |
143 | $daemon->register_stop_command(); | |
144 | $daemon->register_status_command(); | |
145 | ||
146 | our $cmddef = { | |
147 | start => [ __PACKAGE__, 'start', []], | |
148 | restart => [ __PACKAGE__, 'restart', []], | |
149 | stop => [ __PACKAGE__, 'stop', []], | |
150 | status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ], | |
151 | }; | |
152 | ||
9ff4a97a DM |
153 | my $template_toolkit; |
154 | ||
155 | sub get_template_toolkit { | |
156 | ||
157 | return $template_toolkit if $template_toolkit; | |
158 | ||
159 | $template_toolkit = Template->new( | |
6466cf6d | 160 | { INCLUDE_PATH => [$gui_base_dir, $xtermjs_dir]}); |
9ff4a97a DM |
161 | |
162 | return $template_toolkit; | |
163 | } | |
164 | ||
9adf84b5 DC |
165 | sub is_phone { |
166 | my ($ua) = @_; | |
167 | ||
168 | return 0 if !$ua; | |
169 | ||
170 | if ($ua =~ m/( | |
171 | iPhone| | |
172 | iPod| | |
173 | Windows\ Phone| | |
174 | Android| | |
175 | BlackBerry | |
176 | )/xi) { | |
177 | return 1; | |
178 | } | |
179 | ||
180 | return 0; | |
181 | } | |
182 | ||
0854fb22 DM |
183 | sub get_index { |
184 | my ($nodename, $server, $r, $args) = @_; | |
185 | ||
186 | my $lang = 'en'; | |
187 | my $username; | |
188 | my $token = 'null'; | |
9adf84b5 DC |
189 | my $quarantine = ($r->uri->path() =~ m/quarantine$/); |
190 | my $mobile = is_phone($r->header('User-Agent')); | |
191 | if (defined($args->{mobile})) { | |
192 | $mobile = $args->{mobile} ? 1 : 0; | |
193 | } | |
0854fb22 DM |
194 | |
195 | if (my $cookie = $r->header('Cookie')) { | |
196 | if (my $newlang = ($cookie =~ /(?:^|\s)PMGLangCookie=([^;]*)/)[0]) { | |
197 | if ($newlang =~ m/^[a-z]{2,3}(_[A-Z]{2,3})?$/) { | |
198 | $lang = $newlang; | |
199 | } | |
200 | } | |
14ebc02d | 201 | my $ticket = PVE::APIServer::Formatter::extract_auth_value($cookie, $server->{cookie_name}); |
614ee52d DM |
202 | |
203 | if ($ticket =~ m/^PMGQUAR:/) { | |
204 | $username = PMG::Ticket::verify_quarantine_ticket($ticket, 1); | |
205 | } else { | |
5accfdf2 | 206 | $username = PMG::Ticket::verify_ticket($ticket, undef, 1); |
614ee52d DM |
207 | } |
208 | } else { | |
209 | if (defined($args->{ticket})) { | |
210 | my $ticket = uri_unescape($args->{ticket}); | |
211 | $username = PMG::Ticket::verify_quarantine_ticket($ticket, 1); | |
0854fb22 DM |
212 | } |
213 | } | |
214 | ||
614ee52d DM |
215 | $token = PMG::Ticket::assemble_csrf_prevention_token($username) |
216 | if defined($username); | |
217 | ||
7e7cdb14 DM |
218 | my $langfile = 0; |
219 | ||
220 | if (-f "/usr/share/pmg-i18n/pmg-lang-$lang.js") { | |
221 | $langfile = 1; | |
222 | } | |
78c58e58 | 223 | |
3fb6d663 DC |
224 | my $wtversionraw = PVE::Tools::file_read_firstline("$widgettoolkit_dir/proxmoxlib.js"); |
225 | my $wtversion = ''; | |
226 | if ($wtversionraw =~ m|^// (.*)$|) { | |
227 | $wtversion = $1; | |
228 | }; | |
229 | ||
230 | my $versionraw = PVE::Tools::file_read_firstline("$gui_base_dir/js/pmgmanagerlib.js"); | |
231 | my $version = ''; | |
232 | if ($versionraw =~ m|^// (.*)$|) { | |
233 | $version = $1; | |
234 | }; | |
235 | ||
f9fb1781 DC |
236 | my $cfg = PMG::Config->new(); |
237 | my $quarantinelink = $cfg->get('spamquar', 'quarantinelink'); | |
3fb6d663 | 238 | |
0854fb22 DM |
239 | $username = '' if !$username; |
240 | ||
803b4163 DM |
241 | my $page = ''; |
242 | ||
78c58e58 DM |
243 | my $vars = { |
244 | lang => $lang, | |
245 | langfile => $langfile, | |
246 | username => $username, | |
247 | token => $token, | |
248 | console => $args->{console}, | |
249 | nodename => $nodename, | |
250 | debug => $args->{debug} || $server->{debug}, | |
3fb6d663 DC |
251 | version => $version, |
252 | wtversion => $wtversion, | |
f9fb1781 | 253 | quarantinelink => $quarantinelink, |
78c58e58 DM |
254 | }; |
255 | ||
9ff4a97a | 256 | my $template_name; |
6466cf6d | 257 | if (defined($args->{console}) && $args->{xtermjs}) { |
9ff4a97a | 258 | $template_name = "index.html.tpl"; # fixme: use better name |
9adf84b5 DC |
259 | } elsif ($mobile && $quarantine) { |
260 | $template_name = "pmg-mobile-index.html.tt"; | |
9ff4a97a DM |
261 | } else { |
262 | $template_name = "pmg-index.html.tt"; | |
263 | } | |
264 | ||
265 | my $tt = get_template_toolkit(); | |
266 | ||
267 | $tt->process($template_name, $vars, \$page) || | |
268 | die $tt->error() . "\n"; | |
803b4163 | 269 | |
0854fb22 DM |
270 | my $headers = HTTP::Headers->new(Content_Type => "text/html; charset=utf-8"); |
271 | my $resp = HTTP::Response->new(200, "OK", $headers, $page); | |
272 | ||
273 | return $resp; | |
274 | } | |
275 | ||
276 | 1; |