aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBernd Fix <brf@hoi-polloi.org>2022-10-22 16:57:16 +0200
committerBernd Fix <brf@hoi-polloi.org>2022-10-22 16:57:16 +0200
commit6ed1dfac9e0ac7a24114198ae8a364388cb55528 (patch)
tree3f7a33078979c3fc32f6bf4235bda6f20f18f506 /src
parent915c050130855f83c2f58c44682396bcf4d5275b (diff)
downloadgnunet-go-6ed1dfac9e0ac7a24114198ae8a364388cb55528.tar.gz
gnunet-go-6ed1dfac9e0ac7a24114198ae8a364388cb55528.zip
Initial revision of Zonemaster implementation.v0.1.36
Diffstat (limited to 'src')
-rwxr-xr-xsrc/gnunet/build.sh7
-rw-r--r--src/gnunet/cmd/gnunet-service-gns-go/main.go13
-rw-r--r--src/gnunet/cmd/zonemaster-go/main.go144
-rw-r--r--src/gnunet/config/config.go56
-rw-r--r--src/gnunet/config/gnunet-config.json28
-rw-r--r--src/gnunet/crypto/gns.go126
-rw-r--r--src/gnunet/crypto/gns_edkey.go28
-rw-r--r--src/gnunet/crypto/gns_edkey_test.go56
-rw-r--r--src/gnunet/crypto/gns_pkey.go15
-rw-r--r--src/gnunet/crypto/gns_pkey_test.go48
-rw-r--r--src/gnunet/crypto/gns_test.go35
-rw-r--r--src/gnunet/enums/gns.go17
-rw-r--r--src/gnunet/enums/gns_type.go2
-rw-r--r--src/gnunet/enums/gnunet-gns.tpl2
-rw-r--r--src/gnunet/go.mod4
-rw-r--r--src/gnunet/go.sum4
-rw-r--r--src/gnunet/message/factory.go22
-rw-r--r--src/gnunet/message/msg_dht_p2p.go24
-rw-r--r--src/gnunet/message/msg_gns.go81
-rw-r--r--src/gnunet/message/msg_namecache.go97
-rw-r--r--src/gnunet/message/msg_namestore.go199
-rw-r--r--src/gnunet/service/client.go13
-rw-r--r--src/gnunet/service/dht/blocks/gns.go108
-rw-r--r--src/gnunet/service/dht/blocks/gns_test.go (renamed from src/gnunet/message/msg_gns_test.go)99
-rw-r--r--src/gnunet/service/dht/module.go17
-rw-r--r--src/gnunet/service/gns/block_handler.go134
-rw-r--r--src/gnunet/service/gns/box.go169
-rw-r--r--src/gnunet/service/gns/dns.go16
-rw-r--r--src/gnunet/service/gns/module.go45
-rw-r--r--src/gnunet/service/gns/rr/coexist.go146
-rw-r--r--src/gnunet/service/gns/rr/dns.go119
-rw-r--r--src/gnunet/service/gns/rr/gns.go199
-rw-r--r--src/gnunet/service/gns/rr/gns_box.go375
-rw-r--r--src/gnunet/service/gns/service.go12
-rw-r--r--src/gnunet/service/revocation/pow_test.go2
-rw-r--r--src/gnunet/service/store/store_dht_meta.go24
-rw-r--r--src/gnunet/service/store/store_zonemaster.go548
-rw-r--r--src/gnunet/service/store/store_zonemaster.sql47
-rw-r--r--src/gnunet/service/store/store_zonemaster_test.go105
-rw-r--r--src/gnunet/service/zonemaster/gui.go666
-rw-r--r--src/gnunet/service/zonemaster/gui.htpl133
-rw-r--r--src/gnunet/service/zonemaster/gui_css.htpl253
-rw-r--r--src/gnunet/service/zonemaster/gui_debug.htpl14
-rw-r--r--src/gnunet/service/zonemaster/gui_edit.htpl130
-rw-r--r--src/gnunet/service/zonemaster/gui_new.htpl114
-rw-r--r--src/gnunet/service/zonemaster/gui_rr.htpl420
-rw-r--r--src/gnunet/service/zonemaster/module.go84
-rw-r--r--src/gnunet/service/zonemaster/records.go348
-rw-r--r--src/gnunet/service/zonemaster/rpc.go24
-rw-r--r--src/gnunet/service/zonemaster/service.go150
-rw-r--r--src/gnunet/service/zonemaster/zonemaster.go179
-rw-r--r--src/gnunet/util/array.go20
-rw-r--r--src/gnunet/util/misc.go16
-rw-r--r--src/gnunet/util/time.go5
54 files changed, 5209 insertions, 533 deletions
diff --git a/src/gnunet/build.sh b/src/gnunet/build.sh
index 20f6933..03899f7 100755
--- a/src/gnunet/build.sh
+++ b/src/gnunet/build.sh
@@ -1,4 +1,7 @@
1#!/bin/bash 1#!/bin/bash
2 2
3[ "$1" = "withgen" ] && go generate ./... 3if [ "$1" = "withgen" ]; then
4go install -v -gcflags "-N -l" ./... 4 go generate ./...
5 shift
6fi
7go install $* -gcflags "-N -l" ./...
diff --git a/src/gnunet/cmd/gnunet-service-gns-go/main.go b/src/gnunet/cmd/gnunet-service-gns-go/main.go
index ebf2151..2a4cae8 100644
--- a/src/gnunet/cmd/gnunet-service-gns-go/main.go
+++ b/src/gnunet/cmd/gnunet-service-gns-go/main.go
@@ -28,7 +28,6 @@ import (
28 "time" 28 "time"
29 29
30 "gnunet/config" 30 "gnunet/config"
31 "gnunet/core"
32 "gnunet/service" 31 "gnunet/service"
33 "gnunet/service/gns" 32 "gnunet/service/gns"
34 33
@@ -80,17 +79,9 @@ func main() {
80 params = config.Cfg.GNS.Service.Params 79 params = config.Cfg.GNS.Service.Params
81 } 80 }
82 81
83 // instantiate core service
84 ctx, cancel := context.WithCancel(context.Background())
85 var c *core.Core
86 if c, err = core.NewCore(ctx, config.Cfg.Local); err != nil {
87 logger.Printf(logger.ERROR, "[gns] core failed: %s\n", err.Error())
88 return
89 }
90 defer c.Shutdown()
91
92 // start a new GNS service 82 // start a new GNS service
93 gns := gns.NewService(ctx, c) 83 ctx, cancel := context.WithCancel(context.Background())
84 gns := gns.NewService(ctx, nil)
94 srv := service.NewSocketHandler("gns", gns) 85 srv := service.NewSocketHandler("gns", gns)
95 if err = srv.Start(ctx, socket, params); err != nil { 86 if err = srv.Start(ctx, socket, params); err != nil {
96 logger.Printf(logger.ERROR, "[gns] Error: '%s'", err.Error()) 87 logger.Printf(logger.ERROR, "[gns] Error: '%s'", err.Error())
diff --git a/src/gnunet/cmd/zonemaster-go/main.go b/src/gnunet/cmd/zonemaster-go/main.go
new file mode 100644
index 0000000..1e4b985
--- /dev/null
+++ b/src/gnunet/cmd/zonemaster-go/main.go
@@ -0,0 +1,144 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package main
20
21import (
22 "context"
23 "flag"
24 "os"
25 "os/signal"
26 "strings"
27 "syscall"
28 "time"
29
30 "gnunet/config"
31 "gnunet/service"
32 "gnunet/service/zonemaster"
33
34 "github.com/bfix/gospel/logger"
35)
36
37func main() {
38 defer func() {
39 logger.Println(logger.INFO, "[zonemaster] Bye.")
40 // flush last messages
41 logger.Flush()
42 }()
43 // intro
44 logger.SetLogLevel(logger.DBG)
45 logger.Println(logger.INFO, "[zonemaster] Starting service...")
46
47 var (
48 cfgFile string
49 gui string
50 err error
51 logLevel int
52 rpcEndp string
53 )
54 // handle command line arguments
55 flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet configuration file")
56 flag.StringVar(&gui, "g", "", "GUI listen address")
57 flag.IntVar(&logLevel, "L", logger.INFO, "zonemaster log level (default: INFO)")
58 flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)")
59 flag.Parse()
60
61 // read configuration file and set missing arguments.
62 if err = config.ParseConfig(cfgFile); err != nil {
63 logger.Printf(logger.ERROR, "[zonemaster] Invalid configuration file: %s\n", err.Error())
64 return
65 }
66
67 // apply configuration
68 if config.Cfg.Logging.Level > 0 {
69 logLevel = config.Cfg.Logging.Level
70 }
71 logger.SetLogLevel(logLevel)
72 if len(gui) > 0 {
73 config.Cfg.ZoneMaster.GUI = gui
74 }
75
76 // start a new namestore service under zonemaster umbrella
77 ctx, cancel := context.WithCancel(context.Background())
78 srv, ok := zonemaster.NewService(ctx, nil).(*zonemaster.Service)
79 if !ok {
80 logger.Println(logger.ERROR, "[zonemaster] Failed to create service")
81 return
82 }
83 // start UDS listener if service is specified
84 if config.Cfg.ZoneMaster.Service != nil {
85 sockHdlr := service.NewSocketHandler("zonemaster", srv)
86 if err = sockHdlr.Start(ctx, config.Cfg.ZoneMaster.Service.Socket, config.Cfg.ZoneMaster.Service.Params); err != nil {
87 logger.Printf(logger.ERROR, "[zonemaster] Error: '%s'", err.Error())
88 return
89 }
90 }
91
92 // start a new ZONEMASTER (background service with HTTPS backend)
93 zm := zonemaster.NewZoneMaster(config.Cfg, srv)
94 go zm.Run(ctx)
95
96 // handle command-line arguments for RPC
97 if len(rpcEndp) > 0 {
98 parts := strings.Split(rpcEndp, ":")
99 if parts[0] != "tcp" {
100 logger.Println(logger.ERROR, "[zonemaster] RPC must have a TCP/IP endpoint")
101 return
102 }
103 config.Cfg.RPC.Endpoint = parts[1]
104 }
105 // start JSON-RPC server on request
106 if ep := config.Cfg.RPC.Endpoint; len(ep) > 0 {
107 var rpc *service.JRPCServer
108 if rpc, err = service.RunRPCServer(ctx, ep); err != nil {
109 logger.Printf(logger.ERROR, "[zonemaster] RPC failed to start: %s", err.Error())
110 return
111 }
112 srv.InitRPC(rpc)
113 }
114 // handle OS signals
115 sigCh := make(chan os.Signal, 5)
116 signal.Notify(sigCh)
117
118 // heart beat
119 tick := time.NewTicker(5 * time.Minute)
120
121loop:
122 for {
123 select {
124 // handle OS signals
125 case sig := <-sigCh:
126 switch sig {
127 case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM:
128 logger.Printf(logger.INFO, "[zonemaster] Terminating service (on signal '%s')\n", sig)
129 break loop
130 case syscall.SIGHUP:
131 logger.Println(logger.INFO, "[zonemaster] SIGHUP")
132 case syscall.SIGURG:
133 // TODO: https://github.com/golang/go/issues/37942
134 default:
135 logger.Println(logger.INFO, "[zonemaster] Unhandled signal: "+sig.String())
136 }
137 // handle heart beat
138 case now := <-tick.C:
139 logger.Println(logger.INFO, "[zonemaster] Heart beat at "+now.String())
140 }
141 }
142 // terminating service
143 cancel()
144}
diff --git a/src/gnunet/config/config.go b/src/gnunet/config/config.go
index 526b7b8..05f8cc9 100644
--- a/src/gnunet/config/config.go
+++ b/src/gnunet/config/config.go
@@ -95,6 +95,14 @@ type GNSConfig struct {
95 MaxDepth int `json:"maxDepth"` // maximum recursion depth in resolution 95 MaxDepth int `json:"maxDepth"` // maximum recursion depth in resolution
96} 96}
97 97
98// ZoneMasterConfig contains parameters for the GNS ZoneMaster process
99type ZoneMasterConfig struct {
100 Service *ServiceConfig `json:"service"` // socket for NameStore service
101 Period int `json:"period"` // cycle period
102 Storage util.ParameterSet `json:"storage"` // persistence mechanism for zone data
103 GUI string `json:"gui"` // listen address for HTTP GUI
104}
105
98//---------------------------------------------------------------------- 106//----------------------------------------------------------------------
99// DHT configuration 107// DHT configuration
100//---------------------------------------------------------------------- 108//----------------------------------------------------------------------
@@ -159,6 +167,7 @@ type Config struct {
159 DHT *DHTConfig `json:"dht"` 167 DHT *DHTConfig `json:"dht"`
160 GNS *GNSConfig `json:"gns"` 168 GNS *GNSConfig `json:"gns"`
161 Namecache *NamecacheConfig `json:"namecache"` 169 Namecache *NamecacheConfig `json:"namecache"`
170 ZoneMaster *ZoneMasterConfig `json:"zonemaster"`
162 Revocation *RevocationConfig `json:"revocation"` 171 Revocation *RevocationConfig `json:"revocation"`
163 Logging *LoggingConfig `json:"logging"` 172 Logging *LoggingConfig `json:"logging"`
164} 173}
@@ -200,14 +209,19 @@ var (
200// substString is a helper function to substitute environment variables 209// substString is a helper function to substitute environment variables
201// with actual values. 210// with actual values.
202func substString(s string, env map[string]string) string { 211func substString(s string, env map[string]string) string {
203 matches := rx.FindAllStringSubmatch(s, -1) 212 changed := true
204 for _, m := range matches { 213 for changed {
205 if len(m[1]) != 0 { 214 changed = false
206 subst, ok := env[m[1]] 215 matches := rx.FindAllStringSubmatch(s, -1)
207 if !ok { 216 for _, m := range matches {
208 continue 217 if len(m[1]) != 0 {
218 subst, ok := env[m[1]]
219 if !ok {
220 continue
221 }
222 s = strings.Replace(s, "${"+m[1]+"}", subst, -1)
223 changed = true
209 } 224 }
210 s = strings.Replace(s, "${"+m[1]+"}", subst, -1)
211 } 225 }
212 } 226 }
213 return s 227 return s
@@ -225,14 +239,26 @@ func applySubstitutions(x interface{}, env map[string]string) {
225 case reflect.String: 239 case reflect.String:
226 // check for substitution 240 // check for substitution
227 if s, ok := fld.Interface().(string); ok { 241 if s, ok := fld.Interface().(string); ok {
228 for { 242 sOut := substString(s, env)
229 s1 := substString(s, env) 243 if sOut != s {
230 if s1 == s { 244 logger.Printf(logger.DBG, "[config] %s --> %s\n", s, sOut)
231 break 245 fld.SetString(sOut)
246 }
247 }
248
249 case reflect.Map:
250 // substitute values
251 if s, ok := fld.Interface().(util.ParameterSet); ok {
252 for k, v := range s {
253 v1, ok := v.(string)
254 if !ok {
255 continue
256 }
257 sOut := substString(v1, env)
258 if sOut != v1 {
259 logger.Printf(logger.DBG, "[config] %s --> %s\n", v1, sOut)
260 s[k] = sOut
232 } 261 }
233 logger.Printf(logger.DBG, "[config] %s --> %s\n", s, s1)
234 fld.SetString(s1)
235 s = s1
236 } 262 }
237 } 263 }
238 264
@@ -245,8 +271,6 @@ func applySubstitutions(x interface{}, env map[string]string) {
245 e := fld.Elem() 271 e := fld.Elem()
246 if e.IsValid() { 272 if e.IsValid() {
247 process(fld.Elem()) 273 process(fld.Elem())
248 } else {
249 logger.Printf(logger.ERROR, "[config] 'nil' pointer encountered")
250 } 274 }
251 } 275 }
252 } 276 }
diff --git a/src/gnunet/config/gnunet-config.json b/src/gnunet/config/gnunet-config.json
index f6823d7..5e9ec99 100644
--- a/src/gnunet/config/gnunet-config.json
+++ b/src/gnunet/config/gnunet-config.json
@@ -21,11 +21,13 @@
21 }, 21 },
22 "environ": { 22 "environ": {
23 "TMP": "/tmp", 23 "TMP": "/tmp",
24 "RT_SYS": "${TMP}/gnunet-system-runtime" 24 "RT_SYS": "${TMP}/gnunet-system-runtime",
25 "RT_USER": "${TMP}/gnunet-user-runtime",
26 "VAR_LIB": "/var/lib/gnunet"
25 }, 27 },
26 "dht": { 28 "dht": {
27 "service": { 29 "service": {
28 "socket": "${RT_SYS}/gnunet-service-dht.sock", 30 "socket": "${RT_SYS}/gnunet-service-dht-go.sock",
29 "params": { 31 "params": {
30 "perm": "0770" 32 "perm": "0770"
31 } 33 }
@@ -33,7 +35,7 @@
33 "storage": { 35 "storage": {
34 "mode": "file", 36 "mode": "file",
35 "cache": false, 37 "cache": false,
36 "path": "/var/lib/gnunet/dht/store", 38 "path": "${VAR_LIB}/dht/store",
37 "maxGB": 10 39 "maxGB": 10
38 }, 40 },
39 "routing": { 41 "routing": {
@@ -54,7 +56,7 @@
54 }, 56 },
55 "namecache": { 57 "namecache": {
56 "service": { 58 "service": {
57 "socket": "${RT_SYS}/gnunet-service-namecache.sock", 59 "socket": "${RT_SYS}/gnunet-service-namecache-go.sock",
58 "params": { 60 "params": {
59 "perm": "0770" 61 "perm": "0770"
60 } 62 }
@@ -62,7 +64,7 @@
62 "storage": { 64 "storage": {
63 "mode": "file", 65 "mode": "file",
64 "cache": true, 66 "cache": true,
65 "path": "/var/lib/gnunet/namecache", 67 "path": "${VAR_LIB}/namecache",
66 "num": 1000, 68 "num": 1000,
67 "expire": 43200 69 "expire": 43200
68 } 70 }
@@ -81,11 +83,25 @@
81 "id": 15 83 "id": 15
82 } 84 }
83 }, 85 },
86 "zonemaster": {
87 "period": 300,
88 "storage": {
89 "mode": "sqlite3",
90 "file": "${VAR_LIB}/gns/zonemaster.sqlite3"
91 },
92 "gui": "127.0.0.1:8100",
93 "service": {
94 "socket": "${RT_USER}/gnunet-service-namestore-go.sock",
95 "params": {
96 "perm": "0770"
97 }
98 }
99 },
84 "rpc": { 100 "rpc": {
85 "endpoint": "tcp:127.0.0.1:80" 101 "endpoint": "tcp:127.0.0.1:80"
86 }, 102 },
87 "logging": { 103 "logging": {
88 "level": 4, 104 "level": 4,
89 "file": "/tmp/gnunet-go/run.log" 105 "file": "${TMP}/gnunet-go/run.log"
90 } 106 }
91} \ No newline at end of file 107} \ No newline at end of file
diff --git a/src/gnunet/crypto/gns.go b/src/gnunet/crypto/gns.go
index 39eb8c5..301acb1 100644
--- a/src/gnunet/crypto/gns.go
+++ b/src/gnunet/crypto/gns.go
@@ -20,6 +20,7 @@ package crypto
20 20
21import ( 21import (
22 "bytes" 22 "bytes"
23 "crypto/rand"
23 "crypto/sha256" 24 "crypto/sha256"
24 "crypto/sha512" 25 "crypto/sha512"
25 "encoding/binary" 26 "encoding/binary"
@@ -119,6 +120,9 @@ type ZoneKeyImpl interface {
119 120
120 // Verify a signature for binary data 121 // Verify a signature for binary data
121 Verify(data []byte, sig *ZoneSignature) (bool, error) 122 Verify(data []byte, sig *ZoneSignature) (bool, error)
123
124 // ID returns the GNUnet identifier for a public zone key
125 ID() string
122} 126}
123 127
124// ZonePrivateImpl defines the methods for a private zone key. 128// ZonePrivateImpl defines the methods for a private zone key.
@@ -134,6 +138,9 @@ type ZonePrivateImpl interface {
134 138
135 // Public returns the associated public key 139 // Public returns the associated public key
136 Public() ZoneKeyImpl 140 Public() ZoneKeyImpl
141
142 // ID returns the GNUnet identifier for a private zone key
143 ID() string
137} 144}
138 145
139// ZoneSigImpl defines the methods for a signature object. 146// ZoneSigImpl defines the methods for a signature object.
@@ -147,8 +154,8 @@ type ZoneSigImpl interface {
147 154
148//nolint:stylecheck // allow non-camel-case in constants 155//nolint:stylecheck // allow non-camel-case in constants
149var ( 156var (
150 ZONE_PKEY = uint32(enums.GNS_TYPE_PKEY) 157 ZONE_EDKEY = enums.GNS_TYPE_EDKEY
151 ZONE_EDKEY = uint32(enums.GNS_TYPE_EDKEY) 158 ZONE_PKEY = enums.GNS_TYPE_PKEY
152) 159)
153 160
154var ( 161var (
@@ -176,7 +183,7 @@ type ZoneImplementation struct {
176 183
177// keep a mapping of available implementations 184// keep a mapping of available implementations
178var ( 185var (
179 zoneImpl = make(map[uint32]*ZoneImplementation) 186 zoneImpl = make(map[enums.GNSType]*ZoneImplementation)
180) 187)
181 188
182// Error codes 189// Error codes
@@ -187,7 +194,7 @@ var (
187 194
188// GetImplementation return the factory for a given zone type. 195// GetImplementation return the factory for a given zone type.
189// If zje zone type is unregistered, nil is returned. 196// If zje zone type is unregistered, nil is returned.
190func GetImplementation(ztype uint32) *ZoneImplementation { 197func GetImplementation(ztype enums.GNSType) *ZoneImplementation {
191 if impl, ok := zoneImpl[ztype]; ok { 198 if impl, ok := zoneImpl[ztype]; ok {
192 return impl 199 return impl
193 } 200 }
@@ -209,13 +216,22 @@ type ZonePrivate struct {
209 impl ZonePrivateImpl // reference to implementation 216 impl ZonePrivateImpl // reference to implementation
210} 217}
211 218
212// NewZonePrivate returns a new initialized ZonePrivate instance 219// NewZonePrivate returns a new initialized ZonePrivate instance. If no data is
213func NewZonePrivate(ztype uint32, d []byte) (zp *ZonePrivate, err error) { 220// provided, a new random key is created
221func NewZonePrivate(ztype enums.GNSType, d []byte) (zp *ZonePrivate, err error) {
214 // get factory for given zone type 222 // get factory for given zone type
215 impl, ok := zoneImpl[ztype] 223 impl, ok := zoneImpl[ztype]
216 if !ok { 224 if !ok {
217 return nil, ErrNoImplementation 225 return nil, ErrNoImplementation
218 } 226 }
227 // init data available?
228 if d == nil {
229 // no: create random seed
230 d = make([]byte, impl.PrivateSize)
231 if _, err = rand.Read(d); err != nil {
232 return
233 }
234 }
219 // assemble private zone key 235 // assemble private zone key
220 zp = &ZonePrivate{ 236 zp = &ZonePrivate{
221 ZoneKey{ 237 ZoneKey{
@@ -246,11 +262,9 @@ func (zp *ZonePrivate) KeySize() uint {
246 262
247// Derive key (key blinding) 263// Derive key (key blinding)
248func (zp *ZonePrivate) Derive(label, context string) (dzp *ZonePrivate, h *math.Int, err error) { 264func (zp *ZonePrivate) Derive(label, context string) (dzp *ZonePrivate, h *math.Int, err error) {
249 // get factory for given zone type
250 impl := zoneImpl[zp.Type]
251
252 // calculate derived key 265 // calculate derived key
253 h = deriveH(zp.impl.Bytes(), label, context) 266 key := zp.Public().Bytes()
267 h = deriveH(key, label, context)
254 var derived ZonePrivateImpl 268 var derived ZonePrivateImpl
255 if derived, h, err = zp.impl.Derive(h); err != nil { 269 if derived, h, err = zp.impl.Derive(h); err != nil {
256 return 270 return
@@ -264,9 +278,8 @@ func (zp *ZonePrivate) Derive(label, context string) (dzp *ZonePrivate, h *math.
264 }, 278 },
265 derived, 279 derived,
266 } 280 }
267 zp.ZoneKey.KeyData = derived.Public().Bytes() 281 dzp.ZoneKey.KeyData = derived.Public().Bytes()
268 zp.ZoneKey.impl = impl.NewPublic() 282 err = dzp.Init()
269 err = zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
270 return 283 return
271} 284}
272 285
@@ -280,33 +293,45 @@ func (zp *ZonePrivate) Public() *ZoneKey {
280 return &zp.ZoneKey 293 return &zp.ZoneKey
281} 294}
282 295
296// ID returns the human-readable zone private key.
297func (zp *ZonePrivate) ID() string {
298 return zp.impl.ID()
299}
300
283//---------------------------------------------------------------------- 301//----------------------------------------------------------------------
284// Zone key (public) 302// Zone key (public)
285//---------------------------------------------------------------------- 303//----------------------------------------------------------------------
286 304
287// ZoneKey represents the possible types of zone keys (PKEY, EDKEY,...) 305// ZoneKey represents the possible types of zone keys (PKEY, EDKEY,...)
288type ZoneKey struct { 306type ZoneKey struct {
289 Type uint32 `json:"type" order:"big"` 307 Type enums.GNSType `json:"type" order:"big"`
290 KeyData []byte `json:"key" size:"(KeySize)"` 308 KeyData []byte `json:"key" size:"(KeySize)"`
291 309
292 impl ZoneKeyImpl // reference to implementation 310 impl ZoneKeyImpl // reference to implementation
293} 311}
294 312
313// Init a zone key where only the attributes have been read/deserialized.
314func (zk *ZoneKey) Init() (err error) {
315 if zk.impl == nil {
316 // initialize implementation
317 impl, ok := zoneImpl[zk.Type]
318 if !ok {
319 err = ErrUnknownZoneType
320 return
321 }
322 zk.impl = impl.NewPublic()
323 err = zk.impl.Init(zk.KeyData)
324 }
325 return
326}
327
295// NewZoneKey returns a new initialized ZoneKey instance 328// NewZoneKey returns a new initialized ZoneKey instance
296func NewZoneKey(d []byte) (zk *ZoneKey, err error) { 329func NewZoneKey(d []byte) (zk *ZoneKey, err error) {
297 // read zone key from data 330 // read zone key from data
298 zk = new(ZoneKey) 331 zk = new(ZoneKey)
299 if err = data.Unmarshal(zk, d); err != nil { 332 if err = data.Unmarshal(zk, d); err == nil {
300 return 333 err = zk.Init()
301 } 334 }
302 // initialize implementation
303 impl, ok := zoneImpl[zk.Type]
304 if !ok {
305 err = ErrUnknownZoneType
306 return
307 }
308 zk.impl = impl.NewPublic()
309 err = zk.impl.Init(zk.KeyData)
310 return 335 return
311} 336}
312 337
@@ -321,7 +346,8 @@ func (zk *ZoneKey) KeySize() uint {
321 346
322// Derive key (key blinding) 347// Derive key (key blinding)
323func (zk *ZoneKey) Derive(label, context string) (dzk *ZoneKey, h *math.Int, err error) { 348func (zk *ZoneKey) Derive(label, context string) (dzk *ZoneKey, h *math.Int, err error) {
324 h = deriveH(zk.KeyData, label, context) 349 key := zk.Bytes()
350 h = deriveH(key, label, context)
325 var derived ZoneKeyImpl 351 var derived ZoneKeyImpl
326 if derived, h, err = zk.impl.Derive(h); err != nil { 352 if derived, h, err = zk.impl.Derive(h); err != nil {
327 return 353 return
@@ -359,15 +385,7 @@ func (zk *ZoneKey) Verify(data []byte, zs *ZoneSignature) (ok bool, err error) {
359 385
360// ID returns the human-readable zone identifier. 386// ID returns the human-readable zone identifier.
361func (zk *ZoneKey) ID() string { 387func (zk *ZoneKey) ID() string {
362 buf := new(bytes.Buffer) 388 return zk.impl.ID()
363 err := binary.Write(buf, binary.BigEndian, zk.Type)
364 if err == nil {
365 _, err = buf.Write(zk.KeyData)
366 }
367 if err != nil {
368 logger.Printf(logger.ERROR, "[ZoneKey.ID] failed: %s", err.Error())
369 }
370 return util.EncodeBinaryToString(buf.Bytes())
371} 389}
372 390
373// Bytes returns all bytes of a zone key 391// Bytes returns all bytes of a zone key
@@ -402,31 +420,33 @@ type ZoneSignature struct {
402 impl ZoneSigImpl // reference to implementation 420 impl ZoneSigImpl // reference to implementation
403} 421}
404 422
405// NewZoneSignature returns a new initialized ZoneSignature instance 423func (zs *ZoneSignature) Init() (err error) {
406func NewZoneSignature(d []byte) (sig *ZoneSignature, err error) {
407 // read signature
408 sig = new(ZoneSignature)
409 if err = data.Unmarshal(sig, d); err != nil {
410 return
411 }
412 // initialize implementations 424 // initialize implementations
413 impl, ok := zoneImpl[sig.Type] 425 impl, ok := zoneImpl[zs.Type]
414 if !ok { 426 if !ok {
415 err = ErrUnknownZoneType 427 err = ErrUnknownZoneType
416 return 428 return
417 } 429 }
418 // set signature implementation 430 // set signature implementation
419 zs := impl.NewSignature() 431 sig := impl.NewSignature()
420 if err = zs.Init(sig.Signature); err != nil { 432 if err = sig.Init(zs.Signature); err != nil {
421 return 433 return
422 } 434 }
423 sig.impl = zs 435 zs.impl = sig
424 // set public key implementation 436 // set public key implementation
425 zk := impl.NewPublic() 437 zk := impl.NewPublic()
426 if err = zk.Init(sig.KeyData); err != nil { 438 err = zk.Init(zs.KeyData)
427 return 439 zs.ZoneKey.impl = zk
440 return
441}
442
443// NewZoneSignature returns a new initialized ZoneSignature instance
444func NewZoneSignature(d []byte) (sig *ZoneSignature, err error) {
445 // read signature
446 sig = new(ZoneSignature)
447 if err = data.Unmarshal(sig, d); err == nil {
448 err = sig.Init()
428 } 449 }
429 sig.ZoneKey.impl = zk
430 return 450 return
431} 451}
432 452
@@ -465,3 +485,11 @@ func deriveH(key []byte, label, context string) *math.Int {
465 } 485 }
466 return math.NewIntFromBytes(b) 486 return math.NewIntFromBytes(b)
467} 487}
488
489// convert (type|data) to GNUnet identifier
490func asID(t enums.GNSType, data []byte) string {
491 buf := new(bytes.Buffer)
492 _ = binary.Write(buf, binary.BigEndian, t)
493 _, _ = buf.Write(data)
494 return util.EncodeBinaryToString(buf.Bytes())
495}
diff --git a/src/gnunet/crypto/gns_edkey.go b/src/gnunet/crypto/gns_edkey.go
index 7d4323a..08dbc55 100644
--- a/src/gnunet/crypto/gns_edkey.go
+++ b/src/gnunet/crypto/gns_edkey.go
@@ -22,6 +22,7 @@ import (
22 "crypto/sha256" 22 "crypto/sha256"
23 "crypto/sha512" 23 "crypto/sha512"
24 "errors" 24 "errors"
25 "gnunet/enums"
25 "gnunet/util" 26 "gnunet/util"
26 27
27 "github.com/bfix/gospel/crypto/ed25519" 28 "github.com/bfix/gospel/crypto/ed25519"
@@ -57,7 +58,7 @@ func init() {
57 58
58// EDKEYPublicImpl implements the public key scheme. 59// EDKEYPublicImpl implements the public key scheme.
59type EDKEYPublicImpl struct { 60type EDKEYPublicImpl struct {
60 ztype uint32 61 ztype enums.GNSType
61 pub *ed25519.PublicKey 62 pub *ed25519.PublicKey
62} 63}
63 64
@@ -159,6 +160,11 @@ func (pk *EDKEYPublicImpl) BlockKey(label string, expire util.AbsoluteTime) (ske
159 return 160 return
160} 161}
161 162
163// ID returns the GNUnet identifier for a public zone key
164func (pk *EDKEYPublicImpl) ID() string {
165 return asID(enums.GNS_TYPE_EDKEY, pk.pub.Bytes())
166}
167
162//---------------------------------------------------------------------- 168//----------------------------------------------------------------------
163// Private key 169// Private key
164//---------------------------------------------------------------------- 170//----------------------------------------------------------------------
@@ -167,12 +173,14 @@ func (pk *EDKEYPublicImpl) BlockKey(label string, expire util.AbsoluteTime) (ske
167type EDKEYPrivateImpl struct { 173type EDKEYPrivateImpl struct {
168 EDKEYPublicImpl 174 EDKEYPublicImpl
169 175
170 prv *ed25519.PrivateKey 176 seed []byte // seed used to generate key
177 prv *ed25519.PrivateKey // private key
171} 178}
172 179
173// Init instance from binary data. The data represents a big integer 180// Init instance from binary data. The data represents a big integer
174// (in big-endian notation) for the private scalar d. 181// (in big-endian notation) for the private scalar d.
175func (pk *EDKEYPrivateImpl) Init(data []byte) error { 182func (pk *EDKEYPrivateImpl) Init(data []byte) error {
183 pk.seed = util.Clone(data)
176 pk.prv = ed25519.NewPrivateKeyFromSeed(data) 184 pk.prv = ed25519.NewPrivateKeyFromSeed(data)
177 pk.ztype = ZONE_EDKEY 185 pk.ztype = ZONE_EDKEY
178 pk.pub = pk.prv.Public() 186 pk.pub = pk.prv.Public()
@@ -194,14 +202,19 @@ func (pk *EDKEYPrivateImpl) Public() ZoneKeyImpl {
194// (key blinding). Returns the derived key and the blinding value. 202// (key blinding). Returns the derived key and the blinding value.
195func (pk *EDKEYPrivateImpl) Derive(h *math.Int) (dPk ZonePrivateImpl, hOut *math.Int, err error) { 203func (pk *EDKEYPrivateImpl) Derive(h *math.Int) (dPk ZonePrivateImpl, hOut *math.Int, err error) {
196 // limit to allowed value range 204 // limit to allowed value range
197 hOut = h.Mod(ed25519.GetCurve().N) 205 hOut = h.SetBit(255, 0)
206 // derive private key
198 derived := pk.prv.Mult(hOut) 207 derived := pk.prv.Mult(hOut)
208 // derive nonce
209 md := sha256.Sum256(append(pk.prv.Nonce, h.Bytes()...))
210 derived.Nonce = md[:]
211 // assemble EDKEY private key implementation
199 dPk = &EDKEYPrivateImpl{ 212 dPk = &EDKEYPrivateImpl{
200 EDKEYPublicImpl{ 213 EDKEYPublicImpl: EDKEYPublicImpl{
201 pk.ztype, 214 pk.ztype,
202 derived.Public(), 215 derived.Public(),
203 }, 216 },
204 derived, 217 prv: derived,
205 } 218 }
206 return 219 return
207} 220}
@@ -228,6 +241,11 @@ func (pk *EDKEYPrivateImpl) Sign(data []byte) (sig *ZoneSignature, err error) {
228 return 241 return
229} 242}
230 243
244// ID returns the GNUnet identifier for a private zone key
245func (pk *EDKEYPrivateImpl) ID() string {
246 return asID(enums.GNS_TYPE_EDKEY, pk.seed)
247}
248
231//---------------------------------------------------------------------- 249//----------------------------------------------------------------------
232// Signature 250// Signature
233//---------------------------------------------------------------------- 251//----------------------------------------------------------------------
diff --git a/src/gnunet/crypto/gns_edkey_test.go b/src/gnunet/crypto/gns_edkey_test.go
new file mode 100644
index 0000000..aa9728f
--- /dev/null
+++ b/src/gnunet/crypto/gns_edkey_test.go
@@ -0,0 +1,56 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package crypto
20
21import (
22 "bytes"
23 "gnunet/enums"
24 "testing"
25)
26
27func TestEdKeyCreate(t *testing.T) {
28 // create private key
29 zp, err := NewZonePrivate(enums.GNS_TYPE_EDKEY, nil)
30 if err != nil {
31 t.Fatal(err)
32 }
33 t.Log(zp.ID())
34}
35
36func TestDeriveEDKEY(t *testing.T) {
37 // create new key pair
38 zp, err := NewZonePrivate(enums.GNS_TYPE_EDKEY, nil)
39 if err != nil {
40 t.Fatal(err)
41 }
42 zk := zp.Public()
43
44 // derive keys
45 dzp, _, err := zp.Derive("@", "gns")
46 if err != nil {
47 t.Fatal(err)
48 }
49 dzk, _, err := zk.Derive("@", "gns")
50 if err != nil {
51 t.Fatal(err)
52 }
53 if !bytes.Equal(dzp.Public().Bytes(), dzk.Bytes()) {
54 t.Fatal("derive mismatch")
55 }
56}
diff --git a/src/gnunet/crypto/gns_pkey.go b/src/gnunet/crypto/gns_pkey.go
index 13925d4..8184483 100644
--- a/src/gnunet/crypto/gns_pkey.go
+++ b/src/gnunet/crypto/gns_pkey.go
@@ -23,6 +23,7 @@ import (
23 "crypto/cipher" 23 "crypto/cipher"
24 "crypto/sha256" 24 "crypto/sha256"
25 "crypto/sha512" 25 "crypto/sha512"
26 "gnunet/enums"
26 "gnunet/util" 27 "gnunet/util"
27 28
28 "github.com/bfix/gospel/crypto/ed25519" 29 "github.com/bfix/gospel/crypto/ed25519"
@@ -57,7 +58,7 @@ func init() {
57 58
58// PKEYPublicImpl implements the public key scheme. 59// PKEYPublicImpl implements the public key scheme.
59type PKEYPublicImpl struct { 60type PKEYPublicImpl struct {
60 ztype uint32 61 ztype enums.GNSType
61 pub *ed25519.PublicKey 62 pub *ed25519.PublicKey
62} 63}
63 64
@@ -155,6 +156,11 @@ func (pk *PKEYPublicImpl) cipher(encrypt bool, data []byte, label string, expire
155 return 156 return
156} 157}
157 158
159// ID returns the GNUnet identifier for a public zone key
160func (pk *PKEYPublicImpl) ID() string {
161 return asID(enums.GNS_TYPE_PKEY, pk.pub.Bytes())
162}
163
158//---------------------------------------------------------------------- 164//----------------------------------------------------------------------
159// Private key 165// Private key
160//---------------------------------------------------------------------- 166//----------------------------------------------------------------------
@@ -171,7 +177,7 @@ type PKEYPrivateImpl struct {
171func (pk *PKEYPrivateImpl) Init(data []byte) error { 177func (pk *PKEYPrivateImpl) Init(data []byte) error {
172 d := math.NewIntFromBytes(data) 178 d := math.NewIntFromBytes(data)
173 pk.prv = ed25519.NewPrivateKeyFromD(d) 179 pk.prv = ed25519.NewPrivateKeyFromD(d)
174 pk.ztype = ZONE_PKEY 180 pk.ztype = enums.GNS_TYPE_PKEY
175 pk.pub = pk.prv.Public() 181 pk.pub = pk.prv.Public()
176 return nil 182 return nil
177} 183}
@@ -225,6 +231,11 @@ func (pk *PKEYPrivateImpl) Sign(data []byte) (sig *ZoneSignature, err error) {
225 return 231 return
226} 232}
227 233
234// ID returns the GNUnet identifier for a private zone key
235func (pk *PKEYPrivateImpl) ID() string {
236 return asID(enums.GNS_TYPE_PKEY, pk.prv.D.Bytes())
237}
238
228//---------------------------------------------------------------------- 239//----------------------------------------------------------------------
229// Signature 240// Signature
230//---------------------------------------------------------------------- 241//----------------------------------------------------------------------
diff --git a/src/gnunet/crypto/gns_pkey_test.go b/src/gnunet/crypto/gns_pkey_test.go
new file mode 100644
index 0000000..0982227
--- /dev/null
+++ b/src/gnunet/crypto/gns_pkey_test.go
@@ -0,0 +1,48 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package crypto
20
21import (
22 "bytes"
23 "gnunet/enums"
24 "testing"
25)
26
27func TestDerivePKEY(t *testing.T) {
28 // create new key pair
29 zp, err := NewZonePrivate(enums.GNS_TYPE_PKEY, nil)
30 if err != nil {
31 t.Fatal(err)
32 }
33 zk := zp.Public()
34
35 // derive keys
36 dzp, _, err := zp.Derive("@", "gns")
37 if err != nil {
38 t.Fatal(err)
39 }
40 dzk, _, err := zk.Derive("@", "gns")
41 if err != nil {
42 t.Fatal(err)
43 }
44
45 if !bytes.Equal(dzp.Public().Bytes(), dzk.Bytes()) {
46 t.Fatal("derive mismatch")
47 }
48}
diff --git a/src/gnunet/crypto/gns_test.go b/src/gnunet/crypto/gns_test.go
index 12ab603..2cccb4d 100644
--- a/src/gnunet/crypto/gns_test.go
+++ b/src/gnunet/crypto/gns_test.go
@@ -23,6 +23,7 @@ import (
23 "crypto/sha256" 23 "crypto/sha256"
24 "crypto/sha512" 24 "crypto/sha512"
25 "encoding/hex" 25 "encoding/hex"
26 "gnunet/enums"
26 "gnunet/util" 27 "gnunet/util"
27 "testing" 28 "testing"
28 "time" 29 "time"
@@ -222,34 +223,34 @@ func TestDeriveH(t *testing.T) {
222 CONTEXT = "gns" 223 CONTEXT = "gns"
223 224
224 H = []byte{ 225 H = []byte{
225 0x07, 0x1e, 0xfc, 0xa7, 0xdb, 0x28, 0x50, 0xbd, 226 0x06, 0x5b, 0xb7, 0x42, 0x12, 0xa1, 0xae, 0xc3,
226 0x6f, 0x35, 0x4e, 0xbf, 0xe3, 0x8c, 0x5b, 0xbf, 227 0x59, 0x68, 0xdd, 0xdb, 0xca, 0xa3, 0x48, 0xfc,
227 0xd6, 0xba, 0x2f, 0x80, 0x5c, 0xd8, 0xd3, 0xb5, 228 0xb0, 0xcd, 0x89, 0xd4, 0xcf, 0x9a, 0xe0, 0xfe,
228 0x4e, 0xdd, 0x7f, 0x3d, 0xd0, 0x73, 0x0d, 0x1a, 229 0xd1, 0xf9, 0xab, 0x6b, 0xd4, 0x28, 0xf4, 0x95,
229 } 230 }
230 Q = []byte{ 231 Q = []byte{
231 // zone type 232 // zone type
232 0x00, 0x01, 0x00, 0x00, 233 0x00, 0x01, 0x00, 0x00,
233 // derived public key data 234 // derived public key data
234 0x9f, 0x27, 0xad, 0x25, 0xb5, 0x95, 0x4a, 0x46, 235 0xb1, 0x0e, 0x88, 0xd5, 0x17, 0x02, 0xf3, 0x3d,
235 0x7b, 0xc6, 0x5a, 0x67, 0x6b, 0x7a, 0x6d, 0x23, 236 0xc9, 0xcb, 0xa1, 0xe9, 0x16, 0x65, 0x9c, 0x44,
236 0xb2, 0xef, 0x30, 0x0f, 0x7f, 0xc7, 0x00, 0x58, 237 0x47, 0x9c, 0xc8, 0xdb, 0x83, 0x32, 0xd1, 0xd1,
237 0x05, 0x9e, 0x7f, 0x29, 0xe5, 0x94, 0xb5, 0xc1, 238 0xc5, 0x03, 0xdb, 0x50, 0x0e, 0xbd, 0x2d, 0x67,
238 } 239 }
239 QUERY = []byte{ 240 QUERY = []byte{
240 0xa9, 0x1a, 0x2c, 0x46, 0xf1, 0x98, 0x35, 0x50, 241 0xa9, 0x47, 0x81, 0x8a, 0xaf, 0x45, 0x94, 0xda,
241 0x4f, 0x4e, 0x96, 0x78, 0x2d, 0x77, 0xd1, 0x3b, 242 0x89, 0x41, 0xfa, 0x29, 0x77, 0x53, 0x94, 0x9d,
242 0x9d, 0x4e, 0x61, 0xf3, 0x50, 0xe2, 0xe6, 0xa5, 243 0xcb, 0xc5, 0xfb, 0x41, 0xea, 0x77, 0xc6, 0x25,
243 0xc2, 0xd1, 0x36, 0xc1, 0xf1, 0x37, 0x94, 0x79, 244 0x11, 0x3a, 0x59, 0x09, 0x32, 0xfe, 0xeb, 0xb4,
244 0x19, 0xe9, 0xab, 0x2b, 0xae, 0xb5, 0xb9, 0x79, 245 0x59, 0x98, 0x69, 0xe2, 0x83, 0xe9, 0xdb, 0xd9,
245 0xe9, 0x1e, 0xf2, 0x6a, 0xaa, 0x54, 0x81, 0x65, 246 0xc7, 0x24, 0xeb, 0xf2, 0xd5, 0x30, 0x3b, 0x73,
246 0xac, 0xb2, 0xec, 0xca, 0x8e, 0x30, 0x76, 0x1c, 247 0xd7, 0xda, 0x9a, 0x2c, 0xd1, 0xd7, 0x95, 0x70,
247 0xc2, 0x1b, 0xbe, 0x89, 0x0b, 0x34, 0x6d, 0xa1, 248 0xc5, 0x9d, 0x71, 0xb8, 0x32, 0x68, 0xc9, 0xd1,
248 } 249 }
249 ) 250 )
250 251
251 // create private key from scalar 252 // create private key from scalar
252 prv, err := NewZonePrivate(ZONE_PKEY, D) 253 prv, err := NewZonePrivate(enums.GNS_TYPE_PKEY, D)
253 if err != nil { 254 if err != nil {
254 t.Fatal(err) 255 t.Fatal(err)
255 } 256 }
diff --git a/src/gnunet/enums/gns.go b/src/gnunet/enums/gns.go
index f6e58a2..8f786f8 100644
--- a/src/gnunet/enums/gns.go
+++ b/src/gnunet/enums/gns.go
@@ -19,12 +19,15 @@
19//nolint:stylecheck // allow non-camel-case for constants 19//nolint:stylecheck // allow non-camel-case for constants
20package enums 20package enums
21 21
22// GNSFlag type
23type GNSFlag uint32
24
22const ( 25const (
23 // GNS record flags 26 // GNS record flags
24 GNS_FLAG_PRIVATE = 2 // Record is not shared on the DHT 27 GNS_FLAG_PRIVATE GNSFlag = 2 // Record is not shared on the DHT
25 GNS_FLAG_SUPPL = 4 // Supplemental records (e.g. NICK) in a block 28 GNS_FLAG_SUPPL GNSFlag = 4 // Supplemental records (e.g. NICK) in a block
26 GNS_FLAG_EXPREL = 8 // Expire time in record is in relative time. 29 GNS_FLAG_EXPREL GNSFlag = 8 // Expire time in record is in relative time.
27 GNS_FLAG_SHADOW = 16 // Record is ignored if non-expired records of same type exist in block 30 GNS_FLAG_SHADOW GNSFlag = 16 // Record is ignored if non-expired records of same type exist in block
28 31
29 // GNS_LocalOptions 32 // GNS_LocalOptions
30 GNS_LO_DEFAULT = 0 // Defaults, look in cache, then in DHT. 33 GNS_LO_DEFAULT = 0 // Defaults, look in cache, then in DHT.
@@ -39,3 +42,9 @@ const (
39//go:generate go run generate.go gnunet-gns.rec gnunet-gns.tpl gns_type.go 42//go:generate go run generate.go gnunet-gns.rec gnunet-gns.tpl gns_type.go
40 43
41//go:generate stringer -type=GNSType gns_type.go 44//go:generate stringer -type=GNSType gns_type.go
45
46// GNSSpec is the combination of type and flags
47type GNSSpec struct {
48 Type GNSType
49 Flags GNSFlag
50}
diff --git a/src/gnunet/enums/gns_type.go b/src/gnunet/enums/gns_type.go
index 5a13d67..d4cf6a6 100644
--- a/src/gnunet/enums/gns_type.go
+++ b/src/gnunet/enums/gns_type.go
@@ -3,7 +3,7 @@
3//nolint:stylecheck // allow non-camel-case for constants 3//nolint:stylecheck // allow non-camel-case for constants
4package enums 4package enums
5 5
6type GNSType int 6type GNSType uint32
7 7
8// GNS constants 8// GNS constants
9const ( 9const (
diff --git a/src/gnunet/enums/gnunet-gns.tpl b/src/gnunet/enums/gnunet-gns.tpl
index 3249569..4f4f598 100644
--- a/src/gnunet/enums/gnunet-gns.tpl
+++ b/src/gnunet/enums/gnunet-gns.tpl
@@ -3,7 +3,7 @@
3//nolint:stylecheck // allow non-camel-case for constants 3//nolint:stylecheck // allow non-camel-case for constants
4package enums 4package enums
5 5
6type GNSType int 6type GNSType uint32
7 7
8// GNS constants 8// GNS constants
9const ( 9const (
diff --git a/src/gnunet/go.mod b/src/gnunet/go.mod
index 26f0e38..185eaad 100644
--- a/src/gnunet/go.mod
+++ b/src/gnunet/go.mod
@@ -3,7 +3,7 @@ module gnunet
3go 1.18 3go 1.18
4 4
5require ( 5require (
6 github.com/bfix/gospel v1.2.19 6 github.com/bfix/gospel v1.2.20
7 github.com/go-redis/redis/v8 v8.11.5 7 github.com/go-redis/redis/v8 v8.11.5
8 github.com/go-sql-driver/mysql v1.6.0 8 github.com/go-sql-driver/mysql v1.6.0
9 github.com/gorilla/mux v1.8.0 9 github.com/gorilla/mux v1.8.0
@@ -24,4 +24,4 @@ require (
24 golang.org/x/tools v0.1.11 // indirect 24 golang.org/x/tools v0.1.11 // indirect
25) 25)
26 26
27// replace github.com/bfix/gospel v1.2.19 => ../gospel 27// replace github.com/bfix/gospel v1.2.20 => ../gospel
diff --git a/src/gnunet/go.sum b/src/gnunet/go.sum
index a1c014b..a8e3d7e 100644
--- a/src/gnunet/go.sum
+++ b/src/gnunet/go.sum
@@ -1,5 +1,5 @@
1github.com/bfix/gospel v1.2.19 h1:B57L5CMjKPeRPtVxt1JcSx42AKwD+SpN32QaF0DxXFM= 1github.com/bfix/gospel v1.2.20 h1:e/IxmTiC579jIQlIxpMzCX/MIKHNsBzJ1WdMKheCgBw=
2github.com/bfix/gospel v1.2.19/go.mod h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI= 2github.com/bfix/gospel v1.2.20/go.mod h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI=
3github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 3github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
4github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 4github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 5github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
diff --git a/src/gnunet/message/factory.go b/src/gnunet/message/factory.go
index 8f664b0..82af6e9 100644
--- a/src/gnunet/message/factory.go
+++ b/src/gnunet/message/factory.go
@@ -24,6 +24,8 @@ import (
24) 24)
25 25
26// NewEmptyMessage creates a new empty message object for the given type. 26// NewEmptyMessage creates a new empty message object for the given type.
27//
28//nolint:gocyclo // it's a long switch intentionally
27func NewEmptyMessage(msgType enums.MsgType) (Message, error) { 29func NewEmptyMessage(msgType enums.MsgType) (Message, error) {
28 switch msgType { 30 switch msgType {
29 //------------------------------------------------------------------ 31 //------------------------------------------------------------------
@@ -76,7 +78,7 @@ func NewEmptyMessage(msgType enums.MsgType) (Message, error) {
76 case enums.MSG_DHT_P2P_GET: 78 case enums.MSG_DHT_P2P_GET:
77 return NewDHTP2PGetMsg(), nil 79 return NewDHTP2PGetMsg(), nil
78 case enums.MSG_DHT_P2P_PUT: 80 case enums.MSG_DHT_P2P_PUT:
79 return NewDHTP2PPutMsg(), nil 81 return NewDHTP2PPutMsg(nil), nil
80 case enums.MSG_DHT_P2P_RESULT: 82 case enums.MSG_DHT_P2P_RESULT:
81 return NewDHTP2PResultMsg(), nil 83 return NewDHTP2PResultMsg(), nil
82 84
@@ -111,6 +113,24 @@ func NewEmptyMessage(msgType enums.MsgType) (Message, error) {
111 return NewRevocationRevokeMsg(nil), nil 113 return NewRevocationRevokeMsg(nil), nil
112 case enums.MSG_REVOCATION_REVOKE_RESPONSE: 114 case enums.MSG_REVOCATION_REVOKE_RESPONSE:
113 return NewRevocationRevokeResponseMsg(false), nil 115 return NewRevocationRevokeResponseMsg(false), nil
116
117 //------------------------------------------------------------------
118 // Namestore service
119 //------------------------------------------------------------------
120 case enums.MSG_NAMESTORE_ZONE_ITERATION_START:
121 return NewNamestoreZoneIterStartMsg(nil), nil
122 case enums.MSG_NAMESTORE_ZONE_ITERATION_NEXT:
123 case enums.MSG_NAMESTORE_ZONE_ITERATION_STOP:
124 case enums.MSG_NAMESTORE_RECORD_STORE:
125 case enums.MSG_NAMESTORE_RECORD_STORE_RESPONSE:
126 case enums.MSG_NAMESTORE_RECORD_LOOKUP:
127 case enums.MSG_NAMESTORE_RECORD_LOOKUP_RESPONSE:
128 case enums.MSG_NAMESTORE_ZONE_TO_NAME:
129 case enums.MSG_NAMESTORE_ZONE_TO_NAME_RESPONSE:
130 case enums.MSG_NAMESTORE_MONITOR_START:
131 case enums.MSG_NAMESTORE_MONITOR_SYNC:
132 case enums.MSG_NAMESTORE_RECORD_RESULT:
133 case enums.MSG_NAMESTORE_MONITOR_NEXT:
114 } 134 }
115 return nil, fmt.Errorf("unknown message type %d", msgType) 135 return nil, fmt.Errorf("unknown message type %d", msgType)
116} 136}
diff --git a/src/gnunet/message/msg_dht_p2p.go b/src/gnunet/message/msg_dht_p2p.go
index 9d1d815..d4a474c 100644
--- a/src/gnunet/message/msg_dht_p2p.go
+++ b/src/gnunet/message/msg_dht_p2p.go
@@ -24,6 +24,7 @@ import (
24 "encoding/binary" 24 "encoding/binary"
25 "errors" 25 "errors"
26 "fmt" 26 "fmt"
27 "gnunet/config"
27 "gnunet/crypto" 28 "gnunet/crypto"
28 "gnunet/enums" 29 "gnunet/enums"
29 "gnunet/service/dht/blocks" 30 "gnunet/service/dht/blocks"
@@ -122,9 +123,10 @@ type DHTP2PPutMsg struct {
122} 123}
123 124
124// NewDHTP2PPutMsg creates an empty new DHTP2PPutMsg 125// NewDHTP2PPutMsg creates an empty new DHTP2PPutMsg
125func NewDHTP2PPutMsg() *DHTP2PPutMsg { 126func NewDHTP2PPutMsg(block blocks.Block) *DHTP2PPutMsg {
126 return &DHTP2PPutMsg{ 127 // create empty message
127 MsgHeader: MsgHeader{218, enums.MSG_DHT_P2P_PUT}, 128 msg := &DHTP2PPutMsg{
129 MsgHeader: MsgHeader{216, enums.MSG_DHT_P2P_PUT},
128 BType: enums.BLOCK_TYPE_ANY, // block type 130 BType: enums.BLOCK_TYPE_ANY, // block type
129 Flags: 0, // processing flags 131 Flags: 0, // processing flags
130 HopCount: 0, // message hops 132 HopCount: 0, // message hops
@@ -138,6 +140,20 @@ func NewDHTP2PPutMsg() *DHTP2PPutMsg {
138 LastSig: nil, // no signature from last hop 140 LastSig: nil, // no signature from last hop
139 Block: nil, // no block data 141 Block: nil, // no block data
140 } 142 }
143 // initialize with block if available
144 if block != nil {
145 msg.BType = block.Type()
146 msg.HopCount = 0
147 msg.PeerFilter = blocks.NewPeerFilter()
148 msg.ReplLvl = uint16(config.Cfg.GNS.ReplLevel)
149 msg.Expire = block.Expire()
150 msg.Block = block.Bytes()
151 msg.TruncOrigin = nil
152 msg.PutPath = nil
153 msg.LastSig = nil
154 msg.MsgSize += uint16(len(msg.Block))
155 }
156 return msg
141} 157}
142 158
143// IsUsed returns true if an optional field is used 159// IsUsed returns true if an optional field is used
@@ -155,7 +171,7 @@ func (m *DHTP2PPutMsg) IsUsed(field string) bool {
155 171
156// Update message (forwarding) 172// Update message (forwarding)
157func (m *DHTP2PPutMsg) Update(p *path.Path, pf *blocks.PeerFilter, hop uint16) *DHTP2PPutMsg { 173func (m *DHTP2PPutMsg) Update(p *path.Path, pf *blocks.PeerFilter, hop uint16) *DHTP2PPutMsg {
158 msg := NewDHTP2PPutMsg() 174 msg := NewDHTP2PPutMsg(nil)
159 msg.Flags = m.Flags 175 msg.Flags = m.Flags
160 msg.HopCount = hop 176 msg.HopCount = hop
161 msg.PathL = p.NumList 177 msg.PathL = p.NumList
diff --git a/src/gnunet/message/msg_gns.go b/src/gnunet/message/msg_gns.go
index 0b96e88..1c7f9af 100644
--- a/src/gnunet/message/msg_gns.go
+++ b/src/gnunet/message/msg_gns.go
@@ -23,6 +23,7 @@ import (
23 23
24 "gnunet/crypto" 24 "gnunet/crypto"
25 "gnunet/enums" 25 "gnunet/enums"
26 "gnunet/service/dht/blocks"
26 "gnunet/util" 27 "gnunet/util"
27 28
28 "github.com/bfix/gospel/logger" 29 "github.com/bfix/gospel/logger"
@@ -39,7 +40,7 @@ type LookupMsg struct {
39 Zone *crypto.ZoneKey `` // Zone that is to be used for lookup 40 Zone *crypto.ZoneKey `` // Zone that is to be used for lookup
40 Options uint16 `order:"big"` // Local options for where to look for results 41 Options uint16 `order:"big"` // Local options for where to look for results
41 Reserved uint16 `order:"big"` // Always 0 42 Reserved uint16 `order:"big"` // Always 0
42 RType uint32 `order:"big"` // the type of record to look up 43 RType enums.GNSType `order:"big"` // the type of record to look up
43 Name []byte `size:"*"` // zero-terminated name to look up 44 Name []byte `size:"*"` // zero-terminated name to look up
44} 45}
45 46
@@ -51,7 +52,7 @@ func NewGNSLookupMsg() *LookupMsg {
51 Zone: nil, 52 Zone: nil,
52 Options: uint16(enums.GNS_LO_DEFAULT), 53 Options: uint16(enums.GNS_LO_DEFAULT),
53 Reserved: 0, 54 Reserved: 0,
54 RType: uint32(enums.GNS_TYPE_ANY), 55 RType: enums.GNS_TYPE_ANY,
55 Name: nil, 56 Name: nil,
56 } 57 }
57} 58}
@@ -84,78 +85,12 @@ func (m *LookupMsg) String() string {
84// GNS_LOOKUP_RESULT 85// GNS_LOOKUP_RESULT
85//---------------------------------------------------------------------- 86//----------------------------------------------------------------------
86 87
87// RecordSet ist the GNUnet data structure for a list of resource records
88// in a GNSBlock. As part of GNUnet messages, the record set is padded so that
89// the binary size of (records||padding) is the smallest power of two.
90type RecordSet struct {
91 Count uint32 `order:"big"` // number of resource records
92 Records []*ResourceRecord `size:"Count"` // list of resource records
93 Padding []byte `size:"*"` // padding
94}
95
96// NewRecordSet returns an empty resource record set.
97func NewRecordSet() *RecordSet {
98 return &RecordSet{
99 Count: 0,
100 Records: make([]*ResourceRecord, 0),
101 Padding: make([]byte, 0),
102 }
103}
104
105// AddRecord to append a resource record to the set.
106func (rs *RecordSet) AddRecord(rec *ResourceRecord) {
107 rs.Count++
108 rs.Records = append(rs.Records, rec)
109}
110
111// SetPadding (re-)calculates and allocates the padding.
112func (rs *RecordSet) SetPadding() {
113 size := 0
114 for _, rr := range rs.Records {
115 size += int(rr.Size) + 20
116 }
117 n := 1
118 for n < size {
119 n <<= 1
120 }
121 rs.Padding = make([]byte, n-size)
122}
123
124// Expire returns the earliest expiration timestamp for the records.
125func (rs *RecordSet) Expire() util.AbsoluteTime {
126 var expires util.AbsoluteTime
127 for i, rr := range rs.Records {
128 if i == 0 {
129 expires = rr.Expire
130 } else if rr.Expire.Compare(expires) < 0 {
131 expires = rr.Expire
132 }
133 }
134 return expires
135}
136
137// ResourceRecord is the GNUnet-specific representation of resource
138// records (not to be confused with DNS resource records).
139type ResourceRecord struct {
140 Expire util.AbsoluteTime // Expiration time for the record
141 Size uint32 `order:"big"` // Number of bytes in 'Data'
142 RType uint32 `order:"big"` // Type of the GNS/DNS record
143 Flags uint32 `order:"big"` // Flags for the record
144 Data []byte `size:"Size"` // Record data
145}
146
147// String returns a human-readable representation of the message.
148func (r *ResourceRecord) String() string {
149 return fmt.Sprintf("GNSResourceRecord{type=%s,expire=%s,flags=%d,size=%d}",
150 enums.GNSType(r.RType).String(), r.Expire, r.Flags, r.Size)
151}
152
153// LookupResultMsg is a response message for a GNS name lookup request 88// LookupResultMsg is a response message for a GNS name lookup request
154type LookupResultMsg struct { 89type LookupResultMsg struct {
155 MsgHeader 90 MsgHeader
156 ID uint32 `order:"big"` // Unique identifier for this request (for key collisions). 91 ID uint32 `order:"big"` // Unique identifier for this request (for key collisions).
157 Count uint32 `order:"big"` // The number of records contained in response 92 Count uint32 `order:"big"` // The number of records contained in response
158 Records []*ResourceRecord `size:"Count"` // GNS resource records 93 Records []*blocks.ResourceRecord `size:"Count"` // GNS resource records
159} 94}
160 95
161// NewGNSLookupResultMsg returns a new lookup result message 96// NewGNSLookupResultMsg returns a new lookup result message
@@ -164,12 +99,12 @@ func NewGNSLookupResultMsg(id uint32) *LookupResultMsg {
164 MsgHeader: MsgHeader{12, enums.MSG_GNS_LOOKUP_RESULT}, 99 MsgHeader: MsgHeader{12, enums.MSG_GNS_LOOKUP_RESULT},
165 ID: id, 100 ID: id,
166 Count: 0, 101 Count: 0,
167 Records: make([]*ResourceRecord, 0), 102 Records: make([]*blocks.ResourceRecord, 0),
168 } 103 }
169} 104}
170 105
171// AddRecord adds a GNS resource recordto the response message. 106// AddRecord adds a GNS resource recordto the response message.
172func (m *LookupResultMsg) AddRecord(rec *ResourceRecord) error { 107func (m *LookupResultMsg) AddRecord(rec *blocks.ResourceRecord) error {
173 recSize := 20 + int(rec.Size) 108 recSize := 20 + int(rec.Size)
174 if int(m.MsgSize)+recSize > enums.GNS_MAX_BLOCK_SIZE { 109 if int(m.MsgSize)+recSize > enums.GNS_MAX_BLOCK_SIZE {
175 return fmt.Errorf("gns.AddRecord(): MAX_BLOCK_SIZE reached") 110 return fmt.Errorf("gns.AddRecord(): MAX_BLOCK_SIZE reached")
diff --git a/src/gnunet/message/msg_namecache.go b/src/gnunet/message/msg_namecache.go
index ea413a7..53b2f4c 100644
--- a/src/gnunet/message/msg_namecache.go
+++ b/src/gnunet/message/msg_namecache.go
@@ -28,13 +28,31 @@ import (
28) 28)
29 29
30//---------------------------------------------------------------------- 30//----------------------------------------------------------------------
31// Generic Namecache message header
32//----------------------------------------------------------------------
33
34// GenericNamecacheMsg is the common header for Namestore messages
35type GenericNamecacheMsg struct {
36 MsgHeader
37 ID uint32 `order:"big"` // unique reference ID
38}
39
40// return initialized common message header
41func newGenericNamecacheMsg(size uint16, mtype enums.MsgType) GenericNamecacheMsg {
42 return GenericNamecacheMsg{
43 MsgHeader: MsgHeader{size, mtype},
44 ID: uint32(util.NextID()),
45 }
46}
47
48//----------------------------------------------------------------------
31// NAMECACHE_LOOKUP_BLOCK 49// NAMECACHE_LOOKUP_BLOCK
32//---------------------------------------------------------------------- 50//----------------------------------------------------------------------
33 51
34// NamecacheLookupMsg is request message for lookups in local namecache 52// NamecacheLookupMsg is request message for lookups in local namecache
35type NamecacheLookupMsg struct { 53type NamecacheLookupMsg struct {
36 MsgHeader 54 GenericNamecacheMsg
37 ID uint32 `order:"big"` // Request Id 55
38 Query *crypto.HashCode // Query data 56 Query *crypto.HashCode // Query data
39} 57}
40 58
@@ -44,9 +62,8 @@ func NewNamecacheLookupMsg(query *crypto.HashCode) *NamecacheLookupMsg {
44 query = crypto.NewHashCode(nil) 62 query = crypto.NewHashCode(nil)
45 } 63 }
46 return &NamecacheLookupMsg{ 64 return &NamecacheLookupMsg{
47 MsgHeader: MsgHeader{72, enums.MSG_NAMECACHE_LOOKUP_BLOCK}, 65 GenericNamecacheMsg: newGenericNamecacheMsg(72, enums.MSG_NAMECACHE_LOOKUP_BLOCK),
48 ID: 0, 66 Query: query,
49 Query: query,
50 } 67 }
51} 68}
52 69
@@ -62,21 +79,20 @@ func (m *NamecacheLookupMsg) String() string {
62 79
63// NamecacheLookupResultMsg is the response message for namecache lookups. 80// NamecacheLookupResultMsg is the response message for namecache lookups.
64type NamecacheLookupResultMsg struct { 81type NamecacheLookupResultMsg struct {
65 MsgHeader 82 GenericNamecacheMsg
66 ID uint32 `order:"big"` // Request Id 83
67 Expire util.AbsoluteTime `` // Expiration time 84 Expire util.AbsoluteTime `` // Expiration time
68 DerivedKeySig *crypto.ZoneSignature `` // Derived public key 85 DerivedKeySig *crypto.ZoneSignature `` // Derived public key
69 EncData []byte `size:"*"` // Encrypted block data 86 EncData []byte `size:"*"` // Encrypted block data
70} 87}
71 88
72// NewNamecacheLookupResultMsg creates a new default message. 89// NewNamecacheLookupResultMsg creates a new default message.
73func NewNamecacheLookupResultMsg() *NamecacheLookupResultMsg { 90func NewNamecacheLookupResultMsg() *NamecacheLookupResultMsg {
74 return &NamecacheLookupResultMsg{ 91 return &NamecacheLookupResultMsg{
75 MsgHeader: MsgHeader{112, enums.MSG_NAMECACHE_LOOKUP_BLOCK_RESPONSE}, 92 GenericNamecacheMsg: newGenericNamecacheMsg(112, enums.MSG_NAMECACHE_LOOKUP_BLOCK_RESPONSE),
76 ID: 0, 93 Expire: util.AbsoluteTimeNever(),
77 Expire: *new(util.AbsoluteTime), 94 DerivedKeySig: nil,
78 DerivedKeySig: nil, 95 EncData: nil,
79 EncData: nil,
80 } 96 }
81} 97}
82 98
@@ -92,24 +108,38 @@ func (m *NamecacheLookupResultMsg) String() string {
92 108
93// NamecacheCacheMsg is the request message to put a name into the local cache. 109// NamecacheCacheMsg is the request message to put a name into the local cache.
94type NamecacheCacheMsg struct { 110type NamecacheCacheMsg struct {
95 MsgHeader 111 GenericNamecacheMsg
96 ID uint32 `order:"big"` // Request Id 112
97 Expire util.AbsoluteTime `` // Expiration time 113 Expire util.AbsoluteTime `` // Expiration time
98 DerivedKeySig *crypto.ZoneSignature `` // Derived public key and signature 114 DerivedSig []byte `size:"(FldSize)"` // Derived signature
99 EncData []byte `size:"*"` // Encrypted block data 115 DerivedKey []byte `size:"(FldSize)"` // Derived public key
116 EncData []byte `size:"*"` // Encrypted block data
117}
118
119// Size returns buffer sizes for fields
120func (m *NamecacheCacheMsg) FldSize(field string) uint {
121 switch field {
122 case "DerivedSig":
123 return 64
124 case "DerivedKey":
125 return 36
126 }
127 // defaults to empty buffer
128 return 0
100} 129}
101 130
102// NewNamecacheCacheMsg creates a new default message. 131// NewNamecacheCacheMsg creates a new default message.
103func NewNamecacheCacheMsg(block *blocks.GNSBlock) *NamecacheCacheMsg { 132func NewNamecacheCacheMsg(block *blocks.GNSBlock) *NamecacheCacheMsg {
104 msg := &NamecacheCacheMsg{ 133 msg := &NamecacheCacheMsg{
105 MsgHeader: MsgHeader{108, enums.MSG_NAMECACHE_BLOCK_CACHE}, 134 GenericNamecacheMsg: newGenericNamecacheMsg(116, enums.MSG_NAMECACHE_BLOCK_CACHE),
106 ID: 0, 135 Expire: util.AbsoluteTimeNever(),
107 Expire: *new(util.AbsoluteTime), 136 DerivedSig: nil,
108 DerivedKeySig: nil, 137 DerivedKey: nil,
109 EncData: make([]byte, 0), 138 EncData: make([]byte, 0),
110 } 139 }
111 if block != nil { 140 if block != nil {
112 msg.DerivedKeySig = block.DerivedKeySig 141 msg.DerivedKey = util.Clone(block.DerivedKeySig.ZoneKey.Bytes())
142 msg.DerivedSig = util.Clone(block.DerivedKeySig.Signature)
113 msg.Expire = block.Body.Expire 143 msg.Expire = block.Body.Expire
114 size := len(block.Body.Data) 144 size := len(block.Body.Data)
115 msg.EncData = make([]byte, size) 145 msg.EncData = make([]byte, size)
@@ -121,8 +151,8 @@ func NewNamecacheCacheMsg(block *blocks.GNSBlock) *NamecacheCacheMsg {
121 151
122// String returns a human-readable representation of the message. 152// String returns a human-readable representation of the message.
123func (m *NamecacheCacheMsg) String() string { 153func (m *NamecacheCacheMsg) String() string {
124 return fmt.Sprintf("NewNamecacheCacheMsg{id=%d,expire=%s}", 154 return fmt.Sprintf("NamecacheCacheMsg{size=%d,id=%d,expire=%s}",
125 m.ID, m.Expire) 155 m.Size(), m.ID, m.Expire)
126} 156}
127 157
128//---------------------------------------------------------------------- 158//----------------------------------------------------------------------
@@ -131,17 +161,16 @@ func (m *NamecacheCacheMsg) String() string {
131 161
132// NamecacheCacheResponseMsg is the response message for a put request 162// NamecacheCacheResponseMsg is the response message for a put request
133type NamecacheCacheResponseMsg struct { 163type NamecacheCacheResponseMsg struct {
134 MsgHeader 164 GenericNamecacheMsg
135 ID uint32 `order:"big"` // Request Id 165
136 Result int32 `order:"big"` // Result code 166 Result int32 `order:"big"` // Result code
137} 167}
138 168
139// NewNamecacheCacheResponseMsg creates a new default message. 169// NewNamecacheCacheResponseMsg creates a new default message.
140func NewNamecacheCacheResponseMsg() *NamecacheCacheResponseMsg { 170func NewNamecacheCacheResponseMsg() *NamecacheCacheResponseMsg {
141 return &NamecacheCacheResponseMsg{ 171 return &NamecacheCacheResponseMsg{
142 MsgHeader: MsgHeader{12, enums.MSG_NAMECACHE_BLOCK_CACHE_RESPONSE}, 172 GenericNamecacheMsg: newGenericNamecacheMsg(12, enums.MSG_NAMECACHE_BLOCK_CACHE_RESPONSE),
143 ID: 0, 173 Result: 0,
144 Result: 0,
145 } 174 }
146} 175}
147 176
diff --git a/src/gnunet/message/msg_namestore.go b/src/gnunet/message/msg_namestore.go
new file mode 100644
index 0000000..a682ef1
--- /dev/null
+++ b/src/gnunet/message/msg_namestore.go
@@ -0,0 +1,199 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package message
20
21import (
22 "fmt"
23 "gnunet/crypto"
24 "gnunet/enums"
25 "gnunet/service/dht/blocks"
26 "gnunet/util"
27)
28
29//======================================================================
30// NameStore service messages
31//======================================================================
32
33// GenericNamestoreMsg is the common header for Namestore messages
34type GenericNamestoreMsg struct {
35 MsgHeader
36 ID uint32 `order:"big"` // unique reference ID
37}
38
39// return initialized common message header
40func newGenericNamestoreMsg(size uint16, mtype enums.MsgType) GenericNamestoreMsg {
41 return GenericNamestoreMsg{
42 MsgHeader: MsgHeader{size, mtype},
43 ID: uint32(util.NextID()),
44 }
45}
46
47//----------------------------------------------------------------------
48// MSG_NAMESTORE_ZONE_ITERATION_START
49//----------------------------------------------------------------------
50
51// NamestoreZoneIterStartMsg starts a new iteration over all zones
52type NamestoreZoneIterStartMsg struct {
53 GenericNamestoreMsg
54
55 ZoneKey *crypto.ZonePrivate // private zone key
56}
57
58// NewNamecacheCacheMsg creates a new default message.
59func NewNamestoreZoneIterStartMsg(zone *crypto.ZonePrivate) *NamestoreZoneIterStartMsg {
60 return &NamestoreZoneIterStartMsg{
61 GenericNamestoreMsg: newGenericNamestoreMsg(100, enums.MSG_NAMESTORE_ZONE_ITERATION_START),
62 ZoneKey: zone,
63 }
64}
65
66// String returns a human-readable representation of the message.
67func (m *NamestoreZoneIterStartMsg) String() string {
68 return fmt.Sprintf("NamestoreZoneIterStartMsg{id=%d,zone=%s}", m.ID, m.ZoneKey.ID())
69}
70
71//----------------------------------------------------------------------
72// MSG_NAMESTORE_ZONE_ITERATION_NEXT
73//----------------------------------------------------------------------
74
75type NamestoreZoneIterNextMsg struct {
76 GenericNamestoreMsg
77
78 Limit uint64 `order:"big"` // max. number of records in one go
79}
80
81func NewNamestoreZoneIterNextMsg() *NamestoreZoneIterNextMsg {
82 return &NamestoreZoneIterNextMsg{}
83}
84
85// String returns a human-readable representation of the message.
86func (m *NamestoreZoneIterNextMsg) String() string {
87 return fmt.Sprintf("NamestoreZoneIterNextMsg{id=%d,limit=%d}", m.ID, m.Limit)
88}
89
90//----------------------------------------------------------------------
91// MSG_NAMESTORE_ZONE_ITERATION_STOP
92//----------------------------------------------------------------------
93
94type NamestoreZoneIterStopMsg struct {
95 GenericNamestoreMsg
96}
97
98//----------------------------------------------------------------------
99//----------------------------------------------------------------------
100
101type NamestoreRecordStoreMsg struct {
102 GenericNamestoreMsg
103
104 ZoneKey *crypto.ZonePrivate // private zone key
105 Records *blocks.RecordSet // list of records
106}
107
108type NamestoreRecordStoreRespMsg struct {
109 GenericNamestoreMsg
110
111 Status int32 `order:"big"` // result status
112 ErrLen uint16 `order:"big"` // length of error message
113 Reserved uint16 `order:"big"` // alignment
114 Error []byte `size:"ErrLen"` // error message
115}
116
117type NamestoreLabelLookupMsg struct {
118 GenericNamestoreMsg
119
120 LblLen uint32 `order:"big"` // length of label
121 IsEdit uint32 `order:"big"` // lookup corresponds to edit request
122 ZoneKey *crypto.ZonePrivate // private zone key
123 Label []byte `size:"LblLen"` // label string
124}
125
126type NamestoreLabelLookupRespMsg struct {
127 GenericNamestoreMsg
128
129 LblLen uint16 `order:"big"` // Length of label
130 RdLen uint16 `order:"big"` // size of record data
131 RdCount uint16 `order:"big"` // number of records
132 Found int16 `order:"big"` // label found?
133 ZoneKey *crypto.ZonePrivate // private zone key
134 Label []byte `size:"LblLen"` // label string
135 Records []byte `size:"RdLen"` // serialized record data
136}
137
138type NamestoreZoneToNameMsg struct {
139 GenericNamestoreMsg
140
141 ZoneKey *crypto.ZonePrivate // private zone key
142 ZonePublic *crypto.ZoneKey // public zone key
143}
144
145type NamestoreZoneToNameRespMsg struct {
146 GenericNamestoreMsg
147
148 NameLen uint16 `order:"big"` // length of name
149 RdLen uint16 `order:"big"` // size of record data
150 RdCount uint16 `order:"big"` // number of records
151 Status int16 `order:"big"` // result status
152 ZoneKey *crypto.ZonePrivate // private zone key
153 Name []byte `size:"NameLen"` // name string
154 Records []byte `size:"RdLen"` // serialized record data
155}
156
157type NamestoreRecordResultMsg struct {
158 GenericNamestoreMsg
159
160 Expire util.AbsoluteTime `` // expiration date
161 NameLen uint16 `order:"big"` // length of name
162 RdLen uint16 `order:"big"` // size of record data
163 RdCount uint16 `order:"big"` // number of records
164 Reserved uint16 `order:"big"` // alignment
165 ZoneKey *crypto.ZonePrivate // private zone key
166 Name []byte `size:"NameLen"` // name string
167 Records []byte `size:"RdLen"` // serialized record data
168}
169
170type NamestoreTxControlMsg struct {
171 GenericNamestoreMsg
172
173 Control uint16 `order:"big"` // type of control message
174 Reserved uint16 `order:"big"` // alignment
175}
176
177type NamestoreTxControlResultMsg struct {
178 GenericNamestoreMsg
179
180 Control uint16 `order:"big"` // type of control message
181 Status uint16 `order:"big"` // result status
182 Error []byte `size:"*"` // error message (on status != OK)
183}
184
185type NamestoreZoneMonStartMsg struct {
186 GenericNamestoreMsg
187
188 Iterate uint32 `order:"big"` // iterate over all records
189 Filter uint16 `order:"big"` // filter flags
190 Reserved uint16 `order:"big"` // alignment
191 ZoneKey *crypto.ZonePrivate // private zone key
192}
193
194type NamestoreZoneMonNextMsg struct {
195 GenericNamestoreMsg
196
197 Reserved uint32 `order:"big"` // alignment =0
198 Limit uint64 `order:"big"` // max. number of records in one go
199}
diff --git a/src/gnunet/service/client.go b/src/gnunet/service/client.go
index 5f7f8f0..7b5e29b 100644
--- a/src/gnunet/service/client.go
+++ b/src/gnunet/service/client.go
@@ -66,7 +66,8 @@ func RequestResponse(
66 caller string, 66 caller string,
67 callee string, 67 callee string,
68 path string, 68 path string,
69 req message.Message) (message.Message, error) { 69 req message.Message,
70 withResponse bool) (message.Message, error) {
70 // client-connect to the service 71 // client-connect to the service
71 logger.Printf(logger.DBG, "[%s] Connecting to %s service...\n", caller, callee) 72 logger.Printf(logger.DBG, "[%s] Connecting to %s service...\n", caller, callee)
72 cl, err := NewClient(ctx, path) 73 cl, err := NewClient(ctx, path)
@@ -78,11 +79,13 @@ func RequestResponse(
78 if err = cl.SendRequest(ctx, req); err != nil { 79 if err = cl.SendRequest(ctx, req); err != nil {
79 return nil, err 80 return nil, err
80 } 81 }
81 // wait for a single response, then close the connection
82 logger.Printf(logger.DBG, "[%s] Waiting for response from %s service\n", caller, callee)
83 var resp message.Message 82 var resp message.Message
84 if resp, err = cl.ReceiveResponse(ctx); err != nil { 83 if withResponse {
85 return nil, err 84 // wait for a single response, then close the connection
85 logger.Printf(logger.DBG, "[%s] Waiting for response from %s service\n", caller, callee)
86 if resp, err = cl.ReceiveResponse(ctx); err != nil {
87 return nil, err
88 }
86 } 89 }
87 logger.Printf(logger.DBG, "[%s] Closing connection to %s service\n", caller, callee) 90 logger.Printf(logger.DBG, "[%s] Closing connection to %s service\n", caller, callee)
88 cl.Close() 91 cl.Close()
diff --git a/src/gnunet/service/dht/blocks/gns.go b/src/gnunet/service/dht/blocks/gns.go
index 6b41b0b..0c32085 100644
--- a/src/gnunet/service/dht/blocks/gns.go
+++ b/src/gnunet/service/dht/blocks/gns.go
@@ -47,7 +47,7 @@ type GNSQuery struct {
47 GenericQuery 47 GenericQuery
48 Zone *crypto.ZoneKey // Public zone key 48 Zone *crypto.ZoneKey // Public zone key
49 Label string // Atomic label 49 Label string // Atomic label
50 derived *crypto.ZoneKey // Derived zone key from (pkey,label) 50 derived *crypto.ZoneKey // Derived zone key from (zone,label)
51} 51}
52 52
53// Verify the integrity of the block data from a signature. 53// Verify the integrity of the block data from a signature.
@@ -103,6 +103,7 @@ func NewGNSQuery(zkey *crypto.ZoneKey, label string) *GNSQuery {
103 pd, _, err := zkey.Derive(label, "gns") 103 pd, _, err := zkey.Derive(label, "gns")
104 if err != nil { 104 if err != nil {
105 logger.Printf(logger.ERROR, "[NewGNSQuery] failed: %s", err.Error()) 105 logger.Printf(logger.ERROR, "[NewGNSQuery] failed: %s", err.Error())
106 return nil
106 } 107 }
107 gq := crypto.Hash(pd.Bytes()) 108 gq := crypto.Hash(pd.Bytes())
108 return &GNSQuery{ 109 return &GNSQuery{
@@ -140,6 +141,11 @@ type GNSBlock struct {
140 data []byte // decrypted data 141 data []byte // decrypted data
141} 142}
142 143
144// Payload returns the decrypted block data (or nil)
145func (b *GNSBlock) Payload() []byte {
146 return util.Clone(b.data)
147}
148
143// Bytes return th binary representation of block 149// Bytes return th binary representation of block
144func (b *GNSBlock) Bytes() []byte { 150func (b *GNSBlock) Bytes() []byte {
145 buf, _ := data.Marshal(b) 151 buf, _ := data.Marshal(b)
@@ -167,8 +173,12 @@ func NewGNSBlock() Block {
167 return &GNSBlock{ 173 return &GNSBlock{
168 DerivedKeySig: nil, 174 DerivedKeySig: nil,
169 Body: &SignedGNSBlockData{ 175 Body: &SignedGNSBlockData{
170 Purpose: new(crypto.SignaturePurpose), 176 Purpose: &crypto.SignaturePurpose{
171 Data: nil, 177 Size: 16,
178 Purpose: enums.SIG_GNS_RECORD_SIGN,
179 },
180 Expire: util.AbsoluteTimeNever(),
181 Data: nil,
172 }, 182 },
173 checked: false, 183 checked: false,
174 verified: false, 184 verified: false,
@@ -181,6 +191,23 @@ func NewGNSBlock() Block {
181// Not required for GNS blocks 191// Not required for GNS blocks
182func (b *GNSBlock) Prepare(enums.BlockType, util.AbsoluteTime) {} 192func (b *GNSBlock) Prepare(enums.BlockType, util.AbsoluteTime) {}
183 193
194// SetData sets the data for the GNS block
195func (b *GNSBlock) SetData(data []byte) {
196 b.Body.Data = data
197 b.Body.Purpose.Size = uint32(len(data) + 16)
198}
199
200// Sign the block with a derived private key
201func (b *GNSBlock) Sign(sk *crypto.ZonePrivate) error {
202 // get signed data
203 buf, err := data.Marshal(b.Body)
204 if err == nil {
205 // generate signature
206 b.DerivedKeySig, err = sk.Sign(buf)
207 }
208 return err
209}
210
184// Verify the integrity of the block data from a signature. 211// Verify the integrity of the block data from a signature.
185// Only the cryptographic signature is verified; the formal correctness of 212// Only the cryptographic signature is verified; the formal correctness of
186// the association between the block and a GNS label in a GNS zone can't 213// the association between the block and a GNS label in a GNS zone can't
@@ -193,3 +220,78 @@ func (b *GNSBlock) Verify() (ok bool, err error) {
193 } 220 }
194 return b.DerivedKeySig.Verify(buf) 221 return b.DerivedKeySig.Verify(buf)
195} 222}
223
224// RecordSet ist the GNUnet data structure for a list of resource records
225// in a GNSBlock. As part of GNUnet messages, the record set is padded so that
226// the binary size of (records||padding) is the smallest power of two.
227type RecordSet struct {
228 Count uint32 `order:"big"` // number of resource records
229 Records []*ResourceRecord `size:"Count"` // list of resource records
230 Padding []byte `size:"*"` // padding
231}
232
233// NewRecordSet returns an empty resource record set.
234func NewRecordSet() *RecordSet {
235 return &RecordSet{
236 Count: 0,
237 Records: make([]*ResourceRecord, 0),
238 Padding: make([]byte, 0),
239 }
240}
241
242// AddRecord to append a resource record to the set.
243func (rs *RecordSet) AddRecord(rec *ResourceRecord) {
244 rs.Count++
245 rs.Records = append(rs.Records, rec)
246}
247
248// SetPadding (re-)calculates and allocates the padding.
249func (rs *RecordSet) SetPadding() {
250 size := 0
251 for _, rr := range rs.Records {
252 size += int(rr.Size) + 20
253 }
254 n := 1
255 for n < size {
256 n <<= 1
257 }
258 rs.Padding = make([]byte, n-size)
259}
260
261// Expire returns the earliest expiration timestamp for the records.
262func (rs *RecordSet) Expire() util.AbsoluteTime {
263 var expires util.AbsoluteTime
264 for i, rr := range rs.Records {
265 if i == 0 {
266 expires = rr.Expire
267 } else if rr.Expire.Compare(expires) < 0 {
268 expires = rr.Expire
269 }
270 }
271 return expires
272}
273
274// Bytes returns the binary representation
275func (rs *RecordSet) Bytes() []byte {
276 buf, err := data.Marshal(rs)
277 if err != nil {
278 return nil
279 }
280 return buf
281}
282
283// ResourceRecord is the GNUnet-specific representation of resource
284// records (not to be confused with DNS resource records).
285type ResourceRecord struct {
286 Expire util.AbsoluteTime // Expiration time for the record
287 Size uint32 `order:"big"` // Number of bytes in 'Data'
288 RType enums.GNSType `order:"big"` // Type of the GNS/DNS record
289 Flags enums.GNSFlag `order:"big"` // Flags for the record
290 Data []byte `size:"Size"` // Record data
291}
292
293// String returns a human-readable representation of the message.
294func (r *ResourceRecord) String() string {
295 return fmt.Sprintf("GNSResourceRecord{type=%s,expire=%s,flags=%d,size=%d}",
296 r.RType.String(), r.Expire, r.Flags, r.Size)
297}
diff --git a/src/gnunet/message/msg_gns_test.go b/src/gnunet/service/dht/blocks/gns_test.go
index 034e1a8..260d83b 100644
--- a/src/gnunet/message/msg_gns_test.go
+++ b/src/gnunet/service/dht/blocks/gns_test.go
@@ -16,7 +16,7 @@
16// 16//
17// SPDX-License-Identifier: AGPL3.0-or-later 17// SPDX-License-Identifier: AGPL3.0-or-later
18 18
19package message 19package blocks
20 20
21import ( 21import (
22 "bytes" 22 "bytes"
@@ -29,6 +29,97 @@ import (
29 "github.com/bfix/gospel/data" 29 "github.com/bfix/gospel/data"
30) 30)
31 31
32func TestGNSBlock(t *testing.T) {
33 var (
34 ZONEKEY = "000G054G4G3HWZP2WFNVS1XJ4VXWY85G49AVYBZ7TV4EWP5J5V59H5QN40"
35 LABEL = "@"
36
37 QKEY = []byte{
38 0xb6, 0x48, 0xfd, 0x0c, 0x4a, 0x6c, 0xaa, 0x87,
39 0x33, 0x2f, 0xf5, 0x12, 0x90, 0xe4, 0xbd, 0x55,
40 0x0f, 0x8c, 0xe7, 0x9b, 0xc9, 0x5b, 0x3a, 0xfb,
41 0xbb, 0xe2, 0xd7, 0x33, 0xbc, 0x32, 0xc9, 0x7d,
42 0xc5, 0x4a, 0x56, 0x22, 0xbf, 0xfa, 0x49, 0x1a,
43 0x60, 0xd6, 0xdb, 0x77, 0x5d, 0x3d, 0x18, 0x99,
44 0x5b, 0x4f, 0xc3, 0x7d, 0x86, 0x00, 0x15, 0x76,
45 0x42, 0x03, 0x98, 0xcc, 0xdf, 0x83, 0x4d, 0x21,
46 }
47 BLK = []byte{
48 0x00, 0x01, 0x00, 0x14, 0xe0, 0x6b, 0xea, 0x2b,
49 0x1b, 0xd6, 0xc6, 0x9a, 0xd4, 0x30, 0xa5, 0x0f,
50 0x81, 0x16, 0x89, 0xe1, 0x9f, 0xca, 0x1f, 0x86,
51 0x3f, 0x83, 0x6e, 0xe6, 0xa7, 0x54, 0x97, 0xde,
52 0xf2, 0xc4, 0x2a, 0x84, 0xb6, 0x89, 0xe6, 0x7e,
53 0xff, 0x0c, 0xae, 0x84, 0xe6, 0xb1, 0x6c, 0x72,
54 0x83, 0x09, 0x68, 0x5b, 0x2f, 0xa2, 0x9f, 0xbe,
55 0xfa, 0xef, 0x43, 0x52, 0x20, 0x48, 0xe5, 0x57,
56 0x1e, 0x65, 0x21, 0x86, 0xd4, 0x9f, 0x96, 0x51,
57 0x4f, 0xa9, 0x6d, 0xa9, 0x98, 0xaa, 0x2d, 0xf6,
58 0x92, 0xd7, 0x86, 0x36, 0xc0, 0x84, 0x90, 0x00,
59 0x42, 0x2e, 0x4e, 0xc1, 0xaf, 0x6f, 0xe0, 0x7e,
60 0x71, 0xe3, 0xc4, 0x0d, 0x00, 0x00, 0x00, 0x10,
61 0x00, 0x00, 0x00, 0x0f, 0x00, 0x06, 0x08, 0x00,
62 0xb5, 0x99, 0x2a, 0x00, 0xd0, 0x7a, 0x2b, 0x9e,
63 0x02, 0x45, 0x54, 0x0d, 0x65, 0x26, 0xa1, 0x05,
64 0x80, 0x26, 0xce, 0xc2, 0x70, 0xd5, 0x22, 0x38,
65 0x80, 0x9a, 0xed, 0x63, 0x2f, 0x96, 0x60, 0x4d,
66 0x02, 0x59, 0xd0, 0x9a, 0x4e, 0x71, 0xfa, 0x30,
67 0xd6, 0xf9, 0xf4, 0x84, 0x5d, 0xb8, 0x60, 0xa4,
68 0xdf, 0xea, 0x34, 0x06, 0x3f, 0x6f, 0x76, 0x9e,
69 }
70 )
71 // unmarshal block
72 blk := new(GNSBlock)
73 if err := data.Unmarshal(blk, BLK); err != nil {
74 t.Fatal(err)
75 }
76 // Initialize signature
77 if err := blk.DerivedKeySig.Init(); err != nil {
78 t.Fatal(err)
79 }
80 // assemble query from public zone key and label
81 zkData, err := util.DecodeStringToBinary(ZONEKEY, 36)
82 if err != nil {
83 t.Fatal(err)
84 }
85 zk, err := crypto.NewZoneKey(zkData)
86 if err != nil {
87 t.Fatal(err)
88 }
89 query := NewGNSQuery(zk, LABEL)
90
91 // check query key
92 if !bytes.Equal(QKEY, query.Key().Data) {
93 t.Fatal("query key mismatch")
94 }
95
96 // check derived public key (form zone key and label)
97 dkey2, _, err := zk.Derive(LABEL, "gns")
98 if err != nil {
99 t.Fatal(err)
100 }
101 if !bytes.Equal(blk.DerivedKeySig.ZoneKey.Bytes(), dkey2.Bytes()) {
102 t.Logf("expected: %s\n", hex.EncodeToString(blk.DerivedKeySig.ZoneKey.Bytes()))
103 t.Logf("got: %s\n", hex.EncodeToString(dkey2.Bytes()))
104 t.Fatal("key mismatch")
105 }
106
107 // verify signature
108 if err = query.Verify(blk); err != nil {
109 t.Fatal(err)
110 }
111 // decrypt payload
112 if err = query.Decrypt(blk); err != nil {
113 t.Fatal(err)
114 }
115 rrs := new(RecordSet)
116 if err = data.Unmarshal(rrs, blk.Payload()); err != nil {
117 t.Fatal(err)
118 }
119 t.Logf("RecordSet=%v\n", rrs)
120
121}
122
32// TestRecordsetPKEY implements the test case as defined in the GNS draft 123// TestRecordsetPKEY implements the test case as defined in the GNS draft
33// (see section 13. Test vectors, case "PKEY") 124// (see section 13. Test vectors, case "PKEY")
34func TestRecordsetPKEY(t *testing.T) { 125func TestRecordsetPKEY(t *testing.T) {
@@ -69,7 +160,7 @@ func TestRecordsetPKEY(t *testing.T) {
69 Val: uint64(26147096139323793), 160 Val: uint64(26147096139323793),
70 }, 161 },
71 Size: 36, 162 Size: 36,
72 RType: crypto.ZONE_PKEY, 163 RType: enums.GNS_TYPE_PKEY,
73 Flags: 2, 164 Flags: 2,
74 Data: []byte{ 165 Data: []byte{
75 0x00, 0x01, 0x00, 0x00, 166 0x00, 0x01, 0x00, 0x00,
@@ -134,7 +225,7 @@ func TestRecordsetPKEY(t *testing.T) {
134 ) 225 )
135 226
136 // check zone key pair 227 // check zone key pair
137 prv, err := crypto.NewZonePrivate(crypto.ZONE_PKEY, D) 228 prv, err := crypto.NewZonePrivate(enums.GNS_TYPE_PKEY, D)
138 if err != nil { 229 if err != nil {
139 t.Fatal(err) 230 t.Fatal(err)
140 } 231 }
@@ -229,7 +320,7 @@ func TestRecordsetEDKEY(t *testing.T) {
229 Val: uint64(49556645701000000), 320 Val: uint64(49556645701000000),
230 }, 321 },
231 Size: 36, 322 Size: 36,
232 RType: uint32(enums.GNS_TYPE_NICK), 323 RType: enums.GNS_TYPE_NICK,
233 Flags: 2, 324 Flags: 2,
234 Data: []byte{ 325 Data: []byte{
235 0x4d, 0x79, 0x20, 0x4e, 0x69, 0x63, 0x6b, 0x00, 326 0x4d, 0x79, 0x20, 0x4e, 0x69, 0x63, 0x6b, 0x00,
diff --git a/src/gnunet/service/dht/module.go b/src/gnunet/service/dht/module.go
index 718b730..9f3aaa0 100644
--- a/src/gnunet/service/dht/module.go
+++ b/src/gnunet/service/dht/module.go
@@ -248,25 +248,10 @@ func (m *Module) Get(ctx context.Context, query blocks.Query) <-chan blocks.Bloc
248 248
249// Put a block into the DHT ["dht:put"] 249// Put a block into the DHT ["dht:put"]
250func (m *Module) Put(ctx context.Context, query blocks.Query, block blocks.Block) error { 250func (m *Module) Put(ctx context.Context, query blocks.Query, block blocks.Block) error {
251 // get additional query parameters
252 expire, ok := util.GetParam[util.AbsoluteTime](query.Params(), "expire")
253 if !ok {
254 expire = util.AbsoluteTimeNever()
255 }
256 // assemble a new PUT message 251 // assemble a new PUT message
257 msg := message.NewDHTP2PPutMsg() 252 msg := message.NewDHTP2PPutMsg(block)
258 msg.BType = query.Type()
259 msg.Flags = query.Flags() 253 msg.Flags = query.Flags()
260 msg.HopCount = 0
261 msg.PeerFilter = blocks.NewPeerFilter()
262 msg.ReplLvl = uint16(m.cfg.Routing.ReplLevel)
263 msg.Expire = expire
264 msg.Block = block.Bytes()
265 msg.Key = query.Key().Clone() 254 msg.Key = query.Key().Clone()
266 msg.TruncOrigin = nil
267 msg.PutPath = nil
268 msg.LastSig = nil
269 msg.MsgSize += uint16(len(msg.Block))
270 255
271 // send message 256 // send message
272 self := m.core.PeerID() 257 self := m.core.PeerID()
diff --git a/src/gnunet/service/gns/block_handler.go b/src/gnunet/service/gns/block_handler.go
index a0e7874..b00fac2 100644
--- a/src/gnunet/service/gns/block_handler.go
+++ b/src/gnunet/service/gns/block_handler.go
@@ -19,19 +19,21 @@
19package gns 19package gns
20 20
21import ( 21import (
22 "crypto/sha256"
22 "encoding/hex" 23 "encoding/hex"
23 "fmt" 24 "fmt"
24 25
25 "gnunet/crypto" 26 "gnunet/crypto"
26 "gnunet/enums" 27 "gnunet/enums"
27 "gnunet/message" 28 "gnunet/service/dht/blocks"
29 "gnunet/service/gns/rr"
28 "gnunet/util" 30 "gnunet/util"
29 31
30 "github.com/bfix/gospel/logger" 32 "github.com/bfix/gospel/logger"
31) 33)
32 34
33// HdlrInst is the type for functions that instantiate custom block handlers. 35// HdlrInst is the type for functions that instantiate custom block handlers.
34type HdlrInst func(*message.ResourceRecord, []string) (BlockHandler, error) 36type HdlrInst func(*blocks.ResourceRecord, []string) (BlockHandler, error)
35 37
36// Error codes 38// Error codes
37var ( 39var (
@@ -70,7 +72,7 @@ type BlockHandler interface {
70 // processing. The handler can inspect the remaining labels in a path 72 // processing. The handler can inspect the remaining labels in a path
71 // if required. The method returns an error if a record is not accepted 73 // if required. The method returns an error if a record is not accepted
72 // by the block handler (RR not of required type). 74 // by the block handler (RR not of required type).
73 AddRecord(rr *message.ResourceRecord, labels []string) error 75 AddRecord(rr *blocks.ResourceRecord, labels []string) error
74 76
75 // Coexist checks if a custom block handler can co-exist with other 77 // Coexist checks if a custom block handler can co-exist with other
76 // resource records in the same block. 'cm' maps the resource type 78 // resource records in the same block. 'cm' maps the resource type
@@ -80,7 +82,7 @@ type BlockHandler interface {
80 82
81 // Records returns a list of RR of the given types associated with 83 // Records returns a list of RR of the given types associated with
82 // the custom handler 84 // the custom handler
83 Records(kind RRTypeList) *message.RecordSet 85 Records(kind RRTypeList) *blocks.RecordSet
84 86
85 // Name returns the human-readable name of the handler 87 // Name returns the human-readable name of the handler
86 Name() string 88 Name() string
@@ -108,7 +110,7 @@ type BlockHandlerList struct {
108 110
109// NewBlockHandlerList instantiates an a list of active block handlers 111// NewBlockHandlerList instantiates an a list of active block handlers
110// for a given set of records (GNS block). 112// for a given set of records (GNS block).
111func NewBlockHandlerList(records []*message.ResourceRecord, labels []string) (*BlockHandlerList, []*message.ResourceRecord, error) { 113func NewBlockHandlerList(records []*blocks.ResourceRecord, labels []string) (*BlockHandlerList, []*blocks.ResourceRecord, error) {
112 // initialize block handler list 114 // initialize block handler list
113 hl := &BlockHandlerList{ 115 hl := &BlockHandlerList{
114 list: make(map[enums.GNSType]BlockHandler), 116 list: make(map[enums.GNSType]BlockHandler),
@@ -116,19 +118,19 @@ func NewBlockHandlerList(records []*message.ResourceRecord, labels []string) (*B
116 } 118 }
117 119
118 // first pass: build list of shadow records in this block 120 // first pass: build list of shadow records in this block
119 shadows := make([]*message.ResourceRecord, 0) 121 shadows := make([]*blocks.ResourceRecord, 0)
120 for _, rec := range records { 122 for _, rec := range records {
121 // filter out shadow records... 123 // filter out shadow records...
122 if (int(rec.Flags) & enums.GNS_FLAG_SHADOW) != 0 { 124 if (rec.Flags & enums.GNS_FLAG_SHADOW) != 0 {
123 shadows = append(shadows, rec) 125 shadows = append(shadows, rec)
124 } 126 }
125 } 127 }
126 // second pass: normalize block by filtering out expired records (and 128 // second pass: normalize block by filtering out expired records (and
127 // replacing them with shadow records if available 129 // replacing them with shadow records if available
128 active := make([]*message.ResourceRecord, 0) 130 active := make([]*blocks.ResourceRecord, 0)
129 for _, rec := range records { 131 for _, rec := range records {
130 // don't process shadow records again 132 // don't process shadow records again
131 if (int(rec.Flags) & enums.GNS_FLAG_SHADOW) != 0 { 133 if (rec.Flags & enums.GNS_FLAG_SHADOW) != 0 {
132 continue 134 continue
133 } 135 }
134 // check for expired record 136 // check for expired record
@@ -137,7 +139,7 @@ func NewBlockHandlerList(records []*message.ResourceRecord, labels []string) (*B
137 for _, shadow := range shadows { 139 for _, shadow := range shadows {
138 if shadow.RType == rec.RType && !shadow.Expire.Expired() { 140 if shadow.RType == rec.RType && !shadow.Expire.Expired() {
139 // deliver un-expired shadow record instead. 141 // deliver un-expired shadow record instead.
140 shadow.Flags &^= uint32(enums.GNS_FLAG_SHADOW) 142 shadow.Flags &^= enums.GNS_FLAG_SHADOW
141 active = append(active, shadow) 143 active = append(active, shadow)
142 } 144 }
143 } 145 }
@@ -149,11 +151,11 @@ func NewBlockHandlerList(records []*message.ResourceRecord, labels []string) (*B
149 // Third pass: Traverse active list and build list of handler instances. 151 // Third pass: Traverse active list and build list of handler instances.
150 for _, rec := range active { 152 for _, rec := range active {
151 // update counter map for non-supplemental records 153 // update counter map for non-supplemental records
152 if (int(rec.Flags) & enums.GNS_FLAG_SUPPL) != 0 { 154 if (rec.Flags & enums.GNS_FLAG_SUPPL) != 0 {
153 logger.Printf(logger.DBG, "[gns] handler_list: skip %v\n", rec) 155 logger.Printf(logger.DBG, "[gns] handler_list: skip %v\n", rec)
154 continue 156 continue
155 } 157 }
156 rrType := enums.GNSType(rec.RType) 158 rrType := rec.RType
157 hl.counts.Add(rrType) 159 hl.counts.Add(rrType)
158 160
159 // check for custom handler type 161 // check for custom handler type
@@ -205,7 +207,7 @@ func (hl *BlockHandlerList) GetHandler(types ...enums.GNSType) BlockHandler {
205} 207}
206 208
207// FinalizeRecord post-processes records 209// FinalizeRecord post-processes records
208func (hl *BlockHandlerList) FinalizeRecord(rec *message.ResourceRecord) *message.ResourceRecord { 210func (hl *BlockHandlerList) FinalizeRecord(rec *blocks.ResourceRecord) *blocks.ResourceRecord {
209 // no implementation yet 211 // no implementation yet
210 return rec 212 return rec
211} 213}
@@ -216,13 +218,13 @@ func (hl *BlockHandlerList) FinalizeRecord(rec *message.ResourceRecord) *message
216 218
217// ZoneKeyHandler implementing the BlockHandler interface 219// ZoneKeyHandler implementing the BlockHandler interface
218type ZoneKeyHandler struct { 220type ZoneKeyHandler struct {
219 ztype uint32 // zone type 221 ztype enums.GNSType // zone type
220 zkey *crypto.ZoneKey // Zone key 222 zkey *crypto.ZoneKey // Zone key
221 rec *message.ResourceRecord // associated recource record 223 rec *blocks.ResourceRecord // associated recource record
222} 224}
223 225
224// NewZoneHandler returns a new BlockHandler instance 226// NewZoneHandler returns a new BlockHandler instance
225func NewZoneHandler(rec *message.ResourceRecord, labels []string) (BlockHandler, error) { 227func NewZoneHandler(rec *blocks.ResourceRecord, labels []string) (BlockHandler, error) {
226 // check if we have an implementation for the zone type 228 // check if we have an implementation for the zone type
227 if crypto.GetImplementation(rec.RType) == nil { 229 if crypto.GetImplementation(rec.RType) == nil {
228 return nil, ErrInvalidRecordType 230 return nil, ErrInvalidRecordType
@@ -240,7 +242,7 @@ func NewZoneHandler(rec *message.ResourceRecord, labels []string) (BlockHandler,
240} 242}
241 243
242// AddRecord inserts a PKEY record into the handler. 244// AddRecord inserts a PKEY record into the handler.
243func (h *ZoneKeyHandler) AddRecord(rec *message.ResourceRecord, labels []string) (err error) { 245func (h *ZoneKeyHandler) AddRecord(rec *blocks.ResourceRecord, labels []string) (err error) {
244 // check record type 246 // check record type
245 if rec.RType != h.ztype { 247 if rec.RType != h.ztype {
246 return ErrInvalidRecordType 248 return ErrInvalidRecordType
@@ -266,8 +268,8 @@ func (h *ZoneKeyHandler) Coexist(cm util.Counter[enums.GNSType]) bool {
266} 268}
267 269
268// Records returns a list of RR of the given type associated with this handler 270// Records returns a list of RR of the given type associated with this handler
269func (h *ZoneKeyHandler) Records(kind RRTypeList) *message.RecordSet { 271func (h *ZoneKeyHandler) Records(kind RRTypeList) *blocks.RecordSet {
270 rs := message.NewRecordSet() 272 rs := blocks.NewRecordSet()
271 if kind.HasType(enums.GNS_TYPE_PKEY) { 273 if kind.HasType(enums.GNS_TYPE_PKEY) {
272 rs.AddRecord(h.rec) 274 rs.AddRecord(h.rec)
273 } 275 }
@@ -285,20 +287,20 @@ func (h *ZoneKeyHandler) Name() string {
285 287
286// Gns2DnsHandler implementing the BlockHandler interface 288// Gns2DnsHandler implementing the BlockHandler interface
287type Gns2DnsHandler struct { 289type Gns2DnsHandler struct {
288 Query string // DNS query name 290 Query string // DNS query name
289 Servers []string // DNS servers to ask 291 Servers []string // DNS servers to ask
290 recs []*message.ResourceRecord // list of rersource records 292 recs []*blocks.ResourceRecord // list of rersource records
291} 293}
292 294
293// NewGns2DnsHandler returns a new BlockHandler instance 295// NewGns2DnsHandler returns a new BlockHandler instance
294func NewGns2DnsHandler(rec *message.ResourceRecord, labels []string) (BlockHandler, error) { 296func NewGns2DnsHandler(rec *blocks.ResourceRecord, labels []string) (BlockHandler, error) {
295 if enums.GNSType(rec.RType) != enums.GNS_TYPE_GNS2DNS { 297 if rec.RType != enums.GNS_TYPE_GNS2DNS {
296 return nil, ErrInvalidRecordType 298 return nil, ErrInvalidRecordType
297 } 299 }
298 h := &Gns2DnsHandler{ 300 h := &Gns2DnsHandler{
299 Query: "", 301 Query: "",
300 Servers: make([]string, 0), 302 Servers: make([]string, 0),
301 recs: make([]*message.ResourceRecord, 0), 303 recs: make([]*blocks.ResourceRecord, 0),
302 } 304 }
303 if err := h.AddRecord(rec, labels); err != nil { 305 if err := h.AddRecord(rec, labels); err != nil {
304 return nil, err 306 return nil, err
@@ -307,8 +309,8 @@ func NewGns2DnsHandler(rec *message.ResourceRecord, labels []string) (BlockHandl
307} 309}
308 310
309// AddRecord inserts a GNS2DNS record into the handler. 311// AddRecord inserts a GNS2DNS record into the handler.
310func (h *Gns2DnsHandler) AddRecord(rec *message.ResourceRecord, labels []string) error { 312func (h *Gns2DnsHandler) AddRecord(rec *blocks.ResourceRecord, labels []string) error {
311 if enums.GNSType(rec.RType) != enums.GNS_TYPE_GNS2DNS { 313 if rec.RType != enums.GNS_TYPE_GNS2DNS {
312 return ErrInvalidRecordType 314 return ErrInvalidRecordType
313 } 315 }
314 logger.Printf(logger.DBG, "[gns] GNS2DNS data: %s\n", hex.EncodeToString(rec.Data)) 316 logger.Printf(logger.DBG, "[gns] GNS2DNS data: %s\n", hex.EncodeToString(rec.Data))
@@ -341,8 +343,8 @@ func (h *Gns2DnsHandler) Coexist(cm util.Counter[enums.GNSType]) bool {
341} 343}
342 344
343// Records returns a list of RR of the given type associated with this handler 345// Records returns a list of RR of the given type associated with this handler
344func (h *Gns2DnsHandler) Records(kind RRTypeList) *message.RecordSet { 346func (h *Gns2DnsHandler) Records(kind RRTypeList) *blocks.RecordSet {
345 rs := message.NewRecordSet() 347 rs := blocks.NewRecordSet()
346 if kind.HasType(enums.GNS_TYPE_GNS2DNS) { 348 if kind.HasType(enums.GNS_TYPE_GNS2DNS) {
347 for _, rec := range h.recs { 349 for _, rec := range h.recs {
348 rs.AddRecord(rec) 350 rs.AddRecord(rec)
@@ -360,14 +362,21 @@ func (h *Gns2DnsHandler) Name() string {
360// BOX handler 362// BOX handler
361//---------------------------------------------------------------------- 363//----------------------------------------------------------------------
362 364
365// Box record for handler logic
366type Box struct {
367 rr.BOX
368 key string // map key for box instance
369 rec *blocks.ResourceRecord // originating RR
370}
371
363// BoxHandler implementing the BlockHandler interface 372// BoxHandler implementing the BlockHandler interface
364type BoxHandler struct { 373type BoxHandler struct {
365 boxes map[string]*Box // map of found boxes 374 boxes map[string]*Box // map of found boxes
366} 375}
367 376
368// NewBoxHandler returns a new BlockHandler instance 377// NewBoxHandler returns a new BlockHandler instance
369func NewBoxHandler(rec *message.ResourceRecord, labels []string) (BlockHandler, error) { 378func NewBoxHandler(rec *blocks.ResourceRecord, labels []string) (BlockHandler, error) {
370 if enums.GNSType(rec.RType) != enums.GNS_TYPE_BOX { 379 if rec.RType != enums.GNS_TYPE_BOX {
371 return nil, ErrInvalidRecordType 380 return nil, ErrInvalidRecordType
372 } 381 }
373 h := &BoxHandler{ 382 h := &BoxHandler{
@@ -380,8 +389,8 @@ func NewBoxHandler(rec *message.ResourceRecord, labels []string) (BlockHandler,
380} 389}
381 390
382// AddRecord inserts a BOX record into the handler. 391// AddRecord inserts a BOX record into the handler.
383func (h *BoxHandler) AddRecord(rec *message.ResourceRecord, labels []string) error { 392func (h *BoxHandler) AddRecord(rec *blocks.ResourceRecord, labels []string) error {
384 if enums.GNSType(rec.RType) != enums.GNS_TYPE_BOX { 393 if rec.RType != enums.GNS_TYPE_BOX {
385 return ErrInvalidRecordType 394 return ErrInvalidRecordType
386 } 395 }
387 logger.Printf(logger.DBG, "[box-rr] for labels %v\n", labels) 396 logger.Printf(logger.DBG, "[box-rr] for labels %v\n", labels)
@@ -395,7 +404,12 @@ func (h *BoxHandler) AddRecord(rec *message.ResourceRecord, labels []string) err
395 return nil 404 return nil
396 } 405 }
397 // (3) check of "svc" and "proto" match values in the BOX 406 // (3) check of "svc" and "proto" match values in the BOX
398 box := NewBox(rec) 407 hsh := sha256.Sum256(rec.Data)
408 box := &Box{
409 BOX: *rr.NewBOX(rec.Data),
410 key: hex.EncodeToString(hsh[:8]),
411 rec: rec,
412 }
399 if box.Matches(labels) { 413 if box.Matches(labels) {
400 logger.Println(logger.DBG, "[box-rr] MATCH -- adding record") 414 logger.Println(logger.DBG, "[box-rr] MATCH -- adding record")
401 h.boxes[box.key] = box 415 h.boxes[box.key] = box
@@ -411,12 +425,12 @@ func (h *BoxHandler) Coexist(cm util.Counter[enums.GNSType]) bool {
411} 425}
412 426
413// Records returns a list of RR of the given type associated with this handler 427// Records returns a list of RR of the given type associated with this handler
414func (h *BoxHandler) Records(kind RRTypeList) *message.RecordSet { 428func (h *BoxHandler) Records(kind RRTypeList) *blocks.RecordSet {
415 rs := message.NewRecordSet() 429 rs := blocks.NewRecordSet()
416 for _, box := range h.boxes { 430 for _, box := range h.boxes {
417 if kind.HasType(enums.GNSType(box.Type)) { 431 if kind.HasType(box.Type) {
418 // valid box found: assemble new resource record. 432 // valid box found: assemble new resource record.
419 rr := new(message.ResourceRecord) 433 rr := new(blocks.ResourceRecord)
420 rr.Expire = box.rec.Expire 434 rr.Expire = box.rec.Expire
421 rr.Flags = box.rec.Flags 435 rr.Flags = box.rec.Flags
422 rr.RType = box.Type 436 rr.RType = box.Type
@@ -440,12 +454,12 @@ func (h *BoxHandler) Name() string {
440// LehoHandler implementing the BlockHandler interface 454// LehoHandler implementing the BlockHandler interface
441type LehoHandler struct { 455type LehoHandler struct {
442 name string 456 name string
443 rec *message.ResourceRecord 457 rec *blocks.ResourceRecord
444} 458}
445 459
446// NewLehoHandler returns a new BlockHandler instance 460// NewLehoHandler returns a new BlockHandler instance
447func NewLehoHandler(rec *message.ResourceRecord, labels []string) (BlockHandler, error) { 461func NewLehoHandler(rec *blocks.ResourceRecord, labels []string) (BlockHandler, error) {
448 if enums.GNSType(rec.RType) != enums.GNS_TYPE_LEHO { 462 if rec.RType != enums.GNS_TYPE_LEHO {
449 return nil, ErrInvalidRecordType 463 return nil, ErrInvalidRecordType
450 } 464 }
451 h := &LehoHandler{ 465 h := &LehoHandler{
@@ -458,8 +472,8 @@ func NewLehoHandler(rec *message.ResourceRecord, labels []string) (BlockHandler,
458} 472}
459 473
460// AddRecord inserts a LEHO record into the handler. 474// AddRecord inserts a LEHO record into the handler.
461func (h *LehoHandler) AddRecord(rec *message.ResourceRecord, labels []string) error { 475func (h *LehoHandler) AddRecord(rec *blocks.ResourceRecord, labels []string) error {
462 if enums.GNSType(rec.RType) != enums.GNS_TYPE_LEHO { 476 if rec.RType != enums.GNS_TYPE_LEHO {
463 return ErrInvalidRecordType 477 return ErrInvalidRecordType
464 } 478 }
465 h.name = string(rec.Data) 479 h.name = string(rec.Data)
@@ -475,8 +489,8 @@ func (h *LehoHandler) Coexist(cm util.Counter[enums.GNSType]) bool {
475} 489}
476 490
477// Records returns a list of RR of the given type associated with this handler 491// Records returns a list of RR of the given type associated with this handler
478func (h *LehoHandler) Records(kind RRTypeList) *message.RecordSet { 492func (h *LehoHandler) Records(kind RRTypeList) *blocks.RecordSet {
479 rs := message.NewRecordSet() 493 rs := blocks.NewRecordSet()
480 if kind.HasType(enums.GNS_TYPE_LEHO) { 494 if kind.HasType(enums.GNS_TYPE_LEHO) {
481 rs.AddRecord(h.rec) 495 rs.AddRecord(h.rec)
482 } 496 }
@@ -495,12 +509,12 @@ func (h *LehoHandler) Name() string {
495// CnameHandler implementing the BlockHandler interface 509// CnameHandler implementing the BlockHandler interface
496type CnameHandler struct { 510type CnameHandler struct {
497 name string 511 name string
498 rec *message.ResourceRecord 512 rec *blocks.ResourceRecord
499} 513}
500 514
501// NewCnameHandler returns a new BlockHandler instance 515// NewCnameHandler returns a new BlockHandler instance
502func NewCnameHandler(rec *message.ResourceRecord, labels []string) (BlockHandler, error) { 516func NewCnameHandler(rec *blocks.ResourceRecord, labels []string) (BlockHandler, error) {
503 if enums.GNSType(rec.RType) != enums.GNS_TYPE_DNS_CNAME { 517 if rec.RType != enums.GNS_TYPE_DNS_CNAME {
504 return nil, ErrInvalidRecordType 518 return nil, ErrInvalidRecordType
505 } 519 }
506 h := &CnameHandler{ 520 h := &CnameHandler{
@@ -513,8 +527,8 @@ func NewCnameHandler(rec *message.ResourceRecord, labels []string) (BlockHandler
513} 527}
514 528
515// AddRecord inserts a CNAME record into the handler. 529// AddRecord inserts a CNAME record into the handler.
516func (h *CnameHandler) AddRecord(rec *message.ResourceRecord, labels []string) error { 530func (h *CnameHandler) AddRecord(rec *blocks.ResourceRecord, labels []string) error {
517 if enums.GNSType(rec.RType) != enums.GNS_TYPE_DNS_CNAME { 531 if rec.RType != enums.GNS_TYPE_DNS_CNAME {
518 return ErrInvalidRecordType 532 return ErrInvalidRecordType
519 } 533 }
520 if h.rec != nil { 534 if h.rec != nil {
@@ -533,8 +547,8 @@ func (h *CnameHandler) Coexist(cm util.Counter[enums.GNSType]) bool {
533} 547}
534 548
535// Records returns a list of RR of the given type associated with this handler 549// Records returns a list of RR of the given type associated with this handler
536func (h *CnameHandler) Records(kind RRTypeList) *message.RecordSet { 550func (h *CnameHandler) Records(kind RRTypeList) *blocks.RecordSet {
537 rs := message.NewRecordSet() 551 rs := blocks.NewRecordSet()
538 if kind.HasType(enums.GNS_TYPE_DNS_CNAME) { 552 if kind.HasType(enums.GNS_TYPE_DNS_CNAME) {
539 rs.AddRecord(h.rec) 553 rs.AddRecord(h.rec)
540 } 554 }
@@ -552,12 +566,12 @@ func (h *CnameHandler) Name() string {
552 566
553// VpnHandler implementing the BlockHandler interface 567// VpnHandler implementing the BlockHandler interface
554type VpnHandler struct { 568type VpnHandler struct {
555 rec *message.ResourceRecord 569 rec *blocks.ResourceRecord
556} 570}
557 571
558// NewVpnHandler returns a new BlockHandler instance 572// NewVpnHandler returns a new BlockHandler instance
559func NewVpnHandler(rec *message.ResourceRecord, labels []string) (BlockHandler, error) { 573func NewVpnHandler(rec *blocks.ResourceRecord, labels []string) (BlockHandler, error) {
560 if enums.GNSType(rec.RType) != enums.GNS_TYPE_VPN { 574 if rec.RType != enums.GNS_TYPE_VPN {
561 return nil, ErrInvalidRecordType 575 return nil, ErrInvalidRecordType
562 } 576 }
563 h := &VpnHandler{} 577 h := &VpnHandler{}
@@ -568,8 +582,8 @@ func NewVpnHandler(rec *message.ResourceRecord, labels []string) (BlockHandler,
568} 582}
569 583
570// AddRecord inserts a VPN record into the handler. 584// AddRecord inserts a VPN record into the handler.
571func (h *VpnHandler) AddRecord(rec *message.ResourceRecord, labels []string) error { 585func (h *VpnHandler) AddRecord(rec *blocks.ResourceRecord, labels []string) error {
572 if enums.GNSType(rec.RType) != enums.GNS_TYPE_VPN { 586 if rec.RType != enums.GNS_TYPE_VPN {
573 return ErrInvalidRecordType 587 return ErrInvalidRecordType
574 } 588 }
575 if h.rec != nil { 589 if h.rec != nil {
@@ -587,8 +601,8 @@ func (h *VpnHandler) Coexist(cm util.Counter[enums.GNSType]) bool {
587} 601}
588 602
589// Records returns a list of RR of the given type associated with this handler 603// Records returns a list of RR of the given type associated with this handler
590func (h *VpnHandler) Records(kind RRTypeList) *message.RecordSet { 604func (h *VpnHandler) Records(kind RRTypeList) *blocks.RecordSet {
591 rs := message.NewRecordSet() 605 rs := blocks.NewRecordSet()
592 if kind.HasType(enums.GNS_TYPE_VPN) { 606 if kind.HasType(enums.GNS_TYPE_VPN) {
593 rs.AddRecord(h.rec) 607 rs.AddRecord(h.rec)
594 } 608 }
diff --git a/src/gnunet/service/gns/box.go b/src/gnunet/service/gns/box.go
deleted file mode 100644
index f97471e..0000000
--- a/src/gnunet/service/gns/box.go
+++ /dev/null
@@ -1,169 +0,0 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package gns
20
21import (
22 "encoding/hex"
23 "strconv"
24 "strings"
25
26 "gnunet/message"
27
28 "github.com/bfix/gospel/data"
29 "github.com/bfix/gospel/logger"
30)
31
32// Box is an encapsulated RR for special names
33type Box struct {
34 Proto uint16 `order:"big"` // Protcol identifier
35 Svc uint16 `order:"big"` // Service identifier
36 Type uint32 `order:"big"` // Type of embedded RR
37 RR []byte `size:"*"` // embedded RR
38
39 // transient attributes (not serialized)
40 key string // map key for box instance
41 rec *message.ResourceRecord // originating RR
42}
43
44// NewBox creates a new box instance from a BOX resource record.
45func NewBox(rec *message.ResourceRecord) *Box {
46 b := new(Box)
47 if err := data.Unmarshal(b, rec.Data); err != nil {
48 logger.Printf(logger.ERROR, "[gns] Can't unmarshal BOX")
49 return nil
50 }
51 b.key = hex.EncodeToString(rec.Data[:8])
52 b.rec = rec
53 return b
54}
55
56// Matches verifies that the remaining labels comply with the values
57// in the BOX record.
58func (b *Box) Matches(labels []string) bool {
59 // resolve protocol and service names
60 proto, protoName := GetProtocol(labels[0])
61 svc, _ := GetService(labels[1], protoName)
62 // no match on invalid resolution
63 if proto == 0 || svc == 0 {
64 return false
65 }
66 // check for matching values in box
67 return proto == b.Proto && svc == b.Svc
68}
69
70//----------------------------------------------------------------------
71// helper functions
72
73// list of handled protocols in BOX records
74var protocols = map[string]int{
75 "icmp": 1,
76 "igmp": 2,
77 "tcp": 6,
78 "udp": 17,
79 "ipv6-icmp": 58,
80}
81
82// GetProtocol returns the protocol number and name for a given name. The
83// name can be an integer value (e.g. "_6" for "tcp") or a mnemonic name
84// (e.g. like "_tcp").
85func GetProtocol(name string) (uint16, string) {
86 // check for required prefix
87 if name[0] != '_' {
88 return 0, ""
89 }
90 name = strings.ToLower(name[1:])
91
92 // if label is an integer value it is the protocol number
93 if val, err := strconv.Atoi(name); err == nil {
94 // check for valid number (reverse protocol lookup)
95 for label, id := range protocols {
96 if id == val {
97 // return found entry
98 return uint16(val), label
99 }
100 }
101 // number out of range
102 return 0, ""
103 }
104 // try to resolve via protocol map
105 if id, ok := protocols[name]; ok {
106 return uint16(id), name
107 }
108 // resolution failed
109 return 0, ""
110}
111
112// list of services (per protocol) handled in BOX records
113var services = map[string]map[string]int{
114 "udp": {
115 "domain": 53,
116 },
117 "tcp": {
118 "ftp": 21,
119 "ftps": 990,
120 "gopher": 70,
121 "http": 80,
122 "https": 443,
123 "imap2": 143,
124 "imap3": 220,
125 "imaps": 993,
126 "pop3": 110,
127 "pop3s": 995,
128 "smtp": 25,
129 "ssh": 22,
130 "telnet": 23,
131 },
132}
133
134// GetService returns the port number and the name of a service (with given
135// protocol). The name can be an integer value (e.g. "_443" for "https") or
136// a mnemonic name (e.g. like "_https").
137func GetService(name, proto string) (uint16, string) {
138 // check for required prefix
139 if name[0] != '_' {
140 return 0, ""
141 }
142 name = strings.ToLower(name[1:])
143
144 // get list of services for given protocol
145 svcs, ok := services[proto]
146 if !ok {
147 // no services available for this protocol
148 return 0, ""
149 }
150
151 // if label is an integer value it is the port number
152 if val, err := strconv.Atoi(name); err == nil {
153 // check for valid number (reverse service lookup)
154 for label, id := range svcs {
155 if id == val {
156 // return found entry
157 return uint16(val), label
158 }
159 }
160 // number out of range
161 return 0, ""
162 }
163 // try to resolve via services map
164 if id, ok := svcs[name]; ok {
165 return uint16(id), name
166 }
167 // resolution failed
168 return 0, ""
169}
diff --git a/src/gnunet/service/gns/dns.go b/src/gnunet/service/gns/dns.go
index 5942b94..32c71e9 100644
--- a/src/gnunet/service/gns/dns.go
+++ b/src/gnunet/service/gns/dns.go
@@ -27,7 +27,7 @@ import (
27 27
28 "gnunet/crypto" 28 "gnunet/crypto"
29 "gnunet/enums" 29 "gnunet/enums"
30 "gnunet/message" 30 "gnunet/service/dht/blocks"
31 "gnunet/util" 31 "gnunet/util"
32 32
33 "github.com/bfix/gospel/logger" 33 "github.com/bfix/gospel/logger"
@@ -116,7 +116,7 @@ func DNSNameFromBytes(b []byte, offset int) (int, string) {
116} 116}
117 117
118// QueryDNS queries the specified DNS server for a given name and expected result types. 118// QueryDNS queries the specified DNS server for a given name and expected result types.
119func QueryDNS(id int, name string, server net.IP, kind RRTypeList) *message.RecordSet { 119func QueryDNS(id int, name string, server net.IP, kind RRTypeList) *blocks.RecordSet {
120 // get default nameserver if not defined. 120 // get default nameserver if not defined.
121 if server == nil { 121 if server == nil {
122 server = net.IPv4(8, 8, 8, 8) 122 server = net.IPv4(8, 8, 8, 8)
@@ -161,7 +161,7 @@ func QueryDNS(id int, name string, server net.IP, kind RRTypeList) *message.Reco
161 logger.Printf(logger.ERROR, "[dns][%d] No results\n", id) 161 logger.Printf(logger.ERROR, "[dns][%d] No results\n", id)
162 return nil 162 return nil
163 } 163 }
164 set := message.NewRecordSet() 164 set := blocks.NewRecordSet()
165 for _, record := range in.Answer { 165 for _, record := range in.Answer {
166 // check if answer record is of requested type 166 // check if answer record is of requested type
167 if kind.HasType(enums.GNSType(record.Header().Rrtype)) { 167 if kind.HasType(enums.GNSType(record.Header().Rrtype)) {
@@ -174,11 +174,11 @@ func QueryDNS(id int, name string, server net.IP, kind RRTypeList) *message.Reco
174 } 174 }
175 175
176 // create a new GNS resource record 176 // create a new GNS resource record
177 rr := new(message.ResourceRecord) 177 rr := new(blocks.ResourceRecord)
178 expires := time.Now().Add(time.Duration(record.Header().Ttl) * time.Second) 178 expires := time.Now().Add(time.Duration(record.Header().Ttl) * time.Second)
179 rr.Expire = util.NewAbsoluteTime(expires) 179 rr.Expire = util.NewAbsoluteTime(expires)
180 rr.Flags = 0 180 rr.Flags = 0
181 rr.RType = uint32(record.Header().Rrtype) 181 rr.RType = enums.GNSType(record.Header().Rrtype)
182 rr.Size = uint32(record.Header().Rdlength) 182 rr.Size = uint32(record.Header().Rdlength)
183 rr.Data = make([]byte, rr.Size) 183 rr.Data = make([]byte, rr.Size)
184 184
@@ -210,11 +210,11 @@ func (m *Module) ResolveDNS(
210 servers []string, 210 servers []string,
211 kind RRTypeList, 211 kind RRTypeList,
212 zkey *crypto.ZoneKey, 212 zkey *crypto.ZoneKey,
213 depth int) (set *message.RecordSet, err error) { 213 depth int) (set *blocks.RecordSet, err error) {
214 214
215 // start DNS queries concurrently 215 // start DNS queries concurrently
216 logger.Printf(logger.DBG, "[dns] Resolution of '%s' starting...\n", name) 216 logger.Printf(logger.DBG, "[dns] Resolution of '%s' starting...\n", name)
217 res := make(chan *message.RecordSet) 217 res := make(chan *blocks.RecordSet)
218 running := 0 218 running := 0
219 for _, srv := range servers { 219 for _, srv := range servers {
220 // check if srv is an IPv4/IPv6 address 220 // check if srv is an IPv4/IPv6 address
@@ -230,7 +230,7 @@ func (m *Module) ResolveDNS(
230 // traverse resource records for 'A' and 'AAAA' records. 230 // traverse resource records for 'A' and 'AAAA' records.
231 rec_loop: 231 rec_loop:
232 for _, rec := range set.Records { 232 for _, rec := range set.Records {
233 switch enums.GNSType(rec.RType) { 233 switch rec.RType {
234 case enums.GNS_TYPE_DNS_AAAA: 234 case enums.GNS_TYPE_DNS_AAAA:
235 addr = net.IP(rec.Data) 235 addr = net.IP(rec.Data)
236 // we prefer IPv6 236 // we prefer IPv6
diff --git a/src/gnunet/service/gns/module.go b/src/gnunet/service/gns/module.go
index f12a89a..37bbfc5 100644
--- a/src/gnunet/service/gns/module.go
+++ b/src/gnunet/service/gns/module.go
@@ -27,7 +27,6 @@ import (
27 "gnunet/core" 27 "gnunet/core"
28 "gnunet/crypto" 28 "gnunet/crypto"
29 "gnunet/enums" 29 "gnunet/enums"
30 "gnunet/message"
31 "gnunet/service" 30 "gnunet/service"
32 "gnunet/service/dht/blocks" 31 "gnunet/service/dht/blocks"
33 "gnunet/service/revocation" 32 "gnunet/service/revocation"
@@ -102,9 +101,11 @@ func NewModule(ctx context.Context, c *core.Core) (m *Module) {
102 m = &Module{ 101 m = &Module{
103 ModuleImpl: *service.NewModuleImpl(), 102 ModuleImpl: *service.NewModuleImpl(),
104 } 103 }
105 // register as listener for core events 104 if c != nil {
106 listener := m.ModuleImpl.Run(ctx, m.event, m.Filter(), 0, nil) 105 // register as listener for core events
107 c.Register("gns", listener) 106 listener := m.ModuleImpl.Run(ctx, m.event, m.Filter(), 0, nil)
107 c.Register("gns", listener)
108 }
108 return 109 return
109} 110}
110 111
@@ -152,7 +153,7 @@ func (m *Module) Resolve(
152 zkey *crypto.ZoneKey, 153 zkey *crypto.ZoneKey,
153 kind RRTypeList, 154 kind RRTypeList,
154 mode int, 155 mode int,
155 depth int) (set *message.RecordSet, err error) { 156 depth int) (set *blocks.RecordSet, err error) {
156 157
157 // check for recursion depth 158 // check for recursion depth
158 if depth > config.Cfg.GNS.MaxDepth { 159 if depth > config.Cfg.GNS.MaxDepth {
@@ -178,7 +179,7 @@ func (m *Module) ResolveAbsolute(
178 labels []string, 179 labels []string,
179 kind RRTypeList, 180 kind RRTypeList,
180 mode int, 181 mode int,
181 depth int) (set *message.RecordSet, err error) { 182 depth int) (set *blocks.RecordSet, err error) {
182 183
183 // get the zone key for the TLD 184 // get the zone key for the TLD
184 zkey := m.GetZoneKey(labels[0]) 185 zkey := m.GetZoneKey(labels[0])
@@ -189,7 +190,7 @@ func (m *Module) ResolveAbsolute(
189 } 190 }
190 // check if zone key has been revoked 191 // check if zone key has been revoked
191 var valid bool 192 var valid bool
192 set = message.NewRecordSet() 193 set = blocks.NewRecordSet()
193 if valid, err = m.RevocationQuery(ctx, zkey); err != nil || !valid { 194 if valid, err = m.RevocationQuery(ctx, zkey); err != nil || !valid {
194 return 195 return
195 } 196 }
@@ -208,12 +209,12 @@ func (m *Module) ResolveRelative(
208 zkey *crypto.ZoneKey, 209 zkey *crypto.ZoneKey,
209 kind RRTypeList, 210 kind RRTypeList,
210 mode int, 211 mode int,
211 depth int) (set *message.RecordSet, err error) { 212 depth int) (set *blocks.RecordSet, err error) {
212 213
213 // Process all names in sequence 214 // Process all names in sequence
214 var ( 215 var (
215 records []*message.ResourceRecord // final resource records from resolution 216 records []*blocks.ResourceRecord // final resource records from resolution
216 hdlrs *BlockHandlerList // list of block handlers in final step 217 hdlrs *BlockHandlerList // list of block handlers in final step
217 ) 218 )
218 for ; len(labels) > 0; labels = labels[1:] { 219 for ; len(labels) > 0; labels = labels[1:] {
219 logger.Printf(logger.DBG, "[gns] ResolveRelative '%s' in '%s'\n", labels[0], util.EncodeBinaryToString(zkey.Bytes())) 220 logger.Printf(logger.DBG, "[gns] ResolveRelative '%s' in '%s'\n", labels[0], util.EncodeBinaryToString(zkey.Bytes()))
@@ -229,7 +230,7 @@ func (m *Module) ResolveRelative(
229 // if we have no results at this point, return NXDOMAIN 230 // if we have no results at this point, return NXDOMAIN
230 if block == nil { 231 if block == nil {
231 // return record set with no entries as signal for NXDOMAIN 232 // return record set with no entries as signal for NXDOMAIN
232 set = message.NewRecordSet() 233 set = blocks.NewRecordSet()
233 return 234 return
234 } 235 }
235 mode = enums.GNS_LO_DEFAULT 236 mode = enums.GNS_LO_DEFAULT
@@ -270,7 +271,7 @@ func (m *Module) ResolveRelative(
270 var valid bool 271 var valid bool
271 if valid, err = m.RevocationQuery(ctx, inst.zkey); err != nil || !valid { 272 if valid, err = m.RevocationQuery(ctx, inst.zkey); err != nil || !valid {
272 // revoked key -> no results! 273 // revoked key -> no results!
273 records = make([]*message.ResourceRecord, 0) 274 records = make([]*blocks.ResourceRecord, 0)
274 break 275 break
275 } 276 }
276 } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_GNS2DNS); hdlr != nil { 277 } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_GNS2DNS); hdlr != nil {
@@ -338,10 +339,10 @@ func (m *Module) ResolveRelative(
338 } 339 }
339 // Assemble resulting resource record set by filtering for requested types. 340 // Assemble resulting resource record set by filtering for requested types.
340 // Records might get transformed by active block handlers. 341 // Records might get transformed by active block handlers.
341 set = message.NewRecordSet() 342 set = blocks.NewRecordSet()
342 for _, rec := range records { 343 for _, rec := range records {
343 // is this the record type we are looking for? 344 // is this the record type we are looking for?
344 if kind.HasType(enums.GNSType(rec.RType)) { 345 if kind.HasType(rec.RType) {
345 // add it to the result 346 // add it to the result
346 if rec = hdlrs.FinalizeRecord(rec); rec != nil { 347 if rec = hdlrs.FinalizeRecord(rec); rec != nil {
347 set.AddRecord(rec) 348 set.AddRecord(rec)
@@ -364,7 +365,7 @@ func (m *Module) ResolveRelative(
364 // asking for explicitly. 365 // asking for explicitly.
365 if set.Count > 0 { 366 if set.Count > 0 {
366 for _, rec := range records { 367 for _, rec := range records {
367 if !kind.HasType(enums.GNSType(rec.RType)) && (int(rec.Flags)&enums.GNS_FLAG_SUPPL) != 0 { 368 if !kind.HasType(rec.RType) && (rec.Flags&enums.GNS_FLAG_SUPPL) != 0 {
368 set.AddRecord(rec) 369 set.AddRecord(rec)
369 } 370 }
370 } 371 }
@@ -383,7 +384,7 @@ func (m *Module) ResolveUnknown(
383 labels []string, 384 labels []string,
384 zkey *crypto.ZoneKey, 385 zkey *crypto.ZoneKey,
385 kind RRTypeList, 386 kind RRTypeList,
386 depth int) (set *message.RecordSet, err error) { 387 depth int) (set *blocks.RecordSet, err error) {
387 388
388 // relative GNS-based server name? 389 // relative GNS-based server name?
389 if strings.HasSuffix(name, ".+") { 390 if strings.HasSuffix(name, ".+") {
@@ -471,11 +472,11 @@ func (m *Module) Lookup(
471} 472}
472 473
473// newLEHORecord creates a new supplemental GNS record of type LEHO. 474// newLEHORecord creates a new supplemental GNS record of type LEHO.
474func (m *Module) newLEHORecord(name string, expires util.AbsoluteTime) *message.ResourceRecord { 475func (m *Module) newLEHORecord(name string, expires util.AbsoluteTime) *blocks.ResourceRecord {
475 rr := new(message.ResourceRecord) 476 rr := new(blocks.ResourceRecord)
476 rr.Expire = expires 477 rr.Expire = expires
477 rr.Flags = uint32(enums.GNS_FLAG_SUPPL) 478 rr.Flags = enums.GNS_FLAG_SUPPL
478 rr.RType = uint32(enums.GNS_TYPE_LEHO) 479 rr.RType = enums.GNS_TYPE_LEHO
479 rr.Size = uint32(len(name) + 1) 480 rr.Size = uint32(len(name) + 1)
480 rr.Data = make([]byte, rr.Size) 481 rr.Data = make([]byte, rr.Size)
481 copy(rr.Data, []byte(name)) 482 copy(rr.Data, []byte(name))
@@ -484,9 +485,9 @@ func (m *Module) newLEHORecord(name string, expires util.AbsoluteTime) *message.
484} 485}
485 486
486// Records returns the list of resource records from binary data. 487// Records returns the list of resource records from binary data.
487func (m *Module) records(buf []byte) ([]*message.ResourceRecord, error) { 488func (m *Module) records(buf []byte) ([]*blocks.ResourceRecord, error) {
488 // parse data into record set 489 // parse data into record set
489 rs := message.NewRecordSet() 490 rs := blocks.NewRecordSet()
490 if err := data.Unmarshal(rs, buf); err != nil { 491 if err := data.Unmarshal(rs, buf); err != nil {
491 return nil, err 492 return nil, err
492 } 493 }
diff --git a/src/gnunet/service/gns/rr/coexist.go b/src/gnunet/service/gns/rr/coexist.go
new file mode 100644
index 0000000..1037b11
--- /dev/null
+++ b/src/gnunet/service/gns/rr/coexist.go
@@ -0,0 +1,146 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package rr
20
21import (
22 "errors"
23 "gnunet/enums"
24 "gnunet/util"
25
26 "github.com/bfix/gospel/data"
27)
28
29// RR interface for resource records
30type RR interface {
31 // Coexist checks if a new resource record could coexist with given set
32 // of records under a label (can be called with a nil receiver)
33 Coexist(list []*enums.GNSSpec, label string) (bool, enums.GNSFlag)
34
35 // ToMap adds the RR attributes to a stringed map
36 ToMap(map[string]string, string)
37}
38
39// CanCoexist checks if a (new) resource record of type 't' can coexist
40// with a given set of resource records. If ok is true, it can enforce
41// flags for the new record.
42func CanCoexist(t enums.GNSType, list []*enums.GNSSpec, label string) (ok bool, forced enums.GNSFlag) {
43 rr := NilRR(t)
44 if rr == nil {
45 return true, 0
46 }
47 // check if new record against list
48 if ok, forced = rr.Coexist(list, label); !ok {
49 return
50 }
51 // now check if each existing record can coexists with a modified list
52 // swpping new record and tested record.
53 testList := util.Clone(list)
54 eNew := &enums.GNSSpec{
55 Type: t,
56 Flags: forced,
57 }
58 for i, e := range testList {
59 testList[i] = eNew
60 ok, forced = NilRR(e.Type).Coexist(testList, label)
61 if !ok {
62 return
63 }
64 eNew.Flags |= forced
65 testList[i] = e
66 }
67 // all checks passed
68 forced = eNew.Flags
69 return
70}
71
72// ParseRR returns a RR instance from data for given type
73func ParseRR(t enums.GNSType, buf []byte) (rr RR, err error) {
74 // get record instance
75 if rr = NewRR(t); rr == nil {
76 err = errors.New("parse RR failed")
77 return
78 }
79 // reconstruct record
80 err = data.Unmarshal(rr, buf)
81 return
82}
83
84// NewRR returns a new RR instance of given type
85func NewRR(t enums.GNSType) RR {
86 switch t {
87 case enums.GNS_TYPE_PKEY:
88 return new(PKEY)
89 case enums.GNS_TYPE_EDKEY:
90 return new(EDKEY)
91 case enums.GNS_TYPE_REDIRECT:
92 return (*REDIRECT)(nil)
93 case enums.GNS_TYPE_NICK:
94 return new(NICK)
95 case enums.GNS_TYPE_LEHO:
96 return new(LEHO)
97 case enums.GNS_TYPE_GNS2DNS:
98 return new(GNS2DNS)
99 case enums.GNS_TYPE_BOX:
100 return new(BOX)
101 case enums.GNS_TYPE_DNS_CNAME:
102 return new(CNAME)
103 case enums.GNS_TYPE_DNS_A:
104 return new(DNSA)
105 case enums.GNS_TYPE_DNS_AAAA:
106 return new(DNSAAAA)
107 case enums.GNS_TYPE_DNS_MX:
108 return new(MX)
109 case enums.GNS_TYPE_DNS_TXT:
110 return new(TXT)
111 }
112 return nil
113}
114
115// NilRR returns a typed nil reference to a RR that can be used to
116// call type methods that allow a nil receiver.
117func NilRR(t enums.GNSType) RR {
118 switch t {
119 case enums.GNS_TYPE_PKEY:
120 return (*PKEY)(nil)
121 case enums.GNS_TYPE_EDKEY:
122 return (*EDKEY)(nil)
123 case enums.GNS_TYPE_REDIRECT:
124 return (*REDIRECT)(nil)
125 case enums.GNS_TYPE_NICK:
126 return (*NICK)(nil)
127 case enums.GNS_TYPE_LEHO:
128 return (*LEHO)(nil)
129 case enums.GNS_TYPE_GNS2DNS:
130 return (*GNS2DNS)(nil)
131 case enums.GNS_TYPE_BOX:
132 return (*BOX)(nil)
133 case enums.GNS_TYPE_DNS_CNAME:
134 return (*CNAME)(nil)
135 case enums.GNS_TYPE_DNS_A:
136 return (*DNSA)(nil)
137 case enums.GNS_TYPE_DNS_AAAA:
138 return (*DNSAAAA)(nil)
139 case enums.GNS_TYPE_DNS_MX:
140 return (*MX)(nil)
141 case enums.GNS_TYPE_DNS_TXT:
142 return (*TXT)(nil)
143 }
144 // return untyped nil
145 return nil
146}
diff --git a/src/gnunet/service/gns/rr/dns.go b/src/gnunet/service/gns/rr/dns.go
new file mode 100644
index 0000000..a98b529
--- /dev/null
+++ b/src/gnunet/service/gns/rr/dns.go
@@ -0,0 +1,119 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package rr
20
21import (
22 "fmt"
23 "gnunet/enums"
24 "net"
25)
26
27//----------------------------------------------------------------------
28// DNS-related resource records
29//----------------------------------------------------------------------
30
31// DNS CNAME record
32type CNAME struct {
33 Name string
34}
35
36// Coexist checks if a new resource record could coexist with given set
37// of records under a label (can be called with a nil receiver)
38func (rr *CNAME) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
39 return true, 0
40}
41
42// ToMap adds the RR attributes to a stringed map
43func (rr *CNAME) ToMap(params map[string]string, prefix string) {
44 params[prefix+"name"] = rr.Name
45}
46
47//----------------------------------------------------------------------
48
49// DNS TXT record
50type TXT struct {
51 Text string
52}
53
54// Coexist checks if a new resource record could coexist with given set
55// of records under a label (can be called with a nil receiver)
56func (rr *TXT) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
57 return true, 0
58}
59
60// ToMap adds the RR attributes to a stringed map
61func (rr *TXT) ToMap(params map[string]string, prefix string) {
62 params[prefix+"text"] = rr.Text
63}
64
65//----------------------------------------------------------------------
66
67// DNS IPv4 address
68type DNSA struct {
69 Addr net.IP `size:"16"`
70}
71
72// Coexist checks if a new resource record could coexist with given set
73// of records under a label (can be called with a nil receiver)
74func (rr *DNSA) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
75 return true, 0
76}
77
78// ToMap adds the RR attributes to a stringed map
79func (rr *DNSA) ToMap(params map[string]string, prefix string) {
80 params[prefix+"addr"] = rr.Addr.String()
81}
82
83//----------------------------------------------------------------------
84
85// DNS IPv6 address
86type DNSAAAA struct {
87 Addr net.IP `size:"16"`
88}
89
90// Coexist checks if a new resource record could coexist with given set
91// of records under a label (can be called with a nil receiver)
92func (rr *DNSAAAA) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
93 return true, 0
94}
95
96// ToMap adds the RR attributes to a stringed map
97func (rr *DNSAAAA) ToMap(params map[string]string, prefix string) {
98 params[prefix+"addr"] = rr.Addr.String()
99}
100
101//----------------------------------------------------------------------
102
103// MX is a DNS MX record
104type MX struct {
105 Prio uint16 `order:"big"`
106 Server string
107}
108
109// Coexist checks if a new resource record could coexist with given set
110// of records under a label (can be called with a nil receiver)
111func (rr *MX) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
112 return true, 0
113}
114
115// ToMap adds the RR attributes to a stringed map
116func (rr *MX) ToMap(params map[string]string, prefix string) {
117 params[prefix+"prio"] = fmt.Sprintf("%d", rr.Prio)
118 params[prefix+"host"] = rr.Server
119}
diff --git a/src/gnunet/service/gns/rr/gns.go b/src/gnunet/service/gns/rr/gns.go
new file mode 100644
index 0000000..1926a4a
--- /dev/null
+++ b/src/gnunet/service/gns/rr/gns.go
@@ -0,0 +1,199 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package rr
20
21import (
22 "gnunet/crypto"
23 "gnunet/enums"
24)
25
26//----------------------------------------------------------------------
27// GNS resource records
28//----------------------------------------------------------------------
29
30// PKEY (Ed25519+EcDSA) zone key
31type PKEY struct {
32 *crypto.ZoneKey
33}
34
35// Coexist checks if a new resource record could coexist with given set
36// of records under a label (can be called with a nil receiver)
37func (rr *PKEY) Coexist(list []*enums.GNSSpec, label string) (ok bool, forced enums.GNSFlag) {
38 // can't add PKEY to apex label
39 if label == "@" {
40 return
41 }
42 // make sure all existing records are PKEYs too
43 for _, e := range list {
44 if e.Type != enums.GNS_TYPE_PKEY && e.Type != enums.GNS_TYPE_EDKEY {
45 // check failed on non-PKEY
46 return
47 }
48 // check for active PKEY
49 if e.Flags&enums.GNS_FLAG_SHADOW == 0 {
50 // only additional shaow records allowed
51 forced = enums.GNS_FLAG_SHADOW
52 }
53 }
54 ok = true
55 return
56}
57
58// ToMap adds the RR attributes to a stringed map
59func (rr *PKEY) ToMap(params map[string]string, prefix string) {
60 params[prefix+"data"] = rr.ID()
61}
62
63//----------------------------------------------------------------------
64
65// EDKEY (EdDSA) zone key
66type EDKEY struct {
67 *crypto.ZoneKey
68}
69
70// Coexist checks if a new resource record could coexist with given set
71// of records under a label (can be called with a nil receiver)
72func (rr *EDKEY) Coexist(list []*enums.GNSSpec, label string) (ok bool, forced enums.GNSFlag) {
73 // can't add EDKEY to apex label
74 if label == "@" {
75 return
76 }
77 // make sure all existing records are EDKEYs too
78 for _, e := range list {
79 if e.Type != enums.GNS_TYPE_EDKEY && e.Type != enums.GNS_TYPE_PKEY {
80 // check failed on non-EDKEY
81 return
82 }
83 // check for active PKEY
84 if e.Flags&enums.GNS_FLAG_SHADOW == 0 {
85 // only additional shaow records allowed
86 forced = enums.GNS_FLAG_SHADOW
87 }
88 }
89 ok = true
90 return
91}
92
93// ToMap adds the RR attributes to a stringed map
94func (rr *EDKEY) ToMap(params map[string]string, prefix string) {
95 params[prefix+"data"] = rr.ID()
96}
97
98//----------------------------------------------------------------------
99
100// REDIRECT to name
101type REDIRECT struct {
102 Name string
103}
104
105// Coexist checks if a new resource record could coexist with given set
106// of records under a label (can be called with a nil receiver)
107func (rr *REDIRECT) Coexist(list []*enums.GNSSpec, label string) (ok bool, forced enums.GNSFlag) {
108 // no REDIRECT in apex zone
109 if label == "@" {
110 return
111 }
112 // make sure all existing records are supplemental EDKEYs too
113 for _, e := range list {
114 if e.Type != enums.GNS_TYPE_REDIRECT && e.Flags&enums.GNS_FLAG_SUPPL == 0 {
115 // check failed on non-supplemental non-REDIRECT record
116 return
117 }
118 // check for active REDIRECT
119 if e.Flags&enums.GNS_FLAG_SHADOW == 0 {
120 // only additional shaow records allowed
121 forced = enums.GNS_FLAG_SHADOW
122 }
123 }
124 ok = true
125 return
126}
127
128// ToMap adds the RR attributes to a stringed map
129func (rr *REDIRECT) ToMap(params map[string]string, prefix string) {
130 params[prefix+"name"] = rr.Name
131}
132
133//----------------------------------------------------------------------
134
135// GNS NICK record
136type NICK struct {
137 Name string
138}
139
140// Coexist checks if a new resource record could coexist with given set
141// of records under a label (can be called with a nil receiver)
142func (rr *NICK) Coexist(list []*enums.GNSSpec, label string) (ok bool, forced enums.GNSFlag) {
143 // can only be added to the apex label
144 if label != "@" {
145 return
146 }
147 // only one un-shadowed NICK allowed
148 for _, e := range list {
149 if e.Type == enums.GNS_TYPE_NICK && e.Flags&enums.GNS_FLAG_SHADOW == 0 {
150 // only additional shadow records allowed
151 forced = enums.GNS_FLAG_SHADOW
152 }
153 }
154 ok = true
155 return
156}
157
158// ToMap adds the RR attributes to a stringed map
159func (rr *NICK) ToMap(params map[string]string, prefix string) {
160 params[prefix+"name"] = rr.Name
161}
162
163//----------------------------------------------------------------------
164
165// LEHO record
166type LEHO struct {
167 Name string
168}
169
170// Coexist checks if a new resource record could coexist with given set
171// of records under a label (can be called with a nil receiver)
172func (rr *LEHO) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
173 return true, 0
174}
175
176// ToMap adds the RR attributes to a stringed map
177func (rr *LEHO) ToMap(params map[string]string, prefix string) {
178 params[prefix+"name"] = rr.Name
179}
180
181//----------------------------------------------------------------------
182
183// GNS2DNS delegation
184type GNS2DNS struct {
185 Name string
186 Server string
187}
188
189// Coexist checks if a new resource record could coexist with given set
190// of records under a label (can be called with a nil receiver)
191func (rr *GNS2DNS) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
192 return true, 0
193}
194
195// ToMap adds the RR attributes to a stringed map
196func (rr *GNS2DNS) ToMap(params map[string]string, prefix string) {
197 params[prefix+"name"] = rr.Name
198 params[prefix+"server"] = rr.Server
199}
diff --git a/src/gnunet/service/gns/rr/gns_box.go b/src/gnunet/service/gns/rr/gns_box.go
new file mode 100644
index 0000000..5123beb
--- /dev/null
+++ b/src/gnunet/service/gns/rr/gns_box.go
@@ -0,0 +1,375 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package rr
20
21import (
22 "encoding/hex"
23 "strconv"
24 "strings"
25
26 "gnunet/enums"
27 "gnunet/util"
28
29 "github.com/bfix/gospel/data"
30 "github.com/bfix/gospel/logger"
31)
32
33//----------------------------------------------------------------------
34// GNS box record that embeds either a TLSA or SRV record
35//----------------------------------------------------------------------
36
37// BOX is an encapsulated RR for special names
38type BOX struct {
39 Proto uint16 `order:"big"` // Protcol identifier
40 Svc uint16 `order:"big"` // Service identifier
41 Type enums.GNSType `order:"big"` // Type of embedded RR
42 RR []byte `size:"*"` // embedded RR
43}
44
45// NewBOX creates a new box instance from a BOX resource record data.
46func NewBOX(buf []byte) *BOX {
47 b := new(BOX)
48 if err := data.Unmarshal(b, buf); err != nil {
49 logger.Printf(logger.ERROR, "[gns] Can't unmarshal BOX")
50 return nil
51 }
52 return b
53}
54
55// Matches verifies that the remaining labels comply with the values
56// in the BOX record.
57func (b *BOX) Matches(labels []string) bool {
58 // resolve protocol and service names
59 proto, protoName := GetProtocol(labels[0])
60 svc, _ := GetService(labels[1], protoName)
61 // no match on invalid resolution
62 if proto == 0 || svc == 0 {
63 return false
64 }
65 // check for matching values in box
66 return proto == b.Proto && svc == b.Svc
67}
68
69// Coexist checks if a new resource record could coexist with given set
70// of records under a label (can be called with a nil receiver)
71func (b *BOX) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
72 return true, 0
73}
74
75// ToMap adds the RR attributes to a stringed map
76func (b *BOX) ToMap(params map[string]string, prefix string) {
77 // shared attributes
78 protoS := util.CastToString(b.Proto)
79 if pn := GetProtocolName(b.Proto); len(pn) > 0 {
80 protoS += " (" + pn + ")"
81 }
82 params[prefix+"proto"] = protoS
83 svcS := util.CastToString(b.Svc)
84 if sn := GetServiceName(b.Svc, b.Proto); len(sn) > 0 {
85 svcS += " " + sn
86 }
87 params[prefix+"svc"] = svcS
88 params[prefix+"type"] = util.CastToString(int(b.Type))
89 // attributes of embedded record
90 if rr, err := b.EmbeddedRR(); err == nil && rr != nil {
91 rr.ToMap(params, prefix)
92 }
93}
94
95// EmbeddedRR returns the embedded RR as an instance
96func (b *BOX) EmbeddedRR() (rr RR, err error) {
97 switch b.Type {
98 case enums.GNS_TYPE_DNS_TLSA:
99 rr = new(TLSA)
100 case enums.GNS_TYPE_DNS_SRV:
101 rr = new(SRV)
102 }
103 err = data.Unmarshal(rr, b.RR)
104 return
105}
106
107//----------------------------------------------------------------------
108// embedded resource records
109//----------------------------------------------------------------------
110
111var (
112 // TLSAUsage for defined usage values
113 TLSAUsage = map[uint8]string{
114 0: "CA certificate",
115 1: "Service certificate constraint",
116 2: "Trust anchor assertion",
117 3: "Domain-issued certificate",
118 255: "Private use",
119 }
120 // TLSASelector for defined selector values
121 TLSASelector = map[uint8]string{
122 0: "Full certificate",
123 1: "SubjectPublicKeyInfo",
124 255: "Private use",
125 }
126 // TLSAMatch for defined match values
127 TLSAMatch = map[uint8]string{
128 0: "No hash",
129 1: "SHA-256",
130 2: "SHA-512",
131 255: "Private use",
132 }
133)
134
135// TLSA is a DNSSEC TLS asscoication
136type TLSA struct {
137 Usage uint8
138 Selector uint8
139 Match uint8
140 Cert []byte `size:"*"`
141}
142
143// Coexist checks if a new resource record could coexist with given set
144// of records under a label (can be called with a nil receiver)
145func (rr *TLSA) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
146 return true, 0
147}
148
149// ToMap adds the RR attributes to a stringed map
150func (rr *TLSA) ToMap(params map[string]string, prefix string) {
151 params[prefix+"tlsa_usage"] = strconv.Itoa(int(rr.Usage))
152 params[prefix+"tlsa_selector"] = strconv.Itoa(int(rr.Selector))
153 params[prefix+"tlsa_match"] = strconv.Itoa(int(rr.Match))
154 params[prefix+"tlsa_cert"] = hex.EncodeToString(rr.Cert)
155}
156
157//----------------------------------------------------------------------
158
159// SRV for service definitions
160type SRV struct {
161 Host string
162}
163
164// Coexist checks if a new resource record could coexist with given set
165// of records under a label (can be called with a nil receiver)
166func (rr *SRV) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
167 return true, 0
168}
169
170// ToMap adds the RR attributes to a stringed map
171func (rr *SRV) ToMap(params map[string]string, prefix string) {
172 params[prefix+"srv_host"] = rr.Host
173}
174
175//----------------------------------------------------------------------
176// BOX protocols
177//----------------------------------------------------------------------
178
179// list of handled protocols in BOX records
180var protocols = map[string]uint16{
181 "icmp": 1,
182 "igmp": 2,
183 "tcp": 6,
184 "udp": 17,
185 "ipv6-icmp": 58,
186}
187
188// GetProtocolName returns the name of a protocol for given nu,ber
189func GetProtocolName(proto uint16) string {
190 // check for valid number (reverse protocol lookup)
191 for label, id := range protocols {
192 if id == proto {
193 // return found entry
194 return label
195 }
196 }
197 return util.CastToString(proto)
198}
199
200// GetProtocols returns a list of supported protocols for use
201// by caller (e.g. UI handling)
202func GetProtocols() (protos map[uint16]string) {
203 protos = make(map[uint16]string)
204 for name, id := range protocols {
205 protos[id] = name
206 }
207 return
208}
209
210// GetProtocol returns the protocol number and name for a given name. The
211// name can be an integer value (e.g. "_6" for "tcp") or a mnemonic name
212// (e.g. like "_tcp").
213func GetProtocol(name string) (uint16, string) {
214 // check for required prefix
215 if name[0] != '_' {
216 return 0, ""
217 }
218 name = strings.ToLower(name[1:])
219
220 // if label is an integer value it is the protocol number
221 if val, err := strconv.Atoi(name); err == nil {
222 proto := uint16(val)
223 label := GetProtocolName(proto)
224 if len(label) == 0 {
225 proto = 0
226 }
227 return proto, label
228 }
229 // try to resolve via protocol map
230 if id, ok := protocols[name]; ok {
231 return id, name
232 }
233 // resolution failed
234 return 0, ""
235}
236
237//----------------------------------------------------------------------
238// BOX services
239//----------------------------------------------------------------------
240
241// list of services (per protocol) handled in BOX records
242var services = map[string]map[string]uint16{
243 "udp": {
244 "bootpc": 68,
245 "bootps": 67,
246 "domain": 53,
247 "gnunet": 2086,
248 "https": 443,
249 "isakmp": 500,
250 "kerberos4": 750,
251 "kerberos": 88,
252 "ldap": 389,
253 "ldaps": 636,
254 "ntp": 123,
255 "openvpn": 1194,
256 "radius": 1812,
257 "rtsp": 554,
258 "sip": 5060,
259 "sip-tls": 5061,
260 "snmp": 161,
261 "syslog": 514,
262 "tftp": 69,
263 "who": 513,
264 },
265 "tcp": {
266 "domain": 53,
267 "finger": 79,
268 "ftp": 21,
269 "ftp-data": 20,
270 "ftps": 990,
271 "ftps-data": 989,
272 "git": 9418,
273 "gnunet": 2086,
274 "gopher": 70,
275 "http": 80,
276 "https": 443,
277 "imap2": 143,
278 "imaps": 993,
279 "kerberos4": 750,
280 "kerberos": 88,
281 "kermit": 1649,
282 "ldap": 389,
283 "ldaps": 636,
284 "login": 513,
285 "mysql": 3306,
286 "openvpn": 1194,
287 "pop3": 110,
288 "pop3s": 995,
289 "printer": 515,
290 "radius": 1812,
291 "redis": 6379,
292 "rsync": 873,
293 "rtsp": 554,
294 "shell": 514,
295 "sip": 5060,
296 "sip-tls": 5061,
297 "smtp": 25,
298 "snmp": 161,
299 "ssh": 22,
300 "telnet": 23,
301 "telnets": 992,
302 "uucp": 540,
303 "webmin": 10000,
304 "x11": 6000,
305 },
306}
307
308// GetServiceName returns the service spec on given port
309func GetServiceName(svc, proto uint16) string {
310 for n, id := range services[GetProtocolName(proto)] {
311 if id == svc {
312 return n
313 }
314 }
315 return util.CastToString(svc)
316}
317
318// GetServices returns a list of supported services for use
319// by caller (e.g. UI handling)
320func GetServices() (svcs map[uint16]string) {
321 svcs = make(map[uint16]string)
322 for n, id := range services["tcp"] {
323 svcs[id] = n + " (tcp"
324 }
325 for n, id := range services["udp"] {
326 nn, ok := svcs[id]
327 if ok {
328 svcs[id] = nn + "/udp"
329 } else {
330 svcs[id] = n + " (udp"
331 }
332 }
333 for id, n := range svcs {
334 svcs[id] = n + ")"
335 }
336 return
337}
338
339// GetService returns the port number and the name of a service (with given
340// protocol). The name can be an integer value (e.g. "_443" for "https") or
341// a mnemonic name (e.g. like "_https").
342func GetService(name, proto string) (uint16, string) {
343 // check for required prefix
344 if name[0] != '_' {
345 return 0, ""
346 }
347 name = strings.ToLower(name[1:])
348
349 // get list of services for given protocol
350 svcs, ok := services[proto]
351 if !ok {
352 // no services available for this protocol
353 return 0, ""
354 }
355
356 // if label is an integer value it is the port number
357 if val, err := strconv.Atoi(name); err == nil {
358 svc := uint16(val)
359 // check for valid number (reverse service lookup)
360 for label, id := range svcs {
361 if id == svc {
362 // return found entry
363 return svc, label
364 }
365 }
366 // number out of range
367 return 0, ""
368 }
369 // try to resolve via services map
370 if id, ok := svcs[name]; ok {
371 return id, name
372 }
373 // resolution failed
374 return 0, ""
375}
diff --git a/src/gnunet/service/gns/service.go b/src/gnunet/service/gns/service.go
index 010c460..dbbd425 100644
--- a/src/gnunet/service/gns/service.go
+++ b/src/gnunet/service/gns/service.go
@@ -136,7 +136,7 @@ func (s *Service) HandleMessage(ctx context.Context, sender *util.PeerID, msg me
136 logger.Printf(logger.DBG, "[gns%s] Lookup request finished.\n", label) 136 logger.Printf(logger.DBG, "[gns%s] Lookup request finished.\n", label)
137 }() 137 }()
138 138
139 kind := NewRRTypeList(enums.GNSType(m.RType)) 139 kind := NewRRTypeList(m.RType)
140 recset, err := s.Resolve(ctx, label, m.Zone, kind, int(m.Options), 0) 140 recset, err := s.Resolve(ctx, label, m.Zone, kind, int(m.Options), 0)
141 if err != nil { 141 if err != nil {
142 logger.Printf(logger.ERROR, "[gns%s] Failed to lookup block: %s\n", label, err.Error()) 142 logger.Printf(logger.ERROR, "[gns%s] Failed to lookup block: %s\n", label, err.Error())
@@ -159,7 +159,7 @@ func (s *Service) HandleMessage(ctx context.Context, sender *util.PeerID, msg me
159 logger.Printf(logger.DBG, "[gns%s] Record #%d: %v\n", label, i, rec) 159 logger.Printf(logger.DBG, "[gns%s] Record #%d: %v\n", label, i, rec)
160 160
161 // is this the record type we are looking for? 161 // is this the record type we are looking for?
162 if rec.RType == m.RType || enums.GNSType(m.RType) == enums.GNS_TYPE_ANY { 162 if rec.RType == m.RType || m.RType == enums.GNS_TYPE_ANY {
163 // add it to the response message 163 // add it to the response message
164 if err := resp.AddRecord(rec); err != nil { 164 if err := resp.AddRecord(rec); err != nil {
165 logger.Printf(logger.ERROR, "[gns%s] failed: %sv", label, err.Error()) 165 logger.Printf(logger.ERROR, "[gns%s] failed: %sv", label, err.Error())
@@ -192,7 +192,7 @@ func (s *Service) QueryKeyRevocation(ctx context.Context, zkey *crypto.ZoneKey)
192 192
193 // get response from Revocation service 193 // get response from Revocation service
194 var resp message.Message 194 var resp message.Message
195 if resp, err = service.RequestResponse(ctx, "gns", "Revocation", config.Cfg.Revocation.Service.Socket, req); err != nil { 195 if resp, err = service.RequestResponse(ctx, "gns", "Revocation", config.Cfg.Revocation.Service.Socket, req, true); err != nil {
196 return 196 return
197 } 197 }
198 198
@@ -218,7 +218,7 @@ func (s *Service) RevokeKey(ctx context.Context, rd *revocation.RevData) (succes
218 218
219 // get response from Revocation service 219 // get response from Revocation service
220 var resp message.Message 220 var resp message.Message
221 if resp, err = service.RequestResponse(ctx, "gns", "Revocation", config.Cfg.Revocation.Service.Socket, req); err != nil { 221 if resp, err = service.RequestResponse(ctx, "gns", "Revocation", config.Cfg.Revocation.Service.Socket, req, true); err != nil {
222 return 222 return
223 } 223 }
224 224
@@ -247,7 +247,7 @@ func (s *Service) LookupNamecache(ctx context.Context, query *blocks.GNSQuery) (
247 247
248 // get response from Namecache service 248 // get response from Namecache service
249 var resp message.Message 249 var resp message.Message
250 if resp, err = service.RequestResponse(ctx, "gns", "Namecache", config.Cfg.Namecache.Service.Socket, req); err != nil { 250 if resp, err = service.RequestResponse(ctx, "gns", "Namecache", config.Cfg.Namecache.Service.Socket, req, true); err != nil {
251 return 251 return
252 } 252 }
253 253
@@ -308,7 +308,7 @@ func (s *Service) StoreNamecache(ctx context.Context, query *blocks.GNSQuery, bl
308 308
309 // get response from Namecache service 309 // get response from Namecache service
310 var resp message.Message 310 var resp message.Message
311 if resp, err = service.RequestResponse(ctx, "gns", "Namecache", config.Cfg.Namecache.Service.Socket, req); err != nil { 311 if resp, err = service.RequestResponse(ctx, "gns", "Namecache", config.Cfg.Namecache.Service.Socket, req, true); err != nil {
312 return 312 return
313 } 313 }
314 314
diff --git a/src/gnunet/service/revocation/pow_test.go b/src/gnunet/service/revocation/pow_test.go
index 0747d72..41c17b4 100644
--- a/src/gnunet/service/revocation/pow_test.go
+++ b/src/gnunet/service/revocation/pow_test.go
@@ -60,7 +60,7 @@ func TestRevocationRFC(t *testing.T) {
60 if err != nil { 60 if err != nil {
61 t.Fatal(err) 61 t.Fatal(err)
62 } 62 }
63 prv, err := crypto.NewZonePrivate(crypto.ZONE_PKEY, d) 63 prv, err := crypto.NewZonePrivate(enums.GNS_TYPE_PKEY, d)
64 if err != nil { 64 if err != nil {
65 t.Fatal(err) 65 t.Fatal(err)
66 } 66 }
diff --git a/src/gnunet/service/store/store_dht_meta.go b/src/gnunet/service/store/store_dht_meta.go
index 64f658b..d1c4cb7 100644
--- a/src/gnunet/service/store/store_dht_meta.go
+++ b/src/gnunet/service/store/store_dht_meta.go
@@ -97,10 +97,16 @@ func OpenMetaDB(path string) (db *FileMetaDB, err error) {
97// Store metadata in database: creates or updates a record for the metadata 97// Store metadata in database: creates or updates a record for the metadata
98// in the database; primary key is the query key 98// in the database; primary key is the query key
99func (db *FileMetaDB) Store(md *FileMetadata) (err error) { 99func (db *FileMetaDB) Store(md *FileMetadata) (err error) {
100 // work around a SQLite3 bug when storing uint64 with high bit set
101 var exp *uint64
102 if !md.expires.IsNever() {
103 exp = new(uint64)
104 *exp = md.expires.Val
105 }
100 sql := "replace into meta(qkey,btype,bhash,size,stored,expires,lastUsed,usedCount) values(?,?,?,?,?,?,?,?)" 106 sql := "replace into meta(qkey,btype,bhash,size,stored,expires,lastUsed,usedCount) values(?,?,?,?,?,?,?,?)"
101 _, err = db.conn.Exec(sql, 107 _, err = db.conn.Exec(sql,
102 md.key.Data, md.btype, md.bhash.Data, md.size, md.stored.Epoch(), 108 md.key.Data, md.btype, md.bhash.Data, md.size, md.stored.Epoch(),
103 md.expires.Val, md.lastUsed.Epoch(), md.usedCount) 109 exp, md.lastUsed.Epoch(), md.usedCount)
104 return 110 return
105} 111}
106 112
@@ -124,13 +130,19 @@ func (db *FileMetaDB) Get(query blocks.Query) (mds []*FileMetadata, err error) {
124 md.key = query.Key() 130 md.key = query.Key()
125 md.btype = btype 131 md.btype = btype
126 var st, lu uint64 132 var st, lu uint64
127 if err = rows.Scan(&md.size, &md.bhash.Data, &st, &md.expires.Val, &lu, &md.usedCount); err != nil { 133 var exp *uint64
134 if err = rows.Scan(&md.size, &md.bhash.Data, &st, &exp, &lu, &md.usedCount); err != nil {
128 if err == sql.ErrNoRows { 135 if err == sql.ErrNoRows {
129 md = nil 136 md = nil
130 err = nil 137 err = nil
131 } 138 }
132 return 139 return
133 } 140 }
141 if exp != nil {
142 md.expires.Val = *exp
143 } else {
144 md.expires = util.AbsoluteTimeNever()
145 }
134 md.stored.Val = st * 1000000 146 md.stored.Val = st * 1000000
135 md.lastUsed.Val = lu * 1000000 147 md.lastUsed.Val = lu * 1000000
136 mds = append(mds, md) 148 mds = append(mds, md)
@@ -192,10 +204,16 @@ func (db *FileMetaDB) Traverse(f func(*FileMetadata)) error {
192 md := NewFileMetadata() 204 md := NewFileMetadata()
193 for rows.Next() { 205 for rows.Next() {
194 var st, lu uint64 206 var st, lu uint64
195 err = rows.Scan(&md.key.Data, &md.btype, &md.bhash.Data, &md.size, &st, &md.expires.Val, &lu, &md.usedCount) 207 var exp *uint64
208 err = rows.Scan(&md.key.Data, &md.btype, &md.bhash.Data, &md.size, &st, &exp, &lu, &md.usedCount)
196 if err != nil { 209 if err != nil {
197 return err 210 return err
198 } 211 }
212 if exp != nil {
213 md.expires.Val = *exp
214 } else {
215 md.expires = util.AbsoluteTimeNever()
216 }
199 md.stored.Val = st * 1000000 217 md.stored.Val = st * 1000000
200 md.lastUsed.Val = lu * 1000000 218 md.lastUsed.Val = lu * 1000000
201 // call process function 219 // call process function
diff --git a/src/gnunet/service/store/store_zonemaster.go b/src/gnunet/service/store/store_zonemaster.go
new file mode 100644
index 0000000..44b1a68
--- /dev/null
+++ b/src/gnunet/service/store/store_zonemaster.go
@@ -0,0 +1,548 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package store
20
21import (
22 "database/sql"
23 _ "embed"
24 "errors"
25 "fmt"
26 "gnunet/crypto"
27 "gnunet/enums"
28 "gnunet/service/dht/blocks"
29 "gnunet/util"
30 "os"
31 // "https://github.com/go-zeromq/zmq4"
32)
33
34//============================================================
35// Local zone records stored in SQLite3 database
36//============================================================
37
38// Zone is the definition of a local GNS zone
39// and is stored in a SQL database for faster access.
40type Zone struct {
41 ID int64 // database identifier
42 Name string // zone name
43 Created util.AbsoluteTime // date of creation
44 Modified util.AbsoluteTime // date of last modification
45 Key *crypto.ZonePrivate // private zone key (ztype|zdata)
46}
47
48// NewZone creates a new zone for the given private key. The zone is not stored
49// in the database automatically.
50func NewZone(name string, sk *crypto.ZonePrivate) *Zone {
51 // create zone instance
52 return &Zone{
53 Name: name,
54 Created: util.AbsoluteTimeNow(),
55 Modified: util.AbsoluteTimeNow(),
56 Key: sk,
57 }
58}
59
60//----------------------------------------------------------------------
61
62type Label struct {
63 ID int64 // database id of label
64 Zone int64 // database ID of parent zone
65 Name string // label name
66 Created util.AbsoluteTime // date of creation
67 Modified util.AbsoluteTime // date of last modification
68}
69
70func NewLabel(label string) *Label {
71 lbl := new(Label)
72 lbl.ID = 0
73 lbl.Zone = 0
74 lbl.Name = label
75 lbl.Created = util.AbsoluteTimeNow()
76 lbl.Modified = util.AbsoluteTimeNow()
77 return lbl
78}
79
80//----------------------------------------------------------------------
81
82// Record for GNS resource in a zone (generic). It is the responsibility
83// of the caller to provide valid resource data in binary form.
84type Record struct {
85 ID int64 // database id of record
86 Label int64 // database ID of parent label
87 Created util.AbsoluteTime // date of creation
88 Modified util.AbsoluteTime // date of last modification
89
90 blocks.ResourceRecord
91}
92
93// NewRecord creates a new record for given data. The record is not
94// automatically added to the database.
95func NewRecord(expire util.AbsoluteTime, rtype enums.GNSType, flags enums.GNSFlag, data []byte) *Record {
96 rec := new(Record)
97 rec.ID = 0
98 rec.Label = 0
99 rec.Expire = expire
100 rec.RType = rtype
101 rec.Flags = flags
102 rec.Data = data
103 rec.Size = uint32(len(rec.Data))
104 rec.Created = util.AbsoluteTimeNow()
105 rec.Modified = util.AbsoluteTimeNow()
106 return rec
107}
108
109//======================================================================
110// Zone database: A SQLite3 database to hold metadata about
111// managed local zones (see "namestore" in gnunet).
112//======================================================================
113
114//go:embed store_zonemaster.sql
115var initScriptZM []byte
116
117// ZoneDB is a SQLite3 database for locally managed zones
118type ZoneDB struct {
119 conn *DBConn // database connection
120}
121
122// OpenZoneDB opens a zone database in the given filename (including
123// path). If the database file does not exist, it is created and
124// set up with empty tables.
125func OpenZoneDB(fname string) (db *ZoneDB, err error) {
126 // connect to database
127 if _, err = os.Stat(fname); err != nil {
128 var file *os.File
129 if file, err = os.Create(fname); err != nil {
130 return
131 }
132 file.Close()
133 }
134 db = new(ZoneDB)
135 if db.conn, err = DBPool.Connect("sqlite3:" + fname); err != nil {
136 return
137 }
138 // check for initialized database
139 res := db.conn.QueryRow("select name from sqlite_master where type='table' and name='zones'")
140 var s string
141 if res.Scan(&s) != nil {
142 // initialize database
143 if _, err = db.conn.Exec(string(initScriptZM)); err != nil {
144 return
145 }
146 }
147 return
148}
149
150// Close zone database
151func (db *ZoneDB) Close() error {
152 return db.conn.Close()
153}
154
155//----------------------------------------------------------------------
156// Zone handling
157//----------------------------------------------------------------------
158
159// SetZone inserts, updates or deletes a zone in the database.
160// The function does not change timestamps which are in the
161// responsibility of the caller.
162// - insert: Zone.ID is nil (0)
163// - update: Zone.Name is set
164// - remove: otherwise
165func (db *ZoneDB) SetZone(z *Zone) error {
166 // check for zone insert
167 if z.ID == 0 {
168 stmt := "insert into zones(name,created,modified,ztype,zdata) values(?,?,?,?,?)"
169 result, err := db.conn.Exec(stmt, z.Name, z.Created.Val, z.Modified.Val, z.Key.Type, z.Key.KeyData)
170 if err != nil {
171 return err
172 }
173 z.ID, err = result.LastInsertId()
174 return err
175 }
176 // check for zone update (name only)
177 if len(z.Name) > 0 {
178 stmt := "update zones set name=?,modified=? where id=?"
179 result, err := db.conn.Exec(stmt, z.Name, z.Modified.Val, z.ID)
180 if err != nil {
181 return err
182 }
183 var num int64
184 if num, err = result.RowsAffected(); err == nil {
185 if num != 1 {
186 err = errors.New("update zone failed")
187 }
188 }
189 return err
190 }
191 // remove zone from database: also move all dependent labels to "trash bin"
192 // (parent zone reference is nil)
193 if _, err := db.conn.Exec("update labels set zid=null where zid=?", z.ID); err != nil {
194 return err
195 }
196 _, err := db.conn.Exec("delete from zones where id=?", z.ID)
197 return err
198}
199
200// GetZone gets a zone with given identifier
201func (db *ZoneDB) GetZone(id int64) (zone *Zone, err error) {
202 // assemble zone from database row
203 stmt := "select name,created,modified,ztype,zdata from zones where id=?"
204 zone = new(Zone)
205 var ztype enums.GNSType
206 var zdata []byte
207 row := db.conn.QueryRow(stmt, id)
208 if err = row.Scan(&zone.Name, &zone.Created.Val, &zone.Modified.Val, &ztype, &zdata); err == nil {
209 // reconstruct private zone key
210 zone.Key, err = crypto.NewZonePrivate(ztype, zdata)
211 }
212 return
213}
214
215// GetZones retrieves zone instances from database matching a filter
216// ("where" clause)
217func (db *ZoneDB) GetZones(filter string, args ...any) (list []*Zone, err error) {
218 // assemble query
219 stmt := "select id,name,created,modified,ztype,zdata from zones"
220 if len(filter) > 0 {
221 stmt += " where " + fmt.Sprintf(filter, args...)
222 }
223 // select zones
224 var rows *sql.Rows
225 if rows, err = db.conn.Query(stmt); err != nil {
226 return
227 }
228 // process zones
229 defer rows.Close()
230 for rows.Next() {
231 // assemble zone from database row
232 zone := new(Zone)
233 var ztype enums.GNSType
234 var zdata []byte
235 if err = rows.Scan(&zone.ID, &zone.Name, &zone.Created.Val, &zone.Modified.Val, &ztype, &zdata); err != nil {
236 // terminate on error; return list so far
237 return
238 }
239 // reconstruct private zone key
240 if zone.Key, err = crypto.NewZonePrivate(ztype, zdata); err != nil {
241 return
242 }
243 // append to result list
244 list = append(list, zone)
245 }
246 return
247}
248
249//----------------------------------------------------------------------
250// Label handling
251//----------------------------------------------------------------------
252
253// SetLabel inserts, updates or deletes a zone label in the database.
254// The function does not change timestamps which are in the
255// responsibility of the caller.
256// - insert: Label.ID is nil (0)
257// - update: Label.Name is set (eventually modified)
258// - remove: otherwise
259func (db *ZoneDB) SetLabel(l *Label) error {
260 // check for label insert
261 if l.ID == 0 {
262 stmt := "insert into labels(zid,name,created,modified) values(?,?,?,?)"
263 result, err := db.conn.Exec(stmt, l.Zone, l.Name, l.Created.Val, l.Modified.Val)
264 if err != nil {
265 return err
266 }
267 l.ID, err = result.LastInsertId()
268 return err
269 }
270 // check for label update (name only)
271 if len(l.Name) > 0 {
272 stmt := "update labels set name=?,modified=? where id=?"
273 result, err := db.conn.Exec(stmt, l.Name, l.Modified.Val, l.ID)
274 if err != nil {
275 return err
276 }
277 var num int64
278 if num, err = result.RowsAffected(); err == nil {
279 if num != 1 {
280 err = errors.New("update label failed")
281 }
282 }
283 return err
284 }
285 // remove label from database; move dependent records to trash bin
286 // (label id set to nil)
287 if _, err := db.conn.Exec("update records set lid=null where lid=?", l.ID); err != nil {
288 return err
289 }
290 _, err := db.conn.Exec("delete from labels where id=?", l.ID)
291 return err
292}
293
294// GetLabel gets a label with given identifier
295func (db *ZoneDB) GetLabel(id int64) (label *Label, err error) {
296 // assemble label from database row
297 stmt := "select zid,name,created,modified from labels where id=?"
298 label = new(Label)
299 row := db.conn.QueryRow(stmt, id)
300 err = row.Scan(&label.Zone, &label.Name, &label.Created.Val, &label.Modified.Val)
301 return
302}
303
304// GetLabels retrieves record instances from database matching a filter
305// ("where" clause)
306func (db *ZoneDB) GetLabels(filter string, args ...any) (list []*Label, err error) {
307 // assemble querey
308 stmt := "select id,zid,name,created,modified from labels"
309 if len(filter) > 0 {
310 stmt += " where " + fmt.Sprintf(filter, args...)
311 }
312 // select labels
313 var rows *sql.Rows
314 if rows, err = db.conn.Query(stmt); err != nil {
315 return
316 }
317 // process labels
318 defer rows.Close()
319 for rows.Next() {
320 // assemble label from database row
321 lbl := new(Label)
322 if err = rows.Scan(&lbl.ID, &lbl.Zone, &lbl.Name, &lbl.Created.Val, &lbl.Modified.Val); err != nil {
323 // terminate on error; return list so far
324 return
325 }
326 // append to result list
327 list = append(list, lbl)
328 }
329 return
330}
331
332//----------------------------------------------------------------------
333// Record handling
334//----------------------------------------------------------------------
335
336// SetRecord inserts, updates or deletes a record in the database.
337// The function does not change timestamps which are in the
338// responsibility of the caller.
339// - insert: Record.ID is nil (0)
340// - update: Record.ZID is set (eventually modified)
341// - remove: otherwise
342func (db *ZoneDB) SetRecord(r *Record) error {
343 // work around a SQLite3 bug when storing uint64 with high bit set
344 var exp *uint64
345 if !r.Expire.IsNever() {
346 exp = new(uint64)
347 *exp = r.Expire.Val
348 }
349 // check for record insert
350 if r.ID == 0 {
351 stmt := "insert into records(lid,expire,created,modified,flags,rtype,rdata) values(?,?,?,?,?,?,?)"
352 result, err := db.conn.Exec(stmt, r.Label, exp, r.Created.Val, r.Modified.Val, r.Flags, r.RType, r.Data)
353 if err != nil {
354 return err
355 }
356 r.ID, err = result.LastInsertId()
357 return err
358 }
359 // check for record update
360 if r.Label != 0 {
361 stmt := "update records set lid=?,expire=?,modified=?,flags=?,rtype=?,rdata=? where id=?"
362 result, err := db.conn.Exec(stmt, r.Label, exp, r.Modified.Val, r.Flags, r.RType, r.Data, r.ID)
363 if err != nil {
364 return err
365 }
366 var num int64
367 if num, err = result.RowsAffected(); err == nil {
368 if num != 1 {
369 err = errors.New("update record failed")
370 }
371 }
372 return err
373 }
374 // remove record from database
375 _, err := db.conn.Exec("delete from records where id=?", r.ID)
376 return err
377}
378
379// GetRecord gets a resource record with given identifier
380func (db *ZoneDB) GetRecord(id int64) (rec *Record, err error) {
381 // assemble resource record from database row
382 stmt := "select lid,expire,created,modified,flags,rtype,rdata from records where id=?"
383 rec = new(Record)
384 row := db.conn.QueryRow(stmt, id)
385 var exp *uint64
386 if err = row.Scan(&rec.Label, &exp, &rec.Created.Val, &rec.Modified.Val, &rec.Flags, &rec.RType, &rec.Data); err != nil {
387 // terminate on error
388 return
389 }
390 // setup missing fields
391 rec.Size = uint32(len(rec.Data))
392 if exp != nil {
393 rec.Expire.Val = *exp
394 } else {
395 rec.Expire = util.AbsoluteTimeNever()
396 }
397 return
398}
399
400// GetRecords retrieves record instances from database matching a filter
401// ("where" clause)
402func (db *ZoneDB) GetRecords(filter string, args ...any) (list []*Record, err error) {
403 // assemble querey
404 stmt := "select id,lid,expire,created,modified,flags,rtype,rdata from records"
405 if len(filter) > 0 {
406 stmt += " where " + fmt.Sprintf(filter, args...)
407 }
408 // select records
409 var rows *sql.Rows
410 if rows, err = db.conn.Query(stmt); err != nil {
411 return
412 }
413 // process records
414 defer rows.Close()
415 for rows.Next() {
416 // assemble record from database row
417 rec := new(Record)
418 var exp *uint64
419 if err = rows.Scan(&rec.ID, &rec.Label, &exp, &rec.Created.Val, &rec.Modified.Val, &rec.Flags, &rec.RType, &rec.Data); err != nil {
420 // terminate on error; return list so far
421 return
422 }
423 rec.Size = uint32(len(rec.Data))
424 if exp != nil {
425 rec.Expire.Val = *exp
426 } else {
427 rec.Expire = util.AbsoluteTimeNever()
428 }
429 // append to result list
430 list = append(list, rec)
431 }
432 return
433}
434
435//----------------------------------------------------------------------
436// Retrieve database content as a nested struct
437//----------------------------------------------------------------------
438
439// LabelGroup is a nested label entry (with records)
440type LabelGroup struct {
441 Label *Label
442 Records []*Record
443}
444
445// ZoneGroup is a nested zone entry (with labels)
446type ZoneGroup struct {
447 Zone *Zone
448 PubID string
449 Labels []*LabelGroup
450}
451
452// GetContent returns the database content as a nested list of zones, labels
453// and records. Since the use-case for the ZoneManager is the management of
454// local zones, the number of entries is limited.
455func (db *ZoneDB) GetContent() (zg []*ZoneGroup, err error) {
456 // get all zones
457 var zones []*Zone
458 if zones, err = db.GetZones(""); err != nil {
459 return
460 }
461 for _, z := range zones {
462 // create group instance for zone
463 zGroup := &ZoneGroup{
464 Zone: z,
465 PubID: z.Key.Public().ID(),
466 Labels: make([]*LabelGroup, 0),
467 }
468 zg = append(zg, zGroup)
469
470 // get all labels for zone
471 var labels []*Label
472 if labels, err = db.GetLabels("zid=%d", z.ID); err != nil {
473 return
474 }
475 for _, l := range labels {
476 // create group instance for label
477 lGroup := &LabelGroup{
478 Label: l,
479 Records: make([]*Record, 0),
480 }
481 // link to zone group
482 zGroup.Labels = append(zGroup.Labels, lGroup)
483
484 // get all records for label
485 lGroup.Records, err = db.GetRecords("lid=%d", l.ID)
486 }
487 }
488 return
489}
490
491//----------------------------------------------------------------------
492// Retrieve list of used names (Zone,Label) or RR types (Record)
493//----------------------------------------------------------------------
494
495// GetName returns an object name (zone,label) for given id
496func (db *ZoneDB) GetName(tbl string, id int64) (name string, err error) {
497 row := db.conn.QueryRow("select name from "+tbl+" where id=?", id)
498 err = row.Scan(&name)
499 return
500}
501
502// GetNames returns a list of used names (table "zones" and "labels")
503func (db *ZoneDB) GetNames(tbl string) (names []string, err error) {
504 // select all table names
505 var rows *sql.Rows
506 if rows, err = db.conn.Query("select name from " + tbl); err != nil {
507 return
508 }
509 // process names
510 defer rows.Close()
511 var name string
512 for rows.Next() {
513 if err = rows.Scan(&name); err != nil {
514 // terminate on error; return list so far
515 return
516 }
517 // append to result list
518 names = append(names, name)
519 }
520 return
521}
522
523// GetRRTypes returns a list record types stored under a label
524func (db *ZoneDB) GetRRTypes(lid int64) (rrtypes []*enums.GNSSpec, label string, err error) {
525 // select label name
526 row := db.conn.QueryRow("select name from labels where id=?", lid)
527 if err = row.Scan(&label); err != nil {
528 return
529 }
530 // select all record types under label
531 stmt := "select rtype,flags from records where lid=?"
532 var rows *sql.Rows
533 if rows, err = db.conn.Query(stmt, lid); err != nil {
534 return
535 }
536 // process records
537 defer rows.Close()
538 for rows.Next() {
539 e := new(enums.GNSSpec)
540 if err = rows.Scan(&e.Type, &e.Flags); err != nil {
541 // terminate on error; return list so far
542 return
543 }
544 // append to result list
545 rrtypes = append(rrtypes, e)
546 }
547 return
548}
diff --git a/src/gnunet/service/store/store_zonemaster.sql b/src/gnunet/service/store/store_zonemaster.sql
new file mode 100644
index 0000000..169fefd
--- /dev/null
+++ b/src/gnunet/service/store/store_zonemaster.sql
@@ -0,0 +1,47 @@
1-- This file is part of gnunet-go, a GNUnet-implementation in Golang.
2-- Copyright (C) 2019-2022 Bernd Fix >Y<
3--
4-- gnunet-go is free software: you can redistribute it and/or modify it
5-- under the terms of the GNU Affero General Public License as published
6-- by the Free Software Foundation, either version 3 of the License,
7-- or (at your option) any later version.
8--
9-- gnunet-go is distributed in the hope that it will be useful, but
10-- WITHOUT ANY WARRANTY; without even the implied warranty of
11-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12-- Affero General Public License for more details.
13--
14-- You should have received a copy of the GNU Affero General Public License
15-- along with this program. If not, see <http://www.gnu.org/licenses/>.
16--
17-- SPDX-License-Identifier: AGPL3.0-or-later
18
19create table zones (
20 id integer primary key autoincrement,
21 name text unique,
22 created integer,
23 modified integer,
24 ztype integer,
25 zdata blob
26);
27
28
29create table labels (
30 id integer primary key autoincrement,
31 zid integer references zones(id),
32 name text,
33 created integer,
34 modified integer,
35 unique (zid,name)
36);
37
38create table records (
39 id integer primary key autoincrement,
40 lid integer references labels(id),
41 expire integer,
42 created integer,
43 modified integer,
44 flags integer,
45 rtype integer,
46 rdata blob
47);
diff --git a/src/gnunet/service/store/store_zonemaster_test.go b/src/gnunet/service/store/store_zonemaster_test.go
new file mode 100644
index 0000000..6f416b1
--- /dev/null
+++ b/src/gnunet/service/store/store_zonemaster_test.go
@@ -0,0 +1,105 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package store
20
21import (
22 "crypto/rand"
23 "gnunet/crypto"
24 "gnunet/enums"
25 "gnunet/util"
26 "os"
27 "testing"
28 "time"
29)
30
31func TestZoneMaster(t *testing.T) {
32
33 //------------------------------------------------------------------
34 // create database
35 _ = os.Remove("/tmp/zonemaster.db")
36 zdb, err := OpenZoneDB("/tmp/zonemaster.db")
37 if err != nil {
38 t.Fatal(err)
39 }
40
41 //------------------------------------------------------------------
42 // create zone and add zone to database
43 seed := make([]byte, 32)
44 if _, err = rand.Read(seed); err != nil {
45 t.Fatal(err)
46 }
47 zp, err := crypto.NewZonePrivate(enums.GNS_TYPE_PKEY, seed)
48 if err != nil {
49 t.Fatal(err)
50 }
51 zone := NewZone("foo", zp)
52 if err = zdb.SetZone(zone); err != nil {
53 t.Fatal(err)
54 }
55
56 //------------------------------------------------------------------
57 // create label and add to zone and database
58 label := NewLabel("bar")
59 label.Zone = zone.ID
60 if err = zdb.SetLabel(label); err != nil {
61 t.Fatal(err)
62 }
63
64 //------------------------------------------------------------------
65 // add record to label and database
66 rec := NewRecord(util.AbsoluteTimeNever().Add(time.Hour), enums.GNS_TYPE_DNS_TXT, 0, []byte("test entry"))
67 rec.Label = label.ID
68 if err = zdb.SetRecord(rec); err != nil {
69 t.Fatal(err)
70 }
71
72 //------------------------------------------------------------------
73 // search record in database
74 recs, err := zdb.GetRecords("rtype=%d", enums.GNS_TYPE_DNS_TXT)
75 if err != nil {
76 t.Fatal(err)
77 }
78 if len(recs) != 1 {
79 t.Fatalf("record: got %d records, expected 1", len(recs))
80 }
81
82 //------------------------------------------------------------------
83 // rename zone
84 zone.Name = "MyZone"
85 zone.Modified = util.AbsoluteTimeNow()
86 if err = zdb.SetZone(zone); err != nil {
87 t.Fatal(err)
88 }
89
90 //------------------------------------------------------------------
91 // search zone in database
92 zones, err := zdb.GetZones("name like 'My%%'")
93 if err != nil {
94 t.Fatal(err)
95 }
96 if len(zones) != 1 {
97 t.Fatalf("zone: got %d records, expected 1", len(zones))
98 }
99
100 //------------------------------------------------------------------
101 // close database
102 if err = zdb.Close(); err != nil {
103 t.Fatal(err)
104 }
105}
diff --git a/src/gnunet/service/zonemaster/gui.go b/src/gnunet/service/zonemaster/gui.go
new file mode 100644
index 0000000..3885d01
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui.go
@@ -0,0 +1,666 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package zonemaster
20
21import (
22 "bytes"
23 "context"
24 "crypto/rand"
25 "embed"
26 "errors"
27 "fmt"
28 "gnunet/crypto"
29 "gnunet/enums"
30 "gnunet/service/gns/rr"
31 "gnunet/service/store"
32 "gnunet/util"
33 "io"
34 "net"
35 "net/http"
36 "strings"
37 "text/template"
38 "time"
39
40 "github.com/bfix/gospel/logger"
41 "github.com/gorilla/mux"
42)
43
44//======================================================================
45// HTTP service
46//======================================================================
47
48//go:embed gui.htpl gui_css.htpl gui_rr.htpl gui_debug.htpl gui_edit.htpl gui_new.htpl
49var fsys embed.FS
50
51var (
52 tpl *template.Template // HTML templates
53 timeHTML = "2006-01-02T15:04" // time format (defined by HTML, don't change!)
54 timeGUI = "02.01.06 15:04" // time format for GUI
55)
56
57// state-change constants
58const (
59 ChangeNew = iota
60 ChangeUpdate
61 ChangeDelete
62)
63
64//----------------------------------------------------------------------
65
66// Start HTTP server to provide GUI
67func (zm *ZoneMaster) startGUI(ctx context.Context) {
68 logger.Println(logger.INFO, "[zonemaster] Starting HTTP GUI backend...")
69
70 // read and prepare templates
71 tpl = template.New("gui")
72 tpl.Funcs(template.FuncMap{
73 "date": func(ts util.AbsoluteTime) string {
74 return guiTime(ts)
75 },
76 "keytype": func(t enums.GNSType) string {
77 return guiKeyType(t)
78 },
79 "setspecs": func(data map[string]string, spec enums.GNSSpec) string {
80 pf := guiPrefix(spec.Type)
81 data["prefix"] = pf
82 if spec.Flags&enums.GNS_FLAG_PRIVATE != 0 {
83 data[pf+"private"] = "on"
84 }
85 if spec.Flags&enums.GNS_FLAG_SHADOW != 0 {
86 data[pf+"shadow"] = "on"
87 }
88 if spec.Flags&enums.GNS_FLAG_SUPPL != 0 {
89 data[pf+"suppl"] = "on"
90 }
91 return pf
92 },
93 "boxprotos": func() map[uint16]string {
94 return rr.GetProtocols()
95 },
96 "boxsvcs": func() map[uint16]string {
97 return rr.GetServices()
98 },
99 "rrtype": func(t enums.GNSType) string {
100 return strings.Replace(t.String(), "GNS_TYPE_", "", -1)
101 },
102 "rritype": func(ts string) string {
103 t, _ := util.CastFromString[enums.GNSType](ts)
104 return strings.Replace(t.String(), "GNS_TYPE_", "", -1)
105 },
106 "rrflags": func(f enums.GNSFlag) string {
107 flags := make([]string, 0)
108 if f&enums.GNS_FLAG_PRIVATE != 0 {
109 flags = append(flags, "Private")
110 }
111 if f&enums.GNS_FLAG_SHADOW != 0 {
112 flags = append(flags, "Shadow")
113 }
114 if f&enums.GNS_FLAG_SUPPL != 0 {
115 flags = append(flags, "Suppl")
116 }
117 if len(flags) == 0 {
118 return "None"
119 }
120 return strings.Join(flags, ",<br>")
121 },
122 "rrdata": func(t enums.GNSType, buf []byte) string {
123 return guiRRdata(t, buf)
124 },
125 })
126 if _, err := tpl.ParseFS(fsys, "*.htpl"); err != nil {
127 logger.Println(logger.ERROR, "[zonemaster] GUI templates failed: "+err.Error())
128 return
129 }
130
131 // start HTTP server
132 router := mux.NewRouter()
133 router.HandleFunc("/new/{mode}/{id}", zm.new)
134 router.HandleFunc("/edit/{mode}/{id}", zm.edit)
135 router.HandleFunc("/del/{mode}/{id}", zm.remove)
136 router.HandleFunc("/action/{cmd}/{mode}/{id}", zm.action)
137 router.HandleFunc("/", zm.dashboard)
138 srv := &http.Server{
139 Addr: zm.cfg.ZoneMaster.GUI,
140 ReadTimeout: 10 * time.Second,
141 ReadHeaderTimeout: 5 * time.Second,
142 Handler: router,
143 BaseContext: func(l net.Listener) context.Context {
144 return ctx
145 },
146 }
147 go func() {
148 if err := srv.ListenAndServe(); err != http.ErrServerClosed {
149 logger.Printf(logger.ERROR, "[zonemaster] Failed to start GUI: "+err.Error())
150 }
151 }()
152}
153
154//----------------------------------------------------------------------
155
156// dashboard is the main entry point for the GUI
157func (zm *ZoneMaster) dashboard(w http.ResponseWriter, r *http.Request) {
158 // collect information for the GUI
159 zg, err := zm.zdb.GetContent()
160 if err != nil {
161 _, _ = io.WriteString(w, "ERROR: "+err.Error())
162 return
163 }
164 // show dashboard
165 renderPage(w, zg, "dashboard")
166}
167
168//======================================================================
169// Handle GUI actions (add, edit and remove)
170//======================================================================
171
172// action dispatcher
173func (zm *ZoneMaster) action(w http.ResponseWriter, r *http.Request) {
174 // prepare variables and form values
175 vars := mux.Vars(r)
176 mode := vars["mode"]
177 id, ok := util.CastFromString[int64](vars["id"])
178 _ = r.ParseForm()
179
180 var err error
181 if ok {
182 switch vars["cmd"] {
183 case "new":
184 err = zm.actionNew(w, r, mode, id)
185 case "upd":
186 err = zm.actionUpd(w, r, mode, id)
187 }
188 } else {
189 err = errors.New("action: missing object id")
190 }
191 if err != nil {
192 _, _ = io.WriteString(w, "ERROR: "+err.Error())
193 return
194 }
195 // redirect back to dashboard
196 http.Redirect(w, r, "/", http.StatusMovedPermanently)
197}
198
199//----------------------------------------------------------------------
200// NEW: create zone, label or resource record
201//----------------------------------------------------------------------
202
203func (zm *ZoneMaster) actionNew(w http.ResponseWriter, r *http.Request, mode string, id int64) (err error) {
204 switch mode {
205 // new zone
206 case "zone":
207 name := r.FormValue("name")
208 // create private key
209 seed := make([]byte, 32)
210 if _, err = rand.Read(seed); err != nil {
211 return
212 }
213 var zp *crypto.ZonePrivate
214 kt := enums.GNS_TYPE_PKEY
215 if r.FormValue("keytype") == "EDKEY" {
216 kt = enums.GNS_TYPE_EDKEY
217 }
218 zp, err = crypto.NewZonePrivate(kt, seed)
219 if err != nil {
220 return
221 }
222 // add zone to database
223 zone := store.NewZone(name, zp)
224 err = zm.zdb.SetZone(zone)
225
226 // notify listeners
227 zm.OnChange("zones", zone.ID, ChangeNew)
228
229 // new label
230 case "label":
231 name := r.FormValue("name")
232 // add label to database
233 label := store.NewLabel(name)
234 label.Zone = id
235 err = zm.zdb.SetLabel(label)
236
237 // notify listeners
238 zm.OnChange("labels", label.ID, ChangeNew)
239
240 // new resource record
241 case "rr":
242 err = zm.newRec(w, r, id)
243 }
244 return
245}
246
247//----------------------------------------------------------------------
248
249// create new resource record from dialog data
250func (zm *ZoneMaster) newRec(w http.ResponseWriter, r *http.Request, label int64) error {
251 // get list of parameters from resource record dialog
252 params := make(map[string]string)
253 for key, val := range r.Form {
254 params[key] = strings.Join(val, ",")
255 }
256 // parse RR type (and set prefix for map keys)
257 t, ok := util.CastFromString[enums.GNSType](params["type"])
258 if !ok {
259 return errors.New("new: missing resource record type")
260 }
261 pf := dlgPrefix[t]
262
263 // construct RR data
264 exp, flags := guiParse(params, pf)
265 rrdata, err := Map2RRData(t, params)
266 if err == nil {
267 // assemble record and store in database
268 rr := store.NewRecord(exp, t, flags, rrdata)
269 rr.Label = label
270 err = zm.zdb.SetRecord(rr)
271
272 // notify listeners
273 zm.OnChange("records", rr.ID, ChangeNew)
274 }
275 return err
276}
277
278//----------------------------------------------------------------------
279// UPD: update zone, label or resource record
280//----------------------------------------------------------------------
281
282func (zm *ZoneMaster) actionUpd(w http.ResponseWriter, r *http.Request, mode string, id int64) (err error) {
283 // handle type
284 switch mode {
285 case "zone":
286 // update zone name in database
287 var zone *store.Zone
288 if zone, err = zm.zdb.GetZone(id); err != nil {
289 return
290 }
291 zone.Name = r.FormValue("name")
292 zone.Modified = util.AbsoluteTimeNow()
293 err = zm.zdb.SetZone(zone)
294
295 // notify listeners
296 zm.OnChange("zones", zone.ID, ChangeUpdate)
297
298 case "label":
299 // update label name
300 label := store.NewLabel(r.FormValue("name"))
301 label.ID = id
302 label.Modified = util.AbsoluteTimeNow()
303 err = zm.zdb.SetLabel(label)
304
305 // notify listeners
306 zm.OnChange("labels", label.ID, ChangeUpdate)
307
308 case "rr":
309 // update record
310 err = zm.updRec(w, r, id)
311 }
312 return
313}
314
315//----------------------------------------------------------------------
316
317// update resource record
318func (zm *ZoneMaster) updRec(w http.ResponseWriter, r *http.Request, id int64) error {
319 // get list of parameters from resource record dialog
320 oldParams := make(map[string]string)
321 newParams := make(map[string]string)
322 for key, val := range r.Form {
323 v := strings.Join(val, ",")
324 if strings.HasPrefix(key, "old_") {
325 oldParams[key[4:]] = v
326 } else {
327 newParams[key] = v
328 }
329 }
330 // parse RR type (and set prefix for map keys)
331 t, ok := util.CastFromString[enums.GNSType](oldParams["type"])
332 if !ok {
333 return errors.New("new: missing resource record type")
334 }
335 pf := guiPrefix(t)
336
337 // check for changed resource record
338 changed := false
339 for key, val := range newParams {
340 old, ok := oldParams[key]
341 if ok && old != val {
342 changed = true
343 break
344 }
345 }
346 if changed {
347 // reconstruct record from GUI parameters
348 rrData, err := Map2RRData(t, newParams)
349 if err != nil {
350 return err
351 }
352 exp, flags := guiParse(newParams, pf)
353 rec := store.NewRecord(exp, t, flags, rrData)
354 rec.ID = id
355 rec.Label, _ = util.CastFromString[int64](newParams["lid"])
356
357 // update in database
358 if err := zm.zdb.SetRecord(rec); err != nil {
359 return err
360 }
361
362 // notify listeners
363 zm.OnChange("records", rec.ID, ChangeUpdate)
364 }
365 return nil
366}
367
368//----------------------------------------------------------------------
369// Create new zone. label or resource record
370//----------------------------------------------------------------------
371
372type NewEditData struct {
373 Ref int64 // database id of reference object
374 Action string // "new" or "upd" action
375 Button string // "Add new" or "Update"
376 Names []string // list of names in use (ZONE,LABEL)
377 RRspecs []*enums.GNSSpec // list of allowed record types and flags (REC)
378 Params map[string]string // list of current values
379}
380
381func (zm *ZoneMaster) new(w http.ResponseWriter, r *http.Request) {
382 vars := mux.Vars(r)
383 var err error
384 data := new(NewEditData)
385 data.Action = "new"
386 data.Button = "Add new"
387 data.Params = make(map[string]string)
388 switch vars["mode"] {
389
390 // new zone dialog
391 case "zone":
392 if data.Names, err = zm.zdb.GetNames("zones"); err != nil {
393 break
394 }
395 renderPage(w, data, "new_zone")
396 return
397
398 // new label dialog
399 case "label":
400 // get reference id
401 id, ok := util.CastFromString[int64](vars["id"])
402 if !ok {
403 err = errors.New("new label: missing zone id")
404 break
405 }
406 // get all existing label names for zone
407 stmt := fmt.Sprintf("labels where zid=%d", id)
408 if data.Names, err = zm.zdb.GetNames(stmt); err == nil {
409 data.Ref = id
410 data.Params["zone"], _ = zm.zdb.GetName("zones", id)
411 data.Params["zid"] = util.CastToString(id)
412 renderPage(w, data, "new_label")
413 return
414 }
415
416 // new resource record dialog
417 case "rr":
418 // get reference id
419 id, ok := util.CastFromString[int64](vars["id"])
420 if !ok {
421 err = errors.New("new record: missing label id")
422 break
423 }
424 // get all rrtypes used under given label
425 var rrs []*enums.GNSSpec
426 var label string
427 if rrs, label, err = zm.zdb.GetRRTypes(id); err == nil {
428 // compile a list of acceptable types for new records
429 data.RRspecs = compatibleRR(rrs, label)
430 data.Ref = id
431 data.Params["label"] = label
432 data.Params["lid"] = util.CastToString(id)
433 renderPage(w, data, "new_record")
434 return
435 }
436 }
437 // handle error
438 if err != nil {
439 _, _ = io.WriteString(w, "ERROR: "+err.Error())
440 return
441 }
442 // redirect back to dashboard
443 http.Redirect(w, r, "/", http.StatusMovedPermanently)
444}
445
446//----------------------------------------------------------------------
447// Edit zone, label or resource record
448//----------------------------------------------------------------------
449
450func (zm *ZoneMaster) edit(w http.ResponseWriter, r *http.Request) {
451 // get database id of edited object
452 vars := mux.Vars(r)
453 var err error
454 id, ok := util.CastFromString[int64](vars["id"])
455 if !ok {
456 err = errors.New("missing edit id")
457 } else {
458 // create edit data instance
459 data := new(NewEditData)
460 data.Ref = id
461 data.Action = "upd"
462 data.Button = "Update"
463 data.Params = make(map[string]string)
464
465 switch vars["mode"] {
466
467 // edit zone name (type can't be changed)
468 case "zone":
469 // get all existing zone names (including the edited one!)
470 if data.Names, err = zm.zdb.GetNames("zones"); err != nil {
471 break
472 }
473 // get edited zone
474 var zone *store.Zone
475 if zone, err = zm.zdb.GetZone(id); err != nil {
476 break
477 }
478 // set edit attributes
479 data.Params["name"] = zone.Name
480 data.Params["keytype"] = guiKeyType(zone.Key.Type)
481 data.Params["keydata"] = zone.Key.Public().ID()
482 data.Params["prvdata"] = zone.Key.ID()
483 data.Params["created"] = guiTime(zone.Created)
484 data.Params["modified"] = guiTime(zone.Modified)
485
486 // show dialog
487 renderPage(w, data, "edit_zone")
488 return
489
490 // edit label name
491 case "label":
492 // get existing label names (including the edited label!)
493 stmt := fmt.Sprintf("labels where zid=%d", id)
494 if data.Names, err = zm.zdb.GetNames(stmt); err != nil {
495 break
496 }
497 // get edited label
498 var label *store.Label
499 if label, err = zm.zdb.GetLabel(id); err != nil {
500 return
501 }
502 // set edit parameters
503 data.Params["zone"], _ = zm.zdb.GetName("zones", id)
504 data.Params["zid"] = util.CastToString(label.Zone)
505 data.Params["name"] = label.Name
506 data.Params["created"] = guiTime(label.Created)
507 data.Params["modified"] = guiTime(label.Modified)
508
509 // show dialog
510 renderPage(w, data, "edit_label")
511 return
512
513 // edit resource record
514 case "rr":
515 if err = zm.editRec(w, r, data); err == nil {
516 return
517 }
518 }
519 }
520 // handle error
521 if err != nil {
522 _, _ = io.WriteString(w, "ERROR: "+err.Error())
523 return
524 }
525 // redirect back to dashboard
526 http.Redirect(w, r, "/", http.StatusMovedPermanently)
527}
528
529//----------------------------------------------------------------------
530
531func (zm *ZoneMaster) editRec(w http.ResponseWriter, r *http.Request, data *NewEditData) (err error) {
532 // get edited resource record
533 var rec *store.Record
534 if rec, err = zm.zdb.GetRecord(data.Ref); err != nil {
535 return
536 }
537 // build map of attribute values
538 pf := dlgPrefix[rec.RType]
539
540 // save shared attributes
541 data.Params["prefix"] = pf
542 data.Params["type"] = util.CastToString(int(rec.RType))
543 data.Params["created"] = guiTime(rec.Created)
544 data.Params["modified"] = guiTime(rec.Modified)
545 data.Params["label"], _ = zm.zdb.GetName("labels", rec.Label)
546 data.Params["lid"] = util.CastToString(rec.Label)
547 if rec.Expire.IsNever() {
548 data.Params[pf+"never"] = "on"
549 } else {
550 data.Params[pf+"expires"] = htmlTime(rec.Expire)
551 }
552 if rec.Flags&enums.GNS_FLAG_PRIVATE != 0 {
553 data.Params[pf+"private"] = "on"
554 }
555 if rec.Flags&enums.GNS_FLAG_SHADOW != 0 {
556 data.Params[pf+"shadow"] = "on"
557 }
558 if rec.Flags&enums.GNS_FLAG_SUPPL != 0 {
559 data.Params[pf+"suppl"] = "on"
560 }
561 // get record instance
562 var inst rr.RR
563 if inst, err = rr.ParseRR(rec.RType, rec.Data); err == nil {
564 // add RR attributes to list
565 inst.ToMap(data.Params, pf)
566 }
567 // show dialog
568 renderPage(w, data, "edit_rec")
569 return
570}
571
572//----------------------------------------------------------------------
573// Remove zone. label or resource record
574//----------------------------------------------------------------------
575
576func (zm *ZoneMaster) remove(w http.ResponseWriter, r *http.Request) {
577 // get database id of edited object
578 vars := mux.Vars(r)
579 var err error
580 id, ok := util.CastFromString[int64](vars["id"])
581 if !ok {
582 err = errors.New("missing remove id")
583 } else {
584 switch vars["mode"] {
585
586 // remove zone
587 case "zone":
588 // get zone from database
589 var zone *store.Zone
590 if zone, err = zm.zdb.GetZone(id); err != nil {
591 return
592 }
593 // remove zone in database
594 zone.Name = ""
595 if err = zm.zdb.SetZone(zone); err != nil {
596 return
597 }
598 zm.OnChange("zones", id, ChangeDelete)
599
600 // remove label
601 case "label":
602 label := store.NewLabel("")
603 label.ID = id
604 if err = zm.zdb.SetLabel(label); err != nil {
605 return
606 }
607 zm.OnChange("labels", id, ChangeDelete)
608
609 // remove resource record
610 case "rr":
611 rec := new(store.Record)
612 rec.ID = id
613 rec.Label = 0
614 if err = zm.zdb.SetRecord(rec); err != nil {
615 return
616 }
617 zm.OnChange("records", id, ChangeDelete)
618 }
619 }
620 // handle error
621 if err != nil {
622 _, _ = io.WriteString(w, "ERROR: "+err.Error())
623 return
624 }
625 // redirect back to dashboard
626 http.Redirect(w, r, "/", http.StatusMovedPermanently)
627}
628
629//======================================================================
630// Helper methods
631//======================================================================
632
633// render a webpage with given data and template reference
634func renderPage(w io.Writer, data interface{}, page string) {
635 // create content section
636 t := tpl.Lookup(page)
637 if t == nil {
638 _, _ = io.WriteString(w, "No template '"+page+"' found")
639 return
640 }
641 content := new(bytes.Buffer)
642 if err := t.Execute(content, data); err != nil {
643 _, _ = io.WriteString(w, err.Error())
644 return
645 }
646 // emit final page
647 t = tpl.Lookup("main")
648 if t == nil {
649 _, _ = io.WriteString(w, "No main template found")
650 return
651 }
652 if err := t.Execute(w, content.String()); err != nil {
653 _, _ = io.WriteString(w, err.Error())
654 }
655}
656
657//----------------------------------------------------------------------
658// Debug rendering
659//----------------------------------------------------------------------
660
661// DebugData for error page
662type DebugData struct {
663 Params map[string]string
664 RR string
665 Err error
666}
diff --git a/src/gnunet/service/zonemaster/gui.htpl b/src/gnunet/service/zonemaster/gui.htpl
new file mode 100644
index 0000000..ffa9720
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui.htpl
@@ -0,0 +1,133 @@
1{{define "main"}}
2<!doctype html>
3<html lang="en">
4 <head>
5 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6 {{template "css"}}
7 </head>
8 <body>
9 <h1>GNUnet Zone Master</h1>
10 <hr/>
11 {{.}}
12 <script>
13 function notify(msg) {
14 if ('Notification' in window) {
15 if (Notification.permission !== 'denied') {
16 Notification.requestPermission(function (permission) {
17 if (permission === 'granted') {
18 var note = new Notification('GNUnet Zone Master', {
19 body: msg,
20 actions: []
21 });
22 }
23 });
24 }
25 }
26 }
27 </script>
28 </body>
29</html>
30{{end}}
31
32{{define "dashboard"}}
33<div>
34 <ul id="dashboard">
35 {{if .}}
36 {{range $zi, $zone := .}}
37 <li>
38 {{$z := $zone.Zone}}
39 <span class="caret"><b>{{$z.Name}}</b></span> [{{keytype $z.Key.Type}}: {{$zone.PubID}}]
40 <a href="/edit/zone/{{$z.ID}}" title="Edit zone"><button class="icon blue">&#9998;</button></a>
41 <a href="/del/zone/{{$z.ID}}" title="Remove zone"><button class="icon red">&#10006;</button></a>
42 (Created: {{date $z.Created}}, Modified: {{date $z.Modified}})
43 <ul class="nested">
44 {{if $zone.Labels}}
45 {{range $li, $label := $zone.Labels}}
46 <li>
47 {{$l := $label.Label}}
48 <span class="caret"><b>{{$l.Name}}</b></span>
49 <a href="/edit/label/{{$l.ID}}" title="Edit label"><button class="icon blue">&#9998;</button></a>
50 <a href="/del/label/{{$l.ID}}" title="Remove label"><button class="icon red">&#10006;</button></a>
51 (Created: {{date $l.Created}}, Modified: {{date $l.Modified}})
52 <ul class="nested">
53 {{if $label.Records}}
54 <li>
55 <table class="rowed">
56 <tr class="header">
57 <th>Type</th>
58 <th>Value</th>
59 <th>Flags</th>
60 <th>Expires</th>
61 <th>Created</th>
62 <th>Modified</th>
63 <th>Actions</th>
64 </tr>
65 {{range $ri, $rec := $label.Records}}
66 <tr class="row">
67 <td>{{rrtype $rec.RType}}</td>
68 <td>{{rrdata $rec.RType $rec.Data}}</td>
69 <td>{{rrflags $rec.Flags}}</td>
70 <td>{{date $rec.Expire}}</td>
71 <td>{{date $rec.Created}}</td>
72 <td>{{date $rec.Modified}}</td>
73 <td>
74 <a href="/edit/rr/{{$rec.ID}}" title="Edit record"><button class="icon blue">&#9998;</button></a>
75 <a href="/del/rr/{{$rec.ID}}" title="Remove record"><button class="icon red">&#10006;</button></a>
76 </td>
77 </tr>
78 {{end}}
79 </table>
80 </li>
81 {{else}}
82 <li><h3>No resource records for label defined yet.</h3></li>
83 {{end}}
84 <li>
85 <a href="/new/rr/{{$l.ID}}" title="Add new record..."><button class="icon blue">&#10010;</button></a>
86 </li>
87 </ul>
88 </li>
89 {{end}}
90 {{else}}
91 <li><h3>No labels for zone defined yet.</h3></li>
92 {{end}}
93 <li>
94 <a href="/new/label/{{$z.ID}}" title="Add new label..."><button class="icon blue">&#10010;</button></a>
95 </li>
96 </ul>
97 </li>
98 {{end}}
99 {{else}}
100 <li>
101 <h3>No zones defined yet.</h3>
102 </li>
103 {{end}}
104 <li>
105 <a href="/new/zone/0" title="Add new zone..."><button class="icon blue">&#10010;</button></a>
106 </li>
107 </ul>
108</div>
109<script>
110 var toggler = document.getElementsByClassName("caret");
111 for (var i = 0; i < toggler.length; i++) {
112 toggler[i].addEventListener("click", function() {
113 this.parentElement.querySelector(".nested").classList.toggle("active");
114 this.classList.toggle("caret-down");
115 });
116 }
117
118 for (var i = 0; i < toggler.length; i++) {
119 if (localStorage.getItem("t"+i) == "true") {
120 toggler[i].parentElement.querySelector(".nested").classList.toggle("active");
121 toggler[i].classList.toggle("caret-down");
122 }
123 }
124 document.documentElement.scrollTop = document.body.scrollTop = localStorage.getItem("top");
125
126 window.addEventListener('beforeunload', function (e) {
127 for (var i = 0; i < toggler.length; i++) {
128 localStorage.setItem("t"+i, toggler[i].classList.contains("caret-down"));
129 }
130 localStorage.setItem("top", window.pageYOffset || document.documentElement.scrollTop);
131 });
132</script>
133{{end}} \ No newline at end of file
diff --git a/src/gnunet/service/zonemaster/gui_css.htpl b/src/gnunet/service/zonemaster/gui_css.htpl
new file mode 100644
index 0000000..b31e714
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui_css.htpl
@@ -0,0 +1,253 @@
1{{define "css"}}
2<style>
3 * {
4 box-sizing: border-box;
5 }
6 body {
7 margin: 2em 9em 2em 9em;
8 }
9 input[type=text] {
10 font-size: 1.2em;
11 padding: 5px;
12 border: 2px solid #ddd;
13 border-radius: 7px;
14 }
15 input[type=text]:focus {
16 outline: none;
17 border-color: #ace;
18 box-shadow: 0 0 10px #ace;
19 }
20 div.row::after {
21 content: "";
22 clear: both;
23 display: table;
24 }
25 div.cell {
26 display: inline;
27 float: left;
28 }
29 div.box {
30 border: 2px solid black;
31 margin: 0.5em;
32 padding: 0.5em;
33 }
34 div.block {
35 margin: 0.5em;
36 padding: 0.5em;
37 }
38 div.heading {
39 color: white;
40 background-color: orange;
41 font-size: 200%;
42 font-weight: bold;
43 padding: 0.3em;
44 margin: 1em 0 1em 0;
45 }
46 button.icon {
47 border: none;
48 color: black;
49 background-color: transparent;
50 padding: 0 0;
51 text-align: center;
52 text-decoration: none;
53 display: inline-block;
54 font-size: 100%;
55 margin: 4px 2px;
56 cursor: pointer;
57 }
58 .label {
59 text-align: right;
60 vertical-align: top;
61 font-weight: bold;
62 }
63 .title {
64 font-size: 120%;
65 font-weight: bold;
66 margin-bottom: 0.5em;
67 }
68 .large {
69 font-size: 200%;
70 font-weight: bold;
71 }
72 .small {
73 font-size: 75%;
74 }
75 .blue {
76 color: blue !important;
77 }
78 .red {
79 color: red !important;
80 }
81 .disabled {
82 pointer-events:none;
83 }
84 .headline {
85 color: white;
86 padding: 0.3em;
87 }
88 .status-0 {
89 background-color: green;
90 }
91 .status-1 {
92 background-color: orange;
93 }
94 .status-2 {
95 background-color: red;
96 }
97 .spacer-right {
98 margin-right: 2em;
99 }
100 .changed {
101 background-color: #fee;
102 }
103 table.rowed {
104 border-collapse: separate;
105 }
106 table.rowed > tbody > tr {
107 border: solid;
108 border-width: 1px 0;
109 border-color: #ccc;
110 }
111 tr.row:nth-child(even) {
112 background: #fff;
113 }
114 tr.row:nth-child(odd) {
115 background: #eee;
116 }
117 tr.header {
118 background: #eef;
119 color: black;
120 font-weight: bold;
121 }
122 td {
123 padding: 0.5em;
124 }
125 th {
126 padding: 0.5em;
127 text-align: center;
128 }
129 label[for=toggle] {
130 cursor: pointer;
131 border: 1px solid black;
132 border-radius: 0.2em;
133 background-color: #eeeeee;
134 padding: 0.1em;
135 }
136 #toggle {
137 display: none;
138 }
139 #toggle:not(:checked) ~ #toggled {
140 display: none;
141 }
142 ul, #dashboard {
143 list-style-type: none;
144 }
145 #dashboard {
146 margin: 0;
147 padding: 0;
148 }
149 li {
150 margin: 0.5em;
151 }
152 .caret {
153 cursor: pointer;
154 user-select: none;
155 }
156 .caret::before {
157 content: "\25B6";
158 color: black;
159 display: inline-block;
160 margin-right: 6px;
161 }
162 .caret-down::before {
163 transform: rotate(90deg);
164 }
165 .nested {
166 display: none;
167 }
168 .active {
169 display: block;
170 }
171 .tabset > input[type="radio"] {
172 position: absolute;
173 left: -200vw;
174 }
175 .tabset .tab-panel {
176 display: none;
177 }
178 .tabset > input:first-child:checked ~ .tab-panels > .tab-panel:first-child,
179 .tabset > input:nth-child(3):checked ~ .tab-panels > .tab-panel:nth-child(2),
180 .tabset > input:nth-child(5):checked ~ .tab-panels > .tab-panel:nth-child(3),
181 .tabset > input:nth-child(7):checked ~ .tab-panels > .tab-panel:nth-child(4),
182 .tabset > input:nth-child(9):checked ~ .tab-panels > .tab-panel:nth-child(5),
183 .tabset > input:nth-child(11):checked ~ .tab-panels > .tab-panel:nth-child(6),
184 .tabset > input:nth-child(13):checked ~ .tab-panels > .tab-panel:nth-child(7),
185 .tabset > input:nth-child(15):checked ~ .tab-panels > .tab-panel:nth-child(8),
186 .tabset > input:nth-child(17):checked ~ .tab-panels > .tab-panel:nth-child(9),
187 .tabset > input:nth-child(19):checked ~ .tab-panels > .tab-panel:nth-child(10),
188 .tabset > input:nth-child(21):checked ~ .tab-panels > .tab-panel:nth-child(11),
189 .tabset > input:nth-child(23):checked ~ .tab-panels > .tab-panel:nth-child(12),
190 .tabset > input:nth-child(25):checked ~ .tab-panels > .tab-panel:nth-child(13),
191 .tabset > input:nth-child(27):checked ~ .tab-panels > .tab-panel:nth-child(14),
192 .tabset > input:nth-child(29):checked ~ .tab-panels > .tab-panel:nth-child(15),
193 .tabset > input:nth-child(31):checked ~ .tab-panels > .tab-panel:nth-child(16),
194 .tabset > input:nth-child(33):checked ~ .tab-panels > .tab-panel:nth-child(17),
195 .tabset > input:nth-child(35):checked ~ .tab-panels > .tab-panel:nth-child(18),
196 .tabset > input:nth-child(37):checked ~ .tab-panels > .tab-panel:nth-child(19),
197 .tabset > input:nth-child(39):checked ~ .tab-panels > .tab-panel:nth-child(20) {
198 display: block;
199 }
200 .tabset > label {
201 position: relative;
202 display: inline-block;
203 padding: 15px 15px 25px;
204 border: 1px solid transparent;
205 border-bottom: 0;
206 cursor: pointer;
207 font-weight: 600;
208 }
209 .tabset > label::after {
210 content: "";
211 position: absolute;
212 left: 15px;
213 bottom: 10px;
214 width: 22px;
215 height: 4px;
216 background: #8d8d8d;
217 }
218 .tabset > label:hover {
219 color: #f90;
220 }
221 .tabset > input:focus + label {
222 color: #06c;
223 }
224 .tabset > label:hover::after {
225 background: #f90;
226 }
227 .tabset > input:focus + label::after,
228 .tabset > input:checked + label::after {
229 background: #06c;
230 }
231 .tabset > input:checked + label {
232 border-color: #ccc;
233 border-bottom: 1px solid #fff;
234 margin-bottom: -1px;
235 }
236 .tab-panel {
237 padding: 30px 0;
238 border-top: 1px solid #ccc;
239 }
240 div.switch {
241 display: none;
242 }
243 input.switch:checked ~ div.switch {
244 display: block;
245 }
246 div.alternate {
247 display: block;
248 }
249 input.alternate:checked ~ div.alternate {
250 display: none;
251 }
252</style>
253{{end}}
diff --git a/src/gnunet/service/zonemaster/gui_debug.htpl b/src/gnunet/service/zonemaster/gui_debug.htpl
new file mode 100644
index 0000000..3112600
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui_debug.htpl
@@ -0,0 +1,14 @@
1{{define "debug"}}
2 <h1>Debug</h1>
3 <h3>Parameters:</h3>
4 <ul>
5 {{range $k,$v := .Params}}
6 <li><b>{{$k}}</b> = {{$v}}</li>
7 {{end}}
8 </ul>
9 <h3>RR data:</h3>
10 <p>{{.RR}}</p>
11 {{if .Err}}
12 <p>Error: <b>{{.Err}}</b></p>
13 {{end}}
14{{end}} \ No newline at end of file
diff --git a/src/gnunet/service/zonemaster/gui_edit.htpl b/src/gnunet/service/zonemaster/gui_edit.htpl
new file mode 100644
index 0000000..a4673b0
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui_edit.htpl
@@ -0,0 +1,130 @@
1{{define "edit_zone"}}
2 {{$type := index .Params "keytype"}}
3 {{$name := index .Params "name"}}
4 <div>
5 <h3>Edit a [{{$type}}] GNS zone:</h3>
6 <p><small>(Created: {{index .Params "created"}}, Last edited: {{index .Params "modified"}})</small></p>
7 <form action="/action/upd/zone/{{.Ref}}" method="post" onsubmit="return(zone_validate());">
8 <input type="hidden" name="old_name" value="{{$name}}">
9 <table>
10 <tr>
11 <td align="right"><b>Zone name:</b></td>
12 <td><input type="text" id="name" name="name" value="{{$name}}"></td>
13 </tr>
14 <tr>
15 <td colspan="2">
16 <p>The type of the zone key cannot be changed. It is currently set to
17 <b>{{if eq $type "PKEY"}}PKEY (Ed25519+EcDSA){{else}}EDKEY (EdDSA){{end}}</b>:</p>
18 <table>
19 <tr>
20 <td align="right"><b>Public key:</b></td>
21 <td>{{index .Params "keydata"}}</td>
22 </tr>
23 <tr>
24 <td align="right"><b>Private key:</b></td>
25 <td>{{index .Params "prvdata"}}</td>
26 </tr>
27 </table>
28 </td>
29 </tr>
30 </table>
31 <button id="submit">Change zone name</button>
32 </form>
33 <p><a href="/"><button>Leave</button></a></p>
34 </div>
35 <script>
36 const old_zone = "{{$name}}";
37 const zone_names = [
38 {{range $i, $n := .Names}}
39 "{{$n}}",
40 {{end}}
41 ];
42 function zone_validate() {
43 const name = document.getElementById("name").value;
44 if (!name) {
45 alert("Empty zone name not allowed");
46 return false;
47 }
48 if (name == old_zone) {
49 alert("Zone name not changed");
50 return false;
51 }
52 for (var i = 0; i < names.length; i++) {
53 if (zone_names[i] == name) {
54 alert("Zone name already in-use");
55 return false;
56 }
57 }
58 return(true);
59 }
60 </script>
61{{end}}
62
63{{define "edit_label"}}
64 {{$name := index .Params "name"}}
65 {{$zone := index .Params "zone"}}
66 <div>
67 <h3>Edit a GNS label for zone "{{$zone}}":</h3>
68 <p><small>(Created: {{index .Params "created"}}, Last edited: {{index .Params "modified"}})</small></p>
69 <form action="/action/upd/label/{{.Ref}}" method="post" onsubmit="return(label_validate());">
70 <input type="hidden" name="old_name" value="{{$name}}">
71 <input type="hidden" name="zid" value="{{index .Params "zid"}}">
72 <table>
73 <tr>
74 <td align="right">Name:</td>
75 <td><input type="text" id="name" name="name" value="{{$name}}"></td>
76 </tr>
77 </table>
78 <button id="submit">Change label name</button>
79 </form>
80 <p><a href="/"><button>Leave</button></a></p>
81 </div>
82 <script>
83 const old_label = "{{$name}}";
84 const label_names = [
85 {{range $i, $n := .Names}}
86 '{{$n}}',
87 {{end}}
88 ];
89 function label_validate() {
90 const name = document.getElementById("name").value;
91 if (!name) {
92 alert("Empty labels not allowed");
93 return false;
94 }
95 if (name == old_label) {
96 alert("Label name not changed");
97 return false;
98 }
99 for (var i = 0; i < names.length; i++) {
100 if (label_names[i] == name) {
101 alert("Label name already in-use");
102 return false;
103 }
104 }
105 return(true);
106 }
107 </script>
108{{end}}
109
110{{define "edit_rec"}}
111 {{$label := index .Params "label"}}
112 <div>
113 <h3>Edit a resource record for label "{{$label}}":</h3>
114 <p><small>(Created: {{index .Params "created"}}, Last edited: {{index .Params "modified"}})</small></p>
115 {{$t := rritype (index .Params "type")}}
116 {{if eq $t "PKEY"}}{{template "PKEY" .}}{{end}}
117 {{if eq $t "EDKEY"}}{{template "EDKEY" .}}{{end}}
118 {{if eq $t "NICK"}}{{template "NICK" .}}{{end}}
119 {{if eq $t "LEHO"}}{{template "LEHO" .}}{{end}}
120 {{if eq $t "REDIRECT"}}{{template "REDIRECT" .}}{{end}}
121 {{if eq $t "GNS2DNS"}}{{template "GNS2DNS" .}}{{end}}
122 {{if eq $t "BOX"}}{{template "BOX" .}}{{end}}
123 {{if eq $t "DNS_CNAME"}}{{template "DNS_CNAME" .}}{{end}}
124 {{if eq $t "DNS_A"}}{{template "DNS_A" .}}{{end}}
125 {{if eq $t "DNS_AAAA"}}{{template "DNS_AAAA" .}}{{end}}
126 {{if eq $t "DNS_MX"}}{{template "DNS_MX" .}}{{end}}
127 {{if eq $t "DNS_TXT"}}{{template "DNS_TXT" .}}{{end}}
128 </div>
129 <a href="/"><button>Leave</button></a>
130{{end}} \ No newline at end of file
diff --git a/src/gnunet/service/zonemaster/gui_new.htpl b/src/gnunet/service/zonemaster/gui_new.htpl
new file mode 100644
index 0000000..f470de9
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui_new.htpl
@@ -0,0 +1,114 @@
1{{define "new_zone"}}
2<div>
3 <h3>Creating a new GNS zone:</h3>
4 <form action="/action/new/zone/0" method="post" onsubmit="return(zone_validate());">
5 <table>
6 <tr>
7 <td align="right"><b>Zone name:</b></td>
8 <td><input type="text" id="name" name="name"></td>
9 </tr>
10 <tr>
11 <td align="right" valign="top"><b>Key type:</b></td>
12 <td>
13 <input type="radio" id="pkey" name="keytype" value="PKEY" checked="checked">&nbsp;PKEY (Ed25519+EcDSA)<br>
14 <input type="radio" id="edkey" name="keytype" value="EDKEY">&nbsp;EDKEY (EdDSA)
15 </td>
16 </tr>
17 </table>
18 <button id="submit">Add zone</button>
19 </form>
20 <a href="/"><button id="leave">Leave</button></a>
21</div>
22<script>
23 const zone_names = [
24 {{range $i, $n := .Names}}
25 '{{$n}}',
26 {{end}}
27 ];
28 function zone_validate() {
29 const name = document.getElementById("name").value;
30 if (!name) {
31 alert("Empty zone name not allowed");
32 return false;
33 }
34 for (var i = 0; i < names.length; i++) {
35 if (zone_names[i] == name) {
36 alert("Zone name already used");
37 return false;
38 }
39 }
40 return(true);
41 }
42</script>
43{{end}}
44
45{{define "new_label"}}
46<div>
47 <h3>Creating a new GNS label for zone "{{index .Params "zone"}}":</h3>
48 <form action="/action/new/label/{{.Ref}}" onsubmit="return(label_validate());">
49 <table>
50 <tr>
51 <td align="right">Name:</td>
52 <td><input type="text" id="name" name="name"></td>
53 </tr>
54 </table>
55 <button id="submit">Add label</button>
56 </form>
57 <a href="/"><button>Leave</button></a>
58</div>
59<script>
60 const label_names = [
61 {{range $i, $n := .Names}}
62 '{{$n}}',
63 {{end}}
64 ];
65 function label_validate() {
66 const name = document.getElementById("name").value;
67 if (!name) {
68 alert("Empty labels not allowed");
69 return false;
70 }
71 for (var i = 0; i < names.length; i++) {
72 if (label_names[i] == name) {
73 alert("Label already used");
74 return false;
75 }
76 }
77 return(true);
78 }
79</script>
80{{end}}
81
82{{define "new_record"}}
83{{$data := .}}
84<div>
85 <h3>Creating a new GNS resource record for label "{{index .Params "label"}}":</h3>
86 <div class="tabset">
87 {{range $i, $type := .RRspecs}}
88 <input type="radio" name="tabset" id="tab{{$i}}" aria-controls="tab{{$i}}" {{if eq $i 0}}checked{{end}}>
89 <label for="tab{{$i}}">{{rrtype $type.Type}}</label>
90 {{end}}
91 <div class="tab-panels">
92 {{range $i, $spec := .RRspecs}}
93 <section id="tab{{$i}}" class="tab-panel">
94 {{$t := rrtype $spec.Type}}
95 {{$pf := setspecs $data.Params $spec}}
96 {{if eq $t "PKEY"}}{{template "PKEY" $data}}{{end}}
97 {{if eq $t "EDKEY"}}{{template "EDKEY" $data}}{{end}}
98 {{if eq $t "NICK"}}{{template "NICK" $data}}{{end}}
99 {{if eq $t "LEHO"}}{{template "LEHO" $data}}{{end}}
100 {{if eq $t "REDIRECT"}}{{template "REDIRECT" $data}}{{end}}
101 {{if eq $t "GNS2DNS"}}{{template "GNS2DNS" $data}}{{end}}
102 {{if eq $t "BOX"}}{{template "BOX" $data}}{{end}}
103 {{if eq $t "DNS_CNAME"}}{{template "DNS_CNAME" $data}}{{end}}
104 {{if eq $t "DNS_A"}}{{template "DNS_A" $data}}{{end}}
105 {{if eq $t "DNS_AAAA"}}{{template "DNS_AAAA" $data}}{{end}}
106 {{if eq $t "DNS_MX"}}{{template "DNS_MX" $data}}{{end}}
107 {{if eq $t "DNS_TXT"}}{{template "DNS_TXT" $data}}{{end}}
108 </section>
109 {{end}}
110 </div>
111 </div>
112 <a href="/"><button>Leave</button></a>
113</div>
114{{end}}
diff --git a/src/gnunet/service/zonemaster/gui_rr.htpl b/src/gnunet/service/zonemaster/gui_rr.htpl
new file mode 100644
index 0000000..360a734
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui_rr.htpl
@@ -0,0 +1,420 @@
1{{define "RRCommon"}}
2 <input type="hidden" name="lid" value="{{index . "lid"}}">
3 {{range $k, $v := .}}
4 <input type="hidden" name="old_{{$k}}" value="{{$v}}">
5 {{end}}
6 {{$pf := index . "prefix"}}
7 <tr>
8 <td align="right" valign="top"><b>Expires:</b></td>
9 <td>
10 Never <input type="checkbox" class="alternate" name="{{$pf}}never"
11 {{if eq "on" (index . (print $pf "never"))}}checked="checked"{{end}}
12 >
13 <div class="alternate">
14 At given date and time:
15 <input type="datetime-local" id="{{$pf}}expires" name="{{$pf}}expires" required
16 value="{{index . (print $pf "expires")}}"
17 >
18 </div>
19 </td>
20 </tr>
21 <tr>
22 <td align="right" valign="top"><b>Flags:</b></td>
23 <td>
24 <input type="checkbox" name="{{$pf}}private"
25 {{if eq "on" (index . (print $pf "private"))}}checked="checked" class="disabled"{{end}}
26 > Private<br>
27 <input type="checkbox" name="{{$pf}}shadow"
28 {{if eq "on" (index . (print $pf "shadow"))}}checked="checked" class="disabled"{{end}}
29 > Shadow<br>
30 <input type="checkbox" name="{{$pf}}suppl"
31 {{if eq "on" (index . (print $pf "suppl"))}}checked="checked" class="disabled"{{end}}
32 > Supplemental<br>
33 </td>
34 </tr>
35 {{if eq .Action "new"}}
36 <script>
37 var dt = document.getElementById("{{$pf}}expires");
38 if (!dt.value) {
39 var exp = new Date(new Date().getTime() + 31536000000);
40 dt.value = exp.toISOString().slice(0, 16);
41 }
42 </script>
43 {{end}}
44{{end}}
45
46{{define "PKEY"}}
47 <h3>PKEY delegation</h3>
48 <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action "upd"}}method="post"{{end}}>
49 <input type="hidden" name="type" value="65536">
50 <table>
51 <tr><td/>
52 <td>
53 Enter the public zone key (type
54 <a href="https://lsd.gnunet.org/lsd0001/#name-pkey" target="_blank">PKEY</a>
55 ) in
56 <a href="https://lsd.gnunet.org/lsd0001/#name-base32gns" target="_blank">Base32GNS</a>
57 encoding:
58 </td>
59 </tr>
60 <tr>
61 <td align="right"><b>Key:</b></td>
62 <td>
63 <input type="text" name="pkey_data"
64 maxlength="58" minlength="58" size="64"
65 pattern="[0-9A-HJKMNP-TV-Z]{58}"
66 autofocus required
67 value="{{index .Params "pkey_data"}}"
68 >
69 </td>
70 </tr>
71 {{template "RRCommon" .Params}}
72 <tr><td/><td><button id="submit">{{.Button}} record</button></td></tr>
73 </table>
74 </form>
75{{end}}
76{{define "EDKEY"}}
77 <h3>EDKEY delegation</h3>
78 <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action "upd"}}method="post"{{end}}>
79 <input type="hidden" name="type" value="65556">
80 <table>
81 <tr><td/>
82 <td>
83 Enter the public zone key (type
84 <a href="https://lsd.gnunet.org/lsd0001/#name-edkey" target="_blank">EDKEY</a>
85 ) in
86 <a href="https://lsd.gnunet.org/lsd0001/#name-base32gns" target="_blank">Base32GNS</a>
87 encoding:
88 </td>
89 </tr>
90 <tr>
91 <td align="right"><b>Key:</b></td>
92 <td>
93 <input type="text" name="edkey_data"
94 maxlength="58" minlength="58" size="64"
95 pattern="[0-9A-HJKMNP-TV-Z]{58}"
96 autofocus required
97 value="{{index .Params "edkey_data"}}"
98 >
99 </td>
100 </tr>
101 {{template "RRCommon" .Params}}
102 <tr><td/><td><button id="submit">{{.Button}} record</button></td></tr>
103 </table>
104 </form>
105{{end}}
106{{define "REDIRECT"}}
107 <h3>REDIRECT (GNS delegation)</h3>
108 <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action "upd"}}method="post"{{end}}>
109 <input type="hidden" name="type" value="65551">
110 <table>
111 <tr><td/>
112 <td>
113 Enter the redirected GNS name (see
114 <a href="https://lsd.gnunet.org/lsd0001/#name-redirect" target="_blank">specification</a>
115 ):
116 </td>
117 </tr>
118 <tr>
119 <td align="right"><b>Name:</b></td>
120 <td>
121 <input type="text" name="redirect_name"
122 maxlength="63" size="63"
123 autofocus required
124 value="{{index .Params "redirect_name"}}"
125 >
126 </td>
127 </tr>
128 {{template "RRCommon" .Params}}
129 <tr><td/><td><button id="submit">{{.Button}} record</button></td></tr>
130 </table>
131 </form>
132{{end}}
133{{define "LEHO"}}
134 <h3>LEHO (legacy hostname)</h3>
135 <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action "upd"}}method="post"{{end}}>
136 <input type="hidden" name="type" value="65538">
137 <table>
138 <tr>
139 <td align="right"><b>Legacy hostname:</b></td>
140 <td>
141 <input type="text" name="leho_name"
142 maxlength="63" size="63"
143 autofocus required
144 value="{{index .Params "leho_name"}}"
145 >
146 </td>
147 </tr>
148 {{template "RRCommon" .Params}}
149 <tr><td/><td><button id="submit">{{.Button}} record</button></td></tr>
150 </table>
151 </form>
152{{end}}
153{{define "NICK"}}
154 <h3>NICK</h3>
155 <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action "upd"}}method="post"{{end}}>
156 <input type="hidden" name="type" value="65537">
157 <table>
158 <tr>
159 <td align="right"><b>Nick name:</b></td>
160 <td>
161 <input type="text" name="nick_name"
162 maxlength="63" size="63"
163 autofocus required
164 value="{{index .Params "nick_name"}}"
165 >
166 </td>
167 </tr>
168 {{template "RRCommon" .Params}}
169 <tr><td/><td><button id="submit">{{.Button}} record</button></td></tr>
170 </table>
171 </form>
172{{end}}
173{{define "GNS2DNS"}}
174 <h3>GNS2DNS delegation</h3>
175 <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action "upd"}}method="post"{{end}}>
176 <input type="hidden" name="type" value="65540">
177 <table>
178 <tr><td/>
179 <td>
180 Enter DNS name and server as
181 <a href="https://lsd.gnunet.org/lsd0001/#name-gns2dns" target="_blank">specified</a>.
182 </td>
183 </tr>
184 <tr>
185 <td align="right"><b>DNS name:</b></td>
186 <td>
187 <input type="text" name="gns2dns_name"
188 maxlength="63" size="63"
189 autofocus required
190 value="{{index .Params "gns2dns_name"}}"
191 >
192 </td>
193 </tr>
194 <tr>
195 <td align="right"><b>DNS server:</b></td>
196 <td>
197 <input type="text" name="gns2dns_server"
198 maxlength="63" size="63" required
199 value="{{index .Params "gns2dns_server"}}"
200 >
201 </td>
202 </tr>
203 {{template "RRCommon" .Params}}
204 <tr><td/><td><button id="submit">{{.Button}} record</button></td></tr>
205 </table>
206 </form>
207{{end}}
208{{define "BOX"}}
209 <h3>BOX</h3>
210 <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action "upd"}}method="post"{{end}}>
211 <input type="hidden" name="type" value="65541">
212 <table>
213 <tr><td/>
214 <td>
215 Enter protocol, service (port) and type of the boxed resource type as
216 <a href="https://lsd.gnunet.org/lsd0001/#name-box" target="_blank">specified</a>:
217 </td>
218 </tr>
219 <tr>
220 <td align="right"><b>Protocol:</b></td>
221 <td>
222 <input type="text" name="box_proto" size="16" list="protocols" required
223 value="{{index .Params "box_proto"}}"
224 >
225 <datalist id="protocols">
226 {{range $id,$name := boxprotos}}
227 <option value="{{$id}} ({{$name}})">
228 {{end}}
229 </datalist>
230 </td>
231 </tr>
232 <tr>
233 <td align="right"><b>Service:</b></td>
234 <td>
235 <input type="text" name="box_svc" size="16" list="services" required
236 value="{{index .Params "box_svc"}}"
237 >
238 <datalist id="services">
239 {{range $id,$name := boxsvcs}}
240 <option value='{{$id}} {{$name}}'>
241 {{end}}
242 </datalist>
243 </td>
244 </tr>
245 <tr>
246 <td align="right" valign="top"><b>Type:</b></td>
247 <td>
248 <input type="radio" class="switch" name="box_type" value="33"
249 {{if eq (index .Params "box_type") "33"}}checked{{end}}
250 > SRV (Service description)
251 <div class="switch">
252 <div class="block">
253 <label for="box_srv_host">Host:</label>
254 <input type="text" name="box_srv_host" maxlength="63" size="63"
255 value="{{index .Params "box_srv_host"}}"
256 >
257 </div>
258 </div>
259 </td>
260 </tr>
261 </tr>
262 <tr>
263 <td/><td>
264 <input type="radio" class="switch" name="box_type" value="52"
265 {{if eq (index .Params "box_type") "52"}}checked{{end}}
266 > TLSA (TLS Association)
267 <div class="switch">
268 <div class="block">
269 <label for="box_tlsa_usage">Usage:</label>
270 {{$x := index .Params "box_tlsa_usage"}}
271 <select size="1" name="box_tlsa_usage">
272 <option value="0" {{if eq $x "0"}}selected{{end}}>CA certificate</option>
273 <option value="1" {{if eq $x "1"}}selected{{end}}>Service certificate constraint</option>
274 <option value="2" {{if eq $x "2"}}selected{{end}}>Trust anchor assertion</option>
275 <option value="3" {{if eq $x "3"}}selected{{end}}>Domain-issued certificate</option>
276 <option value="255" {{if eq $x "255"}}selected{{end}}>Private use</option>
277 </select>
278 </div>
279 <div class="block">
280 <label for="box_tlsa_selector">Selector:</label>
281 {{$x = index .Params "box_tlsa_selector"}}
282 <select size="1" name="box_tlsa_selector">
283 <option value="0" {{if eq $x "0"}}selected{{end}}>Full certificate</option>
284 <option value="1" {{if eq $x "1"}}selected{{end}}>SubjectPublicKeyInfo</option>
285 <option value="255" {{if eq $x "255"}}selected{{end}}>Private use</option>
286 </select>
287 </div>
288 <div class="block">
289 <label for="box_tlsa_match">Match:</label>
290 {{$x = index .Params "box_tlsa_match"}}
291 <select size="1" name="box_tlsa_match">
292 <option value="0" {{if eq $x "0"}}selected{{end}}>No hash</option>
293 <option value="1" {{if eq $x "1"}}selected{{end}}>SHA-256</option>
294 <option value="2" {{if eq $x "2"}}selected{{end}}>SHA-512</option>
295 <option value="255" {{if eq $x "255"}}selected{{end}}>Private use</option>
296 </select>
297 </div>
298 <div class="block">
299 <label for="box_tlsa_cert">Certificate information (hex):</label><br>
300 <textarea name="box_tlsa_cert" rows="10" cols="50">{{index .Params "box_tlsa_cert"}}</textarea>
301 </div>
302 </div>
303 </td>
304 </tr>
305 {{template "RRCommon" .Params}}
306 <tr><td/><td><button id="submit">{{.Button}} record</button></td></tr>
307 </table>
308 </form>
309{{end}}
310{{define "DNS_A"}}
311 <h3>DNS A (IPv4 address)</h3>
312 <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action "upd"}}method="post"{{end}}>
313 <input type="hidden" name="type" value="1">
314 <table>
315 <tr>
316 <td align="right"><b>Address:</b></td>
317 <td>
318 <input type="text" name="dnsa_addr"
319 maxlength="15" size="15"
320 pattern="[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
321 autofocus required
322 value="{{index .Params "dnsa_addr"}}"
323 >
324 </td>
325 </tr>
326 {{template "RRCommon" .Params}}
327 <tr><td/><td><button id="submit">{{.Button}} record</button></td></tr>
328 </table>
329 </form>
330{{end}}
331{{define "DNS_AAAA"}}
332 <h3>DNS AAAA (IPv6 address)</h3>
333 <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action "upd"}}method="post"{{end}}>
334 <input type="hidden" name="type" value="28">
335 <table>
336 <tr>
337 <td align="right"><b>Address:</b></td>
338 <td>
339 <input type="text" name="dnsaaaa_addr"
340 maxlength="15" size="15"
341 pattern="(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"
342 autofocus required
343 value="{{index .Params "dnsaaaa_addr"}}"
344 >
345 </td>
346 </tr>
347 {{template "RRCommon" .Params}}
348 <tr><td/><td><button id="submit">{{.Button}} record</button></td></tr>
349 </table>
350 </form>
351{{end}}
352{{define "DNS_CNAME"}}
353 <h3>DNS CNAME delegation</h3>
354 <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action "upd"}}method="post"{{end}}>
355 <input type="hidden" name="type" value="5">
356 <table>
357 <tr>
358 <td align="right"><b>Name:</b></td>
359 <td>
360 <input type="text" name="dnscname_name"
361 maxlength="63" size="63"
362 autofocus required
363 value="{{index .Params "dnscname_name"}}"
364 >
365 </td>
366 </tr>
367 {{template "RRCommon" .Params}}
368 <tr><td/><td><button id="submit">{{.Button}} record</button></td></tr>
369 </table>
370 </form>
371{{end}}
372{{define "DNS_TXT"}}
373 <h3>DNS TXT</h3>
374 <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action "upd"}}method="post"{{end}}>
375 <input type="hidden" name="type" value="16">
376 <table>
377 <tr>
378 <td align="right"><b>Text:</b></td>
379 <td>
380 <input type="text" name="dnstxt_text"
381 maxlength="63" size="63"
382 autofocus required
383 value="{{index .Params "dnstxt_text"}}"
384 >
385 </td>
386 </tr>
387 {{template "RRCommon" .Params}}
388 <tr><td/><td><button id="submit">{{.Button}} record</button></td></tr>
389 </table>
390 </form>
391{{end}}
392{{define "DNS_MX"}}
393 <h3>DNS MX (Mailbox)</h3>
394 <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action "upd"}}method="post"{{end}}>
395 <input type="hidden" name="type" value="15">
396 <table>
397 <tr>
398 <td align="right" valign="top"><b>Priority:</b></td>
399 <td>
400 {{$v := index .Params "dnsmx_prio"}}
401 <input type="number" name="dnsmx_prio" min="1" max="100"
402 value="{{if $v}}{{$v}}{{else}}10{{end}}"
403 >
404 </td>
405 </tr>
406 <tr>
407 <td align="right" valign="top"><b>Mailserver:</b></td>
408 <td>
409 <input type="text" name="dnsmx_host"
410 maxlength="63" size="63"
411 autofocus required
412 value="{{index .Params "dnsmx_host"}}"
413 >
414 </td>
415 </tr>
416 {{template "RRCommon" .Params}}
417 <tr><td/><td><button id="submit">{{.Button}} record</button></td></tr>
418 </table>
419 </form>
420{{end}} \ No newline at end of file
diff --git a/src/gnunet/service/zonemaster/module.go b/src/gnunet/service/zonemaster/module.go
new file mode 100644
index 0000000..8f7b000
--- /dev/null
+++ b/src/gnunet/service/zonemaster/module.go
@@ -0,0 +1,84 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package zonemaster
20
21import (
22 "context"
23
24 "gnunet/core"
25 "gnunet/enums"
26 "gnunet/service"
27 "gnunet/service/dht/blocks"
28)
29
30//======================================================================
31// "GNUnet Zonemaster" implementation
32//======================================================================
33
34// Module handles namestore and identity requests.
35type Module struct {
36 service.ModuleImpl
37
38 // Use function references for calls to methods in other modules:
39 StoreLocal func(ctx context.Context, query *blocks.GNSQuery, block *blocks.GNSBlock) error
40 StoreRemote func(ctx context.Context, query blocks.Query, block blocks.Block) error
41}
42
43// NewModule instantiates a new GNS module.
44func NewModule(ctx context.Context, c *core.Core) (m *Module) {
45 m = &Module{
46 ModuleImpl: *service.NewModuleImpl(),
47 }
48 if c != nil {
49 // register as listener for core events
50 listener := m.ModuleImpl.Run(ctx, m.event, m.Filter(), 0, nil)
51 c.Register("zonemaster", listener)
52 }
53 return
54}
55
56//----------------------------------------------------------------------
57
58// Filter returns the event filter for the service
59func (m *Module) Filter() *core.EventFilter {
60 f := core.NewEventFilter()
61 f.AddMsgType(enums.MSG_NAMESTORE_ZONE_ITERATION_START)
62 return f
63}
64
65// Event handler
66func (m *Module) event(ctx context.Context, ev *core.Event) {
67
68}
69
70//----------------------------------------------------------------------
71
72// Export functions
73func (m *Module) Export(fcn map[string]any) {
74 // add exported functions from module
75}
76
77// Import functions
78func (m *Module) Import(fcn map[string]any) {
79 // resolve imports from other modules
80 m.StoreLocal, _ = fcn["namecache:put"].(func(ctx context.Context, query *blocks.GNSQuery, block *blocks.GNSBlock) error)
81 m.StoreRemote, _ = fcn["dht:put"].(func(ctx context.Context, query blocks.Query, block blocks.Block) error)
82}
83
84//----------------------------------------------------------------------
diff --git a/src/gnunet/service/zonemaster/records.go b/src/gnunet/service/zonemaster/records.go
new file mode 100644
index 0000000..3015893
--- /dev/null
+++ b/src/gnunet/service/zonemaster/records.go
@@ -0,0 +1,348 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package zonemaster
20
21import (
22 "encoding/hex"
23 "errors"
24 "fmt"
25 "gnunet/enums"
26 "gnunet/service/gns/rr"
27 "gnunet/util"
28 "net"
29 "time"
30
31 "github.com/bfix/gospel/data"
32)
33
34var (
35 // list of managed RR types
36 rrtypes = []enums.GNSType{
37 enums.GNS_TYPE_PKEY, // PKEY zone delegation
38 enums.GNS_TYPE_EDKEY, // EDKEY zone delegation
39 enums.GNS_TYPE_REDIRECT, // GNS delegation by name
40 enums.GNS_TYPE_GNS2DNS, // DNS delegation by name
41 enums.GNS_TYPE_NICK, // Nick name
42 enums.GNS_TYPE_LEHO, // Legacy hostname
43 enums.GNS_TYPE_BOX, // Boxed resource record
44 enums.GNS_TYPE_DNS_A, // IPv4 address
45 enums.GNS_TYPE_DNS_AAAA, // IPv6 address
46 enums.GNS_TYPE_DNS_CNAME, // CNAME in DNS
47 enums.GNS_TYPE_DNS_TXT, // DNS TXT
48 enums.GNS_TYPE_DNS_MX, // Mailbox
49 }
50)
51
52//======================================================================
53// Convert binary resource records to ParameterSet and vice-versa.
54// The map keys must match the HTML names of dialog fields.
55//======================================================================
56
57//----------------------------------------------------------------------
58// GUI rendering hepers
59//----------------------------------------------------------------------
60
61var (
62 // List of key prefixes based on RR type
63 dlgPrefix = map[enums.GNSType]string{
64 enums.GNS_TYPE_PKEY: "pkey_",
65 enums.GNS_TYPE_EDKEY: "edkey_",
66 enums.GNS_TYPE_REDIRECT: "redirect_",
67 enums.GNS_TYPE_LEHO: "leho_",
68 enums.GNS_TYPE_NICK: "nick_",
69 enums.GNS_TYPE_GNS2DNS: "gns2dns_",
70 enums.GNS_TYPE_BOX: "box_",
71 enums.GNS_TYPE_DNS_A: "dnsa_",
72 enums.GNS_TYPE_DNS_AAAA: "dnsaaaa_",
73 enums.GNS_TYPE_DNS_CNAME: "dnscname_",
74 enums.GNS_TYPE_DNS_TXT: "dnstxt_",
75 enums.GNS_TYPE_DNS_MX: "dnsmx_",
76 }
77)
78
79// convert GNUnet time to string for HTML
80func htmlTime(ts util.AbsoluteTime) string {
81 if ts.IsNever() {
82 return ""
83 }
84 return time.UnixMicro(int64(ts.Val)).Format(timeHTML)
85}
86
87func guiTime(ts util.AbsoluteTime) string {
88 if ts.IsNever() {
89 return "Never"
90 }
91 return time.UnixMicro(int64(ts.Val)).Format(timeGUI)
92}
93
94// convert zone key type to string
95func guiKeyType(t enums.GNSType) string {
96 switch t {
97 case enums.GNS_TYPE_PKEY:
98 return "PKEY"
99 case enums.GNS_TYPE_EDKEY:
100 return "EDKEY"
101 }
102 return "???"
103}
104
105func guiRRdata(t enums.GNSType, buf []byte) string {
106 // get record instance
107 inst, err := rr.ParseRR(t, buf)
108 if err != nil {
109 return "<unknown>"
110 }
111 // type-dependent rendering
112 switch rec := inst.(type) {
113 case *rr.PKEY:
114 return fmt.Sprintf("<span title='public zone key'>%s</span>", rec.ZoneKey.ID())
115 case *rr.EDKEY:
116 return fmt.Sprintf("<span title='public zone key'>%s</span>", rec.ZoneKey.ID())
117 case *rr.REDIRECT:
118 return fmt.Sprintf("<span title='redirect target'>%s</span>", rec.Name)
119 case *rr.NICK:
120 return fmt.Sprintf("<span title='nick name'>%s</span>", rec.Name)
121 case *rr.LEHO:
122 return fmt.Sprintf("<span title='legacy hostname'>%s</span>", rec.Name)
123 case *rr.CNAME:
124 return fmt.Sprintf("<span title='canonical name'>%s</span>", rec.Name)
125 case *rr.TXT:
126 return fmt.Sprintf("<span title='text'>%s</span>", rec.Text)
127 case *rr.DNSA:
128 return fmt.Sprintf("<span title='IPv4 address'>%s</span>", rec.Addr.String())
129 case *rr.DNSAAAA:
130 return fmt.Sprintf("<span title='IPv6 address'>%s</span>", rec.Addr.String())
131 case *rr.MX:
132 s := fmt.Sprintf("<span title='priority'>[%d]</span>&nbsp;", rec.Prio)
133 return s + fmt.Sprintf("<span title='server'>%s</span>", rec.Server)
134 case *rr.BOX:
135 s := fmt.Sprintf("<span title='service'>%s</span>/", rr.GetServiceName(rec.Svc, rec.Proto))
136 s += fmt.Sprintf("<span title='protocol'>%s</span> ", rr.GetProtocolName(rec.Proto))
137 switch rec.Type {
138 case enums.GNS_TYPE_DNS_TLSA:
139 tlsa := new(rr.TLSA)
140 _ = data.Unmarshal(tlsa, rec.RR)
141 s += "TLSA[<br>"
142 s += fmt.Sprintf("&#8729;&nbsp;Usage: %s<br>", rr.TLSAUsage[tlsa.Usage])
143 s += fmt.Sprintf("&#8729;&nbsp;Selector: %s<br>", rr.TLSASelector[tlsa.Selector])
144 s += fmt.Sprintf("&#8729;&nbsp;Match: %s<br>", rr.TLSAMatch[tlsa.Match])
145 s += "&#8729;&nbsp;CertData:<br>"
146 cert := hex.EncodeToString(tlsa.Cert)
147 for len(cert) > 32 {
148 s += "&nbsp;&nbsp;" + cert[:32] + "<br>"
149 cert = cert[32:]
150 }
151 s += "&nbsp;&nbsp;" + cert + "<br>]"
152 return s
153 case enums.GNS_TYPE_DNS_SRV:
154 srv, _ := util.ReadCString(rec.RR, 0)
155 s += fmt.Sprintf("SRV[ %s ]", srv)
156 return s
157 }
158 case *rr.GNS2DNS:
159 s := fmt.Sprintf("<span title='name'>%s</span> (Resolver: ", rec.Name)
160 return s + fmt.Sprintf("<span title='server'>%s</span>)", rec.Server)
161 }
162 return "(unknown)"
163}
164
165// get prefix for GUI fields for given RR type
166func guiPrefix(t enums.GNSType) string {
167 pf, ok := dlgPrefix[t]
168 if !ok {
169 return ""
170 }
171 return pf
172}
173
174// parse expiration time and flags from GUI parameters
175func guiParse(params map[string]string, pf string) (exp util.AbsoluteTime, flags enums.GNSFlag) {
176 // parse expiration time
177 exp = util.AbsoluteTimeNever()
178 if _, ok := params[pf+"never"]; !ok {
179 ts, _ := time.Parse(timeHTML, params[pf+"expires"])
180 exp.Val = uint64(ts.UnixMicro())
181 }
182 // parse flags
183 flags = 0
184 if _, ok := params[pf+"private"]; ok {
185 flags |= enums.GNS_FLAG_PRIVATE
186 }
187 if _, ok := params[pf+"shadow"]; ok {
188 flags |= enums.GNS_FLAG_SHADOW
189 }
190 if _, ok := params[pf+"suppl"]; ok {
191 flags |= enums.GNS_FLAG_SUPPL
192 }
193 return
194}
195
196//----------------------------------------------------------------------
197// Convert RR to string-keyed map and vice-versa.
198//----------------------------------------------------------------------
199
200// RRData2Map converts resource record data in to a map
201func RRData2Map(t enums.GNSType, buf []byte) (set map[string]string) {
202 pf := dlgPrefix[t]
203 set = make(map[string]string)
204 switch t {
205 // Ed25519 public key
206 case enums.GNS_TYPE_PKEY,
207 enums.GNS_TYPE_EDKEY:
208 set[pf+"data"] = util.EncodeBinaryToString(buf)
209
210 // Name string data
211 case enums.GNS_TYPE_REDIRECT,
212 enums.GNS_TYPE_NICK,
213 enums.GNS_TYPE_LEHO,
214 enums.GNS_TYPE_DNS_CNAME:
215 set[pf+"name"], _ = util.ReadCString(buf, 0)
216
217 // DNS TXT
218 case enums.GNS_TYPE_DNS_TXT:
219 set[pf+"text"], _ = util.ReadCString(buf, 0)
220
221 // IPv4/IPv6 address
222 case enums.GNS_TYPE_DNS_A,
223 enums.GNS_TYPE_DNS_AAAA:
224 addr := net.IP(buf)
225 set[pf+"addr"] = addr.String()
226
227 // DNS MX
228 case enums.GNS_TYPE_DNS_MX:
229 mx := new(rr.MX)
230 _ = data.Unmarshal(mx, buf)
231 set[pf+"prio"] = util.CastToString(mx.Prio)
232 set[pf+"host"] = mx.Server
233
234 // BOX
235 case enums.GNS_TYPE_BOX:
236 // get BOX from data
237 box := rr.NewBOX(buf)
238 set[pf+"proto"] = util.CastToString(box.Proto)
239 set[pf+"svc"] = util.CastToString(box.Svc)
240 set[pf+"type"] = util.CastToString(box.Type)
241
242 // handle TLSA and SRV cases
243 switch box.Type {
244 case enums.GNS_TYPE_DNS_TLSA:
245 tlsa := new(rr.TLSA)
246 _ = data.Unmarshal(tlsa, box.RR)
247 set[pf+"tlsa_usage"] = util.CastToString(tlsa.Usage)
248 set[pf+"tlsa_selector"] = util.CastToString(tlsa.Selector)
249 set[pf+"tlsa_match"] = util.CastToString(tlsa.Match)
250 set[pf+"tlsa_cert"] = hex.EncodeToString(tlsa.Cert)
251
252 case enums.GNS_TYPE_DNS_SRV:
253 set[pf+"srv_host"], _ = util.ReadCString(box.RR, 0)
254 }
255
256 // GNS2DNS
257 case enums.GNS_TYPE_GNS2DNS:
258 list := util.StringList(buf)
259 set[pf+"name"] = list[0]
260 set[pf+"server"] = list[1]
261 }
262 return
263}
264
265// Map2RRData converts a map to resource record data
266func Map2RRData(t enums.GNSType, set map[string]string) (buf []byte, err error) {
267 pf := dlgPrefix[t]
268 switch t {
269 // Ed25519 public key
270 case enums.GNS_TYPE_PKEY,
271 enums.GNS_TYPE_EDKEY:
272 return util.DecodeStringToBinary(set[pf+"data"], 36)
273
274 // Name string data
275 case enums.GNS_TYPE_REDIRECT,
276 enums.GNS_TYPE_NICK,
277 enums.GNS_TYPE_LEHO,
278 enums.GNS_TYPE_DNS_CNAME:
279 return util.WriteCString(set[pf+"name"]), nil
280
281 // DNS TXT
282 case enums.GNS_TYPE_DNS_TXT:
283 return util.WriteCString(set[pf+"text"]), nil
284
285 // IPv4/IPv6 address
286 case enums.GNS_TYPE_DNS_A,
287 enums.GNS_TYPE_DNS_AAAA:
288 buf := net.ParseIP(set[pf+"addr"])
289 if buf == nil {
290 return nil, errors.New("ParseIP failed")
291 }
292 return buf, nil
293
294 // DNS MX
295 case enums.GNS_TYPE_DNS_MX:
296 mx := new(rr.MX)
297 mx.Prio, _ = util.CastFromString[uint16](set[pf+"prio"])
298 mx.Server = set[pf+"host"]
299 return data.Marshal(mx)
300
301 // BOX
302 case enums.GNS_TYPE_BOX:
303 // assemble box
304 box := new(rr.BOX)
305 box.Proto, _ = util.CastFromString[uint16](set[pf+"proto"])
306 box.Svc, _ = util.CastFromString[uint16](set[pf+"svc"])
307 box.Type, _ = util.CastFromString[enums.GNSType](set[pf+"type"])
308
309 // handle TLSA and SRV cases
310 switch box.Type {
311 case enums.GNS_TYPE_DNS_TLSA:
312 tlsa := new(rr.TLSA)
313 tlsa.Usage, _ = util.CastFromString[uint8](set[pf+"tlsa_usage"])
314 tlsa.Selector, _ = util.CastFromString[uint8](set[pf+"tlsa_selector"])
315 tlsa.Match, _ = util.CastFromString[uint8](set[pf+"tlsa_match"])
316 tlsa.Cert, _ = hex.DecodeString(set[pf+"tlsa_cert"])
317 box.RR, _ = data.Marshal(tlsa)
318
319 case enums.GNS_TYPE_DNS_SRV:
320 box.RR = util.WriteCString(set[pf+"srv_host"])
321 }
322 return data.Marshal(box)
323
324 // GNS2DNS
325 case enums.GNS_TYPE_GNS2DNS:
326 buf := util.WriteCString(set[pf+"name"])
327 return append(buf, util.WriteCString(set[pf+"server"])...), nil
328 }
329 return nil, errors.New("unknown RR type")
330}
331
332//======================================================================
333// Get list of allowed new RRs given a set of existing RRs.
334//======================================================================
335
336// Create a list of compatible record types from list of
337// existing record types.
338func compatibleRR(in []*enums.GNSSpec, label string) (out []*enums.GNSSpec) {
339 for _, t := range rrtypes {
340 if ok, forced := rr.CanCoexist(t, in, label); ok {
341 out = append(out, &enums.GNSSpec{
342 Type: t,
343 Flags: forced,
344 })
345 }
346 }
347 return
348}
diff --git a/src/gnunet/service/zonemaster/rpc.go b/src/gnunet/service/zonemaster/rpc.go
new file mode 100644
index 0000000..2060e56
--- /dev/null
+++ b/src/gnunet/service/zonemaster/rpc.go
@@ -0,0 +1,24 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package zonemaster
20
21import "gnunet/service"
22
23func (s *Service) InitRPC(rpc *service.JRPCServer) {
24}
diff --git a/src/gnunet/service/zonemaster/service.go b/src/gnunet/service/zonemaster/service.go
new file mode 100644
index 0000000..c73857f
--- /dev/null
+++ b/src/gnunet/service/zonemaster/service.go
@@ -0,0 +1,150 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package zonemaster
20
21import (
22 "context"
23 "fmt"
24 "io"
25
26 "gnunet/config"
27 "gnunet/core"
28 "gnunet/crypto"
29 "gnunet/message"
30 "gnunet/service"
31 "gnunet/service/dht/blocks"
32 "gnunet/transport"
33 "gnunet/util"
34
35 "github.com/bfix/gospel/logger"
36)
37
38type ZoneIterator struct {
39 zk *crypto.ZonePrivate
40}
41
42//----------------------------------------------------------------------
43// "GNUnet Zonemaster" service implementation:
44// The zonemaster service handles Namestore messages
45//----------------------------------------------------------------------
46
47// Service implements a GNS service
48type Service struct {
49 Module
50
51 ZoneIters *util.Map[uint32, *ZoneIterator]
52}
53
54// NewService creates a new GNS service instance
55func NewService(ctx context.Context, c *core.Core) service.Service {
56 // instantiate service
57 mod := NewModule(ctx, c)
58 srv := &Service{
59 Module: *mod,
60 ZoneIters: util.NewMap[uint32, *ZoneIterator](),
61 }
62 // set external function references (external services)
63 srv.StoreLocal = srv.StoreNamecache
64 srv.StoreRemote = srv.StoreDHT
65
66 return srv
67}
68
69// ServeClient processes a client channel.
70func (s *Service) ServeClient(ctx context.Context, id int, mc *service.Connection) {
71 reqID := 0
72 var cancel context.CancelFunc
73 ctx, cancel = context.WithCancel(ctx)
74
75 for {
76 // receive next message from client
77 reqID++
78 logger.Printf(logger.DBG, "[zonemaster:%d:%d] Waiting for client request...\n", id, reqID)
79 msg, err := mc.Receive(ctx)
80 if err != nil {
81 if err == io.EOF {
82 logger.Printf(logger.INFO, "[zonemaster:%d:%d] Client channel closed.\n", id, reqID)
83 } else if err == service.ErrConnectionInterrupted {
84 logger.Printf(logger.INFO, "[zonemaster:%d:%d] Service operation interrupted.\n", id, reqID)
85 } else {
86 logger.Printf(logger.ERROR, "[zonemaster:%d:%d] Message-receive failed: %s\n", id, reqID, err.Error())
87 }
88 break
89 }
90 logger.Printf(logger.INFO, "[zonemaster:%d:%d] Received request: %v\n", id, reqID, msg)
91
92 // handle message
93 valueCtx := context.WithValue(ctx, core.CtxKey("label"), fmt.Sprintf(":%d:%d", id, reqID))
94 s.HandleMessage(valueCtx, nil, msg, mc)
95 }
96 // close client connection
97 mc.Close()
98
99 // cancel all tasks running for this session/connection
100 logger.Printf(logger.INFO, "[zonemaster:%d] Start closing session...\n", id)
101 cancel()
102}
103
104// Handle a single incoming message
105func (s *Service) HandleMessage(ctx context.Context, sender *util.PeerID, msg message.Message, back transport.Responder) bool {
106 // assemble log label
107 label := ""
108 if v := ctx.Value("label"); v != nil {
109 label, _ = v.(string)
110 }
111 // perform lookup
112 switch m := msg.(type) {
113
114 // start new zone iteration
115 case *message.NamestoreZoneIterStartMsg:
116 zi := new(ZoneIterator)
117 zi.zk = m.ZoneKey
118 s.ZoneIters.Put(m.ID, zi, 0)
119
120 default:
121 //----------------------------------------------------------
122 // UNKNOWN message type received
123 //----------------------------------------------------------
124 logger.Printf(logger.ERROR, "[zonemaster%s] Unhandled message of type (%s)\n", label, msg.Type())
125 return false
126 }
127 return true
128}
129
130// storeDHT stores a GNS block in the DHT.
131func (s *Service) StoreDHT(ctx context.Context, query blocks.Query, block blocks.Block) (err error) {
132 // assemble DHT request
133 req := message.NewDHTP2PPutMsg(block)
134 req.Flags = query.Flags()
135 req.Key = query.Key().Clone()
136
137 // store block
138 _, err = service.RequestResponse(ctx, "zonemaster", "dht", config.Cfg.DHT.Service.Socket, req, false)
139 return
140}
141
142// storeNamecache stores a GNS block in the local namecache.
143func (s *Service) StoreNamecache(ctx context.Context, query *blocks.GNSQuery, block *blocks.GNSBlock) (err error) {
144 // assemble Namecache request
145 req := message.NewNamecacheCacheMsg(block)
146
147 // get response from Namecache service
148 _, err = service.RequestResponse(ctx, "zonemaster", "namecache", config.Cfg.Namecache.Service.Socket, req, false)
149 return
150}
diff --git a/src/gnunet/service/zonemaster/zonemaster.go b/src/gnunet/service/zonemaster/zonemaster.go
new file mode 100644
index 0000000..7c2a13c
--- /dev/null
+++ b/src/gnunet/service/zonemaster/zonemaster.go
@@ -0,0 +1,179 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package zonemaster
20
21import (
22 "context"
23 "gnunet/config"
24 "gnunet/enums"
25 "gnunet/service/dht/blocks"
26 "gnunet/service/store"
27 "gnunet/util"
28 "time"
29
30 "github.com/bfix/gospel/logger"
31)
32
33//======================================================================
34// "GNS ZoneMaster" implementation:
35// Manage and publish local zone records
36//======================================================================
37
38// ZoneMaster instance
39type ZoneMaster struct {
40 cfg *config.Config // Zonemaster configuration
41 zdb *store.ZoneDB // ZoneDB connection
42 srv *Service // NameStore service
43}
44
45// NewZoneMaster initializes a new zone master instance.
46func NewZoneMaster(cfg *config.Config, srv *Service) *ZoneMaster {
47 zm := new(ZoneMaster)
48 zm.cfg = cfg
49 return zm
50}
51
52// Run zone master: connect to zone database and start the RPC/HTTP
53// services as background processes. Periodically publish GNS blocks
54// into the DHT.
55func (zm *ZoneMaster) Run(ctx context.Context) {
56 // connect to database
57 logger.Println(logger.INFO, "[zonemaster] Connecting to zone database...")
58 dbFile, ok := util.GetParam[string](zm.cfg.ZoneMaster.Storage, "file")
59 if !ok {
60 logger.Printf(logger.ERROR, "[zonemaster] missing database file specification")
61 return
62 }
63 var err error
64 if zm.zdb, err = store.OpenZoneDB(dbFile); err != nil {
65 logger.Printf(logger.ERROR, "[zonemaster] open database: %v", err)
66 return
67 }
68 defer zm.zdb.Close()
69
70 // start HTTP GUI
71 zm.startGUI(ctx)
72 /*
73 // publish on start-up
74 if err = zm.Publish(ctx); err != nil {
75 logger.Printf(logger.ERROR, "[zonemaster] initial publish failed: %s", err.Error())
76 return
77 }
78 */
79 // periodically publish GNS blocks to the DHT
80 tick := time.NewTicker(time.Duration(zm.cfg.ZoneMaster.Period) * time.Second)
81loop:
82 for {
83 select {
84 case <-tick.C:
85 if err := zm.Publish(ctx); err != nil {
86 logger.Printf(logger.ERROR, "[zonemaster] periodic publish failed: %s", err.Error())
87 }
88
89 // check for termination
90 case <-ctx.Done():
91 break loop
92 }
93 }
94}
95
96// OnChange is called if a zone or record has changed or was inserted
97func (zm *ZoneMaster) OnChange(table string, id int64, mode int) {
98}
99
100// Publish all zone labels to the DHT
101func (zm *ZoneMaster) Publish(ctx context.Context) error {
102 // collect all zones
103 zones, err := zm.zdb.GetZones("")
104 if err != nil {
105 return err
106 }
107 for _, z := range zones {
108 // collect labels for zone
109 var labels []*store.Label
110 if labels, err = zm.zdb.GetLabels("zid=%d", z.ID); err != nil {
111 return err
112 }
113 for _, l := range labels {
114 // publish label
115 if err = zm.PublishZoneLabel(ctx, z, l); err != nil {
116 return err
117 }
118 }
119 }
120 return nil
121}
122
123// PublishZoneLabel with public records
124func (zm *ZoneMaster) PublishZoneLabel(ctx context.Context, zone *store.Zone, label *store.Label) error {
125 zk := zone.Key.Public()
126 logger.Printf(logger.INFO, "[zonemaster] Publishing label '%s' of zone %s", label.Name, zk.ID())
127
128 // collect public records for zone label
129 recs, err := zm.zdb.GetRecords("lid=%d and flags&%d = 0", label.ID, enums.GNS_FLAG_PRIVATE)
130 if err != nil {
131 return err
132 }
133 // assemble record set and find earliest expiration
134 expire := util.AbsoluteTimeNever()
135 rrSet := blocks.NewRecordSet()
136 for _, r := range recs {
137 if r.Expire.Compare(expire) < 0 {
138 expire = r.Expire
139 }
140 rrSet.AddRecord(&r.ResourceRecord)
141 }
142 rrSet.SetPadding()
143 if rrSet.Count == 0 {
144 logger.Println(logger.INFO, "[zonemaster] No resource records -- skipped")
145 return nil
146 }
147
148 // assemble GNS query
149 query := blocks.NewGNSQuery(zk, label.Name)
150
151 // assemble, encrypt and sign GNS block
152 blk, _ := blocks.NewGNSBlock().(*blocks.GNSBlock)
153
154 blk.Body.Expire = expire
155 blk.Body.Data, err = zk.Encrypt(rrSet.Bytes(), label.Name, expire)
156 if err != nil {
157 return err
158 }
159 dzk, _, err := zone.Key.Derive(label.Name, "gns")
160 if err != nil {
161 return err
162 }
163 if err = blk.Sign(dzk); err != nil {
164 return err
165 }
166
167 // DEBUG:
168 // logger.Printf(logger.DBG, "[zonemaster] Query key = %s", hex.EncodeToString(query.Key().Data))
169 // logger.Printf(logger.DBG, "[zonemaster] Block data = %s", hex.EncodeToString(blk.Bytes()))
170
171 // publish GNS block to DHT and Namecache
172 if err = zm.srv.StoreDHT(ctx, query, blk); err != nil {
173 return err
174 }
175 if err = zm.srv.StoreNamecache(ctx, query, blk); err != nil {
176 return err
177 }
178 return nil
179}
diff --git a/src/gnunet/util/array.go b/src/gnunet/util/array.go
index 97d884a..b4a1776 100644
--- a/src/gnunet/util/array.go
+++ b/src/gnunet/util/array.go
@@ -19,6 +19,7 @@
19package util 19package util
20 20
21import ( 21import (
22 "bytes"
22 "fmt" 23 "fmt"
23) 24)
24 25
@@ -114,14 +115,13 @@ func CopyAlignedBlock(out, in []byte) {
114// not terminated, it is skipped. 115// not terminated, it is skipped.
115func StringList(b []byte) []string { 116func StringList(b []byte) []string {
116 res := make([]string, 0) 117 res := make([]string, 0)
117 str := "" 118 pos := 0
118 for _, ch := range b { 119 var str string
119 if ch == 0 { 120 for pos != -1 {
121 str, pos = ReadCString(b, pos)
122 if len(str) > 0 {
120 res = append(res, str) 123 res = append(res, str)
121 str = ""
122 continue
123 } 124 }
124 str += string(ch)
125 } 125 }
126 return res 126 return res
127} 127}
@@ -137,3 +137,11 @@ func ReadCString(buf []byte, pos int) (string, int) {
137 } 137 }
138 return "", -1 138 return "", -1
139} 139}
140
141// WriteCString returns the binary C-representation of a string
142func WriteCString(s string) []byte {
143 buf := new(bytes.Buffer)
144 _, _ = buf.WriteString(s)
145 _ = buf.WriteByte(0)
146 return buf.Bytes()
147}
diff --git a/src/gnunet/util/misc.go b/src/gnunet/util/misc.go
index c5fd308..aeda678 100644
--- a/src/gnunet/util/misc.go
+++ b/src/gnunet/util/misc.go
@@ -21,6 +21,7 @@ package util
21import ( 21import (
22 "encoding/hex" 22 "encoding/hex"
23 "encoding/json" 23 "encoding/json"
24 "fmt"
24 "strings" 25 "strings"
25 26
26 "github.com/bfix/gospel/data" 27 "github.com/bfix/gospel/data"
@@ -73,6 +74,21 @@ func GetParam[V any](params ParameterSet, key string) (i V, ok bool) {
73 return 74 return
74} 75}
75 76
77// CastFromString a string to given type (only works for intrinsic type)
78func CastFromString[V any](s string) (i V, ok bool) {
79 num, err := fmt.Sscanf(s, "%v", &i)
80 ok = true
81 if err != nil || num != 1 {
82 ok = false
83 }
84 return
85}
86
87// CastToString returns a string representation (for intrinsic types)
88func CastToString(v any) string {
89 return fmt.Sprintf("%v", v)
90}
91
76//---------------------------------------------------------------------- 92//----------------------------------------------------------------------
77// additional helpers 93// additional helpers
78//---------------------------------------------------------------------- 94//----------------------------------------------------------------------
diff --git a/src/gnunet/util/time.go b/src/gnunet/util/time.go
index cf3739a..c49755c 100644
--- a/src/gnunet/util/time.go
+++ b/src/gnunet/util/time.go
@@ -60,6 +60,11 @@ func AbsoluteTimeNever() AbsoluteTime {
60 return AbsoluteTime{math.MaxUint64} 60 return AbsoluteTime{math.MaxUint64}
61} 61}
62 62
63// IsNever returns true if time is "never"
64func (t AbsoluteTime) IsNever() bool {
65 return t.Val == math.MaxUint64
66}
67
63// Epoch returns the seconds since Unix epoch. 68// Epoch returns the seconds since Unix epoch.
64func (t AbsoluteTime) Epoch() uint64 { 69func (t AbsoluteTime) Epoch() uint64 {
65 return t.Val / 1000000 70 return t.Val / 1000000