]> git.ipfire.org Git - people/ms/libloc.git/blob - src/writer.c
Log blocks that are being fed into the signature
[people/ms/libloc.git] / src / writer.c
1 /*
2 libloc - A library to determine the location of someone on the Internet
3
4 Copyright (C) 2017 IPFire Development Team <info@ipfire.org>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15 */
16
17 #include <assert.h>
18 #include <errno.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/queue.h>
23 #include <time.h>
24
25 #ifdef HAVE_ENDIAN_H
26 # include <endian.h>
27 #endif
28
29 #include <openssl/bio.h>
30 #include <openssl/err.h>
31 #include <openssl/evp.h>
32 #include <openssl/pem.h>
33
34 #include <loc/libloc.h>
35 #include <loc/as.h>
36 #include <loc/compat.h>
37 #include <loc/country.h>
38 #include <loc/database.h>
39 #include <loc/format.h>
40 #include <loc/network.h>
41 #include <loc/private.h>
42 #include <loc/writer.h>
43
44 struct loc_writer {
45 struct loc_ctx* ctx;
46 int refcount;
47
48 struct loc_stringpool* pool;
49 off_t vendor;
50 off_t description;
51 off_t license;
52
53 // Private key to sign any databases
54 EVP_PKEY* private_key;
55
56 struct loc_as** as;
57 size_t as_count;
58
59 struct loc_country** countries;
60 size_t countries_count;
61
62 struct loc_network_tree* networks;
63 };
64
65 static int parse_private_key(struct loc_writer* writer, FILE* f) {
66 // Free any previously loaded keys
67 if (writer->private_key)
68 EVP_PKEY_free(writer->private_key);
69
70 // Read the key
71 writer->private_key = PEM_read_PrivateKey(f, NULL, NULL, NULL);
72
73 // Log any errors
74 if (!writer->private_key) {
75 char* error = ERR_error_string(ERR_get_error(), NULL);
76 ERROR(writer->ctx, "Could not parse private key: %s\n", error);
77
78 return -1;
79 }
80
81 return 0;
82 }
83
84 LOC_EXPORT int loc_writer_new(struct loc_ctx* ctx, struct loc_writer** writer, FILE* fkey) {
85 struct loc_writer* w = calloc(1, sizeof(*w));
86 if (!w)
87 return -ENOMEM;
88
89 w->ctx = loc_ref(ctx);
90 w->refcount = 1;
91
92 int r = loc_stringpool_new(ctx, &w->pool);
93 if (r) {
94 loc_writer_unref(w);
95 return r;
96 }
97
98 // Initialize the network tree
99 r = loc_network_tree_new(ctx, &w->networks);
100 if (r) {
101 loc_writer_unref(w);
102 return r;
103 }
104
105 // Load the private key to sign databases
106 if (fkey) {
107 r = parse_private_key(w, fkey);
108 if (r) {
109 loc_writer_unref(w);
110 return r;
111 }
112 }
113
114 *writer = w;
115 return 0;
116 }
117
118 LOC_EXPORT struct loc_writer* loc_writer_ref(struct loc_writer* writer) {
119 writer->refcount++;
120
121 return writer;
122 }
123
124 static void loc_writer_free(struct loc_writer* writer) {
125 DEBUG(writer->ctx, "Releasing writer at %p\n", writer);
126
127 // Free private key
128 if (writer->private_key)
129 EVP_PKEY_free(writer->private_key);
130
131 // Unref all AS
132 for (unsigned int i = 0; i < writer->as_count; i++) {
133 loc_as_unref(writer->as[i]);
134 }
135
136 // Release network tree
137 if (writer->networks)
138 loc_network_tree_unref(writer->networks);
139
140 // Unref the string pool
141 loc_stringpool_unref(writer->pool);
142
143 loc_unref(writer->ctx);
144 free(writer);
145 }
146
147 LOC_EXPORT struct loc_writer* loc_writer_unref(struct loc_writer* writer) {
148 if (--writer->refcount > 0)
149 return writer;
150
151 loc_writer_free(writer);
152
153 return NULL;
154 }
155
156 LOC_EXPORT const char* loc_writer_get_vendor(struct loc_writer* writer) {
157 return loc_stringpool_get(writer->pool, writer->vendor);
158 }
159
160 LOC_EXPORT int loc_writer_set_vendor(struct loc_writer* writer, const char* vendor) {
161 // Add the string to the string pool
162 off_t offset = loc_stringpool_add(writer->pool, vendor);
163 if (offset < 0)
164 return offset;
165
166 writer->vendor = offset;
167 return 0;
168 }
169
170 LOC_EXPORT const char* loc_writer_get_description(struct loc_writer* writer) {
171 return loc_stringpool_get(writer->pool, writer->description);
172 }
173
174 LOC_EXPORT int loc_writer_set_description(struct loc_writer* writer, const char* description) {
175 // Add the string to the string pool
176 off_t offset = loc_stringpool_add(writer->pool, description);
177 if (offset < 0)
178 return offset;
179
180 writer->description = offset;
181 return 0;
182 }
183
184 LOC_EXPORT const char* loc_writer_get_license(struct loc_writer* writer) {
185 return loc_stringpool_get(writer->pool, writer->license);
186 }
187
188 LOC_EXPORT int loc_writer_set_license(struct loc_writer* writer, const char* license) {
189 // Add the string to the string pool
190 off_t offset = loc_stringpool_add(writer->pool, license);
191 if (offset < 0)
192 return offset;
193
194 writer->license = offset;
195 return 0;
196 }
197
198 static int __loc_as_cmp(const void* as1, const void* as2) {
199 return loc_as_cmp(*(struct loc_as**)as1, *(struct loc_as**)as2);
200 }
201
202 LOC_EXPORT int loc_writer_add_as(struct loc_writer* writer, struct loc_as** as, uint32_t number) {
203 int r = loc_as_new(writer->ctx, as, number);
204 if (r)
205 return r;
206
207 // We have a new AS to add
208 writer->as_count++;
209
210 // Make space
211 writer->as = realloc(writer->as, sizeof(*writer->as) * writer->as_count);
212 if (!writer->as)
213 return -ENOMEM;
214
215 // Add as last element
216 writer->as[writer->as_count - 1] = loc_as_ref(*as);
217
218 // Sort everything
219 qsort(writer->as, writer->as_count, sizeof(*writer->as), __loc_as_cmp);
220
221 return 0;
222 }
223
224 LOC_EXPORT int loc_writer_add_network(struct loc_writer* writer, struct loc_network** network, const char* string) {
225 int r;
226
227 // Create a new network object
228 r = loc_network_new_from_string(writer->ctx, network, string);
229 if (r)
230 return r;
231
232 // Add it to the local tree
233 return loc_network_tree_add_network(writer->networks, *network);
234 }
235
236 static int __loc_country_cmp(const void* country1, const void* country2) {
237 return loc_country_cmp(*(struct loc_country**)country1, *(struct loc_country**)country2);
238 }
239
240 LOC_EXPORT int loc_writer_add_country(struct loc_writer* writer, struct loc_country** country, const char* country_code) {
241 int r = loc_country_new(writer->ctx, country, country_code);
242 if (r)
243 return r;
244
245 // We have a new country to add
246 writer->countries_count++;
247
248 // Make space
249 writer->countries = realloc(writer->countries, sizeof(*writer->countries) * writer->countries_count);
250 if (!writer->countries)
251 return -ENOMEM;
252
253 // Add as last element
254 writer->countries[writer->countries_count - 1] = loc_country_ref(*country);
255
256 // Sort everything
257 qsort(writer->countries, writer->countries_count, sizeof(*writer->countries), __loc_country_cmp);
258
259 return 0;
260 }
261
262 static void make_magic(struct loc_writer* writer, struct loc_database_magic* magic) {
263 // Copy magic bytes
264 for (unsigned int i = 0; i < strlen(LOC_DATABASE_MAGIC); i++)
265 magic->magic[i] = LOC_DATABASE_MAGIC[i];
266
267 // Set version
268 magic->version = htobe16(LOC_DATABASE_VERSION);
269 }
270
271 static void align_page_boundary(off_t* offset, FILE* f) {
272 // Move to next page boundary
273 while (*offset % LOC_DATABASE_PAGE_SIZE > 0)
274 *offset += fwrite("", 1, 1, f);
275 }
276
277 static int loc_database_write_pool(struct loc_writer* writer,
278 struct loc_database_header_v0* header, off_t* offset, FILE* f) {
279 // Save the offset of the pool section
280 DEBUG(writer->ctx, "Pool starts at %jd bytes\n", (intmax_t)*offset);
281 header->pool_offset = htobe32(*offset);
282
283 // Write the pool
284 size_t pool_length = loc_stringpool_write(writer->pool, f);
285 *offset += pool_length;
286
287 DEBUG(writer->ctx, "Pool has a length of %zu bytes\n", pool_length);
288 header->pool_length = htobe32(pool_length);
289
290 return 0;
291 }
292
293 static int loc_database_write_as_section(struct loc_writer* writer,
294 struct loc_database_header_v0* header, off_t* offset, FILE* f) {
295 DEBUG(writer->ctx, "AS section starts at %jd bytes\n", (intmax_t)*offset);
296 header->as_offset = htobe32(*offset);
297
298 size_t as_length = 0;
299
300 struct loc_database_as_v0 as;
301 for (unsigned int i = 0; i < writer->as_count; i++) {
302 // Convert AS into database format
303 loc_as_to_database_v0(writer->as[i], writer->pool, &as);
304
305 // Write to disk
306 *offset += fwrite(&as, 1, sizeof(as), f);
307 as_length += sizeof(as);
308 }
309
310 DEBUG(writer->ctx, "AS section has a length of %zu bytes\n", as_length);
311 header->as_length = htobe32(as_length);
312
313 align_page_boundary(offset, f);
314
315 return 0;
316 }
317
318 struct node {
319 TAILQ_ENTRY(node) nodes;
320
321 struct loc_network_tree_node* node;
322
323 // Indices of the child nodes
324 uint32_t index_zero;
325 uint32_t index_one;
326 };
327
328 static struct node* make_node(struct loc_network_tree_node* node) {
329 struct node* n = malloc(sizeof(*n));
330 if (!n)
331 return NULL;
332
333 n->node = loc_network_tree_node_ref(node);
334 n->index_zero = n->index_one = 0;
335
336 return n;
337 }
338
339 static void free_node(struct node* node) {
340 loc_network_tree_node_unref(node->node);
341
342 free(node);
343 }
344
345 struct network {
346 TAILQ_ENTRY(network) networks;
347
348 struct loc_network* network;
349 };
350
351 static struct network* make_network(struct loc_network* network) {
352 struct network* n = malloc(sizeof(*n));
353 if (!n)
354 return NULL;
355
356 n->network = loc_network_ref(network);
357
358 return n;
359 }
360
361 static void free_network(struct network* network) {
362 loc_network_unref(network->network);
363
364 free(network);
365 }
366
367 static int loc_database_write_networks(struct loc_writer* writer,
368 struct loc_database_header_v0* header, off_t* offset, FILE* f) {
369 // Write the network tree
370 DEBUG(writer->ctx, "Network tree starts at %jd bytes\n", (intmax_t)*offset);
371 header->network_tree_offset = htobe32(*offset);
372
373 size_t network_tree_length = 0;
374 size_t network_data_length = 0;
375
376 struct node* node;
377 struct node* child_node;
378
379 uint32_t index = 0;
380 uint32_t network_index = 0;
381
382 struct loc_database_network_v0 db_network;
383 struct loc_database_network_node_v0 db_node;
384
385 // Initialize queue for nodes
386 TAILQ_HEAD(node_t, node) nodes;
387 TAILQ_INIT(&nodes);
388
389 // Initialize queue for networks
390 TAILQ_HEAD(network_t, network) networks;
391 TAILQ_INIT(&networks);
392
393 // Add root
394 struct loc_network_tree_node* root = loc_network_tree_get_root(writer->networks);
395 node = make_node(root);
396
397 TAILQ_INSERT_TAIL(&nodes, node, nodes);
398
399 while (!TAILQ_EMPTY(&nodes)) {
400 // Pop first node in list
401 node = TAILQ_FIRST(&nodes);
402 TAILQ_REMOVE(&nodes, node, nodes);
403
404 DEBUG(writer->ctx, "Processing node %p\n", node);
405
406 // Get child nodes
407 struct loc_network_tree_node* node_zero = loc_network_tree_node_get(node->node, 0);
408 if (node_zero) {
409 node->index_zero = ++index;
410
411 child_node = make_node(node_zero);
412 loc_network_tree_node_unref(node_zero);
413
414 TAILQ_INSERT_TAIL(&nodes, child_node, nodes);
415 }
416
417 struct loc_network_tree_node* node_one = loc_network_tree_node_get(node->node, 1);
418 if (node_one) {
419 node->index_one = ++index;
420
421 child_node = make_node(node_one);
422 loc_network_tree_node_unref(node_one);
423
424 TAILQ_INSERT_TAIL(&nodes, child_node, nodes);
425 }
426
427 // Prepare what we are writing to disk
428 db_node.zero = htobe32(node->index_zero);
429 db_node.one = htobe32(node->index_one);
430
431 if (loc_network_tree_node_is_leaf(node->node)) {
432 struct loc_network* network = loc_network_tree_node_get_network(node->node);
433
434 // Append network to be written out later
435 struct network* nw = make_network(network);
436 TAILQ_INSERT_TAIL(&networks, nw, networks);
437
438 db_node.network = htobe32(network_index++);
439 loc_network_unref(network);
440 } else {
441 db_node.network = htobe32(0xffffffff);
442 }
443
444 // Write the current node
445 DEBUG(writer->ctx, "Writing node %p (0 = %d, 1 = %d)\n",
446 node, node->index_zero, node->index_one);
447
448 *offset += fwrite(&db_node, 1, sizeof(db_node), f);
449 network_tree_length += sizeof(db_node);
450
451 free_node(node);
452 }
453
454 loc_network_tree_node_unref(root);
455
456 header->network_tree_length = htobe32(network_tree_length);
457
458 align_page_boundary(offset, f);
459
460 DEBUG(writer->ctx, "Networks data section starts at %jd bytes\n", (intmax_t)*offset);
461 header->network_data_offset = htobe32(*offset);
462
463 // We have now written the entire tree and have all networks
464 // in a queue in order as they are indexed
465 while (!TAILQ_EMPTY(&networks)) {
466 struct network* nw = TAILQ_FIRST(&networks);
467 TAILQ_REMOVE(&networks, nw, networks);
468
469 // Prepare what we are writing to disk
470 int r = loc_network_to_database_v0(nw->network, &db_network);
471 if (r)
472 return r;
473
474 *offset += fwrite(&db_network, 1, sizeof(db_network), f);
475 network_data_length += sizeof(db_network);
476
477 free_network(nw);
478 }
479
480 header->network_data_length = htobe32(network_data_length);
481
482 align_page_boundary(offset, f);
483
484 return 0;
485 }
486
487 static int loc_database_write_countries(struct loc_writer* writer,
488 struct loc_database_header_v0* header, off_t* offset, FILE* f) {
489 DEBUG(writer->ctx, "Countries section starts at %jd bytes\n", (intmax_t)*offset);
490 header->countries_offset = htobe32(*offset);
491
492 size_t countries_length = 0;
493
494 struct loc_database_country_v0 country;
495 for (unsigned int i = 0; i < writer->countries_count; i++) {
496 // Convert country into database format
497 loc_country_to_database_v0(writer->countries[i], writer->pool, &country);
498
499 // Write to disk
500 *offset += fwrite(&country, 1, sizeof(country), f);
501 countries_length += sizeof(country);
502 }
503
504 DEBUG(writer->ctx, "Countries section has a length of %zu bytes\n", countries_length);
505 header->countries_length = htobe32(countries_length);
506
507 align_page_boundary(offset, f);
508
509 return 0;
510 }
511
512 static int loc_writer_create_signature(struct loc_writer* writer,
513 struct loc_database_header_v0* header, FILE* f) {
514 DEBUG(writer->ctx, "Signing database...\n");
515
516 // Read file from the beginning
517 rewind(f);
518
519 // Create a new context for signing
520 EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
521
522 // Initialise the context
523 int r = EVP_DigestSignInit(mdctx, NULL, NULL, NULL, writer->private_key);
524 if (r != 1) {
525 ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
526 goto END;
527 }
528
529 // Read magic
530 struct loc_database_magic magic;
531 fread(&magic, 1, sizeof(magic), f);
532
533 hexdump(writer->ctx, &magic, sizeof(magic));
534
535 // Feed magic into the signature
536 r = EVP_DigestSignUpdate(mdctx, &magic, sizeof(magic));
537 if (r != 1) {
538 ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
539 goto END;
540 }
541
542 hexdump(writer->ctx, header, sizeof(*header));
543
544 // Feed the header into the signature
545 r = EVP_DigestSignUpdate(mdctx, header, sizeof(*header));
546 if (r != 1) {
547 ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
548 goto END;
549 }
550
551 // Skip header
552 fseek(f, sizeof(*header), SEEK_CUR);
553
554 // Walk through the file in chunks of 64kB
555 char buffer[64 * 1024];
556 while (!feof(f)) {
557 size_t bytes_read = fread(buffer, 1, sizeof(buffer), f);
558
559 if (ferror(f)) {
560 ERROR(writer->ctx, "Error reading from file: %s\n", strerror(errno));
561 r = errno;
562 goto END;
563 }
564
565 hexdump(writer->ctx, buffer, bytes_read);
566
567 r = EVP_DigestSignUpdate(mdctx, buffer, bytes_read);
568 if (r != 1) {
569 ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
570 goto END;
571 }
572 }
573
574 // Compute the signature
575 size_t signature_length = sizeof(header->signature);
576
577 r = EVP_DigestSignFinal(mdctx,
578 (unsigned char*)header->signature, &signature_length);
579 if (r != 1) {
580 ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
581 goto END;
582 }
583
584 // Save length of the signature
585 header->signature_length = htobe32(signature_length);
586
587 DEBUG(writer->ctx, "Successfully generated signature of %lu bytes\n",
588 signature_length);
589 r = 0;
590
591 // Dump signature
592 hexdump(writer->ctx, header->signature, signature_length);
593
594 END:
595 EVP_MD_CTX_free(mdctx);
596
597 return r;
598 }
599
600 LOC_EXPORT int loc_writer_write(struct loc_writer* writer, FILE* f) {
601 struct loc_database_magic magic;
602 make_magic(writer, &magic);
603
604 // Make the header
605 struct loc_database_header_v0 header;
606 header.vendor = htobe32(writer->vendor);
607 header.description = htobe32(writer->description);
608 header.license = htobe32(writer->license);
609
610 time_t now = time(NULL);
611 header.created_at = htobe64(now);
612
613 // Clear the signature
614 header.signature_length = 0;
615 for (unsigned int i = 0; i < sizeof(header.signature); i++)
616 header.signature[i] = '\0';
617
618 int r;
619 off_t offset = 0;
620
621 // Start writing at the beginning of the file
622 r = fseek(f, 0, SEEK_SET);
623 if (r)
624 return r;
625
626 // Write the magic
627 offset += fwrite(&magic, 1, sizeof(magic), f);
628
629 // Skip the space we need to write the header later
630 r = fseek(f, sizeof(header), SEEK_CUR);
631 if (r) {
632 DEBUG(writer->ctx, "Could not seek to position after header\n");
633 return r;
634 }
635 offset += sizeof(header);
636
637 align_page_boundary(&offset, f);
638
639 // Write all ASes
640 r = loc_database_write_as_section(writer, &header, &offset, f);
641 if (r)
642 return r;
643
644 // Write all networks
645 r = loc_database_write_networks(writer, &header, &offset, f);
646 if (r)
647 return r;
648
649 // Write countries
650 r = loc_database_write_countries(writer, &header, &offset, f);
651 if (r)
652 return r;
653
654 // Write pool
655 r = loc_database_write_pool(writer, &header, &offset, f);
656 if (r)
657 return r;
658
659 // Create the signature
660 if (writer->private_key) {
661 r = loc_writer_create_signature(writer, &header, f);
662 if (r)
663 return r;
664 }
665
666 // Write the header
667 r = fseek(f, sizeof(magic), SEEK_SET);
668 if (r)
669 return r;
670
671 fwrite(&header, 1, sizeof(header), f);
672
673 return r;
674 }