Blob Blame History Raw
Patch by Robert Scheck <robert@fedoraproject.org> for geoipupdate <= 2.5.0 which backports
the support for hashed license keys. It is based on the following upstream commits:

1. https://github.com/maxmind/geoipupdate/commit/b7862460b6769f5c40b72de835b868882c3f3883
2. https://github.com/maxmind/geoipupdate/commit/4b41ea887e836ddb1a36c0c3a071ac49e8d71a25
3. https://github.com/maxmind/geoipupdate/commit/01fcfc1170dcedc35c758e111b8905cf6513d8b9
4. https://github.com/maxmind/geoipupdate/commit/6794f2eed0063a50749bf400e135f4f47ca2f465
5. https://github.com/maxmind/geoipupdate/commit/0670ee3e1237a72bae785877b354dbfe135de6be
6. https://github.com/maxmind/geoipupdate/commit/cddabc6f645ea3abbb8117ef0d03069df560fc36
7. https://github.com/maxmind/geoipupdate/commit/35a640087a4eca4e37306b717fa8513e26487b47
8. https://github.com/maxmind/geoipupdate/commit/642978928019f87181dd7c108b0363841c0135a9
9. https://github.com/maxmind/geoipupdate/commit/824ef039bbc8a2ed5a90d1da5a51d4a1639fdb09

--- geoipupdate-2.5.0/bin/geoipupdate.c			2017-10-30 15:38:24.000000000 +0100
+++ geoipupdate-2.5.0/bin/geoipupdate.c.licensekey	2023-04-13 21:23:51.340750299 +0200
@@ -22,6 +22,7 @@
 enum gu_status {
     GU_OK = 0,
     GU_ERROR = 1,
+    GU_NO_UPDATE = 2,
 };
 
 typedef struct {
@@ -41,17 +42,14 @@
 static int md5hex(const char *, char *);
 static void common_req(CURL *, geoipupdate_s *);
 static size_t get_expected_file_md5(char *, size_t, size_t, void *);
-static void
+static int
 download_to_file(geoipupdate_s *, const char *, const char *, char *);
 static long get_server_time(geoipupdate_s *);
 static size_t mem_cb(void *, size_t, size_t, void *);
 static in_mem_s *in_mem_s_new(void);
 static void in_mem_s_delete(in_mem_s *);
-static in_mem_s *get(geoipupdate_s *, const char *);
-static void md5hex_license_ipaddr(geoipupdate_s *, const char *, char *);
 static int update_database_general_all(geoipupdate_s *);
 static int update_database_general(geoipupdate_s *, const char *);
-static int update_country_database(geoipupdate_s *);
 static int gunzip_and_replace(geoipupdate_s const *const,
                               char const *const,
                               char const *const,
@@ -196,14 +194,12 @@
                 return GU_ERROR;
             }
 
-            err = (gu->license.account_id == NO_ACCOUNT_ID)
-                      ? update_country_database(gu)
-                      : update_database_general_all(gu);
+            err = update_database_general_all(gu);
         }
         geoipupdate_s_delete(gu);
     }
     curl_global_cleanup();
-    return err ? GU_ERROR : GU_OK;
+    return err & GU_ERROR ? GU_ERROR : GU_OK;
 }
 
 static ssize_t my_getline(char **linep, size_t *linecapp, FILE *stream) {
@@ -242,8 +238,9 @@
             say_if(up->verbose, "AccountID %d\n", up->license.account_id);
             continue;
         }
-        if (sscanf(strt, "LicenseKey %12s", &up->license.license_key[0]) == 1) {
-            say_if(up->verbose, "LicenseKey %s\n", up->license.license_key);
+        if (sscanf(strt, "LicenseKey %99s", &up->license.license_key[0]) == 1) {
+            say_if(
+                up->verbose, "LicenseKey %.4s...\n", up->license.license_key);
             continue;
         }
 
@@ -534,15 +531,11 @@
 // Make an HTTP request and download the response body to a file.
 //
 // If the HTTP status is not 2xx, we have a error message in the body rather
-// than a file. Write it to stderr and exit.
-//
-// TODO(wstorey@maxmind.com): Return boolean/int whether we succeeded rather
-// than exiting. Beyond being cleaner and easier to test, it will allow us to
-// clean up after ourselves better.
-static void download_to_file(geoipupdate_s *gu,
-                             const char *url,
-                             const char *fname,
-                             char *expected_file_md5) {
+// than a file. Write it to stderr and return an error.
+static int download_to_file(geoipupdate_s *gu,
+                            const char *url,
+                            const char *fname,
+                            char *expected_file_md5) {
     FILE *f = fopen(fname, "wb");
     if (f == NULL) {
         fprintf(stderr, "Can't open %s: %s\n", fname, strerror(errno));
@@ -553,6 +546,20 @@
     CURL *curl = gu->curl;
 
     expected_file_md5[0] = '\0';
+
+    char account_id[10] = {0};
+    int n = snprintf(account_id, 10, "%d", gu->license.account_id);
+    exit_if(n < 0,
+            "Error creating account ID string for %d: %s\n",
+            gu->license.account_id,
+            strerror(errno));
+    exit_if(n < 0 || n >= 10,
+            "An unexpectedly large account ID was encountered: %d\n",
+            gu->license.account_id);
+
+    curl_easy_setopt(curl, CURLOPT_USERNAME, account_id);
+    curl_easy_setopt(curl, CURLOPT_PASSWORD, gu->license.license_key);
+
     curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, get_expected_file_md5);
     curl_easy_setopt(curl, CURLOPT_HEADERDATA, expected_file_md5);
 
@@ -577,7 +584,19 @@
         exit(1);
     }
 
-    if (status < 200 || status >= 300) {
+    if (status == 304) {
+        say_if(gu->verbose, "No new updates available\n");
+        unlink(fname);
+        return GU_NO_UPDATE;
+    }
+
+    if (status == 401) {
+        fprintf(stderr, "Your account ID or license key is invalid\n");
+        unlink(fname);
+        return GU_ERROR;
+    }
+
+    if (status != 200) {
         fprintf(stderr,
                 "Received an unexpected HTTP status code of %ld from %s:\n",
                 status,
@@ -589,7 +608,7 @@
             free(message);
         }
         unlink(fname);
-        exit(1);
+        return GU_ERROR;
     }
 
     // We have HTTP 2xx.
@@ -600,8 +619,9 @@
         fprintf(stderr,
                 "Did not receive a valid expected database MD5 from server\n");
         unlink(fname);
-        exit(1);
+        return GU_ERROR;
     }
+    return GU_OK;
 }
 
 // Retrieve the server file time for the previous HTTP request.
@@ -645,7 +665,17 @@
     }
 }
 
-static in_mem_s *get(geoipupdate_s *gu, const char *url) {
+static int update_database_general(geoipupdate_s *gu, const char *edition_id) {
+    char *url = NULL, *geoip_filename = NULL, *geoip_gz_filename = NULL;
+    char hex_digest[33] = {0};
+
+    // Get the filename.
+    xasprintf(&url,
+              "%s://%s/app/update_getfilename?product_id=%s",
+              gu->proto,
+              gu->host,
+              edition_id);
+
     in_mem_s *mem = in_mem_s_new();
 
     say_if(gu->verbose, "url: %s\n", url);
@@ -663,47 +693,16 @@
     long status = 0;
     curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
 
-    exit_if(status < 200 || status >= 300,
-            "Received an unexpected HTTP status code of %ld from %s",
-            status,
-            url);
-
-    return mem;
-}
-
-// Generate an MD5 hash of the concatenation of license key with IP address.
-//
-// This hash is suitable for the challenge parameter for downloading from the
-// /update_secure endpoint.
-static void md5hex_license_ipaddr(geoipupdate_s *gu,
-                                  const char *client_ipaddr,
-                                  char *new_digest_str) {
-    unsigned char digest[16];
-    MD5_CONTEXT context;
-    md5_init(&context);
-    md5_write(&context,
-              (unsigned char *)gu->license.license_key,
-              strlen(gu->license.license_key));
-    md5_write(&context, (unsigned char *)client_ipaddr, strlen(client_ipaddr));
-    md5_final(&context);
-    memcpy(digest, context.buf, 16);
-    for (int i = 0; i < 16; i++) {
-        snprintf(&new_digest_str[2 * i], 3, "%02x", digest[i]);
+    if (status != 200) {
+        fprintf(stderr,
+                "Received an unexpected HTTP status code of %ld from %s\n",
+                status,
+                url);
+        free(url);
+        in_mem_s_delete(mem);
+        return GU_ERROR;
     }
-}
-
-static int update_database_general(geoipupdate_s *gu, const char *edition_id) {
-    char *url = NULL, *geoip_filename = NULL, *geoip_gz_filename = NULL,
-         *client_ipaddr = NULL;
-    char hex_digest[33] = {0}, hex_digest2[33] = {0};
 
-    // Get the filename.
-    xasprintf(&url,
-              "%s://%s/app/update_getfilename?product_id=%s",
-              gu->proto,
-              gu->host,
-              edition_id);
-    in_mem_s *mem = get(gu, url);
     free(url);
     if (mem->size == 0) {
         fprintf(stderr, "edition_id %s not found\n", edition_id);
@@ -718,63 +717,27 @@
     md5hex(geoip_filename, hex_digest);
     say_if(gu->verbose, "md5hex_digest: %s\n", hex_digest);
 
-    // Look up our IP.
-    xasprintf(&url, "%s://%s/app/update_getipaddr", gu->proto, gu->host);
-    mem = get(gu, url);
-    free(url);
-
-    client_ipaddr = strdup(mem->ptr);
-    if (NULL == client_ipaddr) {
-        fprintf(stderr, "Unable to allocate memory for client IP address.\n");
-        free(geoip_filename);
-        in_mem_s_delete(mem);
-        return GU_ERROR;
-    }
-
-    in_mem_s_delete(mem);
-
-    say_if(gu->verbose, "Client IP address: %s\n", client_ipaddr);
-
-    // Make the challenge MD5 hash.
-    md5hex_license_ipaddr(gu, client_ipaddr, hex_digest2);
-
-    free(client_ipaddr);
-    say_if(gu->verbose, "md5hex_digest2 (challenge): %s\n", hex_digest2);
-
     // Download.
     xasprintf(&url,
-              "%s://%s/app/"
-              "update_secure?db_md5=%s&challenge_md5=%s&user_id=%d&edition_id=%"
-              "s",
+              "%s://%s/geoip/databases/%s/update?db_md5=%s",
               gu->proto,
               gu->host,
-              hex_digest,
-              hex_digest2,
-              gu->license.account_id,
-              edition_id);
+              edition_id,
+              hex_digest);
     xasprintf(&geoip_gz_filename, "%s.gz", geoip_filename);
 
     char expected_file_md5[33] = {0};
-    download_to_file(gu, url, geoip_gz_filename, expected_file_md5);
+    int rc = download_to_file(gu, url, geoip_gz_filename, expected_file_md5);
     free(url);
 
-    // Was there actually an update? We can tell because if not we will have
-    // the same MD5 reported back. Note in the past we would check the response
-    // body which does still say whether we have an update.
-    if (strcmp(hex_digest, expected_file_md5) == 0) {
-        say_if(gu->verbose, "No new updates available\n");
-        unlink(geoip_gz_filename);
-        free(geoip_filename);
-        free(geoip_gz_filename);
-        return GU_OK;
-    }
-
-    long filetime = -1;
-    if (gu->preserve_file_times) {
-        filetime = get_server_time(gu);
+    if (rc == GU_OK) {
+        long filetime = -1;
+        if (gu->preserve_file_times) {
+            filetime = get_server_time(gu);
+        }
+        rc = gunzip_and_replace(
+            gu, geoip_gz_filename, geoip_filename, expected_file_md5, filetime);
     }
-    int rc = gunzip_and_replace(
-        gu, geoip_gz_filename, geoip_filename, expected_file_md5, filetime);
 
     free(geoip_gz_filename);
     free(geoip_filename);
@@ -790,53 +753,6 @@
     return err;
 }
 
-static int update_country_database(geoipupdate_s *gu) {
-    char *geoip_filename = NULL, *geoip_gz_filename = NULL, *url = NULL;
-    char hex_digest[33] = {0};
-
-    xasprintf(&geoip_filename, "%s/GeoIP.dat", gu->database_dir);
-    xasprintf(&geoip_gz_filename, "%s/GeoIP.dat.gz", gu->database_dir);
-
-    // Calculate the MD5 hash of the database we currently have, if any. We get
-    // back a zero MD5 hash if we don't have it yet.
-    md5hex(geoip_filename, hex_digest);
-    say_if(gu->verbose, "md5hex_digest: %s\n", hex_digest);
-
-    xasprintf(&url,
-              "%s://%s/app/update?license_key=%s&md5=%s",
-              gu->proto,
-              gu->host,
-              &gu->license.license_key[0],
-              hex_digest);
-
-    char expected_file_md5[33] = {0};
-    download_to_file(gu, url, geoip_gz_filename, expected_file_md5);
-    free(url);
-
-    // Was there actually an update? We can tell because if not we will have
-    // the same MD5 reported back. Note in the past we would check the response
-    // body which does still say whether we have an update.
-    if (strcmp(hex_digest, expected_file_md5) == 0) {
-        say_if(gu->verbose, "No new updates available\n");
-        unlink(geoip_gz_filename);
-        free(geoip_filename);
-        free(geoip_gz_filename);
-        return GU_OK;
-    }
-
-    long filetime = -1;
-    if (gu->preserve_file_times) {
-        filetime = get_server_time(gu);
-    }
-    int rc = gunzip_and_replace(
-        gu, geoip_gz_filename, geoip_filename, expected_file_md5, filetime);
-
-    free(geoip_gz_filename);
-    free(geoip_filename);
-
-    return rc;
-}
-
 // Decompress the compressed database and move it into place in the database
 // directory.
 //
--- geoipupdate-2.5.0/bin/geoipupdate.h			2017-10-30 15:38:24.000000000 +0100
+++ geoipupdate-2.5.0/bin/geoipupdate.h.licensekey	2023-04-13 20:59:03.483969697 +0200
@@ -12,7 +12,11 @@
 
 typedef struct {
     int account_id;
-    char license_key[13];
+    // For a long time, license keys were restricted to 12 characters. However,
+    // we want to change this for newer license keys. The array size is
+    // arbitrarily 100 as that seems big enough to hold any future license
+    // key.
+    char license_key[100];
     edition_s *first;
 } license_s;