cosmetics
[tomato.git] / release / src / router / www / vpn-client.asp
blobb36b0aea20938dcfc01659b31b386fb156a4b1c3
1 <!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0//EN'>
2 <!--
3 Tomato GUI
4 Copyright (C) 2006-2008 Jonathan Zarate
5 http://www.polarcloud.com/tomato/
7 Portions Copyright (C) 2008-2010 Keith Moyer, tomatovpn@keithmoyer.com
9 For use with Tomato Firmware only.
10 No part of this file may be used without permission.
11 -->
12 <html>
13 <head>
14 <meta http-equiv='content-type' content='text/html;charset=utf-8'>
15 <meta name='robots' content='noindex,nofollow'>
16 <title>[<% ident(); %>] VPN: Client</title>
17 <link rel='stylesheet' type='text/css' href='tomato.css'>
18 <link rel='stylesheet' type='text/css' href='color.css'>
19 <script type='text/javascript' src='tomato.js'></script>
20 <script type='text/javascript' src='vpn.js'></script>
21 <script type='text/javascript'>
23 // <% nvram("vpn_client_eas,vpn_client1_poll,vpn_client1_if,vpn_client1_bridge,vpn_client1_nat,vpn_client1_proto,vpn_client1_addr,vpn_client1_port,vpn_client1_retry,vpn_client1_firewall,vpn_client1_crypt,vpn_client1_comp,vpn_client1_cipher,vpn_client1_local,vpn_client1_remote,vpn_client1_nm,vpn_client1_reneg,vpn_client1_hmac,vpn_client1_adns,vpn_client1_rgw,vpn_client1_gw,vpn_client1_custom,vpn_client1_static,vpn_client1_ca,vpn_client1_crt,vpn_client1_key,vpn_client2_poll,vpn_client2_if,vpn_client2_bridge,vpn_client2_nat,vpn_client2_proto,vpn_client2_addr,vpn_client2_port,vpn_client2_retry,vpn_client2_firewall,vpn_client2_crypt,vpn_client2_comp,vpn_client2_cipher,vpn_client2_local,vpn_client2_remote,vpn_client2_nm,vpn_client2_reneg,vpn_client2_hmac,vpn_client2_adns,vpn_client2_rgw,vpn_client2_gw,vpn_client2_custom,vpn_client2_static,vpn_client2_ca,vpn_client2_crt,vpn_client2_key"); %>
25 tabs = [['client1', 'Client 1'],['client2', 'Client 2']];
26 sections = [['basic', 'Basic'],['advanced', 'Advanced'],['keys','Keys'],['status','Status']];
27 statusUpdaters = [];
28 for (i = 0; i < tabs.length; ++i) statusUpdaters.push(new StatusUpdater());
29 ciphers = [['default','Use Default'],['none','None']];
30 for (i = 0; i < vpnciphers.length; ++i) ciphers.push([vpnciphers[i],vpnciphers[i]]);
32 changed = 0;
33 vpn1up = parseInt('<% psup("vpnclient1"); %>');
34 vpn2up = parseInt('<% psup("vpnclient2"); %>');
36 function updateStatus(num)
38 var xob = new XmlHttp();
39 xob.onCompleted = function(text, xml)
41 statusUpdaters[num].update(text);
42 xob = null;
44 xob.onError = function(ex)
46 statusUpdaters[num].errors.innerHTML += 'ERROR! '+ex+'<br>';
47 xob = null;
50 xob.post('/vpnstatus.cgi', 'client=' + (num+1));
53 function tabSelect(name)
55 tgHideIcons();
57 tabHigh(name);
59 for (var i = 0; i < tabs.length; ++i)
61 var on = (name == tabs[i][0]);
62 elem.display(tabs[i][0] + '-tab', on);
65 cookie.set('vpn_client_tab', name);
68 function sectSelect(tab, section)
70 tgHideIcons();
72 for (var i = 0; i < sections.length; ++i)
74 if (section == sections[i][0])
76 elem.addClass(tabs[tab][0]+'-'+sections[i][0]+'-tab', 'active');
77 elem.display(tabs[tab][0]+'-'+sections[i][0], true);
79 else
81 elem.removeClass(tabs[tab][0]+'-'+sections[i][0]+'-tab', 'active');
82 elem.display(tabs[tab][0]+'-'+sections[i][0], false);
86 cookie.set('vpn_client'+tab+'_section', section);
89 function toggle(service, isup)
91 if (changed && !confirm("Unsaved changes will be lost. Continue anyway?")) return;
93 E('_' + service + '_button').disabled = true;
94 form.submitHidden('service.cgi', {
95 _redirect: 'vpn-client.asp',
96 _sleep: '3',
97 _service: service + (isup ? '-stop' : '-start')
98 });
101 function verifyFields(focused, quiet)
103 tgHideIcons();
105 var ret = 1;
107 // When settings change, make sure we restart the right client
108 if (focused)
110 changed = 1;
112 var clientindex = focused.name.indexOf("client");
113 if (clientindex >= 0)
115 var clientnumber = focused.name.substring(clientindex+6,clientindex+7);
116 var stripped = focused.name.substring(0,clientindex+6)+focused.name.substring(clientindex+7);
118 if (stripped == 'vpn_client_local')
119 E('_f_vpn_client'+clientnumber+'_local').value = focused.value;
120 else if (stripped == 'f_vpn_client_local')
121 E('_vpn_client'+clientnumber+'_local').value = focused.value;
123 var fom = E('_fom');
124 if (eval('vpn'+clientnumber+'up') && fom._service.value.indexOf('client'+clientnumber) < 0)
126 if ( fom._service.value != "" ) fom._service.value += ",";
127 fom._service.value += 'vpnclient'+clientnumber+'-restart';
132 // Element varification
133 for (i = 0; i < tabs.length; ++i)
135 t = tabs[i][0];
137 if (!v_range('_vpn_'+t+'_poll', quiet, 0, 1440)) ret = 0;
138 if (!v_ip('_vpn_'+t+'_addr', true) && !v_domain('_vpn_'+t+'_addr', true)) { ferror.set(E('_vpn_'+t+'_addr'), "Invalid server address.", quiet); ret = 0; }
139 if (!v_port('_vpn_'+t+'_port', quiet)) ret = 0;
140 if (!v_ip('_vpn_'+t+'_local', quiet, 1)) ret = 0;
141 if (!v_ip('_f_vpn_'+t+'_local', true, 1)) ret = 0;
142 if (!v_ip('_vpn_'+t+'_remote', quiet, 1)) ret = 0;
143 if (!v_netmask('_vpn_'+t+'_nm', quiet)) ret = 0;
144 if (!v_range('_vpn_'+t+'_retry', quiet, -1, 32767)) ret = 0;
145 if (!v_range('_vpn_'+t+'_reneg', quiet, -1, 2147483647)) ret = 0;
146 if (E('_vpn_'+t+'_gw').value.length > 0 && !v_ip('_vpn_'+t+'_gw', quiet, 1)) ret = 0;
149 // Visability changes
150 for (i = 0; i < tabs.length; ++i)
152 t = tabs[i][0];
154 fw = E('_vpn_'+t+'_firewall');
155 auth = E('_vpn_'+t+'_crypt');
156 iface = E('_vpn_'+t+'_if');
157 bridge = E('_f_vpn_'+t+'_bridge');
158 nat = E('_f_vpn_'+t+'_nat');
159 hmac = E('_vpn_'+t+'_hmac');
160 rgw = E('_f_vpn_'+t+'_rgw');
162 elem.display(PR('_vpn_'+t+'_ca'), PR('_vpn_'+t+'_crt'), PR('_vpn_'+t+'_key'), PR('_vpn_'+t+'_hmac'),
163 PR('_vpn_'+t+'_adns'), PR('_vpn_'+t+'_reneg'), auth.value == "tls");
164 elem.display(PR('_vpn_'+t+'_static'), auth.value == "secret" || (auth.value == "tls" && hmac.value >= 0));
165 elem.display(E(t+'_custom_crypto_text'), auth.value == "custom");
166 elem.display(PR('_f_vpn_'+t+'_bridge'), iface.value == "tap");
167 elem.display(E(t+'_bridge_warn_text'), !bridge.checked);
168 elem.display(PR('_f_vpn_'+t+'_nat'), fw.value != "custom" && (iface.value == "tun" || !bridge.checked));
169 elem.display(E(t+'_nat_warn_text'), fw.value != "custom" && (!nat.checked || (auth.value == "secret" && iface.value == "tun")));
170 elem.display(PR('_vpn_'+t+'_local'), auth.value == "secret" && iface.value == "tun");
171 elem.display(PR('_f_vpn_'+t+'_local'), auth.value == "secret" && (iface.value == "tap" && !bridge.checked));
172 elem.display(E(t+'_gateway'), iface.value == "tap" && rgw.checked);
174 keyHelp = E(t+'-keyhelp');
175 switch (auth.value)
177 case "tls":
178 keyHelp.href = helpURL['TLSKeys'];
179 break;
180 case "secret":
181 keyHelp.href = helpURL['staticKeys'];
182 break;
183 default:
184 keyHelp.href = helpURL['howto'];
185 break;
189 return ret;
192 function save()
194 if (!verifyFields(null, false)) return;
196 var fom = E('_fom');
198 E('vpn_client_eas').value = '';
200 for (i = 0; i < tabs.length; ++i)
202 t = tabs[i][0];
204 if ( E('_f_vpn_'+t+'_eas').checked )
205 E('vpn_client_eas').value += ''+(i+1)+',';
207 E('vpn_'+t+'_bridge').value = E('_f_vpn_'+t+'_bridge').checked ? 1 : 0;
208 E('vpn_'+t+'_nat').value = E('_f_vpn_'+t+'_nat').checked ? 1 : 0;
209 E('vpn_'+t+'_rgw').value = E('_f_vpn_'+t+'_rgw').checked ? 1 : 0;
212 form.submit(fom, 1);
214 changed = 0;
217 function init()
219 tabSelect(cookie.get('vpn_client_tab') || tabs[0][0]);
221 for (i = 0; i < tabs.length; ++i)
223 sectSelect(i, cookie.get('vpn_client'+i+'_section') || sections[i][0]);
225 t = tabs[i][0];
227 statusUpdaters[i].init(null,null,t+'-status-stats-table',t+'-status-time',t+'-status-content',t+'-no-status',t+'-status-errors');
228 updateStatus(i);
231 verifyFields(null, true);
233 </script>
235 <style type='text/css'>
236 textarea {
237 width: 98%;
238 height: 10em;
240 p.keyhelp
242 font-size: smaller;
243 font-style: italic;
245 div.status-header p
247 font-weight: bold;
248 padding-bottom: 4px;
250 table.status-table
252 width: auto;
253 margin-left: auto;
254 margin-right: auto;
255 text-align: center;
257 </style>
259 </head>
260 <body>
261 <form id='_fom' method='post' action='tomato.cgi'>
262 <table id='container' cellspacing=0>
263 <tr><td colspan=2 id='header'>
264 <div class='title'>Tomato</div>
265 <div class='version'>Version <% version(); %></div>
266 </td></tr>
267 <tr id='body'><td id='navi'><script type='text/javascript'>navi()</script></td>
268 <td id='content'>
269 <div id='ident'><% ident(); %></div>
271 <input type='hidden' name='_nextpage' value='vpn-client.asp'>
272 <input type='hidden' name='_nextwait' value='5'>
273 <input type='hidden' name='_service' value=''>
274 <input type='hidden' name='vpn_client_eas' id='vpn_client_eas' value=''>
276 <div class='section-title'>VPN Client Configuration</div>
277 <div class='section'>
278 <script type='text/javascript'>
279 tabCreate.apply(this, tabs);
281 for (i = 0; i < tabs.length; ++i)
283 t = tabs[i][0];
284 W('<div id=\''+t+'-tab\'>');
285 W('<input type=\'hidden\' id=\'vpn_'+t+'_bridge\' name=\'vpn_'+t+'_bridge\'>');
286 W('<input type=\'hidden\' id=\'vpn_'+t+'_nat\' name=\'vpn_'+t+'_nat\'>');
287 W('<input type=\'hidden\' id=\'vpn_'+t+'_rgw\' name=\'vpn_'+t+'_rgw\'>');
289 W('<ul class="tabs">');
290 for (j = 0; j < sections.length; j++)
292 W('<li><a href="javascript:sectSelect('+i+', \''+sections[j][0]+'\')" id="'+t+'-'+sections[j][0]+'-tab">'+sections[j][1]+'</a></li>');
294 W('</ul><div class=\'tabs-bottom\'></div>');
296 W('<div id=\''+t+'-basic\'>');
297 createFieldTable('', [
298 { title: 'Start with WAN', name: 'f_vpn_'+t+'_eas', type: 'checkbox', value: nvram.vpn_client_eas.indexOf(''+(i+1)) >= 0 },
299 { title: 'Interface Type', name: 'vpn_'+t+'_if', type: 'select', options: [ ['tap','TAP'], ['tun','TUN'] ], value: eval( 'nvram.vpn_'+t+'_if' ) },
300 { title: 'Protocol', name: 'vpn_'+t+'_proto', type: 'select', options: [ ['udp','UDP'], ['tcp-client','TCP'] ], value: eval( 'nvram.vpn_'+t+'_proto' ) },
301 { title: 'Server Address/Port', multi: [
302 { name: 'vpn_'+t+'_addr', type: 'text', size: 17, value: eval( 'nvram.vpn_'+t+'_addr' ) },
303 { name: 'vpn_'+t+'_port', type: 'text', maxlen: 5, size: 7, value: eval( 'nvram.vpn_'+t+'_port' ) } ] },
304 { title: 'Firewall', name: 'vpn_'+t+'_firewall', type: 'select', options: [ ['auto', 'Automatic'], ['custom', 'Custom'] ], value: eval( 'nvram.vpn_'+t+'_firewall' ) },
305 { title: 'Authorization Mode', name: 'vpn_'+t+'_crypt', type: 'select', options: [ ['tls', 'TLS'], ['secret', 'Static Key'], ['custom', 'Custom'] ], value: eval( 'nvram.vpn_'+t+'_crypt' ),
306 suffix: '<span id=\''+t+'_custom_crypto_text\'>&nbsp;<small>(must configure manually...)</small></span>' },
307 { title: 'Extra HMAC authorization (tls-auth)', name: 'vpn_'+t+'_hmac', type: 'select', options: [ [-1, 'Disabled'], [2, 'Bi-directional'], [0, 'Incoming (0)'], [1, 'Outgoing (1)'] ], value: eval( 'nvram.vpn_'+t+'_hmac' ) },
308 { title: 'Server is on the same subnet', name: 'f_vpn_'+t+'_bridge', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_bridge' ) != 0,
309 suffix: '<span style="color: red" id=\''+t+'_bridge_warn_text\'>&nbsp<small>Warning: Cannot bridge distinct subnets. Defaulting to routed mode.<small></span>' },
310 { title: 'Create NAT on tunnel', name: 'f_vpn_'+t+'_nat', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_nat' ) != 0,
311 suffix: '<span style="font-style: italic" id=\''+t+'_nat_warn_text\'>&nbsp<small>Routes must be configured manually.<small></span>' },
312 { title: 'Local/remote endpoint addresses', multi: [
313 { name: 'vpn_'+t+'_local', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_local' ) },
314 { name: 'vpn_'+t+'_remote', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_remote' ) } ] },
315 { title: 'Tunnel address/netmask', multi: [
316 { name: 'f_vpn_'+t+'_local', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_local' ) },
317 { name: 'vpn_'+t+'_nm', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_nm' ) } ] }
319 W('</div>');
320 W('<div id=\''+t+'-advanced\'>');
321 createFieldTable('', [
322 { title: 'Poll Interval', name: 'vpn_'+t+'_poll', type: 'text', maxlen: 4, size: 5, value: eval( 'nvram.vpn_'+t+'_poll' ), suffix: '&nbsp;<small>(in minutes, 0 to disable)</small>' },
323 { title: 'Redirect Internet traffic', multi: [
324 { name: 'f_vpn_'+t+'_rgw', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_rgw' ) != 0 },
325 { name: 'vpn_'+t+'_gw', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_gw' ), prefix: '<span id=\''+t+'_gateway\'>Gateway:&nbsp', suffix: '</span>'} ] },
326 { title: 'Accept DNS configuration', name: 'vpn_'+t+'_adns', type: 'select', options: [[0, 'Disabled'],[1, 'Relaxed'],[2, 'Strict'],[3, 'Exclusive']], value: eval( 'nvram.vpn_'+t+'_adns' ) },
327 { title: 'Encryption cipher', name: 'vpn_'+t+'_cipher', type: 'select', options: ciphers, value: eval( 'nvram.vpn_'+t+'_cipher' ) },
328 { title: 'Compression', name: 'vpn_'+t+'_comp', type: 'select', options: [ ['-1', 'Disabled'], ['no', 'None'], ['yes', 'Enabled'], ['adaptive', 'Adaptive'] ], value: eval( 'nvram.vpn_'+t+'_comp' ) },
329 { title: 'TLS Renegotiation Time', name: 'vpn_'+t+'_reneg', type: 'text', maxlen: 10, size: 7, value: eval( 'nvram.vpn_'+t+'_reneg' ),
330 suffix: '&nbsp;<small>(in seconds, -1 for default)</small>' },
331 { title: 'Connection retry', name: 'vpn_'+t+'_retry', type: 'text', maxlen: 5, size: 7, value: eval( 'nvram.vpn_'+t+'_retry' ),
332 suffix: '&nbsp;<small>(in seconds; -1 for infinite)</small>' },
333 { title: 'Custom Configuration', name: 'vpn_'+t+'_custom', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_custom' ) }
335 W('</div>');
336 W('<div id=\''+t+'-keys\'>');
337 W('<p class=\'keyhelp\'>For help generating keys, refer to the OpenVPN <a id=\''+t+'-keyhelp\'>HOWTO</a>.</p>');
338 createFieldTable('', [
339 { title: 'Static Key', name: 'vpn_'+t+'_static', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_static' ) },
340 { title: 'Certificate Authority', name: 'vpn_'+t+'_ca', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_ca' ) },
341 { title: 'Client Certificate', name: 'vpn_'+t+'_crt', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_crt' ) },
342 { title: 'Client Key', name: 'vpn_'+t+'_key', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_key' ) }
344 W('</div>');
345 W('<div id=\''+t+'-status\'>');
346 W('<div id=\''+t+'-no-status\'><p>Client is not running or status could not be read.</p></div>');
347 W('<div id=\''+t+'-status-content\' style=\'display:none\' class=\'status-content\'>');
348 W('<div id=\''+t+'-status-header\' class=\'status-header\'><p>Data current as of <span id=\''+t+'-status-time\'></span>.</p></div>');
349 W('<div id=\''+t+'-status-stats\'><div class=\'section-title\'>General Statistics</div><table class=\'tomato-grid status-table\' id=\''+t+'-status-stats-table\'></table><br></div>');
350 W('<div id=\''+t+'-status-errors\' class=\'error\'></div>');
351 W('</div>');
352 W('<div style=\'text-align:right\'><a href=\'javascript:updateStatus('+i+')\'>Refresh Status</a></div>');
353 W('</div>');
354 W('<input type="button" value="' + (eval('vpn'+(i+1)+'up') ? 'Stop' : 'Start') + ' Now" onclick="toggle(\'vpn'+t+'\', vpn'+(i+1)+'up)" id="_vpn'+t+'_button">');
355 W('</div>');
358 </script>
359 </div>
361 </td></tr>
362 <tr><td id='footer' colspan=2>
363 <span id='footer-msg'></span>
364 <input type='button' value='Save' id='save-button' onclick='save()'>
365 <input type='button' value='Cancel' id='cancel-button' onclick='javascript:reloadPage();'>
366 </td></tr>
367 </table>
368 </form>
369 <script type='text/javascript'>init();</script>
370 </body>
371 </html>