]> Cypherpunks.ru repositories - govpn.git/commitdiff
JSON configuration
authorSergey Matveev <stargrave@stargrave.org>
Thu, 17 Sep 2015 17:44:21 +0000 (20:44 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Fri, 18 Sep 2015 08:49:28 +0000 (11:49 +0300)
Signed-off-by: Sergey Matveev <stargrave@stargrave.org>
18 files changed:
doc/client.texi
doc/cpr.texi
doc/example.texi
doc/identity.texi
doc/news.texi
doc/server.texi
doc/user.texi
doc/verifier.texi
src/govpn/cmd/govpn-client/main.go
src/govpn/cmd/govpn-client/tcp.go
src/govpn/cmd/govpn-client/udp.go
src/govpn/cmd/govpn-server/common.go
src/govpn/cmd/govpn-server/conf.go [new file with mode: 0644]
src/govpn/cmd/govpn-server/main.go
src/govpn/cmd/govpn-server/tcp.go
src/govpn/cmd/govpn-server/udp.go
src/govpn/identify.go
utils/newclient.sh

index 1ed749e746dfb3be08195bb822807ae49990c739..bab6a47c911b947592fb105ddd35b51b89b7dfd3 100644 (file)
@@ -7,7 +7,8 @@ options client has the following ones:
 @table @code
 
 @item -proto
-@ref{Network, network protocol} to use. Can be either @emph{udp} or @emph{tcp}.
+@ref{Network, network protocol} to use. Can be either @emph{udp}
+(default) or @emph{tcp}.
 
 @item -proxy
 Use specified @emph{host:port} @ref{Proxy} server for accessing remote
@@ -37,7 +38,7 @@ how to enter passphrase from stdin silently and store it in the file.
 Enable @ref{Noise}.
 
 @item -cpr
-Enable @ref{CPR} in KiB/sec.
+Set @ref{CPR} in KiB/sec.
 
 @item -up
 Optional path to script that will be executed after connection is
index cd7b48a3f5d0b1bcd384bb484ef4e38512f6f135..5256a81e7f0c565707ce33478b659ed641f40bbe 100644 (file)
@@ -7,4 +7,4 @@ delays other ones.
 
 This mode is turned by @code{-cpr} option, where you specify desired
 outgoing traffic rate in KiB/sec (kibibytes per second). This option also
-forces using of the @ref{Noise}! It is turned off by default.
+@strong{forces} using of the @ref{Noise}! It is turned off by default.
index 9fb3ad1685383f98e7da46a738f5726c25260953..c26284650498e9b2a7e5ff290dc3799bdf0a93e0 100644 (file)
@@ -15,44 +15,38 @@ is 1432.
 @end itemize
 
 @strong{Install}. At first you must @ref{Installation, install} this
-software: download, check the signature, compile.
+software: download, @ref{Integrity, check the signature}, compile.
 
-@strong{Prepare the server}. Create the new client, named (for example)
-"Alice":
+@strong{Prepare the client}. Generate client's identity and verifier for
+Alice as an example:
+@verbatim
+client% ./utils/newclient.sh Alice
+Enter passphrase:
+Your id is: 7012df29deee2170594119df5091d4a2
 
-@example
-server% ./utils/newclient.sh Alice
-Place verifier to peers/6d4ac605ce8dc37c2f0bf21cb542a713/verifier
-@end example
-
-"6d4ac605ce8dc37c2f0bf21cb542a713" -- is the new client's identity.
-
-@strong{Prepare the client}. Generate @ref{Verifier} for known client
-identity:
+Place the following JSON configuration entry on the server's side:
 
-@example
-client% ./utils/storekey.sh /tmp/passphrase
-Enter passphrase:[my secure passphrase is here]
-client% govpn-verifier -id 6d4ac605ce8dc37c2f0bf21cb542a713 -key /tmp/passphrase
-562556cc9ecf0019b4cf45bcdf42706944ae9b3ac7c73ad299d83f2d5a169c55
-client% rm /tmp/passphrase
-@end example
+    "7012df29deee2170594119df5091d4a2": {
+        "name": "Alice",
+        "up": "/path/to/up.sh",
+        "verifier": "fb43255ca3fe5bd884e364e5eae0cd37ad14774930a027fd38d8938fd0b57425"
+    }
 
-"562556cc9ecf0019b4cf45bcdf42706944ae9b3ac7c73ad299d83f2d5a169c55" --
-this is verifier itself.
+Verifier was generated with:
 
-@strong{Save verifier on server}.
+    ./utils/storekey.sh /tmp/passphrase
+    govpn-verifier -id 7012df29deee2170594119df5091d4a2 -key /tmp/passphrase
+@end verbatim
 
-@example
-server% cat > peers/6d4ac605ce8dc37c2f0bf21cb542a713/verifier <<EOF
-562556cc9ecf0019b4cf45bcdf42706944ae9b3ac7c73ad299d83f2d5a169c55
-EOF
-@end example
+@strong{Prepare the server}. Add this entry to @code{peers.json}
+configuration file.
 
 @strong{Prepare network on GNU/Linux IPv4 server}:
 
 @example
-server% echo "echo tap10" >> peers/6d4ac605ce8dc37c2f0bf21cb542a713/up.sh
+server% umask 077
+server% echo "#!/bin/sh" > /path/to/up.sh
+server% echo "echo tap10" >> /path/to/up.sh
 server% ip addr add 192.168.0.1/24 dev wlan0
 server% tunctl -t tap10
 server% ip link set mtu 1432 dev tap10
@@ -83,7 +77,7 @@ client% ip route add default via 172.16.0.1
 @example
 client% govpn-client \
     -key key.txt \
-    -id 6d4ac605ce8dc37c2f0bf21cb542a713 \
+    -id 906e34b98750c4f686d6c5489508763c \
     -iface tap10 \
     -remote 192.168.0.1:1194 \
     -mtu 1472
@@ -103,7 +97,7 @@ client% ifconfig tap10 inet6 fc00::2/96 mtu 1412 up
 client% route -6 add default fc00::1
 client% govpn-client \
     -key key.txt \
-    -id 6d4ac605ce8dc37c2f0bf21cb542a713 \
+    -id 906e34b98750c4f686d6c5489508763c \
     -iface tap10 \
     -remote "[fe80::1%me0]":1194
 @end example
index cb3061f4b0abc11accfed9a1f6e0195990a8ab59..46a6889d2c1fda4041f4c89a1d5c30ffb408ef90 100644 (file)
@@ -7,7 +7,7 @@ to make DPI and deanonymization much harder to success.
 
 @example
 % ./utils/newclient.sh doesnotmatter
-Place verifier to peers/6d4ac605ce8dc37c2f0bf21cb542a713/verifier
+Your id is: 7012df29deee2170594119df5091d4a2
 @end example
 
-"6d4ac605ce8dc37c2f0bf21cb542a713" is client's identity.
+@code{7012df29deee2170594119df5091d4a2} is client's identity.
index e6f6b6209b4abad9b8caa61817d1a1f7e622f7b1..bdb178e977ef5666aa25fdd16cf113518b78a5b6 100644 (file)
@@ -9,6 +9,7 @@
 hidden. Now they are indistinguishable from transport messages.
 @item Parallelized clients processing on the server side.
 @item Much higher overall performance.
+@item Single JSON file server configuration.
 @end itemize
 
 @item Release 3.5
index a9ce065ddbfeb45ca1afcb5de1241dda3eb148a0..49a09fb990438998a7a939d354e650c800761120 100644 (file)
@@ -7,76 +7,69 @@ has the following ones:
 @table @code
 
 @item -proto
-@ref{Network, network protocol} to use. Can be @emph{udp},
+@ref{Network, network protocol} to use. Can be @emph{udp} (default),
 @emph{tcp} or @emph{all}.
 
 @item -bind
 Address (@code{host:port} format) we must bind to.
 
-@item -peers
-Path to the directory containing peers information, database.
+@item -conf
+Path to JSON file with the configuration.
 
 @item -proxy
 Start trivial HTTP @ref{Proxy} server on specified @emph{host:port}.
 
 @end table
 
-Peers directory must contain subdirectories with the names of client's
-identities in hexadecimal notation. Each subdirectory has the following
-files:
-
-@table @code
-
-@item verifier
-@strong{Required}. Contains corresponding verifier used to authenticate
-the client in hexadecimal notation. See @ref{Verifier} for how
-to create it.
-
-@item up.sh
-@strong{Required}. up-script executes each time connection with the
-client is established. It's @emph{stdout} output must contain TAP
-interface name on the first string. This script can be simple
-@code{echo tap10}, or maybe more advanced like this:
-    @example
-    #!/bin/sh
-    $tap=$(ifconfig tap create)
-    ifconfig $tap inet6 fc00::1/96 mtu 1412 up
-    echo $tap
-    @end example
-
-@item down.sh
-Optional. Same as @code{up.sh} above, but executes when connection is
-lost.
-
-@item name
-Optional. Contains human readable username. Used to beauty output of
-@ref{Stats}.
-
-@item timeout
-Optional. Contains @ref{Timeout} setting (decimal notation) in seconds.
-Otherwise default minute timeout will be used.
-
-@item noise
-Optional. Contains either "1" (enable @ref{Noise} adding), or "0".
-
-@item cpr
-Optional. Contains @ref{CPR} setting (decimal notation) in KiB/sec.
-
-@end table
+Configuration file is JSON file with following example structure:
+
+@verbatim
+{
+  "9b40701bdaf522f2b291cb039490312": {  <-- Peer identifier
+    "name": "stargrave",                <-- OPTIONAL human readable name
+    "up": "./stargrave-up.sh",          <-- up-script
+    "down": "./stargrave-down.sh",      <-- OPTIONAL down-script
+    "timeout": 60,                      <-- OPTIONAL overriden timeout
+    "noise": true,                      <-- OPTIONAL noise enabler
+                                            (default: false)
+    "cpr": 64,                          <-- OPTIONAL constant packet
+                                            rate in KiB/sec
+    "verifier": "2c15bbdffc73193bea56db412bce1143c68ccbdaa9e2eade53a684497646a685"
+  },
+  [...]
+}
+@end verbatim
+
+See @ref{Verifier} for its description.
+
+up-script executes each time connection with the client is established.
+Its @emph{stdout} output must contain TAP interface name as the first
+line. This script can be simple @code{echo tap10}, or maybe more
+advanced like this:
+@example
+#!/bin/sh
+$tap=$(ifconfig tap create)
+ifconfig $tap inet6 fc00::1/96 mtu 1412 up
+echo $tap
+@end example
 
-Each minute server refreshes peers directory contents and adds newly
-appeared identities, deletes an obsolete ones.
+Each minute server rereads and refreshes peers configuration and adds
+newly appeared identities, deletes an obsolete ones.
 
 You can use convenient @code{utils/newclient.sh} script for new client
 creation:
 
-@example
+@verbatim
 % ./utils/newclient.sh Alice
-Place verifier to peers/9b40701bdaf522f2b291cb039490312/verifier
-@end example
-
-@code{9b40701bdaf522f2b291cb039490312} is client's identification.
-@code{peers/9b40701bdaf522f2b291cb039490312/name} contains @emph{Alice},
-@code{peers/9b40701bdaf522f2b291cb039490312/verifier} contains dummy
-verifier and @code{peers/9b40701bdaf522f2b291cb039490312/up.sh} contains
-currently dummy empty up-script.
+[...]
+Your id is: 7012df29deee2170594119df5091d4a2
+
+Place the following JSON configuration entry on the server's side:
+
+    "906e34b98750c4f686d6c5489508763c": {
+        "name": "Alice",
+        "up": "/path/to/up.sh",
+        "verifier": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+    }
+[...]
+@end verbatim
index 6eae9a3bcde5081df8eeb0fda83c0364f53b1fb9..73214da36ef357ceae50702ad118233a694749af 100644 (file)
@@ -3,11 +3,11 @@
 
 Announcements about updates and new releases can be found in @ref{Contacts}.
 
-GoVPN is split into two pieces: client and server. Each of them work on
-top of UDP/TCP and TAP virtual network interfaces. GoVPN is just a
-tunnelling of Ethernet frames, nothing less, nothing more. All you
-IP-related network management is not touched by VPN at all. You can
-automate it using up and down shell scripts.
+GoVPN is split into two pieces: @ref{Client} and @ref{Server}. Each of
+them work on top of @ref{Network, UDP/TCP} and TAP virtual network
+interfaces. GoVPN is just a tunnelling of Ethernet frames, nothing less,
+nothing more. All you IP-related network management is not touched by
+VPN at all. You can automate it using up and down shell scripts.
 
 What network performance can user expect? For example single
 @emph{Intel i5-2450M 2.5 GHz} core on @emph{FreeBSD 10.2 amd64}
index 6e97b7e6f02f8363c939a0c23ca94aa921d02574..61b1e4479f954082327f98bb77f76accee825385 100644 (file)
@@ -13,8 +13,8 @@ Enter passphrase:[hello world]
 210e3878542828901a3af9b4aa00b004de530410eef5c1ba2ffb6d04504371b2
 @end example
 
-Store "210...1b2" string on the server's side in corresponding
-@code{verifier} file.
+Store @code{210...1b2} string on the server's side in corresponding
+@code{verifier} configuration file's field.
 
 You can check passphrase against verifier by specifying @code{-verifier}
 option with the path to verifier file:
index 467bec6545645ac9bde4f06d734d884eeb78dd51..6c611e746a1c43a8aed5a8657e88cccb34371b08 100644 (file)
@@ -52,6 +52,7 @@ var (
        timeout     int
        firstUpCall bool = true
        knownPeers  govpn.KnownPeers
+       idsCache    govpn.CipherCache
 )
 
 func main() {
@@ -81,7 +82,7 @@ func main() {
                DSAPub:  pub,
                DSAPriv: priv,
        }
-       govpn.PeersInitDummy(id, conf)
+       idsCache = govpn.NewCipherCache([]govpn.PeerId{*id})
        log.Println(govpn.VersionGet())
 
        tap, err = govpn.TAPListen(*ifaceName)
index c8724007b02664871f241da5b6bbccb61eb7cbbb..6265eeb6087e8f8b80dedf4f2450ede422ea5726 100644 (file)
@@ -72,7 +72,7 @@ HandshakeCycle:
                }
 
                prev += n
-               peerId := govpn.IDsCache.Find(buf[:prev])
+               peerId := idsCache.Find(buf[:prev])
                if peerId == nil {
                        continue
                }
index 3cb3eea4decb3f3e0fe8bfad18b32bc66a6f7a7f..9f45f8be51b68f8ccac1d2eb8dd41a832da9884b 100644 (file)
@@ -77,7 +77,7 @@ MainCycle:
                        }
                        continue
                }
-               if govpn.IDsCache.Find(buf[:n]) == nil {
+               if idsCache.Find(buf[:n]) == nil {
                        log.Println("Invalid identity in handshake packet")
                        continue
                }
index 06e82e64e272c7b060317a8d6e736d96582fc97b..4692e52b4c297270a61f30f81f5213cbeef00827 100644 (file)
@@ -2,7 +2,6 @@ package main
 
 import (
        "bytes"
-       "path"
        "sync"
        "time"
 
@@ -49,8 +48,7 @@ Processor:
 }
 
 func callUp(peerId *govpn.PeerId) (string, error) {
-       upPath := path.Join(govpn.PeersPath, peerId.String(), "up.sh")
-       result, err := govpn.ScriptCall(upPath, "")
+       result, err := govpn.ScriptCall(confs[*peerId].Up, "")
        if err != nil {
                return "", err
        }
diff --git a/src/govpn/cmd/govpn-server/conf.go b/src/govpn/cmd/govpn-server/conf.go
new file mode 100644 (file)
index 0000000..4136fab
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+GoVPN -- simple secure free software virtual private network daemon
+Copyright (C) 2014-2015 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package main
+
+import (
+       "encoding/hex"
+       "encoding/json"
+       "io/ioutil"
+       "log"
+       "time"
+
+       "github.com/agl/ed25519"
+
+       "govpn"
+)
+
+const (
+       RefreshRate = time.Minute
+)
+
+var (
+       confs    map[govpn.PeerId]*govpn.PeerConf
+       idsCache govpn.CipherCache
+)
+
+func confRead() map[govpn.PeerId]*govpn.PeerConf {
+       data, err := ioutil.ReadFile(*confPath)
+       if err != nil {
+               log.Fatalln("Unable to read configuration:", err)
+       }
+       confsRaw := new(map[string]govpn.PeerConf)
+       err = json.Unmarshal(data, confsRaw)
+       if err != nil {
+               log.Fatalln("Unable to parse configuration:", err)
+       }
+
+       confs := make(map[govpn.PeerId]*govpn.PeerConf, len(*confsRaw))
+       for peerIdRaw, pc := range *confsRaw {
+               peerId, err := govpn.IDDecode(peerIdRaw)
+               if err != nil {
+                       log.Fatalln("Invalid peer ID:", peerIdRaw, err)
+               }
+               conf := govpn.PeerConf{
+                       Id:    peerId,
+                       Name:  pc.Name,
+                       Up:    pc.Up,
+                       Down:  pc.Down,
+                       Noise: pc.Noise,
+                       CPR:   pc.CPR,
+               }
+               if pc.TimeoutInt <= 0 {
+                       pc.TimeoutInt = govpn.TimeoutDefault
+               }
+               conf.Timeout = time.Second * time.Duration(pc.TimeoutInt)
+
+               if len(pc.Verifier) != ed25519.PublicKeySize*2 {
+                       log.Fatalln("Verifier must be 64 hex characters long")
+               }
+               keyDecoded, err := hex.DecodeString(string(pc.Verifier))
+               if err != nil {
+                       log.Fatalln("Unable to decode the key:", err.Error(), pc.Verifier)
+               }
+               conf.DSAPub = new([ed25519.PublicKeySize]byte)
+               copy(conf.DSAPub[:], keyDecoded)
+
+               confs[*peerId] = &conf
+       }
+       return confs
+}
+
+func confRefresh() {
+       confs = confRead()
+       ids := make([]govpn.PeerId, 0, len(confs))
+       for peerId, _ := range confs {
+               ids = append(ids, peerId)
+       }
+       idsCache.Update(ids)
+}
+
+func confInit() {
+       idsCache = govpn.NewCipherCache(nil)
+       confRefresh()
+       go func() {
+               for {
+                       time.Sleep(RefreshRate)
+                       confRefresh()
+               }
+       }()
+}
index dd338a2ce913dc596f3e29b98428aaf9bc1519f1..be4e7ebc6c83345ead1ed24837ce3aa2ce0e1b46 100644 (file)
@@ -25,20 +25,19 @@ import (
        "net"
        "os"
        "os/signal"
-       "path"
        "time"
 
        "govpn"
 )
 
 var (
-       bindAddr  = flag.String("bind", "[::]:1194", "Bind to address")
-       proto     = flag.String("proto", "udp", "Protocol to use: udp, tcp or all")
-       peersPath = flag.String("peers", "peers", "Path to peers keys directory")
-       stats     = flag.String("stats", "", "Enable stats retrieving on host:port")
-       proxy     = flag.String("proxy", "", "Enable HTTP proxy on host:port")
-       mtu       = flag.Int("mtu", 1452, "MTU for outgoing packets")
-       egdPath   = flag.String("egd", "", "Optional path to EGD socket")
+       bindAddr = flag.String("bind", "[::]:1194", "Bind to address")
+       proto    = flag.String("proto", "udp", "Protocol to use: udp, tcp or all")
+       confPath = flag.String("conf", "peers.json", "Path to configuration JSON")
+       stats    = flag.String("stats", "", "Enable stats retrieving on host:port")
+       proxy    = flag.String("proxy", "", "Enable HTTP proxy on host:port")
+       mtu      = flag.Int("mtu", 1452, "MTU for outgoing packets")
+       egdPath  = flag.String("egd", "", "Optional path to EGD socket")
 )
 
 func main() {
@@ -48,7 +47,7 @@ func main() {
        log.Println(govpn.VersionGet())
 
        govpn.MTU = *mtu
-       govpn.PeersInit(*peersPath)
+       confInit()
        knownPeers = govpn.KnownPeers(make(map[string]**govpn.Peer))
 
        if *egdPath != "" {
@@ -116,12 +115,10 @@ MainCycle:
                                        delete(peers, addr)
                                        delete(knownPeers, addr)
                                        delete(peersById, *ps.peer.Id)
-                                       downPath := path.Join(
-                                               govpn.PeersPath,
-                                               ps.peer.Id.String(),
-                                               "down.sh",
+                                       go govpn.ScriptCall(
+                                               confs[*ps.peer.Id].Down,
+                                               ps.tap.Name,
                                        )
-                                       go govpn.ScriptCall(downPath, ps.tap.Name)
                                        ps.terminator <- struct{}{}
                                }
                        }
index c1969faa486d511000103a660e1780278e12a3e7..59ee55965e96e03b0ee617511de233a8da647e4f 100644 (file)
@@ -72,12 +72,12 @@ func handleTCP(conn net.Conn) {
                        break
                }
                prev += n
-               peerId := govpn.IDsCache.Find(buf[:prev])
+               peerId := idsCache.Find(buf[:prev])
                if peerId == nil {
                        continue
                }
                if hs == nil {
-                       conf = peerId.Conf()
+                       conf = confs[*peerId]
                        if conf == nil {
                                log.Println("Can not get peer configuration:", peerId.String())
                                break
index 148ff68cf1e2b492cd42c8fac638d0931696fd97..0f5e5551d6bc853ffa3dbe29886434b1ed02d1e0 100644 (file)
@@ -168,12 +168,12 @@ func startUDP() {
                        }
                        goto Finished
                CheckID:
-                       peerId = govpn.IDsCache.Find(buf[:n])
+                       peerId = idsCache.Find(buf[:n])
                        if peerId == nil {
                                log.Println("Unknown identity from:", addr)
                                goto Finished
                        }
-                       conf = peerId.Conf()
+                       conf = confs[*peerId]
                        if conf == nil {
                                log.Println("Unable to get peer configuration:", peerId.String())
                                goto Finished
index 3f506ecc61e09828a2201114890bdb6f936c236b..a0b7d22f65de59d0840de73f8b4efbb8be6626ca 100644 (file)
@@ -22,22 +22,14 @@ import (
        "crypto/subtle"
        "encoding/hex"
        "errors"
-       "io/ioutil"
        "log"
-       "os"
-       "path"
-       "strconv"
-       "strings"
        "sync"
-       "time"
 
-       "github.com/agl/ed25519"
        "golang.org/x/crypto/xtea"
 )
 
 const (
-       IDSize      = 128 / 8
-       RefreshRate = 60 * time.Second
+       IDSize = 128 / 8
 )
 
 type PeerId [IDSize]byte
@@ -46,176 +38,76 @@ func (id PeerId) String() string {
        return hex.EncodeToString(id[:])
 }
 
-// Return human readable name of the peer.
-// It equals either to peers/PEER/name file contents or PEER's hex.
-func (id PeerId) MarshalJSON() ([]byte, error) {
-       result := id.String()
-       if name, err := ioutil.ReadFile(path.Join(PeersPath, result, "name")); err == nil {
-               result = strings.TrimRight(string(name), "\n")
+// Decode identification string.
+// It must be 32 hexadecimal characters long.
+func IDDecode(raw string) (*PeerId, error) {
+       if len(raw) != IDSize*2 {
+               return nil, errors.New("ID must be 32 characters long")
+       }
+       idDecoded, err := hex.DecodeString(raw)
+       if err != nil {
+               return nil, errors.New("ID must contain hexadecimal characters only")
        }
-       return []byte(`"` + result + `"`), nil
+       idP := new([IDSize]byte)
+       copy(idP[:], idDecoded)
+       id := PeerId(*idP)
+       return &id, nil
 }
 
-type cipherCache map[PeerId]*xtea.Cipher
-
-var (
-       PeersPath       string
-       IDsCache        cipherCache
-       cipherCacheLock sync.RWMutex
-       dummyConf       *PeerConf
-)
-
-// Initialize (pre-cache) available peers info.
-func PeersInit(path string) {
-       PeersPath = path
-       IDsCache = make(map[PeerId]*xtea.Cipher)
-       go func() {
-               for {
-                       IDsCache.refresh()
-                       time.Sleep(RefreshRate)
-               }
-       }()
+type CipherCache struct {
+       c map[PeerId]*xtea.Cipher
+       l sync.RWMutex
 }
 
-// Initialize dummy cache for client-side usage.
-func PeersInitDummy(id *PeerId, conf *PeerConf) {
-       IDsCache = make(map[PeerId]*xtea.Cipher)
-       cipher, err := xtea.NewCipher(id[:])
-       if err != nil {
-               panic(err)
-       }
-       IDsCache[*id] = cipher
-       dummyConf = conf
+func NewCipherCache(peerIds []PeerId) CipherCache {
+       cc := CipherCache{c: make(map[PeerId]*xtea.Cipher, len(peerIds))}
+       cc.Update(peerIds)
+       return cc
 }
 
-// Refresh IDsCache: remove disappeared keys, add missing ones with
-// initialized ciphers.
-func (cc cipherCache) refresh() {
-       dir, err := os.Open(PeersPath)
-       if err != nil {
-               panic(err)
-       }
-       peerIds, err := dir.Readdirnames(0)
-       if err != nil {
-               panic(err)
-       }
-       available := make(map[PeerId]bool)
+// Remove disappeared keys, add missing ones with initialized ciphers.
+func (cc CipherCache) Update(peerIds []PeerId) {
+       available := make(map[PeerId]struct{})
        for _, peerId := range peerIds {
-               id, err := IDDecode(peerId)
-               if err != nil {
-                       continue
-               }
-               available[*id] = true
+               available[peerId] = struct{}{}
        }
-
-       cipherCacheLock.Lock()
-       // Cleanup deleted ones from cache
-       for k, _ := range cc {
+       cc.l.Lock()
+       for k, _ := range cc.c {
                if _, exists := available[k]; !exists {
-                       delete(cc, k)
-                       log.Println("Cleaning key: ", k)
+                       log.Println("Cleaning key:", k)
+                       delete(cc.c, k)
                }
        }
-       // Add missing ones
        for peerId, _ := range available {
-               if _, exists := cc[peerId]; !exists {
+               if _, exists := cc.c[peerId]; !exists {
                        log.Println("Adding key", peerId)
                        cipher, err := xtea.NewCipher(peerId[:])
                        if err != nil {
                                panic(err)
                        }
-                       cc[peerId] = cipher
+                       cc.c[peerId] = cipher
                }
        }
-       cipherCacheLock.Unlock()
+       cc.l.Unlock()
 }
 
 // Try to find peer's identity (that equals to an encryption key)
 // by taking first blocksize sized bytes from data at the beginning
 // as plaintext and last bytes as cyphertext.
-func (cc cipherCache) Find(data []byte) *PeerId {
+func (cc CipherCache) Find(data []byte) *PeerId {
        if len(data) < xtea.BlockSize*2 {
                return nil
        }
        buf := make([]byte, xtea.BlockSize)
-       cipherCacheLock.RLock()
-       for pid, cipher := range cc {
+       cc.l.RLock()
+       for pid, cipher := range cc.c {
                cipher.Decrypt(buf, data[len(data)-xtea.BlockSize:])
                if subtle.ConstantTimeCompare(buf, data[:xtea.BlockSize]) == 1 {
                        ppid := PeerId(pid)
-                       cipherCacheLock.RUnlock()
+                       cc.l.RUnlock()
                        return &ppid
                }
        }
-       cipherCacheLock.RUnlock()
+       cc.l.RUnlock()
        return nil
 }
-
-func readIntFromFile(path string) (int, error) {
-       data, err := ioutil.ReadFile(path)
-       if err != nil {
-               return 0, err
-       }
-       val, err := strconv.Atoi(strings.TrimRight(string(data), "\n"))
-       if err != nil {
-               return 0, err
-       }
-       return val, nil
-}
-
-// Get peer related configuration.
-func (id *PeerId) Conf() *PeerConf {
-       if dummyConf != nil {
-               return dummyConf
-       }
-       conf := PeerConf{Id: id, NoiseEnable: false, CPR: 0}
-       peerPath := path.Join(PeersPath, id.String())
-
-       verPath := path.Join(peerPath, "verifier")
-       keyData, err := ioutil.ReadFile(verPath)
-       if err != nil {
-               log.Println("Unable to read verifier:", verPath)
-               return nil
-       }
-       if len(keyData) < ed25519.PublicKeySize*2 {
-               log.Println("Verifier must be 64 hex characters long:", verPath)
-               return nil
-       }
-       keyDecoded, err := hex.DecodeString(string(keyData[:ed25519.PublicKeySize*2]))
-       if err != nil {
-               log.Println("Unable to decode the key:", err.Error(), verPath)
-               return nil
-       }
-       conf.DSAPub = new([ed25519.PublicKeySize]byte)
-       copy(conf.DSAPub[:], keyDecoded)
-
-       timeout := TimeoutDefault
-       if val, err := readIntFromFile(path.Join(peerPath, "timeout")); err == nil {
-               timeout = val
-       }
-       conf.Timeout = time.Second * time.Duration(timeout)
-
-       if val, err := readIntFromFile(path.Join(peerPath, "noise")); err == nil && val == 1 {
-               conf.NoiseEnable = true
-       }
-       if val, err := readIntFromFile(path.Join(peerPath, "cpr")); err == nil {
-               conf.CPR = val
-       }
-       return &conf
-}
-
-// Decode identification string.
-// It must be 32 hexadecimal characters long.
-func IDDecode(raw string) (*PeerId, error) {
-       if len(raw) != IDSize*2 {
-               return nil, errors.New("ID must be 32 characters long")
-       }
-       idDecoded, err := hex.DecodeString(raw)
-       if err != nil {
-               return nil, errors.New("ID must contain hexadecimal characters only")
-       }
-       idP := new([IDSize]byte)
-       copy(idP[:], idDecoded)
-       id := PeerId(*idP)
-       return &id, nil
-}
index e741130957a6f30d6c8956988bcd3c3b58e2f940..9e1cb3578faaf71f131245cf2c128b2df3d07c06 100755 (executable)
@@ -1,16 +1,10 @@
 #!/bin/sh -e
 
-getrand()
-{
-    local size=$1
-    dd if=/dev/urandom bs=$size count=1 2>/dev/null | hexdump -ve '"%02x"'
-}
-
 [ -n "$1" ] || {
     cat <<EOF
 Example script for creating new user peer for GoVPN.
-It just creates directory with random peer ID, dummy verifier,
-dummy up.sh executable script and saves username in it.
+It generates random client's identity, ask for passphrase, generates
+verifier and shows you example JSON entry for server configuration.
 
 Usage: $0 <username>
 EOF
@@ -18,11 +12,41 @@ EOF
 }
 
 username=$1
-peerid=$(getrand 16)
+peerid=$(dd if=/dev/urandom bs=16 count=1 2>/dev/null | hexdump -ve '"%02x"')
+[ $(echo -n $peerid | wc -c) = 32 ] || peerid=0"$peerid"
 umask 077
-mkdir -p peers/$peerid
-echo '0000000000000000000000000000000000000000000000000000000000000000' > peers/$peerid/verifier
-echo $username > peers/$peerid/name
-echo '#!/bin/sh' > peers/$peerid/up.sh
-chmod 700 peers/$peerid/up.sh
-echo Place verifier to peers/$peerid/verifier
+passphrase=$(mktemp)
+$(dirname $0)/storekey.sh $passphrase
+verifier=$(govpn-verifier -id $peerid -key $passphrase)
+rm -f $passphrase
+echo
+
+cat <<EOF
+Your id is: $peerid
+
+Place the following JSON configuration entry on the server's side:
+
+    "$peerid": {
+        "name": "$username",
+        "up": "/path/to/up.sh",
+        "verifier": "$verifier"
+    }
+
+Verifier was generated with:
+
+    $(dirname $0)/storekey.sh /tmp/passphrase
+    govpn-verifier -id $peerid -key /tmp/passphrase
+
+Create up.sh script that will output on the first line TAP interface
+name that must be used for the peer. For example:
+
+    % umask 077
+    % ed /path/to/up.sh
+    a
+    #!/bin/sh
+    echo tap0
+    .
+    wq
+    20
+    % chmod +x /path/to/up.sh
+EOF