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