2019-08-20 13:31:35 +00:00
'use strict' ;
2022-05-17 13:45:20 +00:00
'require fs' ;
2020-08-18 20:51:07 +00:00
'require ui' ;
2022-05-17 13:45:20 +00:00
'require dom' ;
2020-03-03 20:14:04 +00:00
'require uci' ;
2020-08-18 20:51:07 +00:00
'require rpc' ;
2019-08-20 13:31:35 +00:00
'require form' ;
'require network' ;
2022-05-17 13:45:20 +00:00
'require validation' ;
2019-08-20 13:31:35 +00:00
2020-08-18 20:51:07 +00:00
var generateKey = rpc . declare ( {
object : 'luci.wireguard' ,
method : 'generateKeyPair' ,
expect : { keys : { } }
} ) ;
2021-11-11 00:31:43 +00:00
var getPublicAndPrivateKeyFromPrivate = rpc . declare ( {
object : 'luci.wireguard' ,
method : 'getPublicAndPrivateKeyFromPrivate' ,
params : [ 'privkey' ] ,
expect : { keys : { } }
} ) ;
2022-03-18 13:47:44 +00:00
var generatePsk = rpc . declare ( {
object : 'luci.wireguard' ,
method : 'generatePsk' ,
expect : { psk : '' }
} ) ;
2022-05-17 13:45:20 +00:00
var qrIcon = '<svg viewBox="0 0 29 29" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M0 0h29v29H0z"/><path d="M4 4h1v1H4zM5 4h1v1H5zM6 4h1v1H6zM7 4h1v1H7zM8 4h1v1H8zM9 4h1v1H9zM10 4h1v1h-1zM12 4h1v1h-1zM13 4h1v1h-1zM14 4h1v1h-1zM15 4h1v1h-1zM16 4h1v1h-1zM18 4h1v1h-1zM19 4h1v1h-1zM20 4h1v1h-1zM21 4h1v1h-1zM22 4h1v1h-1zM23 4h1v1h-1zM24 4h1v1h-1zM4 5h1v1H4zM10 5h1v1h-1zM12 5h1v1h-1zM14 5h1v1h-1zM16 5h1v1h-1zM18 5h1v1h-1zM24 5h1v1h-1zM4 6h1v1H4zM6 6h1v1H6zM7 6h1v1H7zM8 6h1v1H8zM10 6h1v1h-1zM12 6h1v1h-1zM18 6h1v1h-1zM20 6h1v1h-1zM21 6h1v1h-1zM22 6h1v1h-1zM24 6h1v1h-1zM4 7h1v1H4zM6 7h1v1H6zM7 7h1v1H7zM8 7h1v1H8zM10 7h1v1h-1zM12 7h1v1h-1zM13 7h1v1h-1zM14 7h1v1h-1zM15 7h1v1h-1zM18 7h1v1h-1zM20 7h1v1h-1zM21 7h1v1h-1zM22 7h1v1h-1zM24 7h1v1h-1zM4 8h1v1H4zM6 8h1v1H6zM7 8h1v1H7zM8 8h1v1H8zM10 8h1v1h-1zM16 8h1v1h-1zM18 8h1v1h-1zM20 8h1v1h-1zM21 8h1v1h-1zM22 8h1v1h-1zM24 8h1v1h-1zM4 9h1v1H4zM10 9h1v1h-1zM12 9h1v1h-1zM13 9h1v1h-1zM15 9h1v1h-1zM18 9h1v1h-1zM24 9h1v1h-1zM4 10h1v1H4zM5 10h1v1H5zM6 10h1v1H6zM7 10h1v1H7zM8 10h1v1H8zM9 10h1v1H9zM10 10h1v1h-1zM12 10h1v1h-1zM14 10h1v1h-1zM16 10h1v1h-1zM18 10h1v1h-1zM19 10h1v1h-1zM20 10h1v1h-1zM21 10h1v1h-1zM22 10h1v1h-1zM23 10h1v1h-1zM24 10h1v1h-1zM13 11h1v1h-1zM14 11h1v1h-1zM15 11h1v1h-1zM16 11h1v1h-1zM4 12h1v1H4zM5 12h1v1H5zM8 12h1v1H8zM9 12h1v1H9zM10 12h1v1h-1zM13 12h1v1h-1zM15 12h1v1h-1zM19 12h1v1h-1zM21 12h1v1h-1zM22 12h1v1h-1zM23 12h1v1h-1zM24 12h1v1h-1zM5 13h1v1H5zM6 13h1v1H6zM8 13h1v1H8zM11 13h1v1h-1zM13 13h1v1h-1zM14 13h1v1h-1zM15 13h1v1h-1zM16 13h1v1h-1zM19 13h1v1h-1zM22 13h1v1h-1zM4 14h1v1H4zM5 14h1v1H5zM9 14h1v1H9zM10 14h1v1h-1zM11 14h1v1h-1zM15 14h1v1h-1zM18 14h1v1h-1zM19 14h1v1h-1zM20 14h1v1h-1zM21 14h1v1h-1zM22 14h1v1h-1zM23 14h1v1h-1zM7 15h1v1H7zM8 15h1v1H8zM9 15h1v1H9zM11 15h1v1h-1zM12 15h1v1h-1zM13 15h1v1h-1zM17 15h1v1h-1zM18 15h1v1h-1zM20 15h1v1h-1zM21 15h1v1h-1zM23 15h1v1h-1zM4 16h1v1H4zM6 16h1v1H6zM10 16h1v1h-1zM11 16h1v1h-1zM13 16h1v1h-1zM14 16h1v1h-1zM16 16h1v1h-1zM17 16h1v1h-1zM18 16h1v1h-1zM22 16h1v1h-1zM23 16h1v1h-1zM24 16h1v1h-1zM12 17h1v1h-1zM16 17h1v1h-1zM17 17h1v1h-1zM18 17h1v1h-1zM4 18h1v1H4zM5 18h1v1H5zM6 18h1v1H6zM7 18h1v1H7zM8 18h1v1H8zM9 18h1v1H9zM10 18h1v1h-1zM14 18h1v1h-1zM16 18h1v1h-1zM17 18h1v1h-1zM21 18h1v1h-1zM22 18h1v1h-1zM23 18h1v1h-1zM4 19h1v1H4zM10 19h1v1h-1zM12 19h1v1h-1zM13 19h1v1h-1zM15 19h1v1h-1zM16 19h1v1h-1zM19 19h1v1h-1zM21 19h1v1h-1zM23 19h1v1h-1zM24 19h1v1h-1zM4 20h1v1H4zM6 20h1v1H6zM7 20h1v1H7zM8 20h1v1H8zM10 20h1v1h-1zM12 20h1v1h-1zM13 20h1v1h-1zM15 20h1v1h-1zM18 20h1v1h-1zM19 20h1v1h-1zM20 20h1v1h-1zM22 20h1v1h-1zM23 20h1v1h-1zM24 20h1v1h-1zM4 21h1v1H4zM6 21h1v1H6zM7 21h1v1H7zM8 21h1v1H8zM10 21h1v1h-1zM13 21h1v1h-1zM15 21h1v1h-1zM16 21h1v1h-1zM19 21h1v1h-1zM21 21h1v1h-1zM23 21h1v1h-1zM24 21h1v1h-1zM4 22h1v1H4zM6 22h1v1H6zM7 22h1v1H7zM8 22h1v1H8zM10 22h1v1h-1zM13 22h1v1h-1zM15 22h1v1h-1zM18 22h1v1h-1zM19 22h1v1h-1zM20 22h1v1h-1zM21 22h1v1h-1zM22 22h1v1h-1zM4 23h1v1H4zM10 23h1v1h-1zM12 23h1v1h-1zM13 23h1v1h-1zM14 23h1v1h-1zM17 23h1v1h-1zM18 23h1v1h-1zM20 23h1v1h-1zM22 23h1v1h-1zM4 24h1v1H4zM5 24h1v1H5zM6 24h1v1H6zM7 24h1v1H7zM8 24h1v1H8zM9 24h1v1H9zM10 24h1v1h-1zM12 24h1v1h-1zM13 24h1v1h-1zM14 24h1v1h-1zM16 24h1v1h-1zM17 24h1v1h-1zM18 24h1v1h-1zM22 24h1v1h-1zM24 24h1v1h-1z"/></svg>' ;
2019-08-20 13:31:35 +00:00
function validateBase64 ( section _id , value ) {
2019-09-17 06:28:45 +00:00
if ( value . length == 0 )
return true ;
2020-01-21 17:38:04 +00:00
if ( value . length != 44 || ! value . match ( /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/ ) )
2019-08-20 13:31:35 +00:00
return _ ( 'Invalid Base64 key string' ) ;
2020-05-12 12:45:47 +00:00
if ( value [ 43 ] != "=" )
return _ ( 'Invalid Base64 key string' ) ;
2019-08-20 13:31:35 +00:00
return true ;
}
2022-05-17 13:45:20 +00:00
var stubValidator = {
factory : validation ,
apply : function ( type , value , args ) {
if ( value != null )
this . value = value ;
2021-09-08 10:56:10 +00:00
2022-05-17 13:45:20 +00:00
return validation . types [ type ] . apply ( this , args ) ;
} ,
assert : function ( condition ) {
return ! ! condition ;
}
} ;
2021-09-08 10:56:10 +00:00
2021-09-10 12:01:53 +00:00
function generateDescription ( name , texts ) {
return E ( 'li' , { 'style' : 'color: inherit;' } , [
E ( 'span' , name ) ,
E ( 'ul' , texts . map ( function ( text ) {
return E ( 'li' , { 'style' : 'color: inherit;' } , text ) ;
} ) )
] ) ;
}
2022-05-17 13:45:20 +00:00
function invokeQREncode ( data , code ) {
return fs . exec _direct ( '/usr/bin/qrencode' , [
'--inline' , '--8bit' , '--type=SVG' ,
'--output=-' , '--' , data
] ) . then ( function ( svg ) {
code . style . opacity = '' ;
dom . content ( code , Object . assign ( E ( svg ) , { style : 'width:100%;height:auto' } ) ) ;
} ) . catch ( function ( error ) {
code . style . opacity = '' ;
if ( L . isObject ( error ) && error . name == 'NotFoundError' ) {
dom . content ( code , [
Object . assign ( E ( qrIcon ) , { style : 'width:32px;height:32px;opacity:.2' } ) ,
E ( 'p' , _ ( 'The <em>qrencode</em> package is required for generating an QR code image of the configuration.' ) )
] ) ;
}
else {
dom . content ( code , [
_ ( 'Unable to generate QR code: %s' ) . format ( L . isObject ( error ) ? error . message : error )
] ) ;
}
} ) ;
}
var cbiKeyPairGenerate = form . DummyValue . extend ( {
cfgvalue : function ( section _id , value ) {
return E ( 'button' , {
'class' : 'btn' ,
'click' : ui . createHandlerFn ( this , function ( section _id , ev ) {
var prv = this . section . getUIElement ( section _id , 'private_key' ) ,
pub = this . section . getUIElement ( section _id , 'public_key' ) ,
map = this . map ;
if ( ( prv . getValue ( ) || pub . getValue ( ) ) && ! confirm ( _ ( 'Do you want to replace the current keys?' ) ) )
return ;
return generateKey ( ) . then ( function ( keypair ) {
prv . setValue ( keypair . priv ) ;
pub . setValue ( keypair . pub ) ;
map . save ( null , true ) ;
} ) ;
} , section _id )
} , [ _ ( 'Generate new key pair' ) ] ) ;
}
} ) ;
function handleWindowDragDropIgnore ( ev ) {
ev . preventDefault ( )
}
2019-08-20 13:31:35 +00:00
return network . registerProtocol ( 'wireguard' , {
getI18n : function ( ) {
return _ ( 'WireGuard VPN' ) ;
} ,
getIfname : function ( ) {
return this . _ubus ( 'l3_device' ) || this . sid ;
} ,
getOpkgPackage : function ( ) {
return 'wireguard-tools' ;
} ,
isFloating : function ( ) {
return true ;
} ,
isVirtual : function ( ) {
return true ;
} ,
getDevices : function ( ) {
return null ;
} ,
containsDevice : function ( ifname ) {
return ( network . getIfnameOf ( ifname ) == this . getIfname ( ) ) ;
} ,
renderFormOptions : function ( s ) {
2022-05-17 13:45:20 +00:00
var o , ss , ss2 ;
2019-08-20 13:31:35 +00:00
// -- general ---------------------------------------------------------------------
o = s . taboption ( 'general' , form . Value , 'private_key' , _ ( 'Private Key' ) , _ ( 'Required. Base64-encoded private key for this interface.' ) ) ;
o . password = true ;
o . validate = validateBase64 ;
o . rmempty = false ;
2021-11-11 00:31:43 +00:00
var serverName = this . getIfname ( ) ;
o = s . taboption ( 'general' , form . Value , 'public_key' , _ ( 'Public Key' ) , _ ( 'Base64-encoded public key of this interface for sharing.' ) ) ;
o . rmempty = false ;
o . write = function ( ) { /* write nothing */ } ;
2022-05-17 13:45:20 +00:00
o . load = function ( section _id ) {
var privKey = s . formvalue ( section _id , 'private_key' ) || uci . get ( 'network' , section _id , 'private_key' ) ;
return getPublicAndPrivateKeyFromPrivate ( privKey ) . then (
2021-11-11 00:31:43 +00:00
function ( keypair ) {
return keypair . pub || '' ;
2022-05-17 13:45:20 +00:00
} ,
function ( error ) {
2021-11-11 00:31:43 +00:00
return _ ( 'Error getting PublicKey' ) ;
} , this )
} ;
2022-05-17 13:45:20 +00:00
s . taboption ( 'general' , cbiKeyPairGenerate , '_gen_server_keypair' , ' ' ) ;
2020-08-18 20:51:07 +00:00
2019-08-20 13:31:35 +00:00
o = s . taboption ( 'general' , form . Value , 'listen_port' , _ ( 'Listen Port' ) , _ ( 'Optional. UDP port used for outgoing and incoming packets.' ) ) ;
o . datatype = 'port' ;
o . placeholder = _ ( 'random' ) ;
o . optional = true ;
o = s . taboption ( 'general' , form . DynamicList , 'addresses' , _ ( 'IP Addresses' ) , _ ( 'Recommended. IP addresses of the WireGuard interface.' ) ) ;
o . datatype = 'ipaddr' ;
o . optional = true ;
2020-02-08 18:05:50 +00:00
o = s . taboption ( 'general' , form . Flag , 'nohostroute' , _ ( 'No Host Routes' ) , _ ( 'Optional. Do not create host routes to peers.' ) ) ;
o . optional = true ;
2019-08-20 13:31:35 +00:00
2022-05-17 13:45:20 +00:00
o = s . taboption ( 'general' , form . Button , '_import' , _ ( 'Import configuration' ) , _ ( 'Imports settings from an existing WireGuard configuration file' ) ) ;
o . inputtitle = _ ( 'Load configuration…' ) ;
o . onclick = function ( ) {
2022-07-23 18:24:09 +00:00
return ss . handleConfigImport ( 'full' ) ;
2022-05-17 13:45:20 +00:00
} ;
2019-08-20 13:31:35 +00:00
// -- advanced --------------------------------------------------------------------
o = s . taboption ( 'advanced' , form . Value , 'mtu' , _ ( 'MTU' ) , _ ( 'Optional. Maximum Transmission Unit of tunnel interface.' ) ) ;
2022-04-14 09:59:56 +00:00
o . datatype = 'range(0,8940)' ;
2019-08-20 13:31:35 +00:00
o . placeholder = '1420' ;
o . optional = true ;
o = s . taboption ( 'advanced' , form . Value , 'fwmark' , _ ( 'Firewall Mark' ) , _ ( 'Optional. 32-bit mark for outgoing encrypted packets. Enter value in hex, starting with <code>0x</code>.' ) ) ;
o . optional = true ;
o . validate = function ( section _id , value ) {
2021-06-02 18:35:32 +00:00
if ( value . length > 0 && ! value . match ( /^0x[a-fA-F0-9]{1,8}$/ ) )
2019-08-20 13:31:35 +00:00
return _ ( 'Invalid hexadecimal value' ) ;
return true ;
} ;
// -- peers -----------------------------------------------------------------------
try {
s . tab ( 'peers' , _ ( 'Peers' ) , _ ( 'Further information about WireGuard interfaces and peers at <a href=\'http://wireguard.com\'>wireguard.com</a>.' ) ) ;
}
catch ( e ) { }
2021-11-21 22:31:13 +00:00
o = s . taboption ( 'peers' , form . SectionValue , '_peers' , form . GridSection , 'wireguard_%s' . format ( s . section ) ) ;
2019-08-20 13:31:35 +00:00
o . depends ( 'proto' , 'wireguard' ) ;
ss = o . subsection ;
ss . anonymous = true ;
ss . addremove = true ;
ss . addbtntitle = _ ( 'Add peer' ) ;
2021-11-21 22:31:13 +00:00
ss . nodescriptions = true ;
ss . modaltitle = _ ( 'Edit peer' ) ;
2019-08-20 13:31:35 +00:00
2022-05-17 13:45:20 +00:00
ss . handleDragConfig = function ( ev ) {
ev . stopPropagation ( ) ;
ev . preventDefault ( ) ;
ev . dataTransfer . dropEffect = 'copy' ;
} ;
ss . handleDropConfig = function ( mode , ev ) {
var file = ev . dataTransfer . files [ 0 ] ,
nodes = ev . currentTarget ,
input = nodes . querySelector ( 'textarea' ) ,
reader = new FileReader ( ) ;
if ( file ) {
reader . onload = function ( rev ) {
input . value = rev . target . result . trim ( ) ;
ss . handleApplyConfig ( mode , nodes , file . name , ev ) ;
} ;
reader . readAsText ( file ) ;
}
ev . stopPropagation ( ) ;
ev . preventDefault ( ) ;
} ;
ss . parseConfig = function ( data ) {
var lines = String ( data ) . split ( /(\r?\n)+/ ) ,
section = null ,
config = { } ;
for ( var i = 0 ; i < lines . length ; i ++ ) {
var line = lines [ i ] . replace ( /#.*$/ , '' ) . trim ( ) ;
if ( line . match ( /^\[(\w+)\]$/ ) ) {
section = RegExp . $1 . toLowerCase ( ) ;
}
else if ( section && line . match ( /^(\w+)\s*=\s*(.+)$/ ) ) {
var key = RegExp . $1 ,
val = RegExp . $2 . trim ( ) ;
if ( val . length )
config [ section + '_' + key . toLowerCase ( ) ] = val ;
}
}
if ( config . interface _address ) {
config . interface _address = config . interface _address . split ( /[, ]+/ ) ;
for ( var i = 0 ; i < config . interface _address . length ; i ++ )
if ( ! stubValidator . apply ( 'ipaddr' , config . interface _address [ i ] ) )
return _ ( 'Address setting is invalid' ) ;
}
if ( config . interface _dns ) {
config . interface _dns = config . interface _dns . split ( /[, ]+/ ) ;
for ( var i = 0 ; i < config . interface _dns . length ; i ++ )
if ( ! stubValidator . apply ( 'ipaddr' , config . interface _dns [ i ] , [ 'nomask' ] ) )
return _ ( 'DNS setting is invalid' ) ;
}
if ( ! config . interface _privatekey || validateBase64 ( null , config . interface _privatekey ) !== true )
return _ ( 'PrivateKey setting is missing or invalid' ) ;
if ( ! stubValidator . apply ( 'port' , config . interface _listenport || '0' ) )
return _ ( 'ListenPort setting is invalid' ) ;
2022-07-23 18:24:09 +00:00
if ( config . peer _publickey != null && validateBase64 ( null , config . peer _publickey ) !== true )
return _ ( 'PublicKey setting is invalid' ) ;
2022-05-17 13:45:20 +00:00
if ( config . peer _presharedkey != null && validateBase64 ( null , config . peer _presharedkey ) !== true )
return _ ( 'PresharedKey setting is invalid' ) ;
if ( config . peer _allowedips ) {
config . peer _allowedips = config . peer _allowedips . split ( /[, ]+/ ) ;
for ( var i = 0 ; i < config . peer _allowedips . length ; i ++ )
if ( ! stubValidator . apply ( 'ipaddr' , config . peer _allowedips [ i ] ) )
return _ ( 'AllowedIPs setting is invalid' ) ;
}
else {
config . peer _allowedips = [ '0.0.0.0/0' , '::/0' ] ;
}
if ( config . peer _endpoint ) {
var host _port = config . peer _endpoint . match ( /^\[([a-fA-F0-9:]+)\]:(\d+)$/ ) || config . peer _endpoint . match ( /^(.+):(\d+)$/ ) ;
if ( ! host _port || ! stubValidator . apply ( 'host' , host _port [ 1 ] ) || ! stubValidator . apply ( 'port' , host _port [ 2 ] ) )
return _ ( 'Endpoint setting is invalid' ) ;
config . peer _endpoint = [ host _port [ 1 ] , host _port [ 2 ] ] ;
}
if ( config . peer _persistentkeepalive == 'off' || config . peer _persistentkeepalive == '0' )
delete config . peer _persistentkeepalive ;
if ( ! stubValidator . apply ( 'port' , config . peer _persistentkeepalive || '0' ) )
return _ ( 'PersistentKeepAlive setting is invalid' ) ;
return config ;
} ;
ss . handleApplyConfig = function ( mode , nodes , comment , ev ) {
var input = nodes . querySelector ( 'textarea' ) . value ,
error = nodes . querySelector ( '.alert-message' ) ,
cancel = nodes . nextElementSibling . querySelector ( '.btn' ) ,
config = this . parseConfig ( input ) ;
if ( typeof ( config ) == 'string' ) {
error . firstChild . data = _ ( 'Cannot parse configuration: %s' ) . format ( config ) ;
error . style . display = 'block' ;
return ;
}
2022-07-23 18:24:09 +00:00
if ( mode == 'full' ) {
2022-05-17 13:45:20 +00:00
var prv = s . formvalue ( s . section , 'private_key' ) ;
if ( prv && prv != config . interface _privatekey && ! confirm ( _ ( 'Overwrite the current settings with the imported configuration?' ) ) )
return ;
return getPublicAndPrivateKeyFromPrivate ( config . interface _privatekey ) . then ( function ( keypair ) {
s . getOption ( 'private_key' ) . getUIElement ( s . section ) . setValue ( keypair . priv ) ;
s . getOption ( 'public_key' ) . getUIElement ( s . section ) . setValue ( keypair . pub ) ;
s . getOption ( 'listen_port' ) . getUIElement ( s . section ) . setValue ( config . interface _listenport || '' ) ;
s . getOption ( 'addresses' ) . getUIElement ( s . section ) . setValue ( config . interface _address ) ;
if ( config . interface _dns ) {
s . getOption ( 'peerdns' ) . getUIElement ( s . section ) . setValue ( '0' ) ;
s . getOption ( 'dns' ) . getUIElement ( s . section ) . setValue ( config . interface _dns ) ;
}
var sid = uci . add ( 'network' , 'wireguard_' + s . section ) ;
uci . sections ( 'network' , 'wireguard_' + s . section , function ( peer ) {
if ( peer . public _key == config . peer _publickey )
uci . remove ( 'network' , peer [ '.name' ] ) ;
} ) ;
uci . set ( 'network' , sid , 'description' , comment || _ ( 'Imported peer configuration' ) ) ;
uci . set ( 'network' , sid , 'public_key' , config . peer _publickey ) ;
uci . set ( 'network' , sid , 'preshared_key' , config . peer _presharedkey ) ;
uci . set ( 'network' , sid , 'allowed_ips' , config . peer _allowedips ) ;
uci . set ( 'network' , sid , 'persistent_keepalive' , config . peer _persistentkeepalive ) ;
if ( config . peer _endpoint ) {
uci . set ( 'network' , sid , 'endpoint_host' , config . peer _endpoint [ 0 ] ) ;
uci . set ( 'network' , sid , 'endpoint_port' , config . peer _endpoint [ 1 ] ) ;
}
return s . map . save ( null , true ) ;
} ) . then ( function ( ) {
cancel . click ( ) ;
} ) ;
}
else {
return getPublicAndPrivateKeyFromPrivate ( config . interface _privatekey ) . then ( function ( keypair ) {
var sid = uci . add ( 'network' , 'wireguard_' + s . section ) ;
uci . sections ( 'network' , 'wireguard_' + s . section , function ( peer ) {
if ( peer . public _key == keypair . pub )
uci . remove ( 'network' , peer [ '.name' ] ) ;
} ) ;
uci . set ( 'network' , sid , 'description' , comment || _ ( 'Imported peer configuration' ) ) ;
uci . set ( 'network' , sid , 'public_key' , keypair . pub ) ;
uci . set ( 'network' , sid , 'private_key' , keypair . priv ) ;
uci . set ( 'network' , sid , 'preshared_key' , config . peer _presharedkey ) ;
uci . set ( 'network' , sid , 'allowed_ips' , config . peer _allowedips ) ;
uci . set ( 'network' , sid , 'persistent_keepalive' , config . peer _persistentkeepalive ) ;
return s . map . save ( null , true ) ;
} ) . then ( function ( ) {
cancel . click ( ) ;
} ) ;
}
} ;
ss . handleConfigImport = function ( mode ) {
var mapNode = ss . getActiveModalMap ( ) ,
headNode = mapNode . parentNode . querySelector ( 'h4' ) ,
parent = this . map ;
var nodes = E ( 'div' , {
'dragover' : this . handleDragConfig ,
'drop' : this . handleDropConfig . bind ( this , mode )
} , [
2022-07-23 18:24:09 +00:00
E ( [ ] , ( mode == 'full' ) ? [
E ( 'p' , _ ( 'Drag or paste a valid <em>*.conf</em> file below to configure the local WireGuard interface.' ) )
] : [
E ( 'p' , _ ( 'Paste or drag a WireGuard configuration (commonly <em>wg0.conf</em>) from another system below to create a matching peer entry allowing that system to connect to the local WireGuard interface.' ) ) ,
E ( 'p' , _ ( 'To fully configure the local WireGuard interface from an existing (e.g. provider supplied) configuration file, use the <strong><a class="full-import" href="#">configuration import</a></strong> instead.' ) )
] ) ,
2022-05-17 13:45:20 +00:00
E ( 'p' , [
E ( 'textarea' , {
2022-07-23 18:24:09 +00:00
'placeholder' : ( mode == 'full' )
? _ ( 'Paste or drag supplied WireGuard configuration file…' )
: _ ( 'Paste or drag WireGuard peer configuration (wg0.conf) file…' ) ,
'style' : 'height:5em;width:100%; white-space:pre'
2022-05-17 13:45:20 +00:00
} )
] ) ,
E ( 'div' , {
'class' : 'alert-message' ,
'style' : 'display:none'
} , [ '' ] )
2019-08-20 13:31:35 +00:00
] ) ;
2022-05-17 13:45:20 +00:00
2022-07-23 18:24:09 +00:00
var cancelFn = function ( ) {
nodes . parentNode . removeChild ( nodes . nextSibling ) ;
nodes . parentNode . removeChild ( nodes ) ;
mapNode . classList . remove ( 'hidden' ) ;
mapNode . nextSibling . classList . remove ( 'hidden' ) ;
headNode . removeChild ( headNode . lastChild ) ;
window . removeEventListener ( 'dragover' , handleWindowDragDropIgnore ) ;
window . removeEventListener ( 'drop' , handleWindowDragDropIgnore ) ;
} ;
var a = nodes . querySelector ( 'a.full-import' ) ;
if ( a ) {
a . addEventListener ( 'click' , ui . createHandlerFn ( this , function ( mode ) {
cancelFn ( ) ;
this . handleConfigImport ( 'full' ) ;
} ) ) ;
}
2022-05-17 13:45:20 +00:00
mapNode . classList . add ( 'hidden' ) ;
mapNode . nextElementSibling . classList . add ( 'hidden' ) ;
2022-07-23 18:24:09 +00:00
headNode . appendChild ( E ( 'span' , [ ' » ' , ( mode == 'full' ) ? _ ( 'Import configuration' ) : _ ( 'Import as peer' ) ] ) ) ;
2022-05-17 13:45:20 +00:00
mapNode . parentNode . appendChild ( E ( [ ] , [
nodes ,
E ( 'div' , {
'class' : 'right'
} , [
E ( 'button' , {
'class' : 'btn' ,
2022-07-23 18:24:09 +00:00
'click' : cancelFn
2022-05-17 13:45:20 +00:00
} , [ _ ( 'Cancel' ) ] ) ,
' ' ,
E ( 'button' , {
'class' : 'btn primary' ,
'click' : ui . createHandlerFn ( this , 'handleApplyConfig' , mode , nodes , null )
} , [ _ ( 'Import settings' ) ] )
] )
] ) ) ;
window . addEventListener ( 'dragover' , handleWindowDragDropIgnore ) ;
window . addEventListener ( 'drop' , handleWindowDragDropIgnore ) ;
} ;
ss . renderSectionAdd = function ( /* ... */ ) {
var nodes = this . super ( 'renderSectionAdd' , arguments ) ;
nodes . appendChild ( E ( 'button' , {
'class' : 'btn' ,
'click' : ui . createHandlerFn ( this , 'handleConfigImport' , 'peer' )
} , [ _ ( 'Import peer configuration…' ) ] ) ) ;
return nodes ;
} ;
ss . renderSectionPlaceholder = function ( ) {
return E ( 'em' , _ ( 'No peers defined yet.' ) ) ;
2019-08-20 13:31:35 +00:00
} ;
2021-11-12 22:20:10 +00:00
o = ss . option ( form . Flag , 'disabled' , _ ( 'Peer disabled' ) , _ ( 'Enable / Disable peer. Restart wireguard interface to apply changes.' ) ) ;
2022-05-17 13:45:20 +00:00
o . modalonly = true ;
2021-11-12 22:20:10 +00:00
o . optional = true ;
2019-08-20 13:31:35 +00:00
o = ss . option ( form . Value , 'description' , _ ( 'Description' ) , _ ( 'Optional. Description of peer.' ) ) ;
o . placeholder = 'My Peer' ;
o . datatype = 'string' ;
o . optional = true ;
2022-05-17 13:45:20 +00:00
o . width = '30%' ;
o . textvalue = function ( section _id ) {
var dis = ss . getOption ( 'disabled' ) ,
pub = ss . getOption ( 'public_key' ) ,
prv = ss . getOption ( 'private_key' ) ,
psk = ss . getOption ( 'preshared_key' ) ,
name = this . cfgvalue ( section _id ) ,
key = pub . cfgvalue ( section _id ) ;
var desc = [
E ( 'p' , [
name ? E ( 'span' , [ name ] ) : E ( 'em' , [ _ ( 'Untitled peer' ) ] )
2021-09-10 12:01:53 +00:00
] )
] ;
2021-09-08 10:56:10 +00:00
2022-05-17 13:45:20 +00:00
if ( dis . cfgvalue ( section _id ) == '1' )
desc . push ( E ( 'span' , {
'class' : 'ifacebadge' ,
'data-tooltip' : _ ( 'WireGuard peer is disabled' )
2021-09-08 10:56:10 +00:00
} , [
2022-05-17 13:45:20 +00:00
E ( 'em' , [ _ ( 'Disabled' , 'Label indicating that WireGuard peer is disabled' ) ] )
] ) , ' ' ) ;
2021-09-08 10:56:10 +00:00
2022-05-17 13:45:20 +00:00
if ( ! key || ! pub . isValid ( section _id ) ) {
desc . push ( E ( 'span' , {
'class' : 'ifacebadge' ,
'data-tooltip' : _ ( 'Public key is missing' )
} , [
E ( 'em' , [ _ ( 'Key missing' , 'Label indicating that WireGuard peer lacks public key' ) ] )
] ) ) ;
}
else {
desc . push (
E ( 'span' , {
'class' : 'ifacebadge' ,
'data-tooltip' : _ ( 'Public key: %h' , 'Tooltip displaying full WireGuard peer public key' ) . format ( key )
} , [
E ( 'code' , [ key . replace ( /^(.{5}).+(.{6})$/ , '$1…$2' ) ] )
] ) ,
' ' ,
( prv . cfgvalue ( section _id ) && prv . isValid ( section _id ) )
? E ( 'span' , {
'class' : 'ifacebadge' ,
'data-tooltip' : _ ( 'Private key present' )
} , [ _ ( 'Private' , 'Label indicating that WireGuard peer private key is stored' ) ] ) : '' ,
' ' ,
( psk . cfgvalue ( section _id ) && psk . isValid ( section _id ) )
? E ( 'span' , {
'class' : 'ifacebadge' ,
'data-tooltip' : _ ( 'Preshared key in use' )
} , [ _ ( 'PSK' , 'Label indicating that WireGuard peer uses a PSK' ) ] ) : ''
) ;
}
return E ( [ ] , desc ) ;
} ;
function handleKeyChange ( ev , section _id , value ) {
var prv = this . section . getUIElement ( section _id , 'private_key' ) ,
btn = this . map . findElement ( '.btn.qr-code' ) ;
btn . disabled = ( ! prv . isValid ( ) || ! prv . getValue ( ) ) ;
}
o = ss . option ( form . Value , 'public_key' , _ ( 'Public Key' ) , _ ( 'Required. Public key of the WireGuard peer.' ) ) ;
2021-11-21 22:31:13 +00:00
o . modalonly = true ;
2019-08-20 13:31:35 +00:00
o . validate = validateBase64 ;
2022-05-17 13:45:20 +00:00
o . onchange = handleKeyChange ;
2019-08-20 13:31:35 +00:00
2022-05-17 13:45:20 +00:00
o = ss . option ( form . Value , 'private_key' , _ ( 'Private Key' ) , _ ( 'Optional. Private key of the WireGuard peer. The key is not required for establishing a connection but allows generating a peer configuration or QR code if available. It can be removed after the configuration has been exported.' ) ) ;
2021-11-21 22:31:13 +00:00
o . modalonly = true ;
2019-08-20 13:31:35 +00:00
o . validate = validateBase64 ;
2022-05-17 13:45:20 +00:00
o . onchange = handleKeyChange ;
o . password = true ;
2019-08-20 13:31:35 +00:00
2022-05-17 13:45:20 +00:00
o = ss . option ( cbiKeyPairGenerate , '_gen_peer_keypair' , ' ' ) ;
o . modalonly = true ;
2022-03-18 13:47:44 +00:00
2022-05-17 13:45:20 +00:00
o = ss . option ( form . Value , 'preshared_key' , _ ( 'Preshared Key' ) , _ ( 'Optional. Base64-encoded preshared key. Adds in an additional layer of symmetric-key cryptography for post-quantum resistance.' ) ) ;
o . modalonly = true ;
o . validate = validateBase64 ;
o . password = true ;
o = ss . option ( form . DummyValue , '_gen_psk' , ' ' ) ;
o . modalonly = true ;
o . cfgvalue = function ( section _id , value ) {
return E ( 'button' , {
'class' : 'btn' ,
'click' : ui . createHandlerFn ( this , function ( section _id , ev ) {
var psk = this . section . getUIElement ( section _id , 'preshared_key' ) ,
map = this . map ;
if ( psk . getValue ( ) && ! confirm ( _ ( 'Do you want to replace the current PSK?' ) ) )
return ;
return generatePsk ( ) . then ( function ( key ) {
psk . setValue ( key ) ;
map . save ( null , true ) ;
} ) ;
} , section _id )
} , [ _ ( 'Generate preshared key' ) ] ) ;
} ;
2022-03-18 13:47:44 +00:00
2021-08-29 04:44:38 +00:00
o = ss . option ( form . DynamicList , 'allowed_ips' , _ ( 'Allowed IPs' ) , _ ( "Optional. IP addresses and prefixes that this peer is allowed to use inside the tunnel. Usually the peer's tunnel IP addresses and the networks the peer routes through the tunnel." ) ) ;
2019-08-20 13:31:35 +00:00
o . datatype = 'ipaddr' ;
2022-05-17 13:45:20 +00:00
o . textvalue = function ( section _id ) {
var ips = L . toArray ( this . cfgvalue ( section _id ) ) ,
list = [ ] ;
for ( var i = 0 ; i < ips . length ; i ++ ) {
if ( i > 7 ) {
list . push ( E ( 'em' , {
'class' : 'ifacebadge cbi-tooltip-container'
} , [
_ ( '+ %d more' , 'Label indicating further amount of allowed ips' ) . format ( ips . length - i ) ,
E ( 'span' , {
'class' : 'cbi-tooltip'
} , [
E ( 'ul' , ips . map ( function ( ip ) {
return E ( 'li' , [
E ( 'span' , { 'class' : 'ifacebadge' } , [ ip ] )
] ) ;
} ) )
] )
] ) ) ;
break ;
}
list . push ( E ( 'span' , { 'class' : 'ifacebadge' } , [ ips [ i ] ] ) ) ;
}
if ( ! list . length )
list . push ( '*' ) ;
return E ( 'span' , { 'style' : 'display:inline-flex;flex-wrap:wrap;gap:.125em' } , list ) ;
} ;
2019-08-20 13:31:35 +00:00
o = ss . option ( form . Flag , 'route_allowed_ips' , _ ( 'Route Allowed IPs' ) , _ ( 'Optional. Create routes for Allowed IPs for this peer.' ) ) ;
2021-11-21 22:31:13 +00:00
o . modalonly = true ;
2019-08-20 13:31:35 +00:00
o = ss . option ( form . Value , 'endpoint_host' , _ ( 'Endpoint Host' ) , _ ( 'Optional. Host of peer. Names are resolved prior to bringing up the interface.' ) ) ;
o . placeholder = 'vpn.example.com' ;
o . datatype = 'host' ;
2022-05-17 13:45:20 +00:00
o . textvalue = function ( section _id ) {
var host = this . cfgvalue ( section _id ) ,
port = this . section . cfgvalue ( section _id , 'endpoint_port' ) ;
return ( host && port )
? '%h:%d' . format ( host , port )
: ( host
? '%h:*' . format ( host )
: ( port
? '*:%d' . format ( port )
: '*' ) ) ;
} ;
2019-08-20 13:31:35 +00:00
o = ss . option ( form . Value , 'endpoint_port' , _ ( 'Endpoint Port' ) , _ ( 'Optional. Port of peer.' ) ) ;
2022-05-17 13:45:20 +00:00
o . modalonly = true ;
2019-08-20 13:31:35 +00:00
o . placeholder = '51820' ;
o . datatype = 'port' ;
o = ss . option ( form . Value , 'persistent_keepalive' , _ ( 'Persistent Keep Alive' ) , _ ( 'Optional. Seconds between keep alive messages. Default is 0 (disabled). Recommended value if this device is behind a NAT is 25.' ) ) ;
2021-11-21 22:31:13 +00:00
o . modalonly = true ;
2019-08-20 13:31:35 +00:00
o . datatype = 'range(0,65535)' ;
o . placeholder = '0' ;
2022-05-17 13:45:20 +00:00
o = ss . option ( form . DummyValue , '_keyops' , _ ( 'Configuration Export' ) ,
_ ( 'Generates a configuration suitable for import on a WireGuard peer' ) ) ;
o . modalonly = true ;
o . createPeerConfig = function ( section _id , endpoint ) {
var pub = s . formvalue ( s . section , 'public_key' ) ,
port = s . formvalue ( s . section , 'listen_port' ) || '51820' ,
prv = this . section . formvalue ( section _id , 'private_key' ) ,
psk = this . section . formvalue ( section _id , 'preshared_key' ) ,
ips = L . toArray ( this . section . formvalue ( section _id , 'allowed_ips' ) ) ,
eport = this . section . formvalue ( section _id , 'endpoint_port' ) ,
keep = this . section . formvalue ( section _id , 'persistent_keepalive' ) ;
return [
'[Interface]' ,
'PrivateKey = ' + prv ,
eport ? 'ListenPort = ' + eport : '# ListenPort not defined' ,
'' ,
'[Peer]' ,
'PublicKey = ' + pub ,
psk ? 'PresharedKey = ' + psk : '# PresharedKey not used' ,
'AllowedIPs = ' + ( ips . length ? ips . join ( ', ' ) : '0.0.0.0/0, ::/0' ) ,
endpoint ? 'Endpoint = ' + endpoint + ':' + port : '# Endpoint not defined' ,
keep ? 'PersistentKeepAlive = ' + keep : '# PersistentKeepAlive not defined'
] . join ( '\n' ) ;
} ;
o . handleGenerateQR = function ( section _id , ev ) {
var mapNode = ss . getActiveModalMap ( ) ,
headNode = mapNode . parentNode . querySelector ( 'h4' ) ,
configGenerator = this . createPeerConfig . bind ( this , section _id ) ,
parent = this . map ;
return Promise . all ( [
network . getWANNetworks ( ) ,
network . getWAN6Networks ( ) ,
2022-06-20 22:07:04 +00:00
L . resolveDefault ( uci . load ( 'ddns' ) ) ,
L . resolveDefault ( uci . load ( 'system' ) ) ,
2022-05-17 13:45:20 +00:00
parent . save ( null , true )
] ) . then ( function ( data ) {
var hostnames = [ ] ;
uci . sections ( 'ddns' , 'service' , function ( s ) {
if ( typeof ( s . domain ) == 'string' && s . enabled == '1' )
hostnames . push ( s . domain ) ;
} ) ;
uci . sections ( 'system' , 'system' , function ( s ) {
if ( typeof ( s . hostname ) == 'string' && s . hostname . indexOf ( '.' ) > 0 )
hostnames . push ( s . hostname ) ;
} ) ;
for ( var i = 0 ; i < data [ 0 ] . length ; i ++ )
hostnames . push . apply ( hostnames , data [ 0 ] [ i ] . getIPAddrs ( ) . map ( function ( ip ) { return ip . split ( '/' ) [ 0 ] } ) ) ;
for ( var i = 0 ; i < data [ 1 ] . length ; i ++ )
hostnames . push . apply ( hostnames , data [ 1 ] [ i ] . getIP6Addrs ( ) . map ( function ( ip ) { return ip . split ( '/' ) [ 0 ] } ) ) ;
var qrm , qrs , qro ;
qrm = new form . JSONMap ( { endpoint : { endpoint : hostnames [ 0 ] } } , null , _ ( 'The generated configuration can be imported into a WireGuard client application to setup a connection towards this device.' ) ) ;
qrm . parent = parent ;
qrs = qrm . section ( form . NamedSection , 'endpoint' ) ;
qro = qrs . option ( form . Value , 'endpoint' , _ ( 'Connection endpoint' ) , _ ( 'The public hostname or IP address of this system the peer should connect to. This usually is a static public IP address, a static hostname or a DDNS domain.' ) ) ;
qro . datatype = 'or(ipaddr,hostname)' ;
hostnames . forEach ( function ( hostname ) { qro . value ( hostname ) } ) ;
qro . onchange = function ( ev , section _id , value ) {
var code = this . map . findElement ( '.qr-code' ) ,
conf = this . map . findElement ( '.client-config' ) ;
if ( this . isValid ( section _id ) ) {
conf . firstChild . data = configGenerator ( value ) ;
code . style . opacity = '.5' ;
invokeQREncode ( conf . firstChild . data , code ) ;
}
} ;
qro = qrs . option ( form . DummyValue , 'output' ) ;
qro . renderWidget = function ( ) {
var peer _config = configGenerator ( hostnames [ 0 ] ) ;
var node = E ( 'div' , {
'style' : 'display:flex;flex-wrap:wrap;align-items:center;gap:.5em;width:100%'
} , [
E ( 'div' , {
'class' : 'qr-code' ,
'style' : 'width:320px;flex:0 1 320px;text-align:center'
} , [
E ( 'em' , { 'class' : 'spinning' } , [ _ ( 'Generating QR code…' ) ] )
] ) ,
E ( 'pre' , {
'class' : 'client-config' ,
'style' : 'flex:1;white-space:pre;overflow:auto' ,
'click' : function ( ev ) {
var sel = window . getSelection ( ) ,
range = document . createRange ( ) ;
range . selectNodeContents ( ev . currentTarget ) ;
sel . removeAllRanges ( ) ;
sel . addRange ( range ) ;
}
} , [ peer _config ] )
] ) ;
invokeQREncode ( peer _config , node . firstChild ) ;
return node ;
} ;
return qrm . render ( ) . then ( function ( nodes ) {
mapNode . classList . add ( 'hidden' ) ;
mapNode . nextElementSibling . classList . add ( 'hidden' ) ;
headNode . appendChild ( E ( 'span' , [ ' » ' , _ ( 'Generate configuration' ) ] ) ) ;
mapNode . parentNode . appendChild ( E ( [ ] , [
nodes ,
E ( 'div' , {
'class' : 'right'
} , [
E ( 'button' , {
'class' : 'btn' ,
'click' : function ( ) {
nodes . parentNode . removeChild ( nodes . nextSibling ) ;
nodes . parentNode . removeChild ( nodes ) ;
mapNode . classList . remove ( 'hidden' ) ;
mapNode . nextSibling . classList . remove ( 'hidden' ) ;
headNode . removeChild ( headNode . lastChild ) ;
}
} , [ _ ( 'Back to peer configuration' ) ] )
] )
] ) ) ;
if ( ! s . formvalue ( s . section , 'listen_port' ) ) {
nodes . appendChild ( E ( 'div' , { 'class' : 'alert-message' } , [
E ( 'p' , [
_ ( 'No fixed interface listening port defined, peers might not be able to initiate connections to this WireGuard instance!' )
] )
] ) ) ;
}
} ) ;
} ) ;
} ;
o . cfgvalue = function ( section _id , value ) {
var privkey = this . section . cfgvalue ( section _id , 'private_key' ) ;
return E ( 'button' , {
'class' : 'btn qr-code' ,
'style' : 'display:inline-flex;align-items:center;gap:.5em' ,
'click' : ui . createHandlerFn ( this , 'handleGenerateQR' , section _id ) ,
'disabled' : privkey ? null : ''
} , [
Object . assign ( E ( qrIcon ) , { style : 'width:22px;height:22px' } ) ,
_ ( 'Generate configuration…' )
] ) ;
} ;
2020-03-03 20:14:04 +00:00
} ,
deleteConfiguration : function ( ) {
uci . sections ( 'network' , 'wireguard_%s' . format ( this . sid ) , function ( s ) {
uci . remove ( 'network' , s [ '.name' ] ) ;
} ) ;
2019-08-20 13:31:35 +00:00
}
} ) ;