"Fossies" - the Fresh Open Source Software Archive

Member "geoipupdate-3.1.1/bin/geoipupdate.c" (10 Sep 2018, 30740 Bytes) of package /linux/misc/geoipupdate-3.1.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "geoipupdate.c" see the Fossies "Dox" file reference documentation.

    1 #include "geoipupdate.h"
    2 #include "functions.h"
    3 #include "md5.h"
    4 
    5 #include <ctype.h>
    6 #include <errno.h>
    7 #include <fcntl.h>
    8 #include <getopt.h>
    9 #include <stdarg.h>
   10 #include <stdio.h>
   11 #include <stdlib.h>
   12 #include <string.h>
   13 #include <sys/stat.h>
   14 #include <sys/types.h>
   15 #include <unistd.h>
   16 #include <utime.h>
   17 #include <zlib.h>
   18 
   19 #define OLD_FREE_ACCOUNT_ID (999999)
   20 #define ZERO_LICENSE_KEY ("000000000000")
   21 #define ZERO_MD5 ("00000000000000000000000000000000")
   22 #define say(fmt, ...) say_if(1, fmt, ##__VA_ARGS__)
   23 
   24 enum gu_status {
   25     GU_OK = 0,
   26     GU_ERROR = 1,
   27     GU_NO_UPDATE = 2,
   28 };
   29 
   30 typedef struct {
   31     char *ptr;
   32     size_t size;
   33 } in_mem_s;
   34 
   35 static void xasprintf(char **, const char *, ...);
   36 static void *xrealloc(void *, size_t);
   37 static void usage(void);
   38 static int parse_opts(geoipupdate_s *, int, char *const[]);
   39 static ssize_t my_getline(char **, size_t *, FILE *);
   40 static int parse_license_file(geoipupdate_s *);
   41 static char *join_path(char const *const, char const *const);
   42 static int acquire_run_lock(geoipupdate_s const *const);
   43 static int md5hex(const char *, char *);
   44 static void common_req(CURL *, geoipupdate_s *);
   45 static size_t get_expected_file_md5(char *, size_t, size_t, void *);
   46 static int
   47 download_to_file(geoipupdate_s *, const char *, const char *, char *);
   48 static long get_server_time(geoipupdate_s *);
   49 static size_t mem_cb(void *, size_t, size_t, void *);
   50 static in_mem_s *in_mem_s_new(void);
   51 static void in_mem_s_delete(in_mem_s *);
   52 static int update_database_general_all(geoipupdate_s *);
   53 static int update_database_general(geoipupdate_s *, const char *);
   54 static int gunzip_and_replace(geoipupdate_s const *const,
   55                               char const *const,
   56                               char const *const,
   57                               char const *const,
   58                               long);
   59 
   60 void exit_unless(int expr, const char *fmt, ...) {
   61     va_list ap;
   62     if (expr) {
   63         return;
   64     }
   65     va_start(ap, fmt);
   66     vfprintf(stderr, fmt, ap);
   67     va_end(ap);
   68     exit(1);
   69 }
   70 
   71 static void xasprintf(char **ptr, const char *fmt, ...) {
   72     va_list ap;
   73     va_start(ap, fmt);
   74     int rc = vasprintf(ptr, fmt, ap);
   75     va_end(ap);
   76     exit_if(rc == -1, "Error calling vasprintf: %s\n", strerror(errno));
   77 }
   78 
   79 void say_if(int expr, const char *fmt, ...) {
   80     va_list ap;
   81     if (!expr) {
   82         return;
   83     }
   84     va_start(ap, fmt);
   85     vfprintf(stdout, fmt, ap);
   86     va_end(ap);
   87 }
   88 
   89 void *xcalloc(size_t nmemb, size_t size) {
   90     void *ptr = calloc(nmemb, size);
   91     exit_if(!ptr, "Error allocating memory: %s\n", strerror(errno));
   92     return ptr;
   93 }
   94 
   95 static void *xrealloc(void *ptr, size_t size) {
   96     void *mem = realloc(ptr, size);
   97     exit_if(mem == NULL, "Error reallocating memory: %s\n", strerror(errno));
   98     return mem;
   99 }
  100 
  101 static void usage(void) {
  102     fprintf(
  103         stderr,
  104         "Usage: geoipupdate [-Vhv] [-f license_file] [-d custom directory]\n\n"
  105         "  -d DIR   store downloaded files in DIR\n"
  106         "  -f FILE  use configuration found in FILE (see GeoIP.conf(5) man "
  107         "page)\n"
  108         "  -h       display this help text\n"
  109         "  -v       use verbose output\n"
  110         "  -V       display the version and exit\n");
  111 }
  112 
  113 static int parse_opts(geoipupdate_s *gu, int argc, char *const argv[]) {
  114     int c;
  115 
  116     opterr = 0;
  117 
  118     while ((c = getopt(argc, argv, "Vvhf:d:")) != -1) {
  119         switch (c) {
  120             case 'V':
  121                 puts(PACKAGE_STRING);
  122                 exit(0);
  123             case 'v':
  124                 gu->verbose = 1;
  125                 break;
  126             case 'd':
  127                 free(gu->database_dir);
  128                 gu->database_dir = strdup(optarg);
  129                 exit_if(NULL == gu->database_dir,
  130                         "Unable to allocate memory for database directory "
  131                         "path: %s\n",
  132                         strerror(errno));
  133 
  134                 // The database directory in the config file is ignored if we
  135                 // use -d
  136                 gu->do_not_overwrite_database_directory = 1;
  137                 break;
  138             case 'f':
  139                 free(gu->license_file);
  140                 gu->license_file = strdup(optarg);
  141                 exit_if(NULL == gu->license_file,
  142                         "Unable to allocate memory for license file path: %s\n",
  143                         strerror(errno));
  144                 break;
  145             case 'h':
  146             default:
  147                 usage();
  148                 exit(1);
  149             case '?':
  150                 if (optopt == 'f' || optopt == 'd') {
  151                     fprintf(
  152                         stderr, "Option -%c requires an argument.\n", optopt);
  153                 } else if (isprint(optopt)) {
  154                     fprintf(stderr, "Unknown option `-%c'.\n", optopt);
  155                 } else {
  156                     fprintf(
  157                         stderr, "Unknown option character `\\x%x'.\n", optopt);
  158                 }
  159                 exit(1);
  160         }
  161     }
  162     return GU_OK;
  163 }
  164 
  165 int main(int argc, char *const argv[]) {
  166     struct stat st;
  167     int err = GU_ERROR;
  168     curl_global_init(CURL_GLOBAL_DEFAULT);
  169     geoipupdate_s *gu = geoipupdate_s_new();
  170     if (gu) {
  171         parse_opts(gu, argc, argv);
  172         if (parse_license_file(gu)) {
  173             exit_unless(stat(gu->database_dir, &st) == 0,
  174                         "%s does not exist: %s\n",
  175                         gu->database_dir,
  176                         strerror(errno));
  177             exit_unless(S_ISDIR(st.st_mode),
  178                         "%s is not a directory\n",
  179                         gu->database_dir);
  180             // Note: access(2) checks only the real UID/GID. This is probably
  181             // okay, but we could perform more complex checks using the stat
  182             // struct. Alternatively, simply report more thoroughly when we
  183             // open the file, and avoid potential race issues where permissions
  184             // change between now and then.
  185             exit_unless(access(gu->database_dir, W_OK) == 0,
  186                         "%s is not writable: %s\n",
  187                         gu->database_dir,
  188                         strerror(errno));
  189 
  190             if (acquire_run_lock(gu) != 0) {
  191                 geoipupdate_s_delete(gu);
  192                 curl_global_cleanup();
  193                 return GU_ERROR;
  194             }
  195 
  196             err = update_database_general_all(gu);
  197         }
  198         geoipupdate_s_delete(gu);
  199     }
  200     curl_global_cleanup();
  201     return err & GU_ERROR ? GU_ERROR : GU_OK;
  202 }
  203 
  204 static ssize_t my_getline(char **linep, size_t *linecapp, FILE *stream) {
  205 #if defined HAVE_GETLINE
  206     return getline(linep, linecapp, stream);
  207 #elif defined HAVE_FGETS
  208     // Unbelievable, but OS X 10.6 Snow Leopard did not provide getline
  209     char *p = fgets(*linep, *linecapp, stream);
  210     return p == NULL ? -1 : strlen(p);
  211 #else
  212 #error Your OS is not supported
  213 #endif
  214 }
  215 
  216 static int parse_license_file(geoipupdate_s *up) {
  217     say_if(up->verbose, "%s\n", PACKAGE_STRING);
  218     FILE *fh = fopen(up->license_file, "rb");
  219     exit_unless(!!fh,
  220                 "Can't open license file %s: %s\n",
  221                 up->license_file,
  222                 strerror(errno));
  223     say_if(up->verbose, "Opened License file %s\n", up->license_file);
  224 
  225     const char *sep = " \t\r\n";
  226     size_t bsize = 1024;
  227     char *buffer = (char *)xcalloc(bsize, sizeof(char));
  228     ssize_t read_bytes;
  229     while ((read_bytes = my_getline(&buffer, &bsize, fh)) != -1) {
  230         size_t idx = strspn(buffer, sep);
  231         char *strt = &buffer[idx];
  232         if (*strt == '#') {
  233             continue;
  234         }
  235         if (sscanf(strt, "UserId %d", &up->license.account_id) == 1) {
  236             say_if(up->verbose, "UserId %d\n", up->license.account_id);
  237             continue;
  238         }
  239         if (sscanf(strt, "AccountID %d", &up->license.account_id) == 1) {
  240             say_if(up->verbose, "AccountID %d\n", up->license.account_id);
  241             continue;
  242         }
  243         if (sscanf(strt, "LicenseKey %99s", &up->license.license_key[0]) == 1) {
  244             say_if(
  245                 up->verbose, "LicenseKey %.4s...\n", up->license.license_key);
  246             continue;
  247         }
  248 
  249         char *p, *last;
  250         if ((p = strtok_r(strt, sep, &last))) {
  251             if (!strcmp(p, "ProductIds") || !strcmp(p, "EditionIDs")) {
  252                 while ((p = strtok_r(NULL, sep, &last))) {
  253                     edition_insert_once(up, p);
  254                 }
  255             } else if (!strcmp(p, "PreserveFileTimes")) {
  256                 p = strtok_r(NULL, sep, &last);
  257                 exit_if(NULL == p ||
  258                             (0 != strcmp(p, "0") && 0 != strcmp(p, "1")),
  259                         "PreserveFileTimes must be 0 or 1\n");
  260                 up->preserve_file_times = atoi(p);
  261             } else if (!strcmp(p, "Host")) {
  262                 p = strtok_r(NULL, sep, &last);
  263                 exit_if(NULL == p, "Host must be defined\n");
  264                 free(up->host);
  265                 up->host = strdup(p);
  266                 exit_if(NULL == up->host,
  267                         "Unable to allocate memory for update host: %s\n",
  268                         strerror(errno));
  269             } else if (!strcmp(p, "DatabaseDirectory")) {
  270                 if (!up->do_not_overwrite_database_directory) {
  271                     p = strtok_r(NULL, sep, &last);
  272                     exit_if(NULL == p, "DatabaseDirectory must be defined\n");
  273                     free(up->database_dir);
  274                     up->database_dir = strdup(p);
  275                     exit_if(NULL == up->database_dir,
  276                             "Unable to allocate memory for database directory "
  277                             "path: %s\n",
  278                             strerror(errno));
  279                 }
  280             } else if (!strcmp(p, "Proxy")) {
  281                 p = strtok_r(NULL, sep, &last);
  282                 exit_if(NULL == p, "Proxy must be defined 1.2.3.4:12345\n");
  283                 free(up->proxy);
  284                 up->proxy = strdup(p);
  285                 exit_if(NULL == up->proxy,
  286                         "Unable to allocate memory for proxy host: %s\n",
  287                         strerror(errno));
  288             } else if (!strcmp(p, "ProxyUserPassword")) {
  289                 p = strtok_r(NULL, sep, &last);
  290                 exit_if(NULL == p,
  291                         "ProxyUserPassword must be defined xyz:abc\n");
  292                 free(up->proxy_user_password);
  293                 up->proxy_user_password = strdup(p);
  294                 exit_if(NULL == up->proxy_user_password,
  295                         "Unable to allocate memory for proxy credentials: %s\n",
  296                         strerror(errno));
  297             } else if (!strcmp(p, "LockFile")) {
  298                 p = strtok_r(NULL, sep, &last);
  299                 exit_if(NULL == p, "LockFile must be a file path\n");
  300                 // We could check the value looks like a path, but trying to use
  301                 // it will fail if it isn't.
  302                 free(up->lock_file);
  303                 up->lock_file = strdup(p);
  304                 exit_if(NULL == up->lock_file,
  305                         "Unable to allocate memory for LockFile string: %s\n",
  306                         strerror(errno));
  307             } else {
  308                 say_if(up->verbose, "Skip unknown directive: %s\n", p);
  309             }
  310         }
  311     }
  312 
  313     bool is_zero_license_key = !strncmp(ZERO_LICENSE_KEY,
  314                                         up->license.license_key,
  315                                         sizeof(ZERO_LICENSE_KEY) - 1);
  316 
  317     // We used to recommend using 999999 / 000000000000 for free downloads and
  318     // many people still use this combination. We need to check for the
  319     // ZERO_LICENSE_KEY to ensure that a real AccountID of 999999 will work in
  320     // the future.
  321     if (up->license.account_id == OLD_FREE_ACCOUNT_ID && is_zero_license_key) {
  322         up->license.account_id = NO_ACCOUNT_ID;
  323     }
  324 
  325     exit_if(up->license.account_id == NO_ACCOUNT_ID &&
  326                 up->license.license_key[0] != 0 && !is_zero_license_key,
  327             "AccountID must be set if LicenseKey is set\n");
  328 
  329     // If we don't have a LockFile specified, then default to .geoipupdate.lock
  330     // in the database directory. Do this here as the database directory may
  331     // have been set either on the command line or in the configuration file.
  332     if (strlen(up->lock_file) == 0) {
  333         free(up->lock_file);
  334         up->lock_file = join_path(up->database_dir, ".geoipupdate.lock");
  335         exit_if(NULL == up->lock_file, "Unable to create path to lock file.");
  336     }
  337 
  338     free(buffer);
  339     exit_if(-1 == fclose(fh), "Error closing stream: %s", strerror(errno));
  340     say_if(up->verbose,
  341            "Read in license key %s\nNumber of edition IDs %d\n",
  342            up->license_file,
  343            edition_count(up));
  344     return 1;
  345 }
  346 
  347 // Given a directory and a filename in that directory, combine the two to make a
  348 // path to the file.
  349 //
  350 // TODO: This function assumes Unix style paths (/ separator).
  351 //
  352 // TODO: This function performs no validation on the given inputs beyond that
  353 // they are present.
  354 static char *join_path(char const *const dir, char const *const file) {
  355     size_t sz = -1;
  356     char *path = NULL;
  357 
  358     if (dir == NULL || strlen(dir) == 0 || file == NULL || strlen(file) == 0) {
  359         fprintf(stderr, "join_path: %s\n", strerror(EINVAL));
  360         return NULL;
  361     }
  362 
  363     // dir '/' file '\0'
  364     sz = strlen(dir) + 1 + strlen(file) + 1;
  365 
  366     path = calloc(sz, sizeof(char));
  367     if (path == NULL) {
  368         fprintf(stderr, "join_path: %s\n", strerror(errno));
  369         return NULL;
  370     }
  371 
  372     strcat(path, dir);
  373     strcat(path, "/");
  374     strcat(path, file);
  375 
  376     return path;
  377 }
  378 
  379 // Acquire a lock to ensure this is the only running geoipupdate instance. This
  380 // is to avoid race conditions where multiple geoipupdate instances run at
  381 // once, leading to failures.
  382 //
  383 // Wait for a lock. If locking fails, return non-zero. If it succeeds, return
  384 // zero.
  385 //
  386 // Use fcntl(2) to acquire the lock. The primary rationale to use this over
  387 // something like open(2) with O_EXCL is that we don't need to perform clean up
  388 // to release the lock. In particular, if execution ends unexpectedly, such as
  389 // due to a crash, the lock will be automatically released. It also means we
  390 // don't need to worry about lock bookkeeping even in the normal case, since
  391 // the lock gets released automatically at program exit.
  392 //
  393 // This method does have the drawback that removing the lock file is not
  394 // possible due to the potential for race conditions. Consider the case where
  395 // another instance opens the lock file, then we remove the file and close the
  396 // file (releasing our lock), then that other instance acquires a lock. At the
  397 // same time, another instance runs and creates the file anew and also acquires
  398 // a lock.
  399 static int acquire_run_lock(geoipupdate_s const *const gu) {
  400     int fd = -1;
  401     struct flock fl;
  402     int i = 0;
  403 
  404     memset(&fl, 0, sizeof(struct flock));
  405 
  406     if (gu == NULL || gu->lock_file == NULL || strlen(gu->lock_file) == 0) {
  407         fprintf(stderr, "maybe_acquire_run_lock: %s\n", strerror(EINVAL));
  408         return 1;
  409     }
  410 
  411     fd = open(gu->lock_file, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
  412     if (fd == -1) {
  413         fprintf(stderr,
  414                 "Unable to open lock file %s: %s\n",
  415                 gu->lock_file,
  416                 strerror(errno));
  417         return 1;
  418     }
  419 
  420     fl.l_type = F_WRLCK;
  421 
  422     // Try 3 times to acquire a lock. Arbitrary number.
  423     for (i = 0; i < 3; i++) {
  424         if (fcntl(fd, F_SETLKW, &fl) == 0) {
  425             // Locked.
  426             return 0;
  427         }
  428 
  429         // Interrupted? Retry.
  430         if (errno == EINTR) {
  431             continue;
  432         }
  433 
  434         // Something else went wrong. Abort.
  435         fprintf(stderr,
  436                 "Unable to acquire lock on %s: %s\n",
  437                 gu->lock_file,
  438                 strerror(errno));
  439         close(fd);
  440         return 1;
  441     }
  442 
  443     fprintf(stderr,
  444             "Unable to acquire lock on %s: Gave up after %d attempts\n",
  445             gu->lock_file,
  446             i);
  447     close(fd);
  448     return 1;
  449 }
  450 
  451 static int md5hex(const char *fname, char *hex_digest) {
  452     int bsize = 1024;
  453     unsigned char buffer[bsize], digest[16];
  454 
  455     size_t len;
  456     MD5_CONTEXT context;
  457 
  458     FILE *fh = fopen(fname, "rb");
  459     if (fh == NULL) {
  460         strcpy(hex_digest, ZERO_MD5);
  461         return 0;
  462     }
  463 
  464     struct stat st;
  465     exit_unless(stat(fname, &st) == 0,
  466                 "Unable to stat %s: %s\n",
  467                 fname,
  468                 strerror(errno));
  469     exit_unless(S_ISREG(st.st_mode), "%s is not a file\n", fname);
  470 
  471     md5_init(&context);
  472     while ((len = fread(buffer, 1, bsize, fh)) > 0) {
  473         md5_write(&context, buffer, len);
  474     }
  475     exit_if(ferror(fh), "Unable to read %s: %s\n", fname, strerror(errno));
  476 
  477     md5_final(&context);
  478     memcpy(digest, context.buf, 16);
  479     exit_if(-1 == fclose(fh), "Error closing stream: %s", strerror(errno));
  480     for (int i = 0; i < 16; i++) {
  481         int c = snprintf(&hex_digest[2 * i], 3, "%02x", digest[i]);
  482         exit_if(c < 0, "Unable to write digest: %s\n", strerror(errno));
  483     }
  484     return 1;
  485 }
  486 
  487 static void common_req(CURL *curl, geoipupdate_s *gu) {
  488     curl_easy_setopt(curl, CURLOPT_USERAGENT, GEOIP_USERAGENT);
  489     curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  490 
  491 // CURLOPT_TCP_KEEPALIVE appeared in 7.25. It is a typedef enum, not a
  492 // macro so we resort to version detection.
  493 #if LIBCURL_VERSION_NUM >= 0x071900
  494     curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
  495 #endif
  496 
  497     // These should be the default already, but setting them to ensure
  498     // they are set correctly on all curl versions.
  499     curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
  500     curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
  501 
  502     if (gu->preserve_file_times) {
  503         curl_easy_setopt(curl, CURLOPT_FILETIME, 1L);
  504     }
  505 
  506     if (gu->proxy_user_password && strlen(gu->proxy_user_password)) {
  507         say_if(gu->verbose,
  508                "Use proxy_user_password: %s\n",
  509                gu->proxy_user_password);
  510         curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, gu->proxy_user_password);
  511     }
  512     if (gu->proxy && strlen(gu->proxy)) {
  513         say_if(gu->verbose, "Use proxy: %s\n", gu->proxy);
  514         curl_easy_setopt(curl, CURLOPT_PROXY, gu->proxy);
  515     }
  516 }
  517 
  518 static size_t get_expected_file_md5(char *buffer,
  519                                     size_t size,
  520                                     size_t nitems,
  521                                     void *userdata) {
  522     char *md5 = (char *)userdata;
  523     size_t total_size = size * nitems;
  524     if (strncasecmp(buffer, "X-Database-MD5:", 15) == 0 && total_size > 48) {
  525         char *start = buffer + 16;
  526         char *value = start + strspn(start, " \t\r\n");
  527         strncpy(md5, value, 32);
  528         md5[32] = '\0';
  529     }
  530 
  531     return size * nitems;
  532 }
  533 
  534 // Make an HTTP request and download the response body to a file.
  535 //
  536 // If the HTTP status is 200, we have a file. If it is 304, the file has
  537 // not changed and we display an error message. If it is 401, there was
  538 // an authentication issue and we display an error message. If it is
  539 // any other status code, we assume it is an error and write the body
  540 // to stderr.
  541 static int download_to_file(geoipupdate_s *gu,
  542                             const char *url,
  543                             const char *fname,
  544                             char *expected_file_md5) {
  545     FILE *f = fopen(fname, "wb");
  546     if (f == NULL) {
  547         fprintf(stderr, "Can't open %s: %s\n", fname, strerror(errno));
  548         exit(1);
  549     }
  550 
  551     say_if(gu->verbose, "url: %s\n", url);
  552     CURL *curl = gu->curl;
  553 
  554     expected_file_md5[0] = '\0';
  555 
  556     // If the account ID is not set, the user is likely trying to do a free
  557     // download, e.g., GeoLite2. We don't need to send the basic auth header
  558     // for these.
  559     if (gu->license.account_id != NO_ACCOUNT_ID) {
  560         char account_id[10] = {0};
  561         int n = snprintf(account_id, 10, "%d", gu->license.account_id);
  562         exit_if(n < 0,
  563                 "Error creating account ID string for %d: %s\n",
  564                 gu->license.account_id,
  565                 strerror(errno));
  566         exit_if(n < 0 || n >= 10,
  567                 "An unexpectedly large account ID was encountered: %d\n",
  568                 gu->license.account_id);
  569 
  570         curl_easy_setopt(curl, CURLOPT_USERNAME, account_id);
  571         curl_easy_setopt(curl, CURLOPT_PASSWORD, gu->license.license_key);
  572     }
  573 
  574     curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, get_expected_file_md5);
  575     curl_easy_setopt(curl, CURLOPT_HEADERDATA, expected_file_md5);
  576 
  577     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
  578     curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)f);
  579 
  580     curl_easy_setopt(curl, CURLOPT_URL, url);
  581     common_req(curl, gu);
  582     CURLcode res = curl_easy_perform(curl);
  583 
  584     exit_unless(res == CURLE_OK,
  585                 "curl_easy_perform() failed: %s\nConnect to %s\n",
  586                 curl_easy_strerror(res),
  587                 url);
  588 
  589     long status = 0;
  590     curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
  591 
  592     if (fclose(f) == -1) {
  593         fprintf(stderr, "Error closing file: %s: %s\n", fname, strerror(errno));
  594         unlink(fname);
  595         exit(1);
  596     }
  597 
  598     if (status == 304) {
  599         say_if(gu->verbose, "No new updates available\n");
  600         unlink(fname);
  601         return GU_NO_UPDATE;
  602     }
  603 
  604     if (status == 401) {
  605         fprintf(stderr, "Your account ID or license key is invalid\n");
  606         unlink(fname);
  607         return GU_ERROR;
  608     }
  609 
  610     if (status != 200) {
  611         fprintf(stderr,
  612                 "Received an unexpected HTTP status code of %ld from %s:\n",
  613                 status,
  614                 url);
  615         // The response should contain a message containing exactly why.
  616         char *const message = slurp_file(fname);
  617         if (message) {
  618             fprintf(stderr, "%s\n", message);
  619             free(message);
  620         }
  621         unlink(fname);
  622         return GU_ERROR;
  623     }
  624 
  625     // We have HTTP 2xx.
  626 
  627     // In this case, the server must have told us the current MD5 hash of the
  628     // database we asked for.
  629     if (gu_strnlen(expected_file_md5, 33) != 32) {
  630         fprintf(stderr,
  631                 "Did not receive a valid expected database MD5 from server\n");
  632         unlink(fname);
  633         return GU_ERROR;
  634     }
  635     return GU_OK;
  636 }
  637 
  638 // Retrieve the server file time for the previous HTTP request.
  639 static long get_server_time(geoipupdate_s *gu) {
  640     CURL *curl = gu->curl;
  641     long filetime = -1;
  642     if (curl != NULL) {
  643         curl_easy_getinfo(curl, CURLINFO_FILETIME, &filetime);
  644     }
  645     return filetime;
  646 }
  647 
  648 static size_t mem_cb(void *contents, size_t size, size_t nmemb, void *userp) {
  649     size_t realsize = size * nmemb;
  650 
  651     if (realsize == 0) {
  652         return realsize;
  653     }
  654 
  655     in_mem_s *mem = (in_mem_s *)userp;
  656 
  657     mem->ptr = (char *)xrealloc(mem->ptr, mem->size + realsize + 1);
  658     memcpy(&(mem->ptr[mem->size]), contents, realsize);
  659     mem->size += realsize;
  660     mem->ptr[mem->size] = 0;
  661 
  662     return realsize;
  663 }
  664 
  665 static in_mem_s *in_mem_s_new(void) {
  666     in_mem_s *mem = (in_mem_s *)xcalloc(1, sizeof(in_mem_s));
  667     mem->ptr = (char *)xcalloc(1, sizeof(char));
  668     mem->size = 0;
  669     return mem;
  670 }
  671 
  672 static void in_mem_s_delete(in_mem_s *mem) {
  673     if (mem) {
  674         free(mem->ptr);
  675         free(mem);
  676     }
  677 }
  678 
  679 static int update_database_general(geoipupdate_s *gu, const char *edition_id) {
  680     char *url = NULL, *geoip_filename = NULL, *geoip_gz_filename = NULL;
  681     char hex_digest[33] = {0};
  682 
  683     // Get the filename.
  684     xasprintf(&url,
  685               "https://%s/app/update_getfilename?product_id=%s",
  686               gu->host,
  687               edition_id);
  688 
  689     in_mem_s *mem = in_mem_s_new();
  690 
  691     say_if(gu->verbose, "url: %s\n", url);
  692     CURL *curl = gu->curl;
  693     curl_easy_setopt(curl, CURLOPT_URL, url);
  694     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, mem_cb);
  695     curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)mem);
  696     common_req(curl, gu);
  697     CURLcode res = curl_easy_perform(curl);
  698     exit_unless(res == CURLE_OK,
  699                 "curl_easy_perform() failed: %s\nConnect to %s\n",
  700                 curl_easy_strerror(res),
  701                 url);
  702 
  703     long status = 0;
  704     curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
  705 
  706     if (status != 200) {
  707         fprintf(stderr,
  708                 "Received an unexpected HTTP status code of %ld from %s\n",
  709                 status,
  710                 url);
  711         free(url);
  712         in_mem_s_delete(mem);
  713         return GU_ERROR;
  714     }
  715 
  716     free(url);
  717     if (mem->size == 0) {
  718         fprintf(stderr, "edition_id %s not found\n", edition_id);
  719         in_mem_s_delete(mem);
  720         return GU_ERROR;
  721     }
  722     xasprintf(&geoip_filename, "%s/%s", gu->database_dir, mem->ptr);
  723     in_mem_s_delete(mem);
  724 
  725     // Calculate the MD5 hash of the database we currently have, if any. We get
  726     // back a zero MD5 hash if we don't have it yet.
  727     md5hex(geoip_filename, hex_digest);
  728     say_if(gu->verbose, "md5hex_digest: %s\n", hex_digest);
  729 
  730     // Download.
  731     xasprintf(&url,
  732               "https://%s/geoip/databases/%s/update?db_md5=%s",
  733               gu->host,
  734               edition_id,
  735               hex_digest);
  736     xasprintf(&geoip_gz_filename, "%s.gz", geoip_filename);
  737 
  738     char expected_file_md5[33] = {0};
  739     int rc = download_to_file(gu, url, geoip_gz_filename, expected_file_md5);
  740     free(url);
  741 
  742     if (rc == GU_OK) {
  743         long filetime = -1;
  744         if (gu->preserve_file_times) {
  745             filetime = get_server_time(gu);
  746         }
  747         rc = gunzip_and_replace(
  748             gu, geoip_gz_filename, geoip_filename, expected_file_md5, filetime);
  749     }
  750 
  751     free(geoip_gz_filename);
  752     free(geoip_filename);
  753 
  754     return rc;
  755 }
  756 
  757 static int update_database_general_all(geoipupdate_s *gu) {
  758     int err = 0;
  759     for (edition_s **next = &gu->license.first; *next; next = &(*next)->next) {
  760         err |= update_database_general(gu, (*next)->edition_id);
  761     }
  762     return err;
  763 }
  764 
  765 // Decompress the compressed database and move it into place in the database
  766 // directory.
  767 //
  768 // We are given the path to the compressed (gzip'd) new database, and the path
  769 // to where it should end up once decompressed. We are also given the MD5 hash
  770 // it should have once decompressed for verification purposes.
  771 //
  772 // We verify the file is actually a gzip file. If it isn't we abort with an
  773 // error, and remove the file.
  774 //
  775 // We also remove the gzip file once we successfully decompress and move the
  776 // new database into place.
  777 static int gunzip_and_replace(geoipupdate_s const *const gu,
  778                               char const *const gzipfile,
  779                               char const *const geoip_filename,
  780                               char const *const expected_file_md5,
  781                               long filetime) {
  782     if (gu == NULL || gu->database_dir == NULL ||
  783         strlen(gu->database_dir) == 0 || gzipfile == NULL ||
  784         strlen(gzipfile) == 0 || geoip_filename == NULL ||
  785         strlen(geoip_filename) == 0 || expected_file_md5 == NULL ||
  786         strlen(expected_file_md5) == 0) {
  787         fprintf(stderr, "gunzip_and_replace: %s\n", strerror(EINVAL));
  788         return GU_ERROR;
  789     }
  790 
  791     if (!is_valid_gzip_file(gzipfile)) {
  792         // We should have already reported an error.
  793         unlink(gzipfile);
  794         return GU_ERROR;
  795     }
  796 
  797     // Decompress to the filename with the suffix ".test".
  798     char *file_path_test = NULL;
  799     xasprintf(&file_path_test, "%s.test", geoip_filename);
  800     say_if(gu->verbose, "Uncompress file %s to %s\n", gzipfile, file_path_test);
  801 
  802     gzFile gz_fh = gzopen(gzipfile, "rb");
  803     exit_if(gz_fh == NULL, "Can't open %s: %s\n", gzipfile, strerror(errno));
  804 
  805     FILE *fhw = fopen(file_path_test, "wb");
  806     exit_if(
  807         fhw == NULL, "Can't open %s: %s\n", file_path_test, strerror(errno));
  808 
  809     size_t const bsize = 8192;
  810     char *const buffer = calloc(bsize, sizeof(char));
  811     if (!buffer) {
  812         fprintf(stderr, "gunzip_and_replace: %s\n", strerror(errno));
  813         free(file_path_test);
  814         gzclose(gz_fh);
  815         fclose(fhw);
  816         return GU_ERROR;
  817     }
  818 
  819     for (;;) {
  820         int amt = gzread(gz_fh, buffer, bsize);
  821         if (amt <= 0) {
  822             if (gzeof(gz_fh)) {
  823                 // EOF
  824                 break;
  825             }
  826             int gzerr = 0;
  827             const char *msg = gzerror(gz_fh, &gzerr);
  828             if (gzerr == Z_ERRNO) {
  829                 fprintf(stderr,
  830                         "Unable to read %s: %s\n",
  831                         gzipfile,
  832                         strerror(errno));
  833             } else {
  834                 fprintf(stderr, "Unable to decompress %s: %s\n", gzipfile, msg);
  835             }
  836             exit(1);
  837         }
  838         exit_unless(fwrite(buffer, 1, amt, fhw) == (size_t)amt,
  839                     "Unable to write to %s: %s\n",
  840                     file_path_test,
  841                     strerror(errno));
  842     }
  843     exit_if(-1 == fclose(fhw), "Error closing stream: %s\n", strerror(errno));
  844     if (gzclose(gz_fh) != Z_OK) {
  845         int gzerr = 0;
  846         const char *msg = gzerror(gz_fh, &gzerr);
  847         if (gzerr == Z_ERRNO) {
  848             msg = strerror(errno);
  849         }
  850         fprintf(stderr, "Unable to close %s: %s\n", gzipfile, msg);
  851         exit(1);
  852     }
  853     free(buffer);
  854 
  855     char actual_md5[33] = {0};
  856     md5hex(file_path_test, actual_md5);
  857     exit_if(strncasecmp(actual_md5, expected_file_md5, 32),
  858             "MD5 of new database (%s) does not match expected MD5 (%s)\n",
  859             actual_md5,
  860             expected_file_md5);
  861 
  862     say_if(gu->verbose, "Rename %s to %s\n", file_path_test, geoip_filename);
  863     int err = rename(file_path_test, geoip_filename);
  864     exit_if(err,
  865             "Rename %s to %s failed: %s\n",
  866             file_path_test,
  867             geoip_filename,
  868             strerror(errno));
  869 
  870     if (gu->preserve_file_times && filetime > 0) {
  871         struct utimbuf utb;
  872         utb.modtime = utb.actime = (time_t)filetime;
  873         err = utime(geoip_filename, &utb);
  874         exit_if(err,
  875                 "Setting timestamp of %s to %ld failed: %s\n",
  876                 geoip_filename,
  877                 filetime,
  878                 strerror(errno));
  879     }
  880 
  881     // fsync directory to ensure the rename is durable
  882     int dirfd = open(gu->database_dir, O_DIRECTORY);
  883     exit_if(
  884         -1 == dirfd, "Error opening database directory: %s\n", strerror(errno));
  885     exit_if(-1 == fsync(dirfd),
  886             "Error syncing database directory: %s\n",
  887             strerror(errno));
  888     exit_if(-1 == close(dirfd),
  889             "Error closing database directory: %s\n",
  890             strerror(errno));
  891     exit_if(-1 == unlink(gzipfile),
  892             "Error unlinking %s: %s\n",
  893             gzipfile,
  894             strerror(errno));
  895 
  896     free(file_path_test);
  897     return GU_OK;
  898 }