1 <!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML
4.0//EN'
>
4 Copyright (C) 2006-2010 Jonathan Zarate
5 http://www.polarcloud.com/tomato/
8 Copyright (C) 2014 Lance Fredrickson
9 lancethepants@gmail.com
11 For use with Tomato Firmware only.
12 No part of this file may be used without permission.
16 <meta http-equiv='content-type' content='text/html;charset=utf-
8'
>
17 <meta name='robots' content='noindex,nofollow'
>
18 <title>[<%
ident(); %>] Tinc Mesh VPN
</title>
19 <link rel='stylesheet' type='text/css' href='tomato.css'
>
20 <link rel='stylesheet' type='text/css' href='color.css'
>
21 <script type='text/javascript' src='tomato.js'
></script>
25 <style type='text/css'
>
55 <script type='text/javascript' src='debug.js'
></script>
57 <script type='text/javascript'
>
59 // <% nvram("tinc_wanup,tinc_name,tinc_devicetype,tinc_mode,tinc_vpn_netmask,tinc_private_rsa,tinc_private_ecdsa,tinc_custom,tinc_hosts,tinc_manual_firewall,tinc_manual_tinc_up,tinc_tinc_up,tinc_tinc_down,tinc_host_up,tinc_host_down,tinc_subnet_up,tinc_subnet_down"); %>
61 var tinc_compression
= [['0','0 - None'],['1','1 - Fast zlib'],['2','2'],['3','3'],['4','4'],['5','5'],['6','6'],['7','7'],['8','8'],['9','9 - Best zlib'],['10','10 - Fast lzo'],['11','11 - Best lzo']];
62 var th
= new TomatoGrid();
66 tabs
= [['config', 'Config'],['hosts', 'Hosts'],['scripts', 'Scripts'],['keys', 'Generate Keys'],['status', 'Status']];
68 tincup
= parseInt ('<% psup("tincd"); %>');
70 th
.setup = function() {
71 this.init('th-grid', '', 50, [
73 { type
: 'text', maxlen
: 30 },
74 { type
: 'text', maxlen
: 100 },
75 { type
: 'text', maxlen
: 5 },
76 { type
: 'select', options
: tinc_compression
},
77 { type
: 'text', maxlen
: 20 },
78 { type
: 'textarea', proxy
: "_host_rsa_key" },
79 { type
: 'textarea', proxy
: "_host_ecdsa_key" },
80 { type
: 'textarea', proxy
: "_host_custom" }
82 this.headerSet(['ConnectTo', 'Name', 'Address', 'Port', 'Compression', 'Subnet']);
83 var nv
= nvram
.tinc_hosts
.split('>');
84 for (var i
= 0; i
< nv
.length
; ++i
) {
85 var t
= nv
[i
].split('<');
88 this.insertData(-1, t
);
94 th
.dataToView = function(data
) {
95 return [(data
[0] != '0') ? 'On' : '', data
[1], data
[2], data
[3], data
[4] ,data
[5] ];
98 th
.fieldValuesToData = function(row
) {
99 var f
= fields
.getAll(row
);
100 return [f
[0].checked
? 1 : 0, f
[1].value
, f
[2].value
, f
[3].value
, f
[4].value
, f
[5].value
, _host_rsa_key
.value
, _host_ecdsa_key
.value
, _host_custom
.value
];
104 th
.resetNewEditor = function() {
105 var f
= fields
.getAll(this.newEditor
);
110 f
[4].selectedIndex
= 0;
112 _host_rsa_key
.value
= '';
113 _host_ecdsa_key
.value
= '';
114 _host_custom
.value
= '';
115 ferror
.clearAll(fields
.getAll(this.newEditor
));
116 ferror
.clear(_host_rsa_key
);
117 ferror
.clear(_host_ecdsa_key
);
120 th
.verifyFields = function(row
, quiet
) {
122 var f
= fields
.getAll(row
);
124 if (f
[1].value
== "") { ferror
.set(f
[1], "Host Name is required.", quiet
); return 0 ; }
125 if (f
[0].checked
&& f
[2].value
== "") { ferror
.set(f
[2], "Address must be supplied when ConnectTo is checked.", quiet
); return 0 ; }
126 if (!f
[3].value
== "" ) {
127 if (!v_port(f
[3], quiet
)) return 0 ;
130 if(_tinc_devicetype
.value
== 'tun'){
131 if ((!v_subnet(f
[5], 1)) && (!v_ip(f
[5], 1))) { ferror
.set(f
[5], "Invalid Subnet or IP address.", quiet
); return 0 ; }
133 else if (_tinc_devicetype
.value
== 'tap'){
134 if (f
[5].value
!= '') { ferror
.set(f
[5], "Subnet is left blank when using the TAP Interface Type.", quiet
); return 0 ; }
137 if (_host_rsa_key
.value
== "") { ferror
.set(_host_rsa_key
, "RSA Public Key is required.", quiet
); return 0 ; }
138 if (_host_ecdsa_key
.value
== "") { ferror
.set(_host_ecdsa_key
, "ECDSA Public Key is required.", quiet
); return 0 ; }
143 function verifyFields(focused
, quiet
)
150 // Visibility Changes
153 _tinc_vpn_netmask
: 1,
156 switch (E('_tinc_devicetype').value
) {
159 vis
._tinc_vpn_netmask
= 1 ;
163 vis
._tinc_vpn_netmask
= 0 ;
167 switch(E('_tinc_manual_tinc_up').value
) {
169 _tinc_tinc_up
.disabled
= 1 ;
172 _tinc_tinc_up
.disabled
= 0 ;
179 b
.disabled
= (c
!= 1);
180 PR(b
).style
.display
= c
? '' : 'none';
183 edges
.disabled
= !tincup
;
184 connections
.disabled
= !tincup
;
185 subnets
.disabled
= !tincup
;
186 nodes
.disabled
= !tincup
;
187 info
.disabled
= !tincup
;
188 hostselect
.disabled
= !tincup
;
190 // Element Verification
191 if (_tinc_name
.value
== "") { ferror
.set(_tinc_name
, "Host Name is required.", quiet
); return 0 ; }
192 if (_tinc_private_rsa
.value
== "") { ferror
.set(_tinc_private_rsa
, "RSA Private Key is required.", quiet
); return 0 ; }
193 if (_tinc_private_ecdsa
.value
== "") { ferror
.set(_tinc_private_ecdsa
, "ECDSA Private Key is required.", quiet
); return 0 ; }
194 if (!v_netmask('_tinc_vpn_netmask', quiet
)) return 0;
196 var hostdefined
= false;
197 var hosts
= th
.getAllData();
198 for (var i
= 0; i
< hosts
.length
; ++i
) {
199 if(hosts
[i
][1] == _tinc_name
.value
){
204 if (!hostdefined
) { ferror
.set(_tinc_name
, "Host Name \"" + _tinc_name
.value
+ "\" is not defined in hosts area.", quiet
); return 0 ; }
209 function escapeText(s
)
212 return '&#' + c
.charCodeAt(0) + ';';
214 return s
.replace(/[&"'<>]/g, esc
).replace(/\n/g, ' <br>').replace(/ /g
, ' ');
217 function spin(x
,which
)
219 E(which
).style
.visibility
= x
? 'visible' : 'hidden';
223 // Borrowed from http://snipplr.com/view/14074/
224 String
.prototype.between = function(prefix
, suffix
) {
226 var i
= s
.indexOf(prefix
);
228 s
= s
.substring(i
+ prefix
.length
);
234 i
= s
.indexOf(suffix
);
236 s
= s
.substring(0, i
);
245 function displayKeys()
247 _rsa_private_key
.value
= "-----BEGIN RSA PRIVATE KEY-----\n" + cmdresult
. between('-----BEGIN RSA PRIVATE KEY-----\n','\n-----END RSA PRIVATE KEY-----') + "\n-----END RSA PRIVATE KEY-----";
248 _rsa_public_key
.value
= "-----BEGIN RSA PUBLIC KEY-----\n" + cmdresult
. between('-----BEGIN RSA PUBLIC KEY-----\n','\n-----END RSA PUBLIC KEY-----') + "\n-----END RSA PUBLIC KEY-----";
249 _ecdsa_private_key
.value
= "-----BEGIN EC PRIVATE KEY-----\n" + cmdresult
. between('-----BEGIN EC PRIVATE KEY-----\n','\n-----END EC PRIVATE KEY-----') + "\n-----END EC PRIVATE KEY-----";
250 _ecdsa_public_key
.value
= cmdresult
. between('-----END EC PRIVATE KEY-----\n','\n');
253 spin(0,'generateWait');
254 E('execb').disabled
= 0;
257 function generateKeys()
259 E('execb').disabled
= 1;
260 spin(1,'generateWait');
262 _rsa_private_key
.value
= "";
263 _rsa_public_key
.value
= "";
264 _ecdsa_private_key
.value
= "";
265 _ecdsa_public_key
.value
= "";
268 cmd
.onCompleted = function(text
, xml
) {
272 cmd
.onError = function(x
) {
273 cmdresult
= 'ERROR: ' + x
;
277 var commands
= "/bin/rm -rf /etc/keys \n\
278 /bin/mkdir /etc/keys \n\
279 /bin/echo -e '\n\n\n\n' | /usr/sbin/tinc -c /etc/keys generate-keys \n\
280 /bin/cat /etc/keys/rsa_key.priv \n\
281 /bin/cat /etc/keys/rsa_key.pub \n\
282 /bin/cat /etc/keys/ecdsa_key.priv \n\
283 /bin/cat /etc/keys/ecdsa_key.pub";
285 cmd
.post('shell.cgi', 'action=execute&command=' + escapeCGI(commands
.replace(/\r/g, '')));
289 function displayStatus()
291 E('result').innerHTML
= '<tt>' + escapeText(cmdresult
) + '</tt>';
293 spin(0,'statusWait');
296 function updateStatus(type
)
298 E('result').innerHTML
= '';
299 spin(1,'statusWait');
302 cmd
.onCompleted = function(text
, xml
) {
306 cmd
.onError = function(x
) {
307 cmdresult
= 'ERROR: ' + x
;
312 var commands
= "/usr/sbin/tinc dump " + type
+ "\n";
316 var selects
= document
.getElementById("hostselect");
317 var commands
= "/usr/sbin/tinc " + type
+ " " + selects
.options
[selects
.selectedIndex
].text
+ "\n";
320 cmd
.post('shell.cgi', 'action=execute&command=' + escapeCGI(commands
.replace(/\r/g, '')));
324 function displayNodes()
327 var hostselect
=document
.getElementById("hostselect")
328 var selected
= hostselect
.value
;
330 while(hostselect
.firstChild
){
331 hostselect
.removeChild(hostselect
.firstChild
);
334 var hosts
= cmdresult
.split("\n");
336 for (var i
= 0; i
< hosts
.length
; ++i
)
339 hostselect
.options
[hostselect
.options
.length
]=new Option(hosts
[i
],hosts
[i
]);
340 if(hosts
[i
] == selected
){
341 hostselect
.value
= selected
;
349 function updateNodes()
355 cmd
.onCompleted = function(text
, xml
) {
359 cmd
.onError = function(x
) {
360 cmdresult
= 'ERROR: ' + x
;
364 var commands
= "/usr/sbin/tinc dump nodes | /bin/busybox awk '{print $1}'";
365 cmd
.post('shell.cgi', 'action=execute&command=' + escapeCGI(commands
.replace(/\r/g, '')));
369 function displayVersion()
371 E('version').innerHTML
= "<small>Tinc " + escapeText(cmdresult
) + "</small>";
375 function getVersion()
378 cmd
.onCompleted = function(text
, xml
) {
382 cmd
.onError = function(x
) {
383 cmdresult
= 'ERROR: ' + x
;
387 var commands
= "/usr/sbin/tinc --version | /bin/busybox awk 'NR==1 {print $3}'";
388 cmd
.post('shell.cgi', 'action=execute&command=' + escapeCGI(commands
.replace(/\r/g, '')));
391 function tabSelect(name
)
394 cookie
.set('vpn_tinc_tab', name
);
397 for (var i
= 0; i
< tabs
.length
; ++i
)
399 var on
= (name
== tabs
[i
][0]);
400 elem
.display(tabs
[i
][0] + '-tab', on
);
405 function toggle(service
, isup
)
408 var data
= th
.getAllData();
410 for (var i
= 0; i
< data
.length
; ++i
) {
411 s
+= data
[i
].join('<') + '>';
414 if (nvram
.tinc_hosts
!= s
)
418 if (!confirm("Unsaved changes will be lost. Continue anyway?")) return;
421 E('_' + service
+ '_button1').disabled
= true;
422 E('_' + service
+ '_button2').disabled
= true;
423 E('_' + service
+ '_button3').disabled
= true;
424 E('_' + service
+ '_button4').disabled
= true;
425 form
.submitHidden('/service.cgi', {
426 _redirect
: 'vpn-tinc.asp',
427 _sleep
: ((service
== 'tinc') && (!isup
)) ? '3' : '3',
428 _service
: service
+ (isup
? '-stop' : '-start')
434 if (!verifyFields(null, false)) return;
435 if (th
.isEditing()) return;
437 var data
= th
.getAllData();
439 for (var i
= 0; i
< data
.length
; ++i
) {
440 s
+= data
[i
].join('<') + '>';
443 fom
.tinc_hosts
.value
= s
;
444 fom
.tinc_wanup
.value
= fom
.f_tinc_wanup
.checked
? 1 : 0;
448 fom
._service
.value
= 'tinc-restart';
458 verifyFields(null, true);
462 if (((c
= cookie
.get('vpn_tinc_hosts_vis')) != null) && (c
== '1')) toggleVisibility("hosts");
469 tabSelect(cookie
.get('vpn_tinc_tab') || 'config');
472 function toggleVisibility(whichone
) {
473 if (E('sesdiv_' + whichone
).style
.display
== '') {
474 E('sesdiv_' + whichone
).style
.display
= 'none';
475 E('sesdiv_' + whichone
+ '_showhide').innerHTML
= '(Click here to show)';
476 cookie
.set('vpn_tinc_' + whichone
+ '_vis', 0);
478 E('sesdiv_' + whichone
).style
.display
='';
479 E('sesdiv_' + whichone
+ '_showhide').innerHTML
= '(Click here to hide)';
480 cookie
.set('vpn_tinc_' + whichone
+ '_vis', 1);
487 <body onload='init()'
>
488 <form id='_fom' method='post' action='tomato.cgi'
>
490 <table id='container' cellspacing=
0>
491 <tr><td colspan=
2 id='header'
>
492 <div class='title'
>Tomato
</div>
493 <div class='version'
>Version <%
version(); %></div>
495 <tr id='body'
><td id='navi'
><script type='text/javascript'
>navi()</script></td>
497 <div id='ident'
><%
ident(); %></div>
501 <input type='hidden' name='_nextpage' value='vpn-tinc.asp'
>
502 <input type='hidden' name='_service' value=''
>
504 <div class='section-title' style='float:right' id='version'
></div>
505 <div class='section-title'
>Tinc Configuration
</div>
508 <script type='text/javascript'
>
510 // -------- BEGIN CONFIG TAB -----------
511 tabCreate
.apply(this, tabs
);
514 W('<div id=\''+t
+'-tab\'>');
516 W('<input type=\'hidden\' name=\'tinc_wanup\'>');
517 W('<div class=\'section\'>');
519 createFieldTable('', [
520 { title
: 'Start With WAN ', name
: 'f_tinc_wanup', type
: 'checkbox', value
: (nvram
.tinc_wanup
== 1) },
521 { title
: 'Interface Type', name
: 'tinc_devicetype', type
: 'select', options
: [['tun','TUN'],['tap','TAP']], value
: nvram
.tinc_devicetype
},
522 { title
: 'Mode', name
: 'tinc_mode', type
: 'select', options
: [['switch','Switch'],['hub','Hub']], value
: nvram
.tinc_mode
},
523 { title
: 'VPN Netmask', name
: 'tinc_vpn_netmask', type
: 'text', maxlen
: 15, size
: 25, value
: nvram
.tinc_vpn_netmask
, suffix
: ' <small>The netmask for the entire VPN network.</small>' },
524 { title
: 'Host Name', name
: 'tinc_name', type
: 'text', maxlen
: 30, size
: 25, value
: nvram
.tinc_name
, suffix
: ' <small>Must also be defined in the \'Hosts\' area.</small>' },
525 { title
: 'Firewall', name
: 'tinc_manual_firewall', type
: 'select', options
: [['0','Automatic'],['1','Manual']], value
: nvram
.tinc_manual_firewall
},
526 { title
: 'RSA Private Key', name
: 'tinc_private_rsa', type
: 'textarea', value
: nvram
.tinc_private_rsa
},
527 { title
: 'ECDSA Private Key', name
: 'tinc_private_ecdsa', type
: 'textarea', value
: nvram
.tinc_private_ecdsa
},
528 { title
: 'Custom', name
: 'tinc_custom', type
: 'textarea', value
: nvram
.tinc_custom
}
532 W('<input type="button" value="' + (tincup
? 'Stop' : 'Start') + ' Now" onclick="toggle(\'tinc\', tincup)" id="_tinc_button1">');
534 // -------- END CONFIG TAB -----------
537 // -------- BEGIN HOSTS TAB -----------
539 W('<div id=\''+t
+'-tab\'>');
541 W('<div class=\'section\'>');
542 W('<input type=\'hidden\' name=\'tinc_hosts\'>');
543 W('<table class=\'tomato-grid\' cellspacing=1 id=\'th-grid\'></table>');
547 createFieldTable('', [
548 { title
: 'RSA Public Key', name
: 'host_rsa_key', type
: 'textarea' },
549 { title
: 'ECDSA Public Key', name
: 'host_ecdsa_key', type
: 'textarea' },
550 { title
: 'Custom', name
: 'host_custom', type
: 'textarea' }
554 W('<input type="button" value="' + (tincup
? 'Stop' : 'Start') + ' Now" onclick="toggle(\'tinc\', tincup)" id="_tinc_button2">');
559 W('<div class=\'section-title\'>Notes <small><i><a href=\'javascript:toggleVisibility(\"hosts\");\'><span id=\'sesdiv_hosts_showhide\'>(Click here to show)</span></a></i></small></div>');
560 W('<div class=\'section\' id=\'sesdiv_hosts\' style=\'display:none\'>');
562 W('<li><b>ConnectTo</b> - Tinc will try to establish a meta-connection to the host. Requires the Address field');
563 W('<li><b>Name</b> - Name of the host. There must be an entry for this host.');
564 W('<li><b>Address</b> <i>(optional)</i> - Must resolve to the external IP address where the host can be reached.');
565 W('<li><b>Port</b> <i>(optional)</i> - The port the host listens on. If empty the default value (655) is used.');
566 W('<li><b>Compression</b> - The level of compression used for UDP packets. Possible values are ');
567 W('0 (off), 1 (fast zlib) and any integer up to 9 (best zlib), 10 (fast lzo) and 11 (best lzo).');
568 W('<li><b>Subnet</b> - The subnet which the host will serve.');
574 // ---------- END HOSTS TAB ------------
577 // -------- BEGIN SCRIPTS TAB -----------
579 W('<div id=\''+t
+'-tab\'>');
581 W('<div class=\'section\'>');
583 createFieldTable('', [
584 { title
: 'tinc-up creation', name
: 'tinc_manual_tinc_up', type
: 'select', options
: [['0','Automatic'],['1','Manual']], value
: nvram
.tinc_manual_tinc_up
},
585 { title
: 'tinc-up', name
: 'tinc_tinc_up', type
: 'textarea', value
: nvram
.tinc_tinc_up
},
586 { title
: 'tinc-down', name
: 'tinc_tinc_down', type
: 'textarea', value
: nvram
.tinc_tinc_down
},
587 { title
: 'host-up', name
: 'tinc_host_up', type
: 'textarea', value
: nvram
.tinc_host_up
},
588 { title
: 'host-down', name
: 'tinc_host_down', type
: 'textarea', value
: nvram
.tinc_host_down
},
589 { title
: 'subnet-up', name
: 'tinc_subnet_up', type
: 'textarea', value
: nvram
.tinc_subnet_up
},
590 { title
: 'subnet-down', name
: 'tinc_subnet_down', type
: 'textarea', value
: nvram
.tinc_subnet_down
}
594 W('<input type="button" value="' + (tincup
? 'Stop' : 'Start') + ' Now" onclick="toggle(\'tinc\', tincup)" id="_tinc_button3">');
596 // -------- END SCRIPTS TAB -----------
598 // -------- BEGIN KEYS TAB -----------
600 W('<div id=\''+t
+'-tab\'>');
602 W('<div class=\'section\'>');
604 createFieldTable('', [
605 { title
: 'RSA Private Key', name
: 'rsa_private_key', type
: 'textarea', value
: "" },
606 { title
: 'RSA Public Key', name
: 'rsa_public_key', type
: 'textarea', value
: "" },
607 { title
: 'ECDSA Private Key', name
: 'ecdsa_private_key', type
: 'textarea', value
: "" },
608 { title
: 'ECDSA Public Key', name
: 'ecdsa_public_key', type
: 'textarea', value
: "" }
612 W('<div style=\'float:left\'><input type=\'button\' value=\'Generate Keys\' onclick=\'generateKeys()\' id=\'execb\'></div>');
613 W('<div style=\"visibility:hidden;text-align:right\" id=\"generateWait\">Please wait... <img src=\'spin.gif\' style=\"vertical-align:top\"></div>');
616 // -------- END KEY TAB -----------
618 // -------- BEGIN STATUS TAB -----------
621 W('<div id=\''+t
+'-tab\'>');
624 W('<div class=\'section\'>');
625 W('Tinc is currently '+(!tincup
? 'stopped.' : 'running.')+' ');
626 W('<input type="button" value="' + (tincup
? 'Stop' : 'Start') + ' Now" onclick="toggle(\'tinc\', tincup)" id="_tinc_button4">');
630 W('<div class=\'section\'>');
632 W('<div style=\'float:left\'><input type=\'button\' value=\'Edges\' onclick=\'updateStatus(\"edges\")\' id=\'edges\' style=\"width:85px\"></div>');
633 W('<div style=\'float:left\'><input type=\'button\' value=\'Subnets\' onclick=\'updateStatus(\"subnets\")\' id=\'subnets\' style=\"width:85px\"></div>');
634 W('<div style=\'float:left\'><input type=\'button\' value=\'Connections\' onclick=\'updateStatus(\"connections\")\' id=\'connections\' style=\"width:85px\"></div>');
635 W('<div style=\'float:left\'><input type=\'button\' value=\'Nodes\' onclick=\'updateStatus(\"nodes\")\' id=\'nodes\' style=\"width:85px\"></div>');
636 W('<div style=\"visibility:hidden;text-align:right\" id=\"statusWait\">Please wait... <img src=\'spin.gif\' style=\"vertical-align:top\"></div>');
640 W('<div class=\'section\'>');
641 W('<input type=\'button\' value=\'Info\' onclick=\'updateStatus(\"info\")\' id=\'info\' style=\"width:85px\">');
642 W('<select id=\'hostselect\' style=\"width:170px\"></select>');
645 W('<pre id=\'result\'></pre>');
648 // -------- END KEY TAB -----------
655 <tr><td id='footer' colspan=
2>
656 <span id='footer-msg'
></span>
657 <input type='button' value='Save' id='save-button' onclick='save()'
>
658 <input type='button' value='Cancel' id='cancel-button' onclick='reloadPage();'
>
662 <script type='text/javascript'
>
664 verifyFields(null,true);