]> git.ipfire.org Git - people/ms/libloc.git/blob - src/writer.c
9c1db3acb305cb84536bd9f0c8aba5e752987d58
[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 <libloc/libloc.h>
35 #include <libloc/as.h>
36 #include <libloc/as-list.h>
37 #include <libloc/compat.h>
38 #include <libloc/country.h>
39 #include <libloc/country-list.h>
40 #include <libloc/database.h>
41 #include <libloc/format.h>
42 #include <libloc/network.h>
43 #include <libloc/network-tree.h>
44 #include <libloc/private.h>
45 #include <libloc/writer.h>
46
47 struct loc_writer {
48 struct loc_ctx* ctx;
49 int refcount;
50
51 struct loc_stringpool* pool;
52 off_t vendor;
53 off_t description;
54 off_t license;
55
56 // Private keys to sign any databases
57 EVP_PKEY* private_key1;
58 EVP_PKEY* private_key2;
59
60 // Signatures
61 char signature1[LOC_SIGNATURE_MAX_LENGTH];
62 size_t signature1_length;
63 char signature2[LOC_SIGNATURE_MAX_LENGTH];
64 size_t signature2_length;
65
66 struct loc_network_tree* networks;
67
68 struct loc_as_list* as_list;
69 struct loc_country_list* country_list;
70 };
71
72 static int parse_private_key(struct loc_writer* writer, EVP_PKEY** private_key, FILE* f) {
73 // Free any previously loaded keys
74 if (*private_key)
75 EVP_PKEY_free(*private_key);
76
77 // Read the key
78 *private_key = PEM_read_PrivateKey(f, NULL, NULL, NULL);
79
80 // Log any errors
81 if (!*private_key) {
82 char* error = ERR_error_string(ERR_get_error(), NULL);
83 ERROR(writer->ctx, "Could not parse private key: %s\n", error);
84
85 return -1;
86 }
87
88 return 0;
89 }
90
91 LOC_EXPORT int loc_writer_new(struct loc_ctx* ctx, struct loc_writer** writer,
92 FILE* fkey1, FILE* fkey2) {
93 struct loc_writer* w = calloc(1, sizeof(*w));
94 if (!w)
95 return 1;
96
97 w->ctx = loc_ref(ctx);
98 w->refcount = 1;
99
100 int r = loc_stringpool_new(ctx, &w->pool);
101 if (r) {
102 loc_writer_unref(w);
103 return r;
104 }
105
106 // Add an empty string to the stringpool
107 r = loc_stringpool_add(w->pool, "");
108 if (r) {
109 loc_writer_unref(w);
110 return r;
111 }
112
113 // Initialize the network tree
114 r = loc_network_tree_new(ctx, &w->networks);
115 if (r) {
116 loc_writer_unref(w);
117 return r;
118 }
119
120 // Initialize AS list
121 r = loc_as_list_new(ctx, &w->as_list);
122 if (r) {
123 loc_writer_unref(w);
124 return r;
125 }
126
127 // Initialize countries list
128 r = loc_country_list_new(ctx, &w->country_list);
129 if (r) {
130 loc_writer_unref(w);
131 return r;
132 }
133
134 // Load the private keys to sign databases
135 if (fkey1) {
136 r = parse_private_key(w, &w->private_key1, fkey1);
137 if (r) {
138 loc_writer_unref(w);
139 return r;
140 }
141 }
142
143 if (fkey2) {
144 r = parse_private_key(w, &w->private_key2, fkey2);
145 if (r) {
146 loc_writer_unref(w);
147 return r;
148 }
149 }
150
151 *writer = w;
152 return 0;
153 }
154
155 LOC_EXPORT struct loc_writer* loc_writer_ref(struct loc_writer* writer) {
156 writer->refcount++;
157
158 return writer;
159 }
160
161 static void loc_writer_free(struct loc_writer* writer) {
162 DEBUG(writer->ctx, "Releasing writer at %p\n", writer);
163
164 // Free private keys
165 if (writer->private_key1)
166 EVP_PKEY_free(writer->private_key1);
167 if (writer->private_key2)
168 EVP_PKEY_free(writer->private_key2);
169
170 // Unref all AS
171 if (writer->as_list)
172 loc_as_list_unref(writer->as_list);
173
174 // Unref all countries
175 if (writer->country_list)
176 loc_country_list_unref(writer->country_list);
177
178 // Release network tree
179 if (writer->networks)
180 loc_network_tree_unref(writer->networks);
181
182 // Unref the string pool
183 if (writer->pool)
184 loc_stringpool_unref(writer->pool);
185
186 loc_unref(writer->ctx);
187 free(writer);
188 }
189
190 LOC_EXPORT struct loc_writer* loc_writer_unref(struct loc_writer* writer) {
191 if (--writer->refcount > 0)
192 return writer;
193
194 loc_writer_free(writer);
195
196 return NULL;
197 }
198
199 LOC_EXPORT const char* loc_writer_get_vendor(struct loc_writer* writer) {
200 return loc_stringpool_get(writer->pool, writer->vendor);
201 }
202
203 LOC_EXPORT int loc_writer_set_vendor(struct loc_writer* writer, const char* vendor) {
204 // Add the string to the string pool
205 off_t offset = loc_stringpool_add(writer->pool, vendor);
206 if (offset < 0)
207 return offset;
208
209 writer->vendor = offset;
210 return 0;
211 }
212
213 LOC_EXPORT const char* loc_writer_get_description(struct loc_writer* writer) {
214 return loc_stringpool_get(writer->pool, writer->description);
215 }
216
217 LOC_EXPORT int loc_writer_set_description(struct loc_writer* writer, const char* description) {
218 // Add the string to the string pool
219 off_t offset = loc_stringpool_add(writer->pool, description);
220 if (offset < 0)
221 return offset;
222
223 writer->description = offset;
224 return 0;
225 }
226
227 LOC_EXPORT const char* loc_writer_get_license(struct loc_writer* writer) {
228 return loc_stringpool_get(writer->pool, writer->license);
229 }
230
231 LOC_EXPORT int loc_writer_set_license(struct loc_writer* writer, const char* license) {
232 // Add the string to the string pool
233 off_t offset = loc_stringpool_add(writer->pool, license);
234 if (offset < 0)
235 return offset;
236
237 writer->license = offset;
238 return 0;
239 }
240
241 LOC_EXPORT int loc_writer_add_as(struct loc_writer* writer, struct loc_as** as, uint32_t number) {
242 // Create a new AS object
243 int r = loc_as_new(writer->ctx, as, number);
244 if (r)
245 return r;
246
247 // Append it to the list
248 return loc_as_list_append(writer->as_list, *as);
249 }
250
251 LOC_EXPORT int loc_writer_add_network(struct loc_writer* writer, struct loc_network** network, const char* string) {
252 int r;
253
254 // Create a new network object
255 r = loc_network_new_from_string(writer->ctx, network, string);
256 if (r)
257 return r;
258
259 // Add it to the local tree
260 return loc_network_tree_add_network(writer->networks, *network);
261 }
262
263 LOC_EXPORT int loc_writer_add_country(struct loc_writer* writer, struct loc_country** country, const char* country_code) {
264 // Allocate a new country
265 int r = loc_country_new(writer->ctx, country, country_code);
266 if (r)
267 return r;
268
269 // Append it to the list
270 return loc_country_list_append(writer->country_list, *country);
271 }
272
273 static void make_magic(struct loc_writer* writer, struct loc_database_magic* magic,
274 enum loc_database_version version) {
275 // Copy magic bytes
276 for (unsigned int i = 0; i < strlen(LOC_DATABASE_MAGIC); i++)
277 magic->magic[i] = LOC_DATABASE_MAGIC[i];
278
279 // Set version
280 magic->version = version;
281 }
282
283 static void align_page_boundary(off_t* offset, FILE* f) {
284 // Move to next page boundary
285 while (*offset % LOC_DATABASE_PAGE_SIZE > 0)
286 *offset += fwrite("", 1, 1, f);
287 }
288
289 static int loc_database_write_pool(struct loc_writer* writer,
290 struct loc_database_header_v1* header, off_t* offset, FILE* f) {
291 // Save the offset of the pool section
292 DEBUG(writer->ctx, "Pool starts at %jd bytes\n", (intmax_t)*offset);
293 header->pool_offset = htobe32(*offset);
294
295 // Write the pool
296 size_t pool_length = loc_stringpool_write(writer->pool, f);
297 *offset += pool_length;
298
299 DEBUG(writer->ctx, "Pool has a length of %zu bytes\n", pool_length);
300 header->pool_length = htobe32(pool_length);
301
302 return 0;
303 }
304
305 static int loc_database_write_as_section(struct loc_writer* writer,
306 struct loc_database_header_v1* header, off_t* offset, FILE* f) {
307 DEBUG(writer->ctx, "AS section starts at %jd bytes\n", (intmax_t)*offset);
308 header->as_offset = htobe32(*offset);
309
310 // Sort the AS list first
311 loc_as_list_sort(writer->as_list);
312
313 const size_t as_count = loc_as_list_size(writer->as_list);
314
315 struct loc_database_as_v1 block;
316 size_t block_length = 0;
317
318 for (unsigned int i = 0; i < as_count; i++) {
319 struct loc_as* as = loc_as_list_get(writer->as_list, i);
320 if (!as)
321 return 1;
322
323 // Convert AS into database format
324 loc_as_to_database_v1(as, writer->pool, &block);
325
326 // Write to disk
327 *offset += fwrite(&block, 1, sizeof(block), f);
328 block_length += sizeof(block);
329
330 // Unref AS
331 loc_as_unref(as);
332 }
333
334 DEBUG(writer->ctx, "AS section has a length of %zu bytes\n", block_length);
335 header->as_length = htobe32(block_length);
336
337 align_page_boundary(offset, f);
338
339 return 0;
340 }
341
342 struct node {
343 TAILQ_ENTRY(node) nodes;
344
345 struct loc_network_tree_node* node;
346
347 // Indices of the child nodes
348 uint32_t index_zero;
349 uint32_t index_one;
350 };
351
352 static struct node* make_node(struct loc_network_tree_node* node) {
353 struct node* n = malloc(sizeof(*n));
354 if (!n)
355 return NULL;
356
357 n->node = loc_network_tree_node_ref(node);
358 n->index_zero = n->index_one = 0;
359
360 return n;
361 }
362
363 static void free_node(struct node* node) {
364 loc_network_tree_node_unref(node->node);
365
366 free(node);
367 }
368
369 struct network {
370 TAILQ_ENTRY(network) networks;
371
372 struct loc_network* network;
373 };
374
375 static struct network* make_network(struct loc_network* network) {
376 struct network* n = malloc(sizeof(*n));
377 if (!n)
378 return NULL;
379
380 n->network = loc_network_ref(network);
381
382 return n;
383 }
384
385 static void free_network(struct network* network) {
386 loc_network_unref(network->network);
387
388 free(network);
389 }
390
391 static int loc_database_write_networks(struct loc_writer* writer,
392 struct loc_database_header_v1* header, off_t* offset, FILE* f) {
393 int r;
394
395 // Write the network tree
396 DEBUG(writer->ctx, "Network tree starts at %jd bytes\n", (intmax_t)*offset);
397 header->network_tree_offset = htobe32(*offset);
398
399 size_t network_tree_length = 0;
400 size_t network_data_length = 0;
401
402 struct node* node;
403 struct node* child_node;
404
405 uint32_t index = 0;
406 uint32_t network_index = 0;
407
408 struct loc_database_network_v1 db_network;
409 struct loc_database_network_node_v1 db_node;
410
411 // Initialize queue for nodes
412 TAILQ_HEAD(node_t, node) nodes;
413 TAILQ_INIT(&nodes);
414
415 // Initialize queue for networks
416 TAILQ_HEAD(network_t, network) networks;
417 TAILQ_INIT(&networks);
418
419 // Cleanup the tree before writing it
420 r = loc_network_tree_cleanup(writer->networks);
421 if (r)
422 return r;
423
424 // Add root
425 struct loc_network_tree_node* root = loc_network_tree_get_root(writer->networks);
426 node = make_node(root);
427 if (!node)
428 return 1;
429
430 TAILQ_INSERT_TAIL(&nodes, node, nodes);
431
432 while (!TAILQ_EMPTY(&nodes)) {
433 // Pop first node in list
434 node = TAILQ_FIRST(&nodes);
435 TAILQ_REMOVE(&nodes, node, nodes);
436
437 DEBUG(writer->ctx, "Processing node %p\n", node);
438
439 // Get child nodes
440 struct loc_network_tree_node* node_zero = loc_network_tree_node_get(node->node, 0);
441 if (node_zero) {
442 node->index_zero = ++index;
443
444 child_node = make_node(node_zero);
445 loc_network_tree_node_unref(node_zero);
446
447 TAILQ_INSERT_TAIL(&nodes, child_node, nodes);
448 }
449
450 struct loc_network_tree_node* node_one = loc_network_tree_node_get(node->node, 1);
451 if (node_one) {
452 node->index_one = ++index;
453
454 child_node = make_node(node_one);
455 loc_network_tree_node_unref(node_one);
456
457 TAILQ_INSERT_TAIL(&nodes, child_node, nodes);
458 }
459
460 // Prepare what we are writing to disk
461 db_node.zero = htobe32(node->index_zero);
462 db_node.one = htobe32(node->index_one);
463
464 if (loc_network_tree_node_is_leaf(node->node)) {
465 struct loc_network* network = loc_network_tree_node_get_network(node->node);
466
467 // Append network to be written out later
468 struct network* nw = make_network(network);
469 if (!nw) {
470 free_node(node);
471 return 1;
472 }
473 TAILQ_INSERT_TAIL(&networks, nw, networks);
474
475 db_node.network = htobe32(network_index++);
476 loc_network_unref(network);
477 } else {
478 db_node.network = htobe32(0xffffffff);
479 }
480
481 // Write the current node
482 DEBUG(writer->ctx, "Writing node %p (0 = %u, 1 = %u)\n",
483 node, node->index_zero, node->index_one);
484
485 *offset += fwrite(&db_node, 1, sizeof(db_node), f);
486 network_tree_length += sizeof(db_node);
487
488 free_node(node);
489 }
490
491 loc_network_tree_node_unref(root);
492
493 header->network_tree_length = htobe32(network_tree_length);
494
495 align_page_boundary(offset, f);
496
497 DEBUG(writer->ctx, "Networks data section starts at %jd bytes\n", (intmax_t)*offset);
498 header->network_data_offset = htobe32(*offset);
499
500 // We have now written the entire tree and have all networks
501 // in a queue in order as they are indexed
502 while (!TAILQ_EMPTY(&networks)) {
503 struct network* nw = TAILQ_FIRST(&networks);
504 TAILQ_REMOVE(&networks, nw, networks);
505
506 // Prepare what we are writing to disk
507 r = loc_network_to_database_v1(nw->network, &db_network);
508 if (r)
509 return r;
510
511 *offset += fwrite(&db_network, 1, sizeof(db_network), f);
512 network_data_length += sizeof(db_network);
513
514 free_network(nw);
515 }
516
517 header->network_data_length = htobe32(network_data_length);
518
519 align_page_boundary(offset, f);
520
521 return 0;
522 }
523
524 static int loc_database_write_countries(struct loc_writer* writer,
525 struct loc_database_header_v1* header, off_t* offset, FILE* f) {
526 DEBUG(writer->ctx, "Countries section starts at %jd bytes\n", (intmax_t)*offset);
527 header->countries_offset = htobe32(*offset);
528
529 const size_t countries_count = loc_country_list_size(writer->country_list);
530
531 struct loc_database_country_v1 block;
532 size_t block_length = 0;
533
534 for (unsigned int i = 0; i < countries_count; i++) {
535 struct loc_country* country = loc_country_list_get(writer->country_list, i);
536
537 // Convert country into database format
538 loc_country_to_database_v1(country, writer->pool, &block);
539
540 // Write to disk
541 *offset += fwrite(&block, 1, sizeof(block), f);
542 block_length += sizeof(block);
543 }
544
545 DEBUG(writer->ctx, "Countries section has a length of %zu bytes\n", block_length);
546 header->countries_length = htobe32(block_length);
547
548 align_page_boundary(offset, f);
549
550 return 0;
551 }
552
553 static int loc_writer_create_signature(struct loc_writer* writer,
554 struct loc_database_header_v1* header, FILE* f, EVP_PKEY* private_key,
555 char* signature, size_t* length) {
556 size_t bytes_read = 0;
557
558 DEBUG(writer->ctx, "Creating signature...\n");
559
560 // Read file from the beginning
561 rewind(f);
562
563 // Create a new context for signing
564 EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
565
566 // Initialise the context
567 int r = EVP_DigestSignInit(mdctx, NULL, NULL, NULL, private_key);
568 if (r != 1) {
569 ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
570 goto END;
571 }
572
573 // Read magic
574 struct loc_database_magic magic;
575 bytes_read = fread(&magic, 1, sizeof(magic), f);
576 if (bytes_read < sizeof(magic)) {
577 ERROR(writer->ctx, "Could not read header: %m\n");
578 r = 1;
579 goto END;
580 }
581
582 hexdump(writer->ctx, &magic, sizeof(magic));
583
584 // Feed magic into the signature
585 r = EVP_DigestSignUpdate(mdctx, &magic, sizeof(magic));
586 if (r != 1) {
587 ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
588 goto END;
589 }
590
591 hexdump(writer->ctx, header, sizeof(*header));
592
593 // Feed the header into the signature
594 r = EVP_DigestSignUpdate(mdctx, header, sizeof(*header));
595 if (r != 1) {
596 ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
597 goto END;
598 }
599
600 // Skip header
601 fseek(f, sizeof(*header), SEEK_CUR);
602
603 // Walk through the file in chunks of 64kB
604 char buffer[64 * 1024];
605 while (!feof(f)) {
606 bytes_read = fread(buffer, 1, sizeof(buffer), f);
607
608 if (ferror(f)) {
609 ERROR(writer->ctx, "Error reading from file: %m\n");
610 r = 1;
611 goto END;
612 }
613
614 hexdump(writer->ctx, buffer, bytes_read);
615
616 r = EVP_DigestSignUpdate(mdctx, buffer, bytes_read);
617 if (r != 1) {
618 ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
619 r = -1;
620 goto END;
621 }
622 }
623
624 // Compute the signature
625 r = EVP_DigestSignFinal(mdctx,
626 (unsigned char*)signature, length);
627 if (r != 1) {
628 ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
629 r = -1;
630 goto END;
631 }
632
633 DEBUG(writer->ctx, "Successfully generated signature of %zu bytes\n", *length);
634 r = 0;
635
636 // Dump signature
637 hexdump(writer->ctx, signature, *length);
638
639 END:
640 EVP_MD_CTX_free(mdctx);
641
642 return r;
643 }
644
645 LOC_EXPORT int loc_writer_write(struct loc_writer* writer, FILE* f, enum loc_database_version version) {
646 size_t bytes_written = 0;
647
648 // Check version
649 switch (version) {
650 case LOC_DATABASE_VERSION_UNSET:
651 version = LOC_DATABASE_VERSION_LATEST;
652 break;
653
654 case LOC_DATABASE_VERSION_1:
655 break;
656
657 default:
658 ERROR(writer->ctx, "Invalid database version: %u\n", version);
659 return -1;
660 }
661
662 DEBUG(writer->ctx, "Writing database in version %u\n", version);
663
664 struct loc_database_magic magic;
665 make_magic(writer, &magic, version);
666
667 // Make the header
668 struct loc_database_header_v1 header;
669 header.vendor = htobe32(writer->vendor);
670 header.description = htobe32(writer->description);
671 header.license = htobe32(writer->license);
672
673 time_t now = time(NULL);
674 header.created_at = htobe64(now);
675
676 // Clear the signatures
677 memset(header.signature1, '\0', sizeof(header.signature1));
678 header.signature1_length = 0;
679 memset(header.signature2, '\0', sizeof(header.signature2));
680 header.signature2_length = 0;
681
682 // Clear the padding
683 memset(header.padding, '\0', sizeof(header.padding));
684
685 int r;
686 off_t offset = 0;
687
688 // Start writing at the beginning of the file
689 r = fseek(f, 0, SEEK_SET);
690 if (r)
691 return r;
692
693 // Write the magic
694 offset += fwrite(&magic, 1, sizeof(magic), f);
695
696 // Skip the space we need to write the header later
697 r = fseek(f, sizeof(header), SEEK_CUR);
698 if (r) {
699 DEBUG(writer->ctx, "Could not seek to position after header\n");
700 return r;
701 }
702 offset += sizeof(header);
703
704 align_page_boundary(&offset, f);
705
706 // Write all ASes
707 r = loc_database_write_as_section(writer, &header, &offset, f);
708 if (r)
709 return r;
710
711 // Write all networks
712 r = loc_database_write_networks(writer, &header, &offset, f);
713 if (r)
714 return r;
715
716 // Write countries
717 r = loc_database_write_countries(writer, &header, &offset, f);
718 if (r)
719 return r;
720
721 // Write pool
722 r = loc_database_write_pool(writer, &header, &offset, f);
723 if (r)
724 return r;
725
726 // Create the signatures
727 if (writer->private_key1) {
728 DEBUG(writer->ctx, "Creating signature with first private key\n");
729
730 writer->signature1_length = sizeof(writer->signature1);
731
732 r = loc_writer_create_signature(writer, &header, f,
733 writer->private_key1, writer->signature1, &writer->signature1_length);
734 if (r)
735 return r;
736 }
737
738 if (writer->private_key2) {
739 DEBUG(writer->ctx, "Creating signature with second private key\n");
740
741 writer->signature2_length = sizeof(writer->signature2);
742
743 r = loc_writer_create_signature(writer, &header, f,
744 writer->private_key2, writer->signature2, &writer->signature2_length);
745 if (r)
746 return r;
747 }
748
749 // Copy the signatures into the header
750 if (writer->signature1_length) {
751 DEBUG(writer->ctx, "Copying first signature of %zu byte(s)\n",
752 writer->signature1_length);
753
754 memcpy(header.signature1, writer->signature1, writer->signature1_length);
755 header.signature1_length = htobe16(writer->signature1_length);
756 }
757
758 if (writer->signature2_length) {
759 DEBUG(writer->ctx, "Copying second signature of %zu byte(s)\n",
760 writer->signature2_length);
761
762 memcpy(header.signature2, writer->signature2, writer->signature2_length);
763 header.signature2_length = htobe16(writer->signature2_length);
764 }
765
766 // Write the header
767 r = fseek(f, sizeof(magic), SEEK_SET);
768 if (r)
769 return r;
770
771 bytes_written = fwrite(&header, 1, sizeof(header), f);
772 if (bytes_written < sizeof(header)) {
773 ERROR(writer->ctx, "Could not write header: %s\n", strerror(errno));
774 return r;
775 }
776
777 // Seek back to the end
778 r = fseek(f, 0, SEEK_END);
779 if (r)
780 return r;
781
782 // Flush everything
783 fflush(f);
784
785 return r;
786 }