forked from servalproject/serval-dna
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rhizome.c
346 lines (294 loc) · 11.3 KB
/
rhizome.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
/*
Serval Distributed Numbering Architecture (DNA)
Copyright (C) 2010 Paul Gardner-Stephen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "serval.h"
#include "conf.h"
#include "str.h"
#include "rhizome.h"
#include <stdlib.h>
int is_rhizome_enabled()
{
return config.rhizome.enable;
}
int is_rhizome_http_enabled()
{
return config.rhizome.enable
&& config.rhizome.http.enable
&& rhizome_db;
}
int is_rhizome_mdp_enabled()
{
return config.rhizome.enable
&& config.rhizome.mdp.enable
&& rhizome_db;
}
int is_rhizome_mdp_server_running()
{
return is_rhizome_mdp_enabled();
}
int is_rhizome_advertise_enabled()
{
return config.rhizome.enable
&& config.rhizome.advertise.enable
&& rhizome_db
&& (is_rhizome_http_server_running() || is_rhizome_mdp_server_running());
}
int rhizome_fetch_delay_ms()
{
return config.rhizome.fetch_delay_ms;
}
/* Import a bundle from a pair of files, one containing the manifest and the optional other
containing the payload. The logic is all in rhizome_bundle_import(). This function just wraps
that function and manages file and object buffers and lifetimes.
*/
int rhizome_bundle_import_files(rhizome_manifest *m, const char *manifest_path, const char *filepath)
{
if (config.debug.rhizome)
DEBUGF("(manifest_path=%s, filepath=%s)",
manifest_path ? alloca_str_toprint(manifest_path) : "NULL",
filepath ? alloca_str_toprint(filepath) : "NULL");
unsigned char buffer[MAX_MANIFEST_BYTES];
int buffer_len=0;
// manifest has been appended to the end of the file.
if (strcmp(manifest_path, filepath)==0){
unsigned char marker[4];
int ret=0;
FILE *f = fopen(filepath, "r");
if (f == NULL)
return WHYF_perror("Could not open manifest file %s for reading.", filepath);
if (fseek(f, -sizeof(marker), SEEK_END))
ret=WHY_perror("Unable to seek to end of file");
if (ret==0){
ret = fread(marker, 1, sizeof(marker), f);
if (ret==sizeof(marker))
ret=0;
else
ret=WHY_perror("Unable to read end of manifest marker");
}
if (ret==0){
if (marker[2]!=0x41 || marker[3]!=0x10)
ret=WHYF("Expected 0x4110 marker at end of file");
}
if (ret==0){
buffer_len = read_uint16(marker);
if (buffer_len < 1 || buffer_len > MAX_MANIFEST_BYTES)
ret=WHYF("Invalid manifest length %d", buffer_len);
}
if (ret==0){
if (fseek(f, -(buffer_len+sizeof(marker)), SEEK_END))
ret=WHY_perror("Unable to seek to end of file");
}
if (ret==0){
ret = fread(buffer, 1, buffer_len, f);
if (ret==buffer_len)
ret=0;
else
ret=WHY_perror("Unable to read manifest contents");
}
fclose(f);
if (ret)
return ret;
manifest_path=(char*)buffer;
}
if (rhizome_read_manifest_file(m, manifest_path, buffer_len) == -1)
return WHY("could not read manifest file");
if (rhizome_manifest_verify(m))
return WHY("could not verify manifest");
/* Make sure we store signatures */
// TODO, why do we need this? Why isn't the state correct from rhizome_read_manifest_file?
// This feels like a hack...
m->manifest_bytes=m->manifest_all_bytes;
int status = rhizome_import_file(m, filepath);
if (status<0)
return status;
status = rhizome_manifest_check_duplicate(m, NULL, 0);
if (status)
return status;
return rhizome_add_manifest(m, 1);
}
/* Import a bundle from a finalised manifest struct. The dataFileName element must give the path
of a readable file containing the payload unless the payload is null (zero length). The logic is
all in rhizome_add_manifest(). This function just wraps that function and manages object buffers
and lifetimes.
*/
int rhizome_bundle_import(rhizome_manifest *m, int ttl)
{
if (config.debug.rhizome)
DEBUGF("(m=%p, ttl=%d)", m, ttl);
int ret = rhizome_manifest_check_duplicate(m, NULL, 0);
if (ret == 0) {
ret = rhizome_add_manifest(m, ttl);
if (ret == -1)
WHY("rhizome_add_manifest() failed");
}
return ret;
}
int rhizome_manifest_check_sanity(rhizome_manifest *m_in)
{
/* Ensure manifest meets basic sanity checks. */
const char *service = rhizome_manifest_get(m_in, "service", NULL, 0);
const char *sender = rhizome_manifest_get(m_in, "sender", NULL, 0);
const char *recipient = rhizome_manifest_get(m_in, "recipient", NULL, 0);
if (service == NULL || !service[0])
return WHY("Manifest missing 'service' field");
if (rhizome_manifest_get_ll(m_in, "date") == -1)
return WHY("Manifest missing 'date' field");
/* Get manifest version number. */
m_in->version = rhizome_manifest_get_ll(m_in, "version");
if (m_in->version==-1)
return WHY("Manifest must have a version number");
if (strcasecmp(service, RHIZOME_SERVICE_FILE) == 0) {
const char *name = rhizome_manifest_get(m_in, "name", NULL, 0);
if (name == NULL)
return WHY("Manifest missing 'name' field");
} else if (strcasecmp(service, RHIZOME_SERVICE_MESHMS) == 0) {
if (sender == NULL || !sender[0])
return WHY("MeshMS Manifest missing 'sender' field");
if (!str_is_subscriber_id(sender))
return WHYF("MeshMS Manifest contains invalid 'sender' field: %s", sender);
if (recipient == NULL || !recipient[0])
return WHY("MeshMS Manifest missing 'recipient' field");
if (!str_is_subscriber_id(recipient))
return WHYF("MeshMS Manifest contains invalid 'recipient' field: %s", recipient);
} else {
return WHY("Invalid service type");
}
if (config.debug.rhizome)
DEBUGF("sender='%s'", sender ? sender : "(null)");
/* passes all sanity checks */
return 0;
}
/*
A bundle can either be an ordinary manifest-payload pair, or a group description.
- Group descriptions are manifests with no payload that have the "isagroup" variable set. They
get stored in the manifests table AND a reference is added to the grouplist table. Any
manifest, including any group manifest, may be a member of zero or one group. This allows a
nested, i.e., multi-level group hierarchy where sub-groups will only typically be discovered
by joining the parent group.
*/
int rhizome_manifest_bind_id(rhizome_manifest *m_in)
{
if (rhizome_manifest_createid(m_in) == -1)
return -1;
/* The ID is implicit in transit, but we need to store it in the file, so that reimporting
manifests on receiver nodes works easily. We might implement something that strips the id
variable out of the manifest when sending it, or some other scheme to avoid sending all the
extra bytes. */
rhizome_manifest_set(m_in, "id", alloca_tohex_bid(m_in->cryptoSignPublic));
if (!is_sid_any(m_in->author)) {
/* Set the BK using the provided authorship information.
Serval Security Framework defines BK as being:
BK = privateKey XOR sha512(RS##BID), where BID = cryptoSignPublic,
and RS is the rhizome secret for the specified author.
The nice thing about this specification is that:
privateKey = BK XOR sha512(RS##BID), so the same function can be used
to encrypt and decrypt the BK field. */
const unsigned char *rs;
int rs_len=0;
unsigned char bkbytes[RHIZOME_BUNDLE_KEY_BYTES];
if (rhizome_find_secret(m_in->author,&rs_len,&rs)) {
return WHYF("Failed to obtain RS for %s to calculate BK",
alloca_tohex_sid(m_in->author));
}
if (!rhizome_secret2bk(m_in->cryptoSignPublic,rs,rs_len,bkbytes,m_in->cryptoSignSecret)) {
char bkhex[RHIZOME_BUNDLE_KEY_STRLEN + 1];
(void) tohex(bkhex, bkbytes, RHIZOME_BUNDLE_KEY_BYTES);
if (config.debug.rhizome) DEBUGF("set BK=%s", bkhex);
rhizome_manifest_set(m_in, "BK", bkhex);
} else {
return WHY("Failed to set BK");
}
}
return 0;
}
/* Check if a manifest is already stored for the same payload with the same details.
This catches the case of "rhizome add file <filename>" on the same file more than once.
(Debounce!) */
int rhizome_manifest_check_duplicate(rhizome_manifest *m_in, rhizome_manifest **m_out, int check_author)
{
if (config.debug.rhizome) DEBUG("Checking for duplicate");
if (m_out) *m_out = NULL;
rhizome_manifest *dupm = NULL;
if (rhizome_find_duplicate(m_in, &dupm, check_author) == -1)
return WHY("Errors encountered searching for duplicate manifest");
if (dupm) {
/* If the caller wants the duplicate manifest, it must be finalised, otherwise discarded. */
if (m_out) {
*m_out = dupm;
}
else
rhizome_manifest_free(dupm);
if (config.debug.rhizome) DEBUG("Found a duplicate");
return 2;
}
if (config.debug.rhizome) DEBUG("No duplicate found");
return 0;
}
int rhizome_add_manifest(rhizome_manifest *m_in,int ttl)
{
if (config.debug.rhizome)
DEBUGF("rhizome_add_manifest(m_in=%p, ttl=%d)",m_in, ttl);
if (m_in->finalised==0)
return WHY("Manifest must be finalised before being stored");
/* Store time to live, clamped to within legal range */
m_in->ttl = ttl < 0 ? 0 : ttl > 254 ? 254 : ttl;
if (rhizome_manifest_check_sanity(m_in))
return -1;
if (m_in->fileLength){
if (!rhizome_exists(m_in->fileHexHash))
return WHY("File has not been imported");
}
/* If the manifest already has an ID */
char id[SID_STRLEN + 1];
if (!rhizome_manifest_get(m_in, "id", id, SID_STRLEN + 1))
/* no manifest ID */
return WHY("Manifest does not have an ID");
str_toupper_inplace(id);
/* Discard the new manifest unless it is newer than the most recent known version with the same ID */
long long storedversion = -1;
switch (sqlite_exec_int64(&storedversion, "SELECT version from manifests where id='%s';", id)) {
case -1:
return WHY("Select failed");
case 0:
if (config.debug.rhizome) DEBUG("No existing manifest");
break;
case 1:
if (config.debug.rhizome) DEBUGF("Found existing version=%lld, new version=%lld", storedversion, m_in->version);
if (m_in->version < storedversion)
return WHY("Newer version exists");
if (m_in->version == storedversion)
return WHY("Same version of manifest exists, not adding");
break;
default:
return WHY("Select found too many rows!");
}
/* Okay, it is written, and can be put directly into the rhizome database now */
return rhizome_store_bundle(m_in);
}
/* Update an existing Rhizome bundle */
int rhizome_bundle_push_update(char *id,long long version,unsigned char *data,int appendP)
{
return WHY("Not implemented");
}
/* When voice traffic is being carried, we need to throttle Rhizome down
to a more sensible level. Or possibly even supress it entirely.
*/
time_ms_t rhizome_voice_timeout = -1;
int rhizome_saw_voice_traffic()
{
/* We are in "voice mode" for a second after sending a voice frame */
rhizome_voice_timeout=gettime_ms()+1000;
return 0;
}