1 /* Copyright © 2012 Brandon L Black <blblack@gmail.com>
2  *
3  * This file is part of gdnsd.
4  *
5  * gdnsd-plugin-geoip is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * gdnsd-plugin-geoip is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with gdnsd.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 // fips104.c - FIPS 10-4 2-letter region code -> full text
21 
22 #include <config.h>
23 #include "fips104.h"
24 
25 #include <gdnsd/alloc.h>
26 #include <gdnsd/dmn.h>
27 #include <gdnsd/log.h>
28 #include <gdnsd/paths.h>
29 #include <gdnsd/misc.h>
30 
31 #include <stdio.h>
32 #include <string.h>
33 #include <inttypes.h>
34 
35 // Data source URL is http://www.maxmind.com/download/geoip/misc/region_codes.csv
36 // As of this writing (Mar 13, 2014), the file had a Last-Modified header of
37 //  "Thu, 30 Jan 2014 18:27:35 GMT" and was 75728 bytes long with 4066 lines/records.
38 
39 // I expect the record count to be relatively stable in the long
40 //  term, so I'm picking a fixed hash table size that's a bit
41 //  over 4x the count and doing an open-address thing.
42 // Must be a power of two for simple masking.
43 #define FIPS_HASH_SIZE 16384
44 #define FIPS_HASH_MASK (FIPS_HASH_SIZE - 1)
45 
46 typedef struct {
47     char* val;
48     uint32_t key;
49 } fips_node_t;
50 
51 struct _fips_t {
52     fips_node_t table[FIPS_HASH_SIZE];
53 };
54 
55 // keys are a uint32_t made of 4 bytes: CCRR (Country/Region)
56 F_CONST
fips_hash(uint32_t key)57 static unsigned fips_hash(uint32_t key) {
58    dmn_assert(key);
59    return gdnsd_lookup2((const uint8_t*)&key, 4) & FIPS_HASH_MASK;
60 }
61 
62 // It is assumed there are no duplicates in the input data.
63 F_NONNULL
fips_hash_add(fips_t * fips,const uint32_t key,const char * val)64 static void fips_hash_add(fips_t* fips, const uint32_t key, const char* val) {
65     unsigned jmpby = 1;
66     unsigned slotnum = fips_hash(key);
67     while(fips->table[slotnum].key)
68         slotnum = (slotnum + jmpby++) & FIPS_HASH_MASK;
69     fips->table[slotnum].key = key;
70     fips->table[slotnum].val = strdup(val);
71 }
72 
73 F_NONNULL
fips_parse(fips_t * fips,FILE * file)74 static void fips_parse(fips_t* fips, FILE* file) {
75     unsigned line = 0;
76     while(1) {
77         char ccrr[5];
78         char rname[81];
79 
80         line++;
81         const int fsf_rv = fscanf(file, "%2[A-Z0-9],%2[A-Z0-9],\"%80[^\"\n]\"\n",
82             ccrr, ccrr + 2, rname);
83 
84         if(fsf_rv != 3) {
85             if(fsf_rv != EOF)
86                 log_fatal("plugin_geoip: parse error in FIPS region name data, line %u", line);
87             return;
88         }
89 
90         uint32_t key = ((unsigned)ccrr[0])
91             + ((unsigned)ccrr[1] << 8U)
92             + ((unsigned)ccrr[2] << 16U)
93             + ((unsigned)ccrr[3] << 24U);
94 
95         fips_hash_add(fips, key, rname);
96     }
97 }
98 
99 /**** public interface ****/
100 
fips_lookup(const fips_t * fips,const uint32_t key)101 const char* fips_lookup(const fips_t* fips, const uint32_t key) {
102     dmn_assert(key);
103 
104     unsigned jmpby = 1;
105     unsigned slotnum = fips_hash(key);
106     while(fips->table[slotnum].key) {
107         if(fips->table[slotnum].key == key)
108             return fips->table[slotnum].val;
109         slotnum = (slotnum + jmpby++) & FIPS_HASH_MASK;
110     }
111 
112     return NULL;
113 }
114 
fips_init(const char * pathname)115 fips_t* fips_init(const char* pathname) {
116     FILE* file = fopen(pathname, "r");
117     if(!file)
118         log_fatal("plugin_geoip: Cannot fopen() FIPS region file '%s' for reading: %s", pathname, dmn_logf_errno());
119     fips_t* fips = xcalloc(1, sizeof(fips_t));
120     fips_parse(fips, file);
121     if(fclose(file))
122         log_fatal("plugin_geoip: fclose() of FIPS region file '%s' failed: %s", pathname, dmn_logf_errno());
123     return fips;
124 }
125