diff options
author | Bernd Fix <brf@hoi-polloi.org> | 2022-10-22 16:57:16 +0200 |
---|---|---|
committer | Bernd Fix <brf@hoi-polloi.org> | 2022-10-22 16:57:16 +0200 |
commit | 6ed1dfac9e0ac7a24114198ae8a364388cb55528 (patch) | |
tree | 3f7a33078979c3fc32f6bf4235bda6f20f18f506 /src | |
parent | 915c050130855f83c2f58c44682396bcf4d5275b (diff) | |
download | gnunet-go-6ed1dfac9e0ac7a24114198ae8a364388cb55528.tar.gz gnunet-go-6ed1dfac9e0ac7a24114198ae8a364388cb55528.zip |
Initial revision of Zonemaster implementation.v0.1.36
Diffstat (limited to 'src')
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 ./... | 3 | if [ "$1" = "withgen" ]; then |
4 | go install -v -gcflags "-N -l" ./... | 4 | go generate ./... |
5 | shift | ||
6 | fi | ||
7 | go 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 | |||
19 | package main | ||
20 | |||
21 | import ( | ||
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 | |||
37 | func 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 | |||
121 | loop: | ||
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 | ||
99 | type 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. |
202 | func substString(s string, env map[string]string) string { | 211 | func 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 | ||
21 | import ( | 21 | import ( |
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 |
149 | var ( | 156 | var ( |
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 | ||
154 | var ( | 161 | var ( |
@@ -176,7 +183,7 @@ type ZoneImplementation struct { | |||
176 | 183 | ||
177 | // keep a mapping of available implementations | 184 | // keep a mapping of available implementations |
178 | var ( | 185 | var ( |
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. |
190 | func GetImplementation(ztype uint32) *ZoneImplementation { | 197 | func 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 |
213 | func NewZonePrivate(ztype uint32, d []byte) (zp *ZonePrivate, err error) { | 220 | // provided, a new random key is created |
221 | func 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) |
248 | func (zp *ZonePrivate) Derive(label, context string) (dzp *ZonePrivate, h *math.Int, err error) { | 264 | func (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. | ||
297 | func (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,...) |
288 | type ZoneKey struct { | 306 | type 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. | ||
314 | func (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 |
296 | func NewZoneKey(d []byte) (zk *ZoneKey, err error) { | 329 | func 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) |
323 | func (zk *ZoneKey) Derive(label, context string) (dzk *ZoneKey, h *math.Int, err error) { | 348 | func (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. |
361 | func (zk *ZoneKey) ID() string { | 387 | func (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 | 423 | func (zs *ZoneSignature) Init() (err error) { |
406 | func 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 | ||
444 | func 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 | ||
490 | func 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. |
59 | type EDKEYPublicImpl struct { | 60 | type 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 | ||
164 | func (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 | |||
167 | type EDKEYPrivateImpl struct { | 173 | type 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. |
175 | func (pk *EDKEYPrivateImpl) Init(data []byte) error { | 182 | func (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. |
195 | func (pk *EDKEYPrivateImpl) Derive(h *math.Int) (dPk ZonePrivateImpl, hOut *math.Int, err error) { | 203 | func (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 | ||
245 | func (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 | |||
19 | package crypto | ||
20 | |||
21 | import ( | ||
22 | "bytes" | ||
23 | "gnunet/enums" | ||
24 | "testing" | ||
25 | ) | ||
26 | |||
27 | func 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 | |||
36 | func 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. |
59 | type PKEYPublicImpl struct { | 60 | type 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 | ||
160 | func (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 { | |||
171 | func (pk *PKEYPrivateImpl) Init(data []byte) error { | 177 | func (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 | ||
235 | func (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 | |||
19 | package crypto | ||
20 | |||
21 | import ( | ||
22 | "bytes" | ||
23 | "gnunet/enums" | ||
24 | "testing" | ||
25 | ) | ||
26 | |||
27 | func 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 |
20 | package enums | 20 | package enums |
21 | 21 | ||
22 | // GNSFlag type | ||
23 | type GNSFlag uint32 | ||
24 | |||
22 | const ( | 25 | const ( |
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 | ||
47 | type 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 |
4 | package enums | 4 | package enums |
5 | 5 | ||
6 | type GNSType int | 6 | type GNSType uint32 |
7 | 7 | ||
8 | // GNS constants | 8 | // GNS constants |
9 | const ( | 9 | const ( |
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 |
4 | package enums | 4 | package enums |
5 | 5 | ||
6 | type GNSType int | 6 | type GNSType uint32 |
7 | 7 | ||
8 | // GNS constants | 8 | // GNS constants |
9 | const ( | 9 | const ( |
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 | |||
3 | go 1.18 | 3 | go 1.18 |
4 | 4 | ||
5 | require ( | 5 | require ( |
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 @@ | |||
1 | github.com/bfix/gospel v1.2.19 h1:B57L5CMjKPeRPtVxt1JcSx42AKwD+SpN32QaF0DxXFM= | 1 | github.com/bfix/gospel v1.2.20 h1:e/IxmTiC579jIQlIxpMzCX/MIKHNsBzJ1WdMKheCgBw= |
2 | github.com/bfix/gospel v1.2.19/go.mod h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI= | 2 | github.com/bfix/gospel v1.2.20/go.mod h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI= |
3 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= | 3 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= |
4 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | 4 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= |
5 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= | 5 | github.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 | ||
27 | func NewEmptyMessage(msgType enums.MsgType) (Message, error) { | 29 | func 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 |
125 | func NewDHTP2PPutMsg() *DHTP2PPutMsg { | 126 | func 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) |
157 | func (m *DHTP2PPutMsg) Update(p *path.Path, pf *blocks.PeerFilter, hop uint16) *DHTP2PPutMsg { | 173 | func (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. | ||
90 | type 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. | ||
97 | func 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. | ||
106 | func (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. | ||
112 | func (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. | ||
125 | func (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). | ||
139 | type 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. | ||
148 | func (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 |
154 | type LookupResultMsg struct { | 89 | type 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. |
172 | func (m *LookupResultMsg) AddRecord(rec *ResourceRecord) error { | 107 | func (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 | ||
35 | type GenericNamecacheMsg struct { | ||
36 | MsgHeader | ||
37 | ID uint32 `order:"big"` // unique reference ID | ||
38 | } | ||
39 | |||
40 | // return initialized common message header | ||
41 | func 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 |
35 | type NamecacheLookupMsg struct { | 53 | type 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. |
64 | type NamecacheLookupResultMsg struct { | 81 | type 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. |
73 | func NewNamecacheLookupResultMsg() *NamecacheLookupResultMsg { | 90 | func 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. |
94 | type NamecacheCacheMsg struct { | 110 | type 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 | ||
120 | func (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. |
103 | func NewNamecacheCacheMsg(block *blocks.GNSBlock) *NamecacheCacheMsg { | 132 | func 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. |
123 | func (m *NamecacheCacheMsg) String() string { | 153 | func (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 |
133 | type NamecacheCacheResponseMsg struct { | 163 | type 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. |
140 | func NewNamecacheCacheResponseMsg() *NamecacheCacheResponseMsg { | 170 | func 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 | |||
19 | package message | ||
20 | |||
21 | import ( | ||
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 | ||
34 | type GenericNamestoreMsg struct { | ||
35 | MsgHeader | ||
36 | ID uint32 `order:"big"` // unique reference ID | ||
37 | } | ||
38 | |||
39 | // return initialized common message header | ||
40 | func 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 | ||
52 | type NamestoreZoneIterStartMsg struct { | ||
53 | GenericNamestoreMsg | ||
54 | |||
55 | ZoneKey *crypto.ZonePrivate // private zone key | ||
56 | } | ||
57 | |||
58 | // NewNamecacheCacheMsg creates a new default message. | ||
59 | func 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. | ||
67 | func (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 | |||
75 | type NamestoreZoneIterNextMsg struct { | ||
76 | GenericNamestoreMsg | ||
77 | |||
78 | Limit uint64 `order:"big"` // max. number of records in one go | ||
79 | } | ||
80 | |||
81 | func NewNamestoreZoneIterNextMsg() *NamestoreZoneIterNextMsg { | ||
82 | return &NamestoreZoneIterNextMsg{} | ||
83 | } | ||
84 | |||
85 | // String returns a human-readable representation of the message. | ||
86 | func (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 | |||
94 | type NamestoreZoneIterStopMsg struct { | ||
95 | GenericNamestoreMsg | ||
96 | } | ||
97 | |||
98 | //---------------------------------------------------------------------- | ||
99 | //---------------------------------------------------------------------- | ||
100 | |||
101 | type NamestoreRecordStoreMsg struct { | ||
102 | GenericNamestoreMsg | ||
103 | |||
104 | ZoneKey *crypto.ZonePrivate // private zone key | ||
105 | Records *blocks.RecordSet // list of records | ||
106 | } | ||
107 | |||
108 | type 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 | |||
117 | type 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 | |||
126 | type 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 | |||
138 | type NamestoreZoneToNameMsg struct { | ||
139 | GenericNamestoreMsg | ||
140 | |||
141 | ZoneKey *crypto.ZonePrivate // private zone key | ||
142 | ZonePublic *crypto.ZoneKey // public zone key | ||
143 | } | ||
144 | |||
145 | type 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 | |||
157 | type 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 | |||
170 | type NamestoreTxControlMsg struct { | ||
171 | GenericNamestoreMsg | ||
172 | |||
173 | Control uint16 `order:"big"` // type of control message | ||
174 | Reserved uint16 `order:"big"` // alignment | ||
175 | } | ||
176 | |||
177 | type 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 | |||
185 | type 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 | |||
194 | type 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) | ||
145 | func (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 |
144 | func (b *GNSBlock) Bytes() []byte { | 150 | func (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 |
182 | func (b *GNSBlock) Prepare(enums.BlockType, util.AbsoluteTime) {} | 192 | func (b *GNSBlock) Prepare(enums.BlockType, util.AbsoluteTime) {} |
183 | 193 | ||
194 | // SetData sets the data for the GNS block | ||
195 | func (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 | ||
201 | func (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. | ||
227 | type 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. | ||
234 | func 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. | ||
243 | func (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. | ||
249 | func (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. | ||
262 | func (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 | ||
275 | func (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). | ||
285 | type 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. | ||
294 | func (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 | ||
19 | package message | 19 | package blocks |
20 | 20 | ||
21 | import ( | 21 | import ( |
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 | ||
32 | func 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") |
34 | func TestRecordsetPKEY(t *testing.T) { | 125 | func 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"] |
250 | func (m *Module) Put(ctx context.Context, query blocks.Query, block blocks.Block) error { | 250 | func (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 @@ | |||
19 | package gns | 19 | package gns |
20 | 20 | ||
21 | import ( | 21 | import ( |
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. |
34 | type HdlrInst func(*message.ResourceRecord, []string) (BlockHandler, error) | 36 | type HdlrInst func(*blocks.ResourceRecord, []string) (BlockHandler, error) |
35 | 37 | ||
36 | // Error codes | 38 | // Error codes |
37 | var ( | 39 | var ( |
@@ -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). |
111 | func NewBlockHandlerList(records []*message.ResourceRecord, labels []string) (*BlockHandlerList, []*message.ResourceRecord, error) { | 113 | func 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 |
208 | func (hl *BlockHandlerList) FinalizeRecord(rec *message.ResourceRecord) *message.ResourceRecord { | 210 | func (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 |
218 | type ZoneKeyHandler struct { | 220 | type 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 |
225 | func NewZoneHandler(rec *message.ResourceRecord, labels []string) (BlockHandler, error) { | 227 | func 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. |
243 | func (h *ZoneKeyHandler) AddRecord(rec *message.ResourceRecord, labels []string) (err error) { | 245 | func (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 |
269 | func (h *ZoneKeyHandler) Records(kind RRTypeList) *message.RecordSet { | 271 | func (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 |
287 | type Gns2DnsHandler struct { | 289 | type 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 |
294 | func NewGns2DnsHandler(rec *message.ResourceRecord, labels []string) (BlockHandler, error) { | 296 | func 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. |
310 | func (h *Gns2DnsHandler) AddRecord(rec *message.ResourceRecord, labels []string) error { | 312 | func (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 |
344 | func (h *Gns2DnsHandler) Records(kind RRTypeList) *message.RecordSet { | 346 | func (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 | ||
366 | type 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 |
364 | type BoxHandler struct { | 373 | type 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 |
369 | func NewBoxHandler(rec *message.ResourceRecord, labels []string) (BlockHandler, error) { | 378 | func 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. |
383 | func (h *BoxHandler) AddRecord(rec *message.ResourceRecord, labels []string) error { | 392 | func (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 |
414 | func (h *BoxHandler) Records(kind RRTypeList) *message.RecordSet { | 428 | func (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 |
441 | type LehoHandler struct { | 455 | type 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 |
447 | func NewLehoHandler(rec *message.ResourceRecord, labels []string) (BlockHandler, error) { | 461 | func 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. |
461 | func (h *LehoHandler) AddRecord(rec *message.ResourceRecord, labels []string) error { | 475 | func (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 |
478 | func (h *LehoHandler) Records(kind RRTypeList) *message.RecordSet { | 492 | func (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 |
496 | type CnameHandler struct { | 510 | type 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 |
502 | func NewCnameHandler(rec *message.ResourceRecord, labels []string) (BlockHandler, error) { | 516 | func 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. |
516 | func (h *CnameHandler) AddRecord(rec *message.ResourceRecord, labels []string) error { | 530 | func (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 |
536 | func (h *CnameHandler) Records(kind RRTypeList) *message.RecordSet { | 550 | func (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 |
554 | type VpnHandler struct { | 568 | type 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 |
559 | func NewVpnHandler(rec *message.ResourceRecord, labels []string) (BlockHandler, error) { | 573 | func 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. |
571 | func (h *VpnHandler) AddRecord(rec *message.ResourceRecord, labels []string) error { | 585 | func (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 |
590 | func (h *VpnHandler) Records(kind RRTypeList) *message.RecordSet { | 604 | func (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 | |||
19 | package gns | ||
20 | |||
21 | import ( | ||
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 | ||
33 | type 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. | ||
45 | func 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. | ||
58 | func (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 | ||
74 | var 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"). | ||
85 | func 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 | ||
113 | var 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"). | ||
137 | func 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. |
119 | func QueryDNS(id int, name string, server net.IP, kind RRTypeList) *message.RecordSet { | 119 | func 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. |
474 | func (m *Module) newLEHORecord(name string, expires util.AbsoluteTime) *message.ResourceRecord { | 475 | func (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. |
487 | func (m *Module) records(buf []byte) ([]*message.ResourceRecord, error) { | 488 | func (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 | |||
19 | package rr | ||
20 | |||
21 | import ( | ||
22 | "errors" | ||
23 | "gnunet/enums" | ||
24 | "gnunet/util" | ||
25 | |||
26 | "github.com/bfix/gospel/data" | ||
27 | ) | ||
28 | |||
29 | // RR interface for resource records | ||
30 | type 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. | ||
42 | func 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 | ||
73 | func 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 | ||
85 | func 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. | ||
117 | func 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 | |||
19 | package rr | ||
20 | |||
21 | import ( | ||
22 | "fmt" | ||
23 | "gnunet/enums" | ||
24 | "net" | ||
25 | ) | ||
26 | |||
27 | //---------------------------------------------------------------------- | ||
28 | // DNS-related resource records | ||
29 | //---------------------------------------------------------------------- | ||
30 | |||
31 | // DNS CNAME record | ||
32 | type 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) | ||
38 | func (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 | ||
43 | func (rr *CNAME) ToMap(params map[string]string, prefix string) { | ||
44 | params[prefix+"name"] = rr.Name | ||
45 | } | ||
46 | |||
47 | //---------------------------------------------------------------------- | ||
48 | |||
49 | // DNS TXT record | ||
50 | type 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) | ||
56 | func (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 | ||
61 | func (rr *TXT) ToMap(params map[string]string, prefix string) { | ||
62 | params[prefix+"text"] = rr.Text | ||
63 | } | ||
64 | |||
65 | //---------------------------------------------------------------------- | ||
66 | |||
67 | // DNS IPv4 address | ||
68 | type 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) | ||
74 | func (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 | ||
79 | func (rr *DNSA) ToMap(params map[string]string, prefix string) { | ||
80 | params[prefix+"addr"] = rr.Addr.String() | ||
81 | } | ||
82 | |||
83 | //---------------------------------------------------------------------- | ||
84 | |||
85 | // DNS IPv6 address | ||
86 | type 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) | ||
92 | func (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 | ||
97 | func (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 | ||
104 | type 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) | ||
111 | func (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 | ||
116 | func (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 | |||
19 | package rr | ||
20 | |||
21 | import ( | ||
22 | "gnunet/crypto" | ||
23 | "gnunet/enums" | ||
24 | ) | ||
25 | |||
26 | //---------------------------------------------------------------------- | ||
27 | // GNS resource records | ||
28 | //---------------------------------------------------------------------- | ||
29 | |||
30 | // PKEY (Ed25519+EcDSA) zone key | ||
31 | type 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) | ||
37 | func (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 | ||
59 | func (rr *PKEY) ToMap(params map[string]string, prefix string) { | ||
60 | params[prefix+"data"] = rr.ID() | ||
61 | } | ||
62 | |||
63 | //---------------------------------------------------------------------- | ||
64 | |||
65 | // EDKEY (EdDSA) zone key | ||
66 | type 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) | ||
72 | func (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 | ||
94 | func (rr *EDKEY) ToMap(params map[string]string, prefix string) { | ||
95 | params[prefix+"data"] = rr.ID() | ||
96 | } | ||
97 | |||
98 | //---------------------------------------------------------------------- | ||
99 | |||
100 | // REDIRECT to name | ||
101 | type 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) | ||
107 | func (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 | ||
129 | func (rr *REDIRECT) ToMap(params map[string]string, prefix string) { | ||
130 | params[prefix+"name"] = rr.Name | ||
131 | } | ||
132 | |||
133 | //---------------------------------------------------------------------- | ||
134 | |||
135 | // GNS NICK record | ||
136 | type 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) | ||
142 | func (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 | ||
159 | func (rr *NICK) ToMap(params map[string]string, prefix string) { | ||
160 | params[prefix+"name"] = rr.Name | ||
161 | } | ||
162 | |||
163 | //---------------------------------------------------------------------- | ||
164 | |||
165 | // LEHO record | ||
166 | type 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) | ||
172 | func (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 | ||
177 | func (rr *LEHO) ToMap(params map[string]string, prefix string) { | ||
178 | params[prefix+"name"] = rr.Name | ||
179 | } | ||
180 | |||
181 | //---------------------------------------------------------------------- | ||
182 | |||
183 | // GNS2DNS delegation | ||
184 | type 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) | ||
191 | func (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 | ||
196 | func (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 | |||
19 | package rr | ||
20 | |||
21 | import ( | ||
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 | ||
38 | type 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. | ||
46 | func 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. | ||
57 | func (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) | ||
71 | func (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 | ||
76 | func (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 | ||
96 | func (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 | |||
111 | var ( | ||
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 | ||
136 | type 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) | ||
145 | func (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 | ||
150 | func (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 | ||
160 | type 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) | ||
166 | func (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 | ||
171 | func (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 | ||
180 | var 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 | ||
189 | func 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) | ||
202 | func 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"). | ||
213 | func 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 | ||
242 | var 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 | ||
309 | func 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) | ||
320 | func 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"). | ||
342 | func 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 |
99 | func (db *FileMetaDB) Store(md *FileMetadata) (err error) { | 99 | func (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 | |||
19 | package store | ||
20 | |||
21 | import ( | ||
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. | ||
40 | type 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. | ||
50 | func 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 | |||
62 | type 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 | |||
70 | func 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. | ||
84 | type 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. | ||
95 | func 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 | ||
115 | var initScriptZM []byte | ||
116 | |||
117 | // ZoneDB is a SQLite3 database for locally managed zones | ||
118 | type 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. | ||
125 | func 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 | ||
151 | func (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 | ||
165 | func (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 | ||
201 | func (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) | ||
217 | func (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 | ||
259 | func (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 | ||
295 | func (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) | ||
306 | func (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 | ||
342 | func (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 | ||
380 | func (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) | ||
402 | func (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) | ||
440 | type LabelGroup struct { | ||
441 | Label *Label | ||
442 | Records []*Record | ||
443 | } | ||
444 | |||
445 | // ZoneGroup is a nested zone entry (with labels) | ||
446 | type 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. | ||
455 | func (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 | ||
496 | func (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") | ||
503 | func (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 | ||
524 | func (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 | |||
19 | create 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 | |||
29 | create 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 | |||
38 | create 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 | |||
19 | package store | ||
20 | |||
21 | import ( | ||
22 | "crypto/rand" | ||
23 | "gnunet/crypto" | ||
24 | "gnunet/enums" | ||
25 | "gnunet/util" | ||
26 | "os" | ||
27 | "testing" | ||
28 | "time" | ||
29 | ) | ||
30 | |||
31 | func 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 | |||
19 | package zonemaster | ||
20 | |||
21 | import ( | ||
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 | ||
49 | var fsys embed.FS | ||
50 | |||
51 | var ( | ||
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 | ||
58 | const ( | ||
59 | ChangeNew = iota | ||
60 | ChangeUpdate | ||
61 | ChangeDelete | ||
62 | ) | ||
63 | |||
64 | //---------------------------------------------------------------------- | ||
65 | |||
66 | // Start HTTP server to provide GUI | ||
67 | func (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 | ||
157 | func (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 | ||
173 | func (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 | |||
203 | func (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 | ||
250 | func (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 | |||
282 | func (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 | ||
318 | func (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 | |||
372 | type 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 | |||
381 | func (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 | |||
450 | func (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 | |||
531 | func (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 | |||
576 | func (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 | ||
634 | func 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 | ||
662 | type 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">✎</button></a> | ||
41 | <a href="/del/zone/{{$z.ID}}" title="Remove zone"><button class="icon red">✖</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">✎</button></a> | ||
50 | <a href="/del/label/{{$l.ID}}" title="Remove label"><button class="icon red">✖</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">✎</button></a> | ||
75 | <a href="/del/rr/{{$rec.ID}}" title="Remove record"><button class="icon red">✖</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">✚</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">✚</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">✚</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"> PKEY (Ed25519+EcDSA)<br> | ||
14 | <input type="radio" id="edkey" name="keytype" value="EDKEY"> 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 | |||
19 | package zonemaster | ||
20 | |||
21 | import ( | ||
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. | ||
35 | type 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. | ||
44 | func 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 | ||
59 | func (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 | ||
66 | func (m *Module) event(ctx context.Context, ev *core.Event) { | ||
67 | |||
68 | } | ||
69 | |||
70 | //---------------------------------------------------------------------- | ||
71 | |||
72 | // Export functions | ||
73 | func (m *Module) Export(fcn map[string]any) { | ||
74 | // add exported functions from module | ||
75 | } | ||
76 | |||
77 | // Import functions | ||
78 | func (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 | |||
19 | package zonemaster | ||
20 | |||
21 | import ( | ||
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 | |||
34 | var ( | ||
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 | |||
61 | var ( | ||
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 | ||
80 | func htmlTime(ts util.AbsoluteTime) string { | ||
81 | if ts.IsNever() { | ||
82 | return "" | ||
83 | } | ||
84 | return time.UnixMicro(int64(ts.Val)).Format(timeHTML) | ||
85 | } | ||
86 | |||
87 | func 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 | ||
95 | func 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 | |||
105 | func 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> ", 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("∙ Usage: %s<br>", rr.TLSAUsage[tlsa.Usage]) | ||
143 | s += fmt.Sprintf("∙ Selector: %s<br>", rr.TLSASelector[tlsa.Selector]) | ||
144 | s += fmt.Sprintf("∙ Match: %s<br>", rr.TLSAMatch[tlsa.Match]) | ||
145 | s += "∙ CertData:<br>" | ||
146 | cert := hex.EncodeToString(tlsa.Cert) | ||
147 | for len(cert) > 32 { | ||
148 | s += " " + cert[:32] + "<br>" | ||
149 | cert = cert[32:] | ||
150 | } | ||
151 | s += " " + 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 | ||
166 | func 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 | ||
175 | func 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 | ||
201 | func 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 | ||
266 | func 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. | ||
338 | func 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 | |||
19 | package zonemaster | ||
20 | |||
21 | import "gnunet/service" | ||
22 | |||
23 | func (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 | |||
19 | package zonemaster | ||
20 | |||
21 | import ( | ||
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 | |||
38 | type 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 | ||
48 | type Service struct { | ||
49 | Module | ||
50 | |||
51 | ZoneIters *util.Map[uint32, *ZoneIterator] | ||
52 | } | ||
53 | |||
54 | // NewService creates a new GNS service instance | ||
55 | func 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. | ||
70 | func (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 | ||
105 | func (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. | ||
131 | func (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. | ||
143 | func (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 | |||
19 | package zonemaster | ||
20 | |||
21 | import ( | ||
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 | ||
39 | type 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. | ||
46 | func 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. | ||
55 | func (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) | ||
81 | loop: | ||
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 | ||
97 | func (zm *ZoneMaster) OnChange(table string, id int64, mode int) { | ||
98 | } | ||
99 | |||
100 | // Publish all zone labels to the DHT | ||
101 | func (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 | ||
124 | func (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 @@ | |||
19 | package util | 19 | package util |
20 | 20 | ||
21 | import ( | 21 | import ( |
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. |
115 | func StringList(b []byte) []string { | 116 | func 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 | ||
142 | func 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 | |||
21 | import ( | 21 | import ( |
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) | ||
78 | func 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) | ||
88 | func 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" | ||
64 | func (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. |
64 | func (t AbsoluteTime) Epoch() uint64 { | 69 | func (t AbsoluteTime) Epoch() uint64 { |
65 | return t.Val / 1000000 | 70 | return t.Val / 1000000 |