pax_global_header00006660000000000000000000000064137657143320014525gustar00rootroot0000000000000052 comment=7b5009fa4cd889997783005cea56180b4b85974e geoipupdate-4.6.0/000077500000000000000000000000001376571433200140425ustar00rootroot00000000000000geoipupdate-4.6.0/.github/000077500000000000000000000000001376571433200154025ustar00rootroot00000000000000geoipupdate-4.6.0/.github/workflows/000077500000000000000000000000001376571433200174375ustar00rootroot00000000000000geoipupdate-4.6.0/.github/workflows/codeql-analysis.yml000066400000000000000000000030471376571433200232560ustar00rootroot00000000000000name: "Code scanning - action" on: push: pull_request: schedule: - cron: '0 11 * * 2' jobs: CodeQL-Build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 # If this run was triggered by a pull request event, then checkout # the head of the pull request instead of the merge commit. - run: git checkout HEAD^2 if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 # Override language selection by uncommenting this and choosing your languages # with: # languages: go, javascript, csharp, python, cpp, java # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 geoipupdate-4.6.0/.github/workflows/go.yml000066400000000000000000000016201376571433200205660ustar00rootroot00000000000000name: Go on: push: pull_request: schedule: - cron: '5 10 * * SUN' jobs: build: strategy: matrix: # We test back to 1.11 as we need to support old, EOL'd Go versions # to build PPA packages for older versions of Ubuntu. go-version: [1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} name: "Build ${{ matrix.go-version }} test on ${{ matrix.platform }}" steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Get dependencies run: go get -v -t -d ./... - name: Build run: go build -v ./... - name: Test run: go test -race -v ./... geoipupdate-4.6.0/.github/workflows/golangci-lint.yml000066400000000000000000000004641376571433200227150ustar00rootroot00000000000000name: golangci-lint on: push: pull_request: schedule: - cron: '5 5 * * SUN' jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: version: latest geoipupdate-4.6.0/.gitignore000066400000000000000000000000701376571433200160270ustar00rootroot00000000000000*.swp /build /cmd/geoipupdate/geoipupdate /vendor .idea geoipupdate-4.6.0/.golangci.toml000066400000000000000000000025621376571433200166050ustar00rootroot00000000000000[run] deadline = "10m" tests = true [linters] disable-all = true enable = [ "bodyclose", "deadcode", "depguard", "errcheck", "exhaustive", "exportloopref", "goconst", "gocyclo", "gocritic", "gofumpt", "golint", "gosec", "gosimple", "govet", "ineffassign", "lll", "maligned", "misspell", "nakedret", "noctx", "nolintlint", # https://github.com/golangci/golangci-lint/issues/287 # "safesql", "sqlclosecheck", "staticcheck", "structcheck", "stylecheck", "typecheck", "unconvert", "unparam", "unused", "varcheck", "vetshadow", ] [linters-settings.errcheck] # Ignoring Close so that we don't have to have a bunch of # `defer func() { _ = r.Close() }()` constructs when we # don't actually care about the error. ignore = "Close,fmt:.*" [linters-settings.exhaustive] default-signifies-exhaustive = true [linters-settings.gofumpt] extra-rules = true [linters-settings.lll] line-length = 120 tab-width = 4 [issues] exclude-use-default = false [[issues.exclude-rules]] linters = [ "gosec" ] # G306 - "Expect WriteFile permissions to be 0600 or less". text = "G306" # This goes off for MD5 usage, which we use heavily [[issues.exclude-rules]] text = "weak cryptographic primitive" linters = ["gosec"] geoipupdate-4.6.0/.goreleaser-packages.yml000066400000000000000000000034201376571433200205460ustar00rootroot00000000000000project_name: 'geoipupdate' archives: - id: main wrap_in_directory: true files: - 'CHANGELOG.md' - 'LICENSE-APACHE' - 'LICENSE-MIT' - 'README.md' - 'GeoIP.conf' - 'GeoIP.conf.md' - 'geoipupdate.md' builds: - main: './cmd/geoipupdate' binary: 'geoipupdate' goarch: - '386' - 'amd64' - 'arm' - 'arm64' goos: - 'linux' hooks: post: 'make data BUILDDIR=. CONFFILE=/etc/GeoIP.conf DATADIR=/usr/share/GeoIP' ldflags: - '-s -w -X main.version={{.Version}} -X main.defaultConfigFile=/etc/GeoIP.conf -X main.defaultDatabaseDirectory=/usr/share/GeoIP' env: - CGO_ENABLED=0 checksum: name_template: 'checksums-dpkg-rpm.txt' nfpms: - vendor: 'MaxMind, Inc.' homepage: https://www.maxmind.com/ maintainer: 'MaxMind, Inc. ' description: Program to perform automatic updates of GeoIP2 and GeoIP Legacy binary databases. license: Apache 2.0 or MIT formats: - deb - rpm bindir: /usr/bin empty_folders: - /usr/share/GeoIP files: 'CHANGELOG.md': '/usr/share/doc/geoipupdate/CHANGELOG.md' 'LICENSE-APACHE': '/usr/share/doc/geoipupdate/LICENSE-APACHE' 'LICENSE-MIT': '/usr/share/doc/geoipupdate/LICENSE-MIT' 'README.md': '/usr/share/doc/geoipupdate/README.md' 'GeoIP.conf': '/usr/share/doc/geoipupdate/GeoIP.conf' 'GeoIP.conf.md': '/usr/share/doc/geoipupdate/GeoIP.conf.md' 'geoipupdate.md': '/usr/share/doc/geoipupdate/geoipupdate.md' config_files: 'GeoIP.conf': '/etc/GeoIP.conf' release: # We disable the release as there is no way to disable the creation of # the archive version and we don't want to upload those. We also can # only do one release. disable: true geoipupdate-4.6.0/.goreleaser-windows.yml000066400000000000000000000011271376571433200204640ustar00rootroot00000000000000project_name: 'geoipupdate' archives: - id: main wrap_in_directory: true files: - 'CHANGELOG.md' - 'LICENSE-APACHE' - 'LICENSE-MIT' - 'README.md' - 'GeoIP.conf' - 'GeoIP.conf.md' - 'geoipupdate.md' format: zip builds: - main: './cmd/geoipupdate' binary: 'geoipupdate' goarch: - '386' - 'amd64' goos: - 'windows' hooks: post: 'make data OS=Windows_NT BUILDDIR=.' env: - CGO_ENABLED=0 checksum: name_template: 'checksums-windows.txt' release: # We can only do one release. disable: true geoipupdate-4.6.0/.goreleaser.yml000066400000000000000000000015321376571433200167740ustar00rootroot00000000000000project_name: 'geoipupdate' archives: - id: main wrap_in_directory: true files: - 'CHANGELOG.md' - 'LICENSE-APACHE' - 'LICENSE-MIT' - 'README.md' - 'GeoIP.conf' - 'GeoIP.conf.md' - 'geoipupdate.md' builds: - main: './cmd/geoipupdate' binary: 'geoipupdate' goarch: - '386' - 'amd64' - 'arm' - 'arm64' goos: - 'darwin' - 'linux' hooks: post: 'make data BUILDDIR=.' env: - CGO_ENABLED=0 dockers: - image_templates: - "maxmindinc/geoipupdate:{{ .Tag }}" - "maxmindinc/geoipupdate:v{{ .Major }}" - "maxmindinc/geoipupdate:v{{ .Major }}.{{ .Minor }}" - "maxmindinc/geoipupdate:latest" dockerfile: docker/Dockerfile extra_files: - docker/entry.sh checksum: name_template: 'checksums-darwin-linux.txt' geoipupdate-4.6.0/.yamllintrc000066400000000000000000000002471376571433200162240ustar00rootroot00000000000000extends: default rules: braces: disable brackets: disable line-length: disable colons: {max-spaces-before: 0, max-spaces-after: -1} document-start: disable geoipupdate-4.6.0/CHANGELOG.md000066400000000000000000000114141376571433200156540ustar00rootroot00000000000000# CHANGELOG ## 4.6.0 (2020-12-14) * Show version number in verbose output. * Retry downloads in more scenarios. Previously we would not retry failures occurring when reading the response body, but now we do. ## 4.5.0 (2020-10-28) * We no longer use a third party library for exponential backoff. This restores support for older Go versions. ## 4.4.0 (2020-10-28) * The edition ID is now included when there is a failure retrieving a database. * The Docker image no longer prints the generated `GeoIP.conf` when starting up. This prevents a possible leak of the account's license key. Pull request by Nate Gay. GitHub #109. * The minimum Go version is now 1.11. * Failing HTTP requests are now retried using an exponential backoff. The period to keep retrying any failed request is set to 5 minutes by default and can be adjusted using the new `RetryFor` configuration option. * When using the go package rather than the command-line tool, the null value for `RetryFor` will be 0 seconds, which means no retries will be performed. To change that, set `RetryFor` explicitly in the `Config` you provide, or obtain your `Config` value via `geoipupdate.NewConfig`. ## 4.3.0 (2020-04-16) * First release to Docker Hub. Requested by Shun Yanaura. GitHub #24. * The binary builds are now built with `CGO_ENABLED=0`. Request by CrazyMax. GitHub #63. ## 4.2.2 (2020-02-21) * Re-release for PPA. No other changes. ## 4.2.1 (2020-02-21) * The minimum Go version is now 1.10 again as this was needed to build the PPA packages. ## 4.2.0 (2020-02-20) * The major version of the module is now included at the end of the module path. Previously, it was not possible to import the module in projects that were using Go modules. Reported by Roman Glushko. GitHub #81. * The minimum Go version is now 1.13. * A valid account ID and license key combination is now required for database downloads, so those configuration options are now required. * The error handling when closing a local database file would previously ignore errors and, upon upgrading to `github.com/pkg/errors` 0.9.0, would fail to ignore expected errors. Reported by Ilya Skrypitsa and pgnd. GitHub #69 and #70. * The RPM release was previously lacking the correct owner and group on files and directories. Among other things, this caused the package to conflict with the `GeoIP` package in CentOS 7 and `GeoIP-GeoLite-data` in CentOS 8. The files are now owned by `root`. Reported by neonknight. GitHub #76. ## 4.1.5 (2019-11-08) * Respect the defaultConfigFile and defaultDatabaseDirectory variables in the main package again. They were ignored in 4.1.0 through 4.1.4. If not specified, the GitHub and PPA releases for these versions used the config /usr/local/etc/GeoIP.conf instead of /etc/GeoIP.conf and the database directory /usr/local/share/GeoIP instead of /usr/share/GeoIP. ## 4.1.4 (2019-11-07) * Re-release of 4.1.3 as two commits were missing. No changes. ## 4.1.3 (2019-11-07) * Remove formatting, linting, and testing from the geoipupdate target in the Makefile. ## 4.1.2 (2019-11-07) * Re-release of 4.1.1 to fix Ubuntu PPA release issue. No code changes. ## 4.1.1 (2019-11-07) * Re-release of 4.1.0 to fix Ubuntu PPA release issue. No code changes. ## 4.1.0 (2019-11-07) * Improve man page formatting and organization. Pull request by Faidon Liambotis. GitHub #44. * Provide update functionality as an importable package as well as a standalone program. Pull request by amzhughe. GitHub #48. ## 4.0.6 (2019-09-13) * Re-release of 4.0.5 to fix Ubuntu PPA release issue. No code changes. ## 4.0.5 (2019-09-13) * Ignore errors when syncing file system. These errors were primarily due to the file system not supporting the sync call. Reported by devkappa. GitHub #37. * Use CRLF line endings on Windows for text files. * Fix tests on Windows. * Improve man page formatting. Reported by Faidon Liambotis. GitHub #38. * Dependencies are no longer vendored. Reported by Faidon Liambotis. GitHub #39. ## 4.0.4 (2019-08-30) * Do not try to sync the database directory when running on Windows. Syncing this way is not supported there and would lead to an error. Pull request by Nicholi. GitHub #32. ## 4.0.3 (2019-06-07) * Update flock dependency from `theckman/go-flock` to `gofrs/flock`. Pull request by Paul Howarth. GitHub #22. * Switch to Go modules and update dependencies. * Fix version output on Ubuntu PPA and Homebrew releases. ## 4.0.2 (2019-01-18) * Fix dependency in `Makefile`. ## 4.0.1 (2019-01-17) * Improve documentation. * Add script to generate man pages to `Makefile`. ## 4.0.0 (2019-01-14) * Expand installation instructions. * First full release. ## 0.0.2 (2018-11-28) * Fix the output when the version output, `-V`, is passed to `geoipupdate`. ## 0.0.1 (2018-11-27) * Initial version geoipupdate-4.6.0/LICENSE-APACHE000066400000000000000000000261361376571433200157760ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. geoipupdate-4.6.0/LICENSE-MIT000066400000000000000000000017771376571433200155120ustar00rootroot00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. geoipupdate-4.6.0/Makefile000066400000000000000000000034711376571433200155070ustar00rootroot00000000000000ifndef BUILDDIR BUILDDIR=build endif ifndef CONFFILE ifeq ($(OS),Windows_NT) CONFFILE=%SystemDrive%\ProgramData\MaxMind\GeoIPUpdate\GeoIP.conf else CONFFILE=/usr/local/etc/GeoIP.conf endif endif ifndef DATADIR ifeq ($(OS),Windows_NT) DATADIR=%SystemDrive%\ProgramData\MaxMind\GeoIPUpdate\GeoIP else DATADIR=/usr/local/share/GeoIP endif endif ifeq ($(OS),Windows_NT) MAYBE_CR=\r endif ifndef VERSION VERSION=unknown endif all: \ $(BUILDDIR)/geoipupdate \ data data: \ $(BUILDDIR)/GeoIP.conf \ $(BUILDDIR)/GeoIP.conf.md \ $(BUILDDIR)/geoipupdate.md \ $(BUILDDIR)/GeoIP.conf.5 \ $(BUILDDIR)/geoipupdate.1 $(BUILDDIR): mkdir -p $(BUILDDIR) $(BUILDDIR)/geoipupdate: $(BUILDDIR) (cd cmd/geoipupdate && go build -ldflags '-X main.defaultConfigFile=$(CONFFILE) -X main.defaultDatabaseDirectory=$(DATADIR) -X "main.version=$(VERSION)"') cp cmd/geoipupdate/geoipupdate $(BUILDDIR) $(BUILDDIR)/GeoIP.conf: $(BUILDDIR) conf/GeoIP.conf.default sed -e 's|CONFFILE|$(CONFFILE)|g' -e 's|DATADIR|$(DATADIR)|g' -e 's|$$|$(MAYBE_CR)|g' conf/GeoIP.conf.default > $(BUILDDIR)/GeoIP.conf $(BUILDDIR)/GeoIP.conf.md: $(BUILDDIR) doc/GeoIP.conf.md sed -e 's|CONFFILE|$(CONFFILE)|g' -e 's|DATADIR|$(DATADIR)|g' -e 's|$$|$(MAYBE_CR)|g' doc/GeoIP.conf.md > $(BUILDDIR)/GeoIP.conf.md $(BUILDDIR)/geoipupdate.md: $(BUILDDIR) doc/geoipupdate.md sed -e 's|CONFFILE|$(CONFFILE)|g' -e 's|DATADIR|$(DATADIR)|g' -e 's|$$|$(MAYBE_CR)|g' doc/geoipupdate.md > $(BUILDDIR)/geoipupdate.md $(BUILDDIR)/GeoIP.conf.5: $(BUILDDIR)/GeoIP.conf.md $(BUILDDIR)/geoipupdate.md dev-bin/make-man-pages.pl "$(BUILDDIR)" $(BUILDDIR)/geoipupdate.1: $(BUILDDIR)/GeoIP.conf.5 clean: rm -rf $(BUILDDIR)/GeoIP.conf \ $(BUILDDIR)/GeoIP.conf.md \ $(BUILDDIR)/geoipupdate \ $(BUILDDIR)/geoipupdate.md \ $(BUILDDIR)/GeoIP.conf.5 \ $(BUILDDIR)/geoipupdate.1 geoipupdate-4.6.0/README.dev.md000066400000000000000000000025101376571433200160740ustar00rootroot00000000000000# Releasing * Make sure you have [`hub`](https://github.com/github/hub), [`goreleaser`](https://goreleaser.com/), and rpmbuild installed. (rpmbuild is in the Ubuntu package `rpm`). * Set release date in `CHANGELOG.md` and commit it. * Run `GITHUB_TOKEN= ./dev-bin/release.sh`. For `goreleaser` you will need a token with the `repo` scope. You may create a token [here](https://github.com/settings/tokens/new). Then release to our PPA: * Switch to the `ubuntu-ppa` branch. Merge the released tag into it. e.g. `git merge v4.1.0`. * Set up to release to launchpad. You can see some information about prerequisites for this [here](https://github.com/maxmind/libmaxminddb/blob/master/README.dev.md). * Ensure you have the `dh-golang` and `golang-any` packages installed. * Delete `dist` directory. * Check whether you need to update the `$DISTS` variable in `dev-bin/ppa-release.sh`. We should include all currently supported Ubuntu releases. * Run `dev-bin/ppa-release.sh` Finally release to Homebrew: * Go to https://github.com/Homebrew/homebrew-core/blob/master/Formula/geoipupdate.rb * Edit the file to update the url and sha256. You can get the sha256 for the tarball with the `sha256sum` command line utility. * Make a commit with the summary `geoipupdate ` * Submit a PR with the changes you just made. geoipupdate-4.6.0/README.md000066400000000000000000000106641376571433200153300ustar00rootroot00000000000000# GeoIP Update The GeoIP Update program performs automatic updates of GeoIP2 and GeoIP Legacy binary databases. CSV databases are _not_ supported. This is the new version of GeoIP Update. If for some reason you need the legacy C version, you can find it [here](https://github.com/maxmind/geoipupdate-legacy). ## Installation We provide releases for Linux, macOS (darwin), and Windows. Please see the [Releases](https://github.com/maxmind/geoipupdate/releases) tab for the latest release. After you install geoipupdate, please refer to our [documentation](https://dev.maxmind.com/geoip/geoipupdate/) for information about configuration. If you're upgrading from geoipupdate 3.x, please see our [upgrade guide](https://dev.maxmind.com/geoip/geoipupdate/upgrading-to-geoip-update-4-x/). ### Installing on Linux via the tarball Download and extract the appropriate tarball for your system. You will end up with a directory named something like `geoipupdate_4.0.0_linux_amd64` depending on the version and architecture. Copy `geoipupdate` to where you want it to live. To install it to `/usr/local/bin/geoipupdate`, run the equivalent of `sudo cp geoipupdate_4.0.0_linux_amd64/geoipupdate /usr/local/bin`. `geoipupdate` looks for the config file `/usr/local/etc/GeoIP.conf` by default. ### Installing on Ubuntu via PPA MaxMind provides a PPA for recent versions of Ubuntu. To add the PPA to your sources, run: ``` $ sudo add-apt-repository ppa:maxmind/ppa ``` Then install `geoipupdate` by running: ``` $ sudo apt update $ sudo apt install geoipupdate ``` ### Installing on Ubuntu or Debian via the deb You can also use the tarball. Download the appropriate .deb for your system. Run `dpkg -i path/to/geoipupdate_4.0.0_linux_amd64.deb` (replacing the version number and architecture as necessary). You will need to be root. For Ubuntu you can prefix the command with `sudo`. This will install `geoipupdate` to `/usr/bin/geoipupdate`. `geoipupdate` looks for the config file `/etc/GeoIP.conf` by default. ### Installing on RedHat or CentOS via the rpm You can also use the tarball. Download the appropriate .rpm for your system. Run `rpm -Uvhi path/to/geoipupdate_4.0.0_linux_amd64.rpm` (replacing the version number and architecture as necessary). You will need to be root. This will install `geoipupdate` to `/usr/bin/geoipupdate`. `geoipupdate` looks for the config file `/etc/GeoIP.conf` by default. ### Installing on macOS (darwin) via the tarball This is the same as installing on Linux via the tarball, except choose a tarball with "darwin" in the name. ### Installing on macOS via Homebrew If you are on macOS and you have [Homebrew](http://brew.sh/) you can install `geoipupdate` via `brew` ``` $ brew install geoipupdate ``` ### Installing on Windows Download and extract the appropriate zip for your system. You will end up with a directory named something like `geoipupdate_4.0.0_windows_amd64` depending on the version and architecture. Copy `geoipupdate.exe` to where you want it to live. `geoipupdate` looks for the config file `\ProgramData\MaxMind/GeoIPUpdate\GeoIP.conf` on your system drive by default. ### Installing via Docker Please see our [Docker documentation](doc/docker.md). ### Installation from source or Git You need the Go compiler (1.8+). You can get it at the [Go website](https://golang.org). The easiest way is via `go get`: $ env GO111MODULE=on go get -u github.com/maxmind/geoipupdate/v4/cmd/geoipupdate This installs `geoipupdate` to `$GOPATH/bin/geoipupdate`. # Configuring Please see our [online guide](https://dev.maxmind.com/geoip/geoipupdate/) for directions on how to configure GeoIP Update. # Documentation See our documentation for the [`geoipupdate` program](doc/geoipupdate.md) and the [`GeoIP.conf` configuration file](doc/GeoIP.conf.md). # Default config file and database directory paths We define default paths for the config file and database directory. If these defaults are not appropriate for you, you can change them at build time using flags: go build -ldflags "-X main.defaultConfigFile=/etc/GeoIP.conf \ -X main.defaultDatabaseDirectory=/usr/share/GeoIP" # Bug Reports Please report bugs by filing an issue with our GitHub issue tracker at https://github.com/maxmind/geoipupdate/issues # Copyright and License This software is Copyright (c) 2018 - 2020 by MaxMind, Inc. This is free software, licensed under the [Apache License, Version 2.0](LICENSE-APACHE) or the [MIT License](LICENSE-MIT), at your option. geoipupdate-4.6.0/_config.yml000066400000000000000000000000471376571433200161720ustar00rootroot00000000000000exclude: ['README.dev.md', 'vendor'] geoipupdate-4.6.0/_layouts/000077500000000000000000000000001376571433200157015ustar00rootroot00000000000000geoipupdate-4.6.0/_layouts/default.html000066400000000000000000000032421376571433200202140ustar00rootroot00000000000000 {{ page.title }}
{{ content }}
geoipupdate-4.6.0/cmd/000077500000000000000000000000001376571433200146055ustar00rootroot00000000000000geoipupdate-4.6.0/cmd/geoipupdate/000077500000000000000000000000001376571433200171135ustar00rootroot00000000000000geoipupdate-4.6.0/cmd/geoipupdate/args.go000066400000000000000000000024331376571433200204000ustar00rootroot00000000000000package main import ( "log" "os" flag "github.com/spf13/pflag" ) // Args are command line arguments. type Args struct { ConfigFile string DatabaseDirectory string StackTrace bool Verbose bool } func getArgs() *Args { configFile := flag.StringP( "config-file", "f", defaultConfigFile, "Configuration file", ) databaseDirectory := flag.StringP( "database-directory", "d", "", "Store databases in this directory (uses config if not specified)", ) help := flag.BoolP("help", "h", false, "Display help and exit") stackTrace := flag.Bool("stack-trace", false, "Show a stack trace along with any error message.") verbose := flag.BoolP("verbose", "v", false, "Use verbose output") displayVersion := flag.BoolP("version", "V", false, "Display the version and exit") flag.Parse() if *help { printUsage() } if *displayVersion { log.Printf("geoipupdate %s", version) os.Exit(0) } if *configFile == "" { log.Printf("You must provide a configuration file.") printUsage() } return &Args{ ConfigFile: *configFile, DatabaseDirectory: *databaseDirectory, StackTrace: *stackTrace, Verbose: *verbose, } } func printUsage() { log.Printf("Usage: %s \n", os.Args[0]) flag.PrintDefaults() os.Exit(1) } geoipupdate-4.6.0/cmd/geoipupdate/end_to_end_test.go000066400000000000000000000046451376571433200226100ustar00rootroot00000000000000package main import ( "bytes" "compress/gzip" "crypto/md5" "fmt" "io" "io/ioutil" "log" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "github.com/maxmind/geoipupdate/v4/pkg/geoipupdate" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestMultipleDatabaseDownload(t *testing.T) { databaseContent := "database content goes here" server := httptest.NewServer( http.HandlerFunc( func(rw http.ResponseWriter, r *http.Request) { err := r.ParseForm() require.NoError(t, err, "parse form") if strings.HasPrefix(r.URL.Path, "/geoip/databases") { buf := &bytes.Buffer{} gzWriter := gzip.NewWriter(buf) md5Writer := md5.New() multiWriter := io.MultiWriter(gzWriter, md5Writer) _, err := multiWriter.Write([]byte( databaseContent + " " + r.URL.Path, )) require.NoError(t, err) err = gzWriter.Close() require.NoError(t, err) rw.Header().Set( "X-Database-MD5", fmt.Sprintf("%x", md5Writer.Sum(nil)), ) rw.Header().Set("Last-Modified", time.Now().Format(time.RFC1123)) _, err = rw.Write(buf.Bytes()) require.NoError(t, err) return } if r.URL.Path == "/app/update_getfilename" { _, err := rw.Write([]byte(r.Form.Get("product_id") + ".mmdb")) require.NoError(t, err) return } rw.WriteHeader(http.StatusBadRequest) }, ), ) defer server.Close() client := server.Client() tempDir, err := ioutil.TempDir("", "gutest-") require.NoError(t, err) defer func() { err := os.RemoveAll(tempDir) require.NoError(t, err) }() config := &geoipupdate.Config{ AccountID: 123, DatabaseDirectory: tempDir, EditionIDs: []string{"GeoLite2-City", "GeoLite2-Country"}, LicenseKey: "testing", LockFile: filepath.Join(tempDir, ".geoipupdate.lock"), URL: server.URL, } logOutput := &bytes.Buffer{} log.SetOutput(logOutput) err = run(client, config) assert.NoError(t, err, "run successfully") assert.Equal(t, "", logOutput.String(), "no logged output") for _, editionID := range config.EditionIDs { path := filepath.Join(config.DatabaseDirectory, editionID+".mmdb") buf, err := ioutil.ReadFile(path) //nolint:gosec require.NoError(t, err, "read file") assert.Equal( t, databaseContent+" /geoip/databases/"+editionID+"/update", string(buf), "correct database", ) } } geoipupdate-4.6.0/cmd/geoipupdate/main.go000066400000000000000000000041041376571433200203650ustar00rootroot00000000000000package main import ( "fmt" "log" "net/http" "os" "path/filepath" "github.com/maxmind/geoipupdate/v4/pkg/geoipupdate" "github.com/maxmind/geoipupdate/v4/pkg/geoipupdate/database" "github.com/pkg/errors" ) var ( version = "unknown" defaultConfigFile string defaultDatabaseDirectory string ) func main() { log.SetFlags(0) if defaultConfigFile == "" { defaultConfigFile = geoipupdate.DefaultConfigFile } if defaultDatabaseDirectory == "" { defaultDatabaseDirectory = geoipupdate.DefaultDatabaseDirectory } args := getArgs() fatalLogger := func(message string, err error) { if args.StackTrace { log.Print(fmt.Sprintf("%s: %+v", message, err)) } else { log.Print(fmt.Sprintf("%s: %s", message, err)) } os.Exit(1) } config, err := geoipupdate.NewConfig( args.ConfigFile, defaultDatabaseDirectory, args.DatabaseDirectory, args.Verbose) if err != nil { fatalLogger(fmt.Sprintf("error loading configuration file %s", args.ConfigFile), err) } if config.Verbose { log.Printf("geoipupdate version %s", version) log.Printf("Using config file %s", args.ConfigFile) log.Printf("Using database directory %s", config.DatabaseDirectory) } client := geoipupdate.NewClient(config) if err = run(client, config); err != nil { fatalLogger("error retrieving updates", err) } } func run(client *http.Client, config *geoipupdate.Config) error { dbReader := database.NewHTTPDatabaseReader(client, config) for _, editionID := range config.EditionIDs { filename, err := geoipupdate.GetFilename(config, editionID, client) if err != nil { return errors.Wrapf(err, "error retrieving filename for %s", editionID) } filePath := filepath.Join(config.DatabaseDirectory, filename) dbWriter, err := database.NewLocalFileDatabaseWriter(filePath, config.LockFile, config.Verbose) if err != nil { return errors.Wrapf(err, "error creating database writer for %s", editionID) } if err := dbReader.Get(dbWriter, editionID); err != nil { return errors.WithMessagef(err, "error while getting database for %s", editionID) } } return nil } geoipupdate-4.6.0/cmd/geoipupdate/version.go000066400000000000000000000002671376571433200211340ustar00rootroot00000000000000// +build go1.12 package main import "runtime/debug" func init() { if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "(devel)" { version = info.Main.Version } } geoipupdate-4.6.0/conf/000077500000000000000000000000001376571433200147675ustar00rootroot00000000000000geoipupdate-4.6.0/conf/GeoIP.conf.default000066400000000000000000000033771376571433200202360ustar00rootroot00000000000000# Please see https://dev.maxmind.com/geoip/geoipupdate/ for instructions # on setting up geoipupdate, including information on how to download a # pre-filled GeoIP.conf file. # Replace YOUR_ACCOUNT_ID_HERE and YOUR_LICENSE_KEY_HERE with an active account # ID and license key combination associated with your MaxMind account. These # are available from https://www.maxmind.com/en/my_license_key. AccountID YOUR_ACCOUNT_ID_HERE LicenseKey YOUR_LICENSE_KEY_HERE # Enter the edition IDs of the databases you would like to update. # Multiple edition IDs are separated by spaces. EditionIDs GeoLite2-Country GeoLite2-City # The remaining settings are OPTIONAL. # The directory to store the database files. Defaults to DATADIR # DatabaseDirectory DATADIR # The server to use. Defaults to "updates.maxmind.com". # Host updates.maxmind.com # The proxy host name or IP address. You may optionally specify a # port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080 # will be used. # Proxy 127.0.0.1:8888 # The user name and password to use with your proxy server. # ProxyUserPassword username:password # Whether to preserve modification times of files downloaded from the server. # Defaults to "0". # PreserveFileTimes 0 # The lock file to use. This ensures only one geoipupdate process can run at a # time. # Note: Once created, this lockfile is not removed from the filesystem. # Defaults to ".geoipupdate.lock" under the DatabaseDirectory. # LockFile DATADIR/.geoipupdate.lock # The amount of time to retry for when errors during HTTP transactions are # encountered. It can be specified as a (possibly fractional) decimal number # followed by a unit suffix. Valid time units are "ns", "us" (or "µs"), "ms", # "s", "m", "h". # Defaults to "5m" (5 minutes). # RetryFor 5m geoipupdate-4.6.0/dev-bin/000077500000000000000000000000001376571433200153665ustar00rootroot00000000000000geoipupdate-4.6.0/dev-bin/make-man-pages.pl000077500000000000000000000022501376571433200205100ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; use File::Temp qw( tempfile ); sub main { my $build_dir = $ARGV[0] // 'build'; _make_man( 'geoipupdate', 1, "$build_dir/geoipupdate.md", "$build_dir/geoipupdate.1", ); _make_man( 'GeoIP.conf', 5, "$build_dir/GeoIP.conf.md", "$build_dir/GeoIP.conf.5", ); return 1; } sub _make_man { my ( $name, $section, $input, $output ) = @_; my ( $fh, $tmp ) = tempfile(); binmode $fh or die $!; print {$fh} "% $name($section)\n\n" or die $!; my $contents = _read($input); print {$fh} $contents or die $!; close $fh or die $!; system( 'pandoc', '-s', '-f', 'markdown', '-t', 'man', $tmp, '-o', $output, ) == 0 or die 'pandoc failed'; return; } sub _read { my ($file) = @_; open my $fh, '<', $file or die $!; binmode $fh or die $!; my $contents = ''; while ( !eof($fh) ) { my $line = <$fh>; die 'error reading' unless defined $line; $contents .= $line; } close $fh or die $!; return $contents; } exit( main() ? 0 : 1 ); geoipupdate-4.6.0/dev-bin/release.sh000077500000000000000000000036011376571433200173450ustar00rootroot00000000000000#!/bin/bash set -eu -o pipefail changelog=$(cat CHANGELOG.md) if [[ -z ${GITHUB_TOKEN:-} ]]; then echo 'GITHUB_TOKEN must be set for goreleaser!' exit 1 fi regex=' ## ([0-9]+\.[0-9]+\.[0-9]+) \(([0-9]{4}-[0-9]{2}-[0-9]{2})\) ((.| )*)' if [[ ! $changelog =~ $regex ]]; then echo "Could not find date line in change log!" exit 1 fi version="${BASH_REMATCH[1]}" date="${BASH_REMATCH[2]}" notes="$(echo "${BASH_REMATCH[3]}" | sed -n -e '/^## [0-9]\+\.[0-9]\+\.[0-9]\+/,$!p')" if [[ "$date" != $(date +"%Y-%m-%d") ]]; then echo "$date is not today!" exit 1 fi if [ -n "$(git status --porcelain)" ]; then echo ". is not clean." >&2 exit 1 fi tag="v$version" echo $'\nRelease notes:' echo "$notes" read -p "Continue? (y/n) " ok if [ "$ok" != "y" ]; then echo "Aborting" exit 1 fi echo "Creating tag $tag" message="$version $notes" git tag -a -m "$message" "$tag" # It's important to push before running any hub commands as hub works off # what's pushed. git push # goreleaser's `--rm-dist' should clear out `dist', but it didn't work for me. rm -rf dist goreleaser release --rm-dist -f .goreleaser.yml --release-notes <(echo "$message") make clean BUILDDIR=. rm -rf dist goreleaser release --rm-dist -f .goreleaser-windows.yml --skip-publish hub release edit -m "$message" \ -a "dist/geoipupdate_${version}_windows_386.zip" \ -a "dist/geoipupdate_${version}_windows_amd64.zip" \ -a dist/checksums-windows.txt \ "$tag" make clean BUILDDIR=. rm -rf dist goreleaser release --rm-dist -f .goreleaser-packages.yml --skip-publish hub release edit -m "$message" \ -a dist/checksums-dpkg-rpm.txt \ -a "dist/geoipupdate_${version}_linux_386.deb" \ -a "dist/geoipupdate_${version}_linux_amd64.deb" \ -a "dist/geoipupdate_${version}_linux_386.rpm" \ -a "dist/geoipupdate_${version}_linux_amd64.rpm" \ "$tag" make clean BUILDDIR=. geoipupdate-4.6.0/doc/000077500000000000000000000000001376571433200146075ustar00rootroot00000000000000geoipupdate-4.6.0/doc/GeoIP.conf.md000066400000000000000000000044051376571433200170230ustar00rootroot00000000000000# NAME GeoIP.conf - Configuration file for geoipupdate # SYNOPSIS This file allows you to configure your `geoipupdate` program to download GeoIP2, GeoLite2, and GeoIP Legacy databases. # DESCRIPTION The file consists of one setting per line. Lines starting with `#` are comments and will not be processed. All setting keywords are case sensitive. ## Required settings: `AccountID` : Your MaxMind account ID. This was formerly known as `UserId`. `LicenseKey` : Your case-sensitive MaxMind license key. `EditionIDs` : List of space-separated database edition IDs. Edition IDs may consist of letters, digits, and dashes. For example, `GeoIP2-City 106` would download the GeoIP2 City database (`GeoIP2-City`) and the GeoIP Legacy Country database (`106`). Note: this was formerly called `ProductIds`. ## Optional settings: `DatabaseDirectory` : The directory to store the database files. If not set, the default is DATADIR. This can be overridden at run time by the `-d` command line argument. `Host` : The host name of the server to use. The default is `updates.maxmind.com`. `Proxy` : The proxy host name or IP address. You may optionally specify a port number, e.g., `127.0.0.1:8888`. If no port number is specified, 1080 will be used. `ProxyUserPassword` : The proxy user name and password, separated by a colon. For instance, `username:password`. `PreserveFileTimes` : Whether to preserve modification times of files downloaded from the server. This option is either `0` or `1`. The default is `0`. `LockFile` : The lock file to use. This ensures only one `geoipupdate` process can run at a time. Note: Once created, this lockfile is not removed from the filesystem. The default is `.geoipupdate.lock` under the `DatabaseDirectory`. `RetryFor` : The amount of time to retry for when errors during HTTP transactions are encountered. It can be specified as a (possibly fractional) decimal number followed by a unit suffix. Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. The default is `5m` (5 minutes). ## Deprecated settings: The following are deprecated and will be ignored if present: `Protocol` `SkipPeerVerification` `SkipHostnameVerification` # SEE ALSO `geoipupdate`(1) geoipupdate-4.6.0/doc/docker.md000066400000000000000000000055401376571433200164040ustar00rootroot00000000000000# Docker ## Image information The image is available on [Docker Hub](https://hub.docker.com/r/maxmindinc/geoipupdate). The source code is available on [GitHub](https://github.com/maxmind/geoipupdate). ## Configuring The Docker image is configured by environment variables. The following variables are required: * `GEOIPUPDATE_ACCOUNT_ID` - Your MaxMind account ID. * `GEOIPUPDATE_LICENSE_KEY` - Your case-sensitive MaxMind license key. * `GEOIPUPDATE_EDITION_IDS` - List of space-separated database edition IDs. Edition IDs may consist of letters, digits, and dashes. For example, `GeoIP2-City 106` would download the GeoIP2 City database (`GeoIP2-City`) and the GeoIP Legacy Country database (`106`). The following are optional: * `GEOIPUPDATE_FREQUENCY` - The number of hours between `geoipupdate` runs. If this is not set or is set to `0`, `geoipupdate` will run once and exit. * `GEOIPUPDATE_HOST` - The host name of the server to use. The default is `updates.maxmind.com`. * `GEOIPUPDATE_PROXY` - The proxy host name or IP address. You may optionally specify a port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080 will be used. * `GEOIPUPDATE_PROXY_USER_PASSWORD` - The proxy user name and password, separated by a colon. For instance, `username:password`. * `GEOIPUPDATE_PRESERVE_FILE_TIMES` - Whether to preserve modification times of files downloaded from the server. This option is either `0` or `1`. The default is `0`. * `GEOIPUPDATE_VERBOSE` - Enable verbose mode. Prints out the steps that `geoipupdate` takes. Set to **anything** (e.g., `1`) to enable. The environment variables can be placed in a file with one per line and passed in with the `--env-file` flag. Alternatively, you may pass them in individually with the `-e` flag. ## Running ### docker run Run the latest image with: ``` docker run --env-file -v :/usr/share/GeoIP maxmindinc/geoipupdate ``` `` should be the environment variable file with your configuration. `` should be the local directory that you want to download the databases to. ### docker-compose Run the latest image with: ``` version: '3' services: geoipupdate: container_name: geoipupdate image: maxmindinc/geoipupdate restart: unless-stopped environment: - GEOIPUPDATE_ACCOUNT_ID=XXXXXX - GEOIPUPDATE_LICENSE_KEY=XXXXXXXXXXXXXXXX - 'GEOIPUPDATE_EDITION_IDS=GeoLite2-ASN GeoLite2-City GeoLite2-Country' - GEOIPUPDATE_FREQUENCY=72 networks: - geoipupdate volumes: - 'geoipupdate_data:/usr/share/GeoIP' networks: geoipupdate: volumes: geoipupdate_data: driver: local ``` Note - When using docker-compose, you need to either: - set `GEOIPUPDATE_FREQUENCY` equal to something greater than 0 or - set `restart: on-failure` If you don't, the container will continuously restart. geoipupdate-4.6.0/doc/geoipupdate.md000066400000000000000000000041601376571433200174400ustar00rootroot00000000000000# NAME geoipupdate - GeoIP2, GeoLite2, and GeoIP Legacy Update Program # SYNOPSIS **geoipupdate** [-Vvh] [-f *CONFIG_FILE*] [-d *TARGET_DIRECTORY*] # DESCRIPTION `geoipupdate` automatically updates GeoIP2, GeoLite2, and GeoIP Legacy databases. The program connects to the MaxMind GeoIP Update server to check for new databases. If a new database is available, the program will download and install it. If you are using a firewall, you must have the DNS and HTTPS ports open. # OPTIONS `-d`, `--database-directory` : Install databases to a custom directory. This is optional. If provided, it overrides any `DatabaseDirectory` set in the configuration file. `-f`, `--config-file` : The configuration file to use. See `GeoIP.conf` and its documentation for more information. This is optional. It defaults to CONFFILE. `-h`, `--help` : Display help and exit. `--stack-trace` : Show a stack trace on any error message. This is primarily useful for debugging. `-V`, `--version` : Display version information and exit. `-v`, `--verbose` : Enable verbose mode. Prints out the steps that `geoipupdate` takes. # EXIT STATUS `geoipupdate` returns 0 on success and 1 on error. # NOTES Typically you should run `geoipupdate` weekly. On most Unix-like systems, this can be achieved by using cron. Below is a sample crontab file that runs `geoipupdate` on each Wednesday at noon: # top of crontab MAILTO=your@email.com 0 12 * * 3 geoipupdate # end of crontab To use with a proxy server, update your `GeoIP.conf` file as specified in the `GeoIP.conf` man page or set the `http_proxy` environment variable. # BUGS Report bugs to [support@maxmind.com](mailto:support@maxmind.com). # AUTHORS Written by William Storey. This software is Copyright (c) 2018-2020 by MaxMind, Inc. This is free software, licensed under the Apache License, Version 2.0 or the MIT License, at your option. # MORE INFORMATION Visit [our website](https://www.maxmind.com/en/geoip2-services-and-databases) to learn more about the GeoIP2 and GeoIP Legacy databases or to sign up for a subscription. # SEE ALSO `GeoIP.conf`(5) geoipupdate-4.6.0/docker/000077500000000000000000000000001376571433200153115ustar00rootroot00000000000000geoipupdate-4.6.0/docker/Dockerfile000066400000000000000000000002421376571433200173010ustar00rootroot00000000000000FROM alpine:3.11.5 COPY geoipupdate /usr/bin/geoipupdate COPY docker/entry.sh /usr/bin/entry.sh ENTRYPOINT ["/usr/bin/entry.sh"] VOLUME [ "/usr/share/GeoIP" ] geoipupdate-4.6.0/docker/entry.sh000077500000000000000000000025701376571433200170150ustar00rootroot00000000000000#!/bin/sh set -e conf_file=/etc/GeoIP.conf database_dir=/usr/share/GeoIP flags= frequency=$((GEOIPUPDATE_FREQUENCY * 60 * 60)) if [ -z "$GEOIPUPDATE_ACCOUNT_ID" ] || [ -z "$GEOIPUPDATE_LICENSE_KEY" ] || [ -z "$GEOIPUPDATE_EDITION_IDS" ]; then echo "ERROR: You must set the environment variables GEOIPUPDATE_ACCOUNT_ID, GEOIPUPDATE_LICENSE_KEY, and GEOIPUPDATE_EDITION_IDS!" exit 1 fi # Create configuration file echo "# STATE: Creating configuration file at $conf_file" cat < "$conf_file" AccountID $GEOIPUPDATE_ACCOUNT_ID LicenseKey $GEOIPUPDATE_LICENSE_KEY EditionIDs $GEOIPUPDATE_EDITION_IDS EOF if [ ! -z "$GEOIPUPDATE_HOST" ]; then echo "Host $GEOIPUPDATE_HOST" >> "$conf_file" fi if [ ! -z "$GEOIPUPDATE_PROXY" ]; then echo "Proxy $GEOIPUPDATE_PROXY" >> "$conf_file" fi if [ ! -z "$GEOIPUPDATE_PROXY_USER_PASSWORD" ]; then echo "ProxyUserPassword $GEOIPUPDATE_PROXY_USER_PASSWORD" >> "$conf_file" fi if [ ! -z "$GEOIPUPDATE_PRESERVE_FILE_TIMES" ]; then echo "PreserveFileTimes $GEOIPUPDATE_PRESERVE_FILE_TIMES" >> "$conf_file" fi if [ "$GEOIPUPDATE_VERBOSE" ]; then flags="-v" fi while true; do echo "# STATE: Running geoipupdate" /usr/bin/geoipupdate -d "$database_dir" -f "$conf_file" $flags if [ "$frequency" -eq 0 ]; then break fi echo "# STATE: Sleeping for $GEOIPUPDATE_FREQUENCY hours" sleep "$frequency" done geoipupdate-4.6.0/go.mod000066400000000000000000000010441376571433200151470ustar00rootroot00000000000000module github.com/maxmind/geoipupdate/v4 go 1.11 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/gofrs/flock v0.8.0 github.com/kr/text v0.2.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pkg/errors v0.9.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.6.1 golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1 // indirect gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect ) geoipupdate-4.6.0/go.sum000066400000000000000000000055271376571433200152060ustar00rootroot00000000000000github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1 h1:/DtoiOYKoQCcIFXQjz07RnWNPRCbqmSXSpgEzhC9ZHM= golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= geoipupdate-4.6.0/pkg/000077500000000000000000000000001376571433200146235ustar00rootroot00000000000000geoipupdate-4.6.0/pkg/geoipupdate/000077500000000000000000000000001376571433200171315ustar00rootroot00000000000000geoipupdate-4.6.0/pkg/geoipupdate/config.go000066400000000000000000000131131376571433200207240ustar00rootroot00000000000000package geoipupdate import ( "bufio" "log" "net/url" "os" "path/filepath" "regexp" "strconv" "strings" "time" "github.com/pkg/errors" ) // Config is a parsed configuration file. type Config struct { AccountID int DatabaseDirectory string LicenseKey string LockFile string URL string EditionIDs []string Proxy *url.URL PreserveFileTimes bool Verbose bool RetryFor time.Duration } // NewConfig parses the configuration file. func NewConfig( // nolint: gocyclo file, defaultDatabaseDirectory, databaseDirectory string, verbose bool, ) (*Config, error) { fh, err := os.Open(filepath.Clean(file)) if err != nil { return nil, errors.Wrap(err, "error opening file") } defer func() { if err := fh.Close(); err != nil { log.Fatalf("Error closing config file: %+v", errors.Wrap(err, "closing file")) } }() config := &Config{} scanner := bufio.NewScanner(fh) lineNumber := 0 keysSeen := map[string]struct{}{} var host, proxy, proxyUserPassword string for scanner.Scan() { lineNumber++ line := strings.TrimSpace(scanner.Text()) if line == "" || line[0] == '#' { continue } fields := strings.Fields(line) if len(fields) < 2 { return nil, errors.Errorf("invalid format on line %d", lineNumber) } key := fields[0] value := strings.Join(fields[1:], " ") if _, ok := keysSeen[key]; ok { return nil, errors.Errorf("`%s' is in the config multiple times", key) } keysSeen[key] = struct{}{} switch key { case "AccountID", "UserId": accountID, err := strconv.Atoi(value) if err != nil { return nil, errors.Wrap(err, "invalid account ID format") } config.AccountID = accountID keysSeen["AccountID"] = struct{}{} keysSeen["UserId"] = struct{}{} case "DatabaseDirectory": config.DatabaseDirectory = filepath.Clean(value) case "EditionIDs", "ProductIds": config.EditionIDs = strings.Fields(value) keysSeen["EditionIDs"] = struct{}{} keysSeen["ProductIds"] = struct{}{} case "Host": host = value case "LicenseKey": config.LicenseKey = value case "LockFile": config.LockFile = filepath.Clean(value) case "PreserveFileTimes": if value != "0" && value != "1" { return nil, errors.New("`PreserveFileTimes' must be 0 or 1") } if value == "1" { config.PreserveFileTimes = true } case "Proxy": proxy = value case "ProxyUserPassword": proxyUserPassword = value case "Protocol", "SkipHostnameVerification", "SkipPeerVerification": // Deprecated. case "RetryFor": dur, err := time.ParseDuration(value) if err != nil || dur < 0 { return nil, errors.Errorf("'%s' is not a valid duration", value) } config.RetryFor = dur default: return nil, errors.Errorf("unknown option on line %d", lineNumber) } } if err := scanner.Err(); err != nil { return nil, errors.Wrap(err, "error reading file") } if _, ok := keysSeen["EditionIDs"]; !ok { return nil, errors.Errorf("the `EditionIDs` option is required") } if _, ok := keysSeen["AccountID"]; !ok { return nil, errors.Errorf("the `AccountID` option is required") } if _, ok := keysSeen["LicenseKey"]; !ok { return nil, errors.Errorf("the `LicenseKey` option is required") } // Set defaults & post-process. if _, ok := keysSeen["RetryFor"]; !ok { config.RetryFor = 5 * time.Minute } // Argument takes precedence. if databaseDirectory != "" { config.DatabaseDirectory = filepath.Clean(databaseDirectory) } if config.DatabaseDirectory == "" { config.DatabaseDirectory = filepath.Clean(defaultDatabaseDirectory) } config.Verbose = verbose if host == "" { host = "updates.maxmind.com" } if config.LockFile == "" { config.LockFile = filepath.Join(config.DatabaseDirectory, ".geoipupdate.lock") } config.URL = "https://" + host config.Proxy, err = parseProxy(proxy, proxyUserPassword) if err != nil { return nil, err } // We used to recommend using 999999 / 000000000000 for free downloads // and many people still use this combination. With a real account id // and license key now being required, we want to give those people a // sensible error message. if (config.AccountID == 0 || config.AccountID == 999999) && config.LicenseKey == "000000000000" { return nil, errors.New("geoipupdate requires a valid AccountID and LicenseKey combination") } return config, nil } var schemeRE = regexp.MustCompile(`(?i)\A([a-z][a-z0-9+\-.]*)://`) func parseProxy( proxy, proxyUserPassword string, ) (*url.URL, error) { if proxy == "" { return nil, nil } // If no scheme is provided, use http. matches := schemeRE.FindStringSubmatch(proxy) if matches == nil { proxy = "http://" + proxy } else { scheme := strings.ToLower(matches[1]) // The http package only supports http and socks5. if scheme != "http" && scheme != "socks5" { return nil, errors.Errorf("unsupported proxy type: %s", scheme) } } // Now that we have a scheme, we should be able to parse. u, err := url.Parse(proxy) if err != nil { return nil, errors.Wrap(err, "error parsing proxy URL") } if !strings.Contains(u.Host, ":") { u.Host += ":1080" // The 1080 default historically came from cURL. } // Historically if the Proxy option had a username and password they would // override any specified in the ProxyUserPassword option. Continue that. if u.User != nil { return u, nil } if proxyUserPassword == "" { return u, nil } userPassword := strings.SplitN(proxyUserPassword, ":", 2) if len(userPassword) != 2 { return nil, errors.New("proxy user/password is malformed") } u.User = url.UserPassword(userPassword[0], userPassword[1]) return u, nil } geoipupdate-4.6.0/pkg/geoipupdate/config_test.go000066400000000000000000000333721376571433200217740ustar00rootroot00000000000000package geoipupdate import ( "fmt" "io/ioutil" "net/url" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewConfig(t *testing.T) { tests := []struct { Description string Input string Output *Config Err string }{ { Description: "Default config", Input: `# Please see https://dev.maxmind.com/geoip/geoipupdate/ for instructions # on setting up geoipupdate, including information on how to download a # pre-filled GeoIP.conf file. # Enter your account ID and license key below. These are available from # https://www.maxmind.com/en/my_license_key. If you are only using free # GeoLite databases, you may leave the 0 values. AccountID 42 LicenseKey 000000000001 # Enter the edition IDs of the databases you would like to update. # Multiple edition IDs are separated by spaces. EditionIDs GeoLite2-Country GeoLite2-City # The remaining settings are OPTIONAL. # The directory to store the database files. Defaults to DATADIR # DatabaseDirectory DATADIR # The server to use. Defaults to "updates.maxmind.com". # Host updates.maxmind.com # The proxy host name or IP address. You may optionally specify a # port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080 # will be used. # Proxy 127.0.0.1:8888 # The user name and password to use with your proxy server. # ProxyUserPassword username:password # Whether to preserve modification times of files downloaded from the server. # Defaults to "0". # PreserveFileTimes 0 # The lock file to use. This ensures only one geoipupdate process can run at a # time. # Note: Once created, this lockfile is not removed from the filesystem. # Defaults to ".geoipupdate.lock" under the DatabaseDirectory. # LockFile DATADIR/.geoipupdate.lock # The amount of time to retry for when errors during HTTP transactions are # encountered. It can be specified as a (possibly fractional) decimal number # followed by a unit suffix. Valid time units are "ns", "us" (or "µs"), "ms", # "s", "m", "h". # Defaults to "5m" (5 minutes). # RetryFor 5m `, Output: &Config{ AccountID: 42, LicenseKey: "000000000001", DatabaseDirectory: filepath.Clean("/tmp"), EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, }, }, { Description: "Default config, old names", Input: `# Please see https://dev.maxmind.com/geoip/geoipupdate/ for instructions # on setting up geoipupdate, including information on how to download a # pre-filled GeoIP.conf file. # Enter your account ID and license key below. These are available from # https://www.maxmind.com/en/my_license_key. If you are only using free # GeoLite databases, you may leave the 0 values. UserId 42 LicenseKey 000000000001 # Enter the edition IDs of the databases you would like to update. # Multiple edition IDs are separated by spaces. ProductIds GeoLite2-Country GeoLite2-City # The remaining settings are OPTIONAL. # The directory to store the database files. Defaults to DATADIR # DatabaseDirectory DATADIR # The server to use. Defaults to "updates.maxmind.com". # Host updates.maxmind.com # The proxy host name or IP address. You may optionally specify a # port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080 # will be used. # Proxy 127.0.0.1:8888 # The user name and password to use with your proxy server. # ProxyUserPassword username:password # Whether to preserve modification times of files downloaded from the server. # Defaults to "0". # PreserveFileTimes 0 # The lock file to use. This ensures only one geoipupdate process can run at a # time. # Note: Once created, this lockfile is not removed from the filesystem. # Defaults to ".geoipupdate.lock" under the DatabaseDirectory. # LockFile DATADIR/.geoipupdate.lock `, Output: &Config{ AccountID: 42, LicenseKey: "000000000001", DatabaseDirectory: filepath.Clean("/tmp"), EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, }, }, { Description: "Everything populated", Input: `# Please see https://dev.maxmind.com/geoip/geoipupdate/ for instructions # on setting up geoipupdate, including information on how to download a # pre-filled GeoIP.conf file. # Enter your account ID and license key below. These are available from # https://www.maxmind.com/en/my_license_key. If you are only using free # GeoLite databases, you may leave the 0 values. AccountID 1234 LicenseKey abcdefghi # Enter the edition IDs of the databases you would like to update. # Multiple edition IDs are separated by spaces. EditionIDs GeoLite2-Country GeoLite2-City GeoIP2-City # The remaining settings are OPTIONAL. # The directory to store the database files. Defaults to DATADIR DatabaseDirectory /home # The server to use. Defaults to "updates.maxmind.com". Host updates.example.com # The proxy host name or IP address. You may optionally specify a # port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080 # will be used. Proxy 127.0.0.1:8888 # The user name and password to use with your proxy server. ProxyUserPassword username:password # Whether to preserve modification times of files downloaded from the server. # Defaults to "0". PreserveFileTimes 1 # The lock file to use. This ensures only one geoipupdate process can run at a # time. # Note: Once created, this lockfile is not removed from the filesystem. # Defaults to ".geoipupdate.lock" under the DatabaseDirectory. LockFile /usr/lock RetryFor 10m `, Output: &Config{ AccountID: 1234, DatabaseDirectory: filepath.Clean("/tmp"), // Argument takes precedence EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City", "GeoIP2-City"}, LicenseKey: "abcdefghi", LockFile: filepath.Clean("/usr/lock"), Proxy: &url.URL{ Scheme: "http", User: url.UserPassword("username", "password"), Host: "127.0.0.1:8888", }, PreserveFileTimes: true, URL: "https://updates.example.com", RetryFor: 10 * time.Minute, }, }, { Description: "Invalid line", Input: `AccountID 123 LicenseKey # Host updates.maxmind.com `, Err: "invalid format on line 2", }, { Description: "Option is there multiple times", Input: `AccountID 123 AccountID 456 `, Err: "`AccountID' is in the config multiple times", }, { Description: "Option is there multiple times with different names", Input: `AccountID 123 UserId 456 `, Err: "`UserId' is in the config multiple times", }, { Description: "Invalid account ID", Input: `AccountID 1a `, Err: `invalid account ID format: strconv.Atoi: parsing "1a": invalid syntax`, }, { Description: "Invalid PreserveFileTimes", Input: `PreserveFileTimes true `, Err: "`PreserveFileTimes' must be 0 or 1", }, { Description: "Unknown option", Input: `AccountID 123 EditionID GeoIP2-City `, Err: "unknown option on line 2", }, { Description: "Missing required key in options", Input: ``, Err: "the `EditionIDs` option is required", }, { Description: "LicenseKey is found but AccountID is not", Input: `LicenseKey abcd EditionIDs GeoIP2-City `, Err: "the `AccountID` option is required", }, { Description: "AccountID is found but LicenseKey is not", Input: `AccountID 123 EditionIDs GeoIP2-City`, Err: "the `LicenseKey` option is required", }, { Description: "AccountID 0 with the LicenseKey 000000000000 is treated as no AccountID/LicenseKey", Input: `AccountID 0 LicenseKey 000000000000 EditionIDs GeoIP2-City`, Err: "geoipupdate requires a valid AccountID and LicenseKey combination", }, { Description: "AccountID 999999 with the LicenseKey 000000000000 is treated as no AccountID/LicenseKey", Input: `AccountID 999999 LicenseKey 000000000000 EditionIDs GeoIP2-City`, Err: "geoipupdate requires a valid AccountID and LicenseKey combination", }, { Description: "RetryFor needs a unit", Input: `AccountID 42 LicenseKey 000000000001 RetryFor 5`, Err: "'5' is not a valid duration", }, { Description: "RetryFor needs to be non-negative", Input: `AccountID 42 LicenseKey 000000000001 RetryFor -5m`, Err: "'-5m' is not a valid duration", }, { Description: "AccountID 999999 with a non-000000000000 LicenseKey is treated normally", Input: `AccountID 999999 LicenseKey abcd EditionIDs GeoIP2-City`, Output: &Config{ AccountID: 999999, DatabaseDirectory: filepath.Clean("/tmp"), EditionIDs: []string{"GeoIP2-City"}, LicenseKey: "abcd", LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, }, }, { Description: "Deprecated options", Input: `AccountID 123 LicenseKey abcd EditionIDs GeoIP2-City Protocol http SkipHostnameVerification 1 SkipPeerVerification 1 `, Output: &Config{ AccountID: 123, DatabaseDirectory: filepath.Clean("/tmp"), EditionIDs: []string{"GeoIP2-City"}, LicenseKey: "abcd", LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, }, }, { Description: "CRLF line ending works", Input: "AccountID 123\r\nLicenseKey 123\r\nEditionIDs GeoIP2-City\r\n", Output: &Config{ AccountID: 123, DatabaseDirectory: filepath.Clean("/tmp"), EditionIDs: []string{"GeoIP2-City"}, LicenseKey: "123", LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, }, }, { Description: "CR line ending does not work", Input: "AccountID 0\rLicenseKey 123\rEditionIDs GeoIP2-City\r", // nolint: lll Err: `invalid account ID format: strconv.Atoi: parsing "0 LicenseKey 123 EditionIDs GeoIP2-City": invalid syntax`, }, { Description: "Multiple spaces between option and value works", Input: `AccountID 123 LicenseKey 456 EditionIDs GeoLite2-City GeoLite2-Country `, Output: &Config{ AccountID: 123, DatabaseDirectory: filepath.Clean("/tmp"), EditionIDs: []string{"GeoLite2-City", "GeoLite2-Country"}, LicenseKey: "456", LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, }, }, { Description: "Tabs between options and values works", Input: "AccountID\t123\nLicenseKey\t\t456\nEditionIDs\t\t\tGeoLite2-City\t\t\t\tGeoLite2-Country\t\t\t\t\n", Output: &Config{ AccountID: 123, DatabaseDirectory: filepath.Clean("/tmp"), EditionIDs: []string{"GeoLite2-City", "GeoLite2-Country"}, LicenseKey: "456", LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, }, }, } tempFh, err := ioutil.TempFile("", "conf-test") require.NoError(t, err) tempName := tempFh.Name() require.NoError(t, tempFh.Close()) defer func() { _ = os.Remove(tempName) }() for _, test := range tests { t.Run(test.Description, func(t *testing.T) { require.NoError(t, ioutil.WriteFile(tempName, []byte(test.Input), 0600)) config, err := NewConfig(tempName, DefaultDatabaseDirectory, "/tmp", false) if test.Err == "" { assert.NoError(t, err, test.Description) } else { assert.EqualError(t, err, test.Err, test.Description) } assert.Equal(t, test.Output, config, test.Description) }) } } func TestParseProxy(t *testing.T) { tests := []struct { Proxy string UserPassword string Output string Err string }{ { Proxy: "127.0.0.1", Output: "http://127.0.0.1:1080", }, { Proxy: "127.0.0.1:8888", Output: "http://127.0.0.1:8888", }, { Proxy: "http://127.0.0.1:8888", Output: "http://127.0.0.1:8888", }, { Proxy: "socks5://127.0.0.1", Output: "socks5://127.0.0.1:1080", }, { Proxy: "socks5://127.0.0.1:8888", Output: "socks5://127.0.0.1:8888", }, { Proxy: "Garbage", Output: "http://Garbage:1080", }, { Proxy: "ftp://127.0.0.1", Err: "unsupported proxy type: ftp", }, { Proxy: "ftp://127.0.0.1:8888", Err: "unsupported proxy type: ftp", }, { Proxy: "login:password@127.0.0.1", Output: "http://login:password@127.0.0.1:1080", }, { Proxy: "login:password@127.0.0.1", UserPassword: "something:else", Output: "http://login:password@127.0.0.1:1080", }, { Proxy: "127.0.0.1", UserPassword: "something:else", Output: "http://something:else@127.0.0.1:1080", }, { Proxy: "127.0.0.1:8888", UserPassword: "something:else", Output: "http://something:else@127.0.0.1:8888", }, { Proxy: "user:password@127.0.0.1:8888", UserPassword: "user2:password2", Output: "http://user:password@127.0.0.1:8888", }, { Proxy: "http://user:password@127.0.0.1:8888", UserPassword: "user2:password2", Output: "http://user:password@127.0.0.1:8888", }, } for _, test := range tests { t.Run( fmt.Sprintf("%s - %s", test.Proxy, test.UserPassword), func(t *testing.T) { output, err := parseProxy(test.Proxy, test.UserPassword) if test.Err != "" { assert.EqualError(t, err, test.Err) assert.Nil(t, output) } else { assert.NoError(t, err) assert.Equal(t, test.Output, output.String()) } }, ) } } geoipupdate-4.6.0/pkg/geoipupdate/database/000077500000000000000000000000001376571433200206755ustar00rootroot00000000000000geoipupdate-4.6.0/pkg/geoipupdate/database/http_reader.go000066400000000000000000000137221376571433200235320ustar00rootroot00000000000000// Package database provides an abstraction over getting and writing a // database file. package database import ( "compress/gzip" "fmt" "io" "io/ioutil" "log" "net/http" "net/url" "os" "time" "github.com/maxmind/geoipupdate/v4/pkg/geoipupdate" "github.com/maxmind/geoipupdate/v4/pkg/geoipupdate/internal" "github.com/pkg/errors" ) // HTTPDatabaseReader is a Reader that uses an HTTP client to retrieve // databases. type HTTPDatabaseReader struct { client *http.Client retryFor time.Duration url string licenseKey string accountID int preserveFileTimes bool verbose bool } // NewHTTPDatabaseReader creates a Reader that downloads database updates via // HTTP. func NewHTTPDatabaseReader(client *http.Client, config *geoipupdate.Config) Reader { return &HTTPDatabaseReader{ client: client, retryFor: config.RetryFor, url: config.URL, licenseKey: config.LicenseKey, accountID: config.AccountID, preserveFileTimes: config.PreserveFileTimes, verbose: config.Verbose, } } // Get retrieves the given edition ID using an HTTP client, writes it to the // Writer, and validates the hash before committing. func (reader *HTTPDatabaseReader) Get(destination Writer, editionID string) error { defer func() { if err := destination.Close(); err != nil { log.Println(err) } }() updateURL := fmt.Sprintf( "%s/geoip/databases/%s/update?db_md5=%s", reader.url, url.PathEscape(editionID), url.QueryEscape(destination.GetHash()), ) if reader.verbose { log.Printf("Performing update request to %s", updateURL) } var modified bool // It'd be nice to not use a temporary file here. However the Writer API does // not currently support multiple attempts to download the file (it assumes // we'll begin writing once). Using a temporary file here should be a less // disruptive alternative to changing the API. If we change that API in the // future, adding something like Reset() may be desirable. tempFile, err := ioutil.TempFile("", "geoipupdate") if err != nil { return errors.Wrap(err, "error opening temporary file") } defer func() { if err := tempFile.Close(); err != nil { log.Printf("error closing temporary file: %s", err) } if err := os.Remove(tempFile.Name()); err != nil { log.Printf("error removing temporary file: %s", err) } }() var newMD5 string var modificationTime time.Time err = internal.RetryWithBackoff( func() error { newMD5, modificationTime, modified, err = reader.download( updateURL, editionID, tempFile, ) return err }, reader.retryFor, ) if err != nil { return err } if !modified { return nil } if _, err := tempFile.Seek(0, 0); err != nil { return errors.Wrap(err, "error seeking") } if _, err = io.Copy(destination, tempFile); err != nil { return errors.Wrap(err, "error writing response") } if err := destination.ValidHash(newMD5); err != nil { return err } if err := destination.Commit(); err != nil { return errors.Wrap(err, "encountered an issue committing database update") } if reader.preserveFileTimes { err = destination.SetFileModificationTime(modificationTime) if err != nil { return errors.Wrap(err, "unable to set modification time") } } return nil } func (reader *HTTPDatabaseReader) download( updateURL, editionID string, tempFile *os.File, ) (string, time.Time, bool, error) { // Prepare a clean slate for this download attempt. if err := tempFile.Truncate(0); err != nil { return "", time.Time{}, false, errors.Wrap(err, "error truncating") } if _, err := tempFile.Seek(0, 0); err != nil { return "", time.Time{}, false, errors.Wrap(err, "error seeking") } // Perform the download. req, err := http.NewRequest(http.MethodGet, updateURL, nil) // nolint: noctx if err != nil { return "", time.Time{}, false, errors.Wrap(err, "error creating request") } req.SetBasicAuth(fmt.Sprintf("%d", reader.accountID), reader.licenseKey) response, err := reader.client.Do(req) if err != nil { return "", time.Time{}, false, errors.Wrap(err, "error performing HTTP request") } defer func() { if err := response.Body.Close(); err != nil { log.Fatalf("Error closing response body: %+v", errors.Wrap(err, "closing body")) } }() if response.StatusCode == http.StatusNotModified { if reader.verbose { log.Printf("No new updates available for %s", editionID) } return "", time.Time{}, false, nil } if response.StatusCode != http.StatusOK { buf, err := ioutil.ReadAll(io.LimitReader(response.Body, 256)) if err == nil { return "", time.Time{}, false, errors.Errorf("unexpected HTTP status code: %s: %s", response.Status, buf) } return "", time.Time{}, false, errors.Errorf("unexpected HTTP status code: %s", response.Status) } gzReader, err := gzip.NewReader(response.Body) if err != nil { return "", time.Time{}, false, errors.Wrap(err, "encountered an error creating GZIP reader") } defer func() { if err := gzReader.Close(); err != nil { log.Printf("error closing gzip reader: %s", err) } }() if _, err := io.Copy(tempFile, gzReader); err != nil { //nolint:gosec return "", time.Time{}, false, errors.Wrap(err, "error writing response") } newMD5 := response.Header.Get("X-Database-MD5") if newMD5 == "" { return "", time.Time{}, false, errors.New("no X-Database-MD5 header found") } modificationTime, err := lastModified(response.Header.Get("Last-Modified")) if err != nil { return "", time.Time{}, false, errors.Wrap(err, "unable to get last modified time") } return newMD5, modificationTime, true, nil } // LastModified retrieves the date that the MaxMind database was last modified. func lastModified(lastModified string) (time.Time, error) { if lastModified == "" { return time.Time{}, errors.New("no Last-Modified header found") } t, err := time.ParseInLocation(time.RFC1123, lastModified, time.UTC) if err != nil { return time.Time{}, errors.Wrap(err, "error parsing time") } return t, nil } geoipupdate-4.6.0/pkg/geoipupdate/database/http_reader_test.go000066400000000000000000000153611376571433200245720ustar00rootroot00000000000000package database import ( "bytes" "compress/gzip" "crypto/md5" "fmt" "io" "io/ioutil" "net/http" "net/http/httptest" "os" "path/filepath" "regexp" "testing" "time" "github.com/maxmind/geoipupdate/v4/pkg/geoipupdate" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestHTTPDatabaseReader(t *testing.T) { tests := []struct { Description string CreateDirectory bool DatabaseBefore string DatabaseAfter string DownloadStatus int DownloadBody string DownloadHeaders map[string]string ExpectedTime time.Time Err string }{ { Description: "Initial download, success", CreateDirectory: true, DatabaseAfter: "database goes here", DownloadStatus: http.StatusOK, DownloadBody: "database goes here", }, { Description: "No update, success", CreateDirectory: true, DatabaseBefore: "database goes here", DatabaseAfter: "database goes here", DownloadStatus: http.StatusNotModified, DownloadBody: "database goes here", }, { Description: "Update, success", CreateDirectory: true, DatabaseBefore: "database goes here", DatabaseAfter: "new database goes here", DownloadStatus: http.StatusOK, DownloadBody: "new database goes here", }, { Description: "Update, success, and modification time is set", CreateDirectory: true, DatabaseBefore: "new database goes here", DatabaseAfter: "newer database goes here", DownloadStatus: http.StatusOK, DownloadBody: "newer database goes here", DownloadHeaders: map[string]string{ "Last-Modified": time.Date(2018, 7, 24, 0, 0, 0, 0, time.UTC).Format(time.RFC1123), }, ExpectedTime: time.Date(2018, 7, 24, 0, 0, 0, 0, time.UTC), }, { Description: "Download request fails", CreateDirectory: true, DatabaseBefore: "database goes here", DatabaseAfter: "database goes here", DownloadStatus: http.StatusBadRequest, Err: "unexpected HTTP status code: 400 Bad Request", }, { Description: "Download request is missing X-Database-MD5", CreateDirectory: true, DatabaseBefore: "database goes here", DatabaseAfter: "database goes here", DownloadStatus: http.StatusOK, DownloadBody: "new database goes here", DownloadHeaders: map[string]string{ "X-Database-MD5": "", }, Err: "no X-Database-MD5 header found", }, { Description: "Download fails because provided checksum does not match", CreateDirectory: true, DatabaseBefore: "database goes here", DatabaseAfter: "database goes here", DownloadStatus: http.StatusOK, DownloadBody: "new database goes here", DownloadHeaders: map[string]string{ "X-Database-MD5": "5d41402abc4b2a76b9719d911017c592", // "hello" }, // nolint: lll Err: `md5 of new database \(985ecf3d7959b146208b3dc0189b21a5\) does not match expected md5 \(5d41402abc4b2a76b9719d911017c592\)`, }, { Description: "Download request redirects are followed", CreateDirectory: true, DatabaseBefore: "database goes here", DatabaseAfter: "database goes here", DownloadStatus: http.StatusMovedPermanently, DownloadHeaders: map[string]string{ "Location": "/go-here", }, }, { Description: "MD5 sums are case insensitive", CreateDirectory: true, DatabaseBefore: "database goes here", DatabaseAfter: "new database goes here", DownloadStatus: http.StatusOK, DownloadBody: "new database goes here", DownloadHeaders: map[string]string{ "X-Database-MD5": "985ECF3D7959B146208B3DC0189B21A5", }, }, } updateRE := regexp.MustCompile(`\A/geoip/databases/\S+/update\z`) tempDir, err := ioutil.TempDir("", "gutest-") require.NoError(t, err) err = os.RemoveAll(tempDir) require.NoError(t, err) for _, test := range tests { t.Run(test.Description, func(t *testing.T) { server := httptest.NewServer( http.HandlerFunc( func(rw http.ResponseWriter, r *http.Request) { if updateRE.MatchString(r.URL.Path) { buf := &bytes.Buffer{} gzWriter := gzip.NewWriter(buf) md5Writer := md5.New() multiWriter := io.MultiWriter(gzWriter, md5Writer) _, err := multiWriter.Write([]byte(test.DownloadBody)) require.NoError(t, err) err = gzWriter.Close() require.NoError(t, err) rw.Header().Set( "X-Database-MD5", fmt.Sprintf("%x", md5Writer.Sum(nil)), ) if test.DownloadStatus == http.StatusOK { rw.Header().Set( "Last-Modified", time.Now().Format(time.RFC1123), ) } for k, v := range test.DownloadHeaders { rw.Header().Set(k, v) } rw.WriteHeader(test.DownloadStatus) if test.DownloadStatus == http.StatusOK { _, err := rw.Write(buf.Bytes()) require.NoError(t, err) } return } if r.URL.Path == "/go-here" { rw.WriteHeader(http.StatusNotModified) return } rw.WriteHeader(http.StatusBadRequest) }, ), ) config := &geoipupdate.Config{ AccountID: 123, DatabaseDirectory: tempDir, EditionIDs: []string{"GeoIP2-City"}, LicenseKey: "testing", LockFile: filepath.Join(tempDir, ".geoipupdate.lock"), URL: server.URL, } if !test.ExpectedTime.IsZero() { config.PreserveFileTimes = true } if test.CreateDirectory { err := os.Mkdir(config.DatabaseDirectory, 0755) //nolint:gosec require.NoError(t, err) } currentDatabasePath := filepath.Join( config.DatabaseDirectory, "GeoIP2-City.mmdb", ) if test.DatabaseBefore != "" { err := ioutil.WriteFile( currentDatabasePath, []byte(test.DatabaseBefore), 0600, ) require.NoError(t, err) } client := geoipupdate.NewClient(config) dbReader := NewHTTPDatabaseReader(client, config) dbWriter, err := NewLocalFileDatabaseWriter(currentDatabasePath, config.LockFile, config.Verbose) assert.NoError(t, err, test.Description) err = dbReader.Get(dbWriter, config.EditionIDs[0]) if test.Err == "" { assert.NoError(t, err, test.Description) } else { // regex because some errors have filenames. assert.Regexp(t, test.Err, err.Error(), test.Description) } server.Close() if test.DatabaseAfter != "" { buf, err := ioutil.ReadFile(filepath.Clean(currentDatabasePath)) require.NoError(t, err, test.Description) assert.Equal(t, test.DatabaseAfter, string(buf)) } if !test.ExpectedTime.IsZero() { fi, err := os.Stat(currentDatabasePath) require.NoError(t, err) assert.WithinDuration(t, test.ExpectedTime, fi.ModTime(), 0) } if test.CreateDirectory { err := os.RemoveAll(config.DatabaseDirectory) require.NoError(t, err) } }) } } geoipupdate-4.6.0/pkg/geoipupdate/database/local_file_writer.go000066400000000000000000000113661376571433200247200ustar00rootroot00000000000000package database import ( "crypto/md5" "fmt" "hash" "io" "log" "os" "path/filepath" "strings" "time" "github.com/gofrs/flock" "github.com/pkg/errors" ) // LocalFileDatabaseWriter is a database.Writer that stores the database to the // local file system. type LocalFileDatabaseWriter struct { filePath string lockFilePath string verbose bool lock *flock.Flock oldHash string fileWriter io.Writer temporaryFile *os.File md5Writer hash.Hash } // NewLocalFileDatabaseWriter create a LocalFileDatabaseWriter. It creates the // necessary lock and temporary files to protect the database from concurrent // writes. func NewLocalFileDatabaseWriter(filePath, lockFilePath string, verbose bool) (*LocalFileDatabaseWriter, error) { dbWriter := &LocalFileDatabaseWriter{ filePath: filePath, lockFilePath: lockFilePath, verbose: verbose, } var err error if dbWriter.lock, err = CreateLockFile(lockFilePath, verbose); err != nil { return nil, err } if err = dbWriter.createOldMD5Hash(); err != nil { return nil, err } temporaryFilename := fmt.Sprintf("%s.temporary", dbWriter.filePath) dbWriter.temporaryFile, err = os.OpenFile( //nolint:gosec temporaryFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644, ) if err != nil { return nil, errors.Wrap(err, "error creating temporary file") } dbWriter.md5Writer = md5.New() dbWriter.fileWriter = io.MultiWriter(dbWriter.md5Writer, dbWriter.temporaryFile) return dbWriter, nil } func (writer *LocalFileDatabaseWriter) createOldMD5Hash() error { currentDatabaseFile, err := os.Open(writer.filePath) if err != nil { if os.IsNotExist(err) { writer.oldHash = ZeroMD5 return nil } return errors.Wrap(err, "error opening database") } defer func() { err := currentDatabaseFile.Close() if err != nil { log.Println(errors.Wrap(err, "error closing database")) } }() oldHash := md5.New() if _, err := io.Copy(oldHash, currentDatabaseFile); err != nil { return errors.Wrap(err, "error calculating database hash") } writer.oldHash = fmt.Sprintf("%x", oldHash.Sum(nil)) if writer.verbose { log.Printf("Calculated MD5 sum for %s: %s", writer.filePath, writer.oldHash) } return nil } // Write writes to the temporary file. func (writer *LocalFileDatabaseWriter) Write(p []byte) (int, error) { return writer.fileWriter.Write(p) } // Close closes the temporary file and releases the file lock. func (writer *LocalFileDatabaseWriter) Close() error { err := writer.temporaryFile.Close() if err != nil { if perr, ok := err.(*os.PathError); !ok || perr.Err != os.ErrClosed { return errors.Wrap(err, "error closing temporary file") } } if err := os.Remove(writer.temporaryFile.Name()); err != nil && !os.IsNotExist(err) { return errors.Wrap(err, "error removing temporary file") } if err := writer.lock.Unlock(); err != nil { return errors.Wrap(err, "error releasing lock file") } return nil } // ValidHash checks that the temporary file's MD5 matches the given hash. func (writer *LocalFileDatabaseWriter) ValidHash(expectedHash string) error { actualHash := fmt.Sprintf("%x", writer.md5Writer.Sum(nil)) if !strings.EqualFold(actualHash, expectedHash) { return errors.Errorf("md5 of new database (%s) does not match expected md5 (%s)", actualHash, expectedHash) } return nil } // SetFileModificationTime sets the database's file access and modified times // to the given time. func (writer *LocalFileDatabaseWriter) SetFileModificationTime(lastModified time.Time) error { if err := os.Chtimes(writer.filePath, lastModified, lastModified); err != nil { return errors.Wrap(err, "error setting times on file") } return nil } // Commit renames the temporary file to the name of the database file and syncs // the directory. func (writer *LocalFileDatabaseWriter) Commit() error { if err := writer.temporaryFile.Sync(); err != nil { return errors.Wrap(err, "error syncing temporary file") } if err := writer.temporaryFile.Close(); err != nil { return errors.Wrap(err, "error closing temporary file") } if err := os.Rename(writer.temporaryFile.Name(), writer.filePath); err != nil { return errors.Wrap(err, "error moving database into place") } // fsync the directory. http://austingroupbugs.net/view.php?id=672 dh, err := os.Open(filepath.Dir(writer.filePath)) if err != nil { return errors.Wrap(err, "error opening database directory") } defer func() { if err := dh.Close(); err != nil { log.Fatalf("Error closing directory: %+v", errors.Wrap(err, "closing directory")) } }() // We ignore Sync errors as they primarily happen on file systems that do // not support sync. _ = dh.Sync() return nil } // GetHash returns the hash of the current database file. func (writer *LocalFileDatabaseWriter) GetHash() string { return writer.oldHash } geoipupdate-4.6.0/pkg/geoipupdate/database/reader.go000066400000000000000000000002741376571433200224710ustar00rootroot00000000000000package database // Reader provides an interface for retrieving a database update and copying it // into place. type Reader interface { Get(destination Writer, editionID string) error } geoipupdate-4.6.0/pkg/geoipupdate/database/writer.go000066400000000000000000000024311376571433200225400ustar00rootroot00000000000000package database import ( "io" "log" "os" "path/filepath" "time" "github.com/gofrs/flock" "github.com/pkg/errors" ) // ZeroMD5 is the default value provided as an MD5 hash for a non-existent // database. const ZeroMD5 = "00000000000000000000000000000000" // Writer provides an interface for writing a database to a target location. type Writer interface { io.WriteCloser ValidHash(expectedHash string) error GetHash() string SetFileModificationTime(lastModified time.Time) error Commit() error } // CreateLockFile takes the provided filePath and lockFilePath name to create a // file lock. All output errors are wrapped in more detailed messages for // debugging. func CreateLockFile(lockFilePath string, verbose bool) (*flock.Flock, error) { fi, err := os.Stat(filepath.Dir(lockFilePath)) if err != nil { return nil, errors.Wrap(err, "database directory is not available") } if !fi.IsDir() { return nil, errors.New("database directory is not a directory") } lock := flock.New(lockFilePath) ok, err := lock.TryLock() if err != nil { return nil, errors.Wrap(err, "error acquiring a lock") } if !ok { return nil, errors.Errorf("could not acquire lock on %s", lockFilePath) } if verbose { log.Printf("Acquired lock file lock (%s)", lockFilePath) } return lock, nil } geoipupdate-4.6.0/pkg/geoipupdate/database/writer_test.go000066400000000000000000000021661376571433200236040ustar00rootroot00000000000000package database import ( "io/ioutil" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestCreateLockFile(t *testing.T) { tests := []struct { Description string LockFilename string ExpectedError string }{ { Description: "Database shouldn't have errors with good file paths", LockFilename: ".geoipupdate.lock", ExpectedError: "", }, { Description: "Database should fail to build with bad file path", LockFilename: "bad/file/path.geoipupdate.lock", ExpectedError: `database directory is not available`, }, } for _, test := range tests { t.Run(test.Description, func(t *testing.T) { tempDir, err := ioutil.TempDir("", "gutest-") require.NoError(t, err) err = os.RemoveAll(tempDir) require.NoError(t, err) _, err = CreateLockFile(filepath.Join(tempDir, test.LockFilename), false) if err != nil { // regex because some errors have filenames. assert.Regexp(t, test.ExpectedError, err.Error(), test.Description) } else { require.Equal(t, test.ExpectedError, "", test.Description) } }) } } geoipupdate-4.6.0/pkg/geoipupdate/defaults_notwin.go000066400000000000000000000006501376571433200226660ustar00rootroot00000000000000// +build !windows package geoipupdate var ( // These match what you'd get building the C geoipupdate from source. // DefaultConfigFile is the default location that GeoipUpdate will look for the *.conf file DefaultConfigFile = "/usr/local/etc/GeoIP.conf" // DefaultDatabaseDirectory is the default directory that will be used for saving to the local file system DefaultDatabaseDirectory = "/usr/local/share/GeoIP" ) geoipupdate-4.6.0/pkg/geoipupdate/defaults_windows.go000066400000000000000000000005741376571433200230470ustar00rootroot00000000000000package geoipupdate import ( "os" ) var ( // I'm not sure these make sense. However they can be overridden at runtime // and in the configuration, so we have some flexibility. DefaultConfigFile = os.Getenv("SYSTEMDRIVE") + `\ProgramData\MaxMind\GeoIPUpdate\GeoIP.conf` DefaultDatabaseDirectory = os.Getenv("SYSTEMDRIVE") + `\ProgramData\MaxMind\GeoIPUpdate\GeoIP` ) geoipupdate-4.6.0/pkg/geoipupdate/geoip_updater.go000066400000000000000000000040131376571433200223050ustar00rootroot00000000000000// Package geoipupdate provides a library for using MaxMind's GeoIP Update // service. package geoipupdate import ( "bytes" "fmt" "io" "io/ioutil" "log" "net/http" "net/url" "github.com/maxmind/geoipupdate/v4/pkg/geoipupdate/internal" "github.com/pkg/errors" ) // NewClient creates an *http.Client for use in updating. func NewClient( config *Config, ) *http.Client { transport := http.DefaultTransport if config.Proxy != nil { proxy := http.ProxyURL(config.Proxy) transport.(*http.Transport).Proxy = proxy } return &http.Client{Transport: transport} } // GetFilename looks up the filename for the given edition ID. func GetFilename( config *Config, editionID string, client *http.Client, ) (string, error) { maxMindURL := fmt.Sprintf( "%s/app/update_getfilename?product_id=%s", config.URL, url.QueryEscape(editionID), ) if config.Verbose { log.Printf("Performing get filename request to %s", maxMindURL) } var buf []byte err := internal.RetryWithBackoff( func() error { req, err := http.NewRequest(http.MethodGet, maxMindURL, nil) // nolint: noctx if err != nil { return errors.Wrap(err, "error creating HTTP request") } res, err := client.Do(req) if err != nil { return errors.Wrap(err, "error performing HTTP request") } defer func() { if err := res.Body.Close(); err != nil { log.Fatalf("error closing response body: %+v", errors.Wrap(err, "closing body")) } }() buf, err = ioutil.ReadAll(io.LimitReader(res.Body, 256)) if err != nil { return errors.Wrap(err, "error reading response body") } if res.StatusCode != http.StatusOK { return errors.Errorf("unexpected HTTP status code: %s: %s", res.Status, buf) } if len(buf) == 0 { return errors.New("response body is empty") } if bytes.Count(buf, []byte("\n")) > 0 || bytes.Count(buf, []byte("\x00")) > 0 { return errors.New("invalid characters in filename") } return nil }, config.RetryFor, ) if err != nil { return "", err } return string(buf), nil } geoipupdate-4.6.0/pkg/geoipupdate/geoip_updater_test.go000066400000000000000000000044571376571433200233600ustar00rootroot00000000000000package geoipupdate import ( "io/ioutil" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGetFileName(t *testing.T) { tests := []struct { Description string FilenameStatus int FilenameBody string ExpectedError string ExpectedOutput string }{ { Description: "Simple Success", FilenameStatus: http.StatusOK, FilenameBody: "aSimpleFileName", ExpectedOutput: "aSimpleFileName", }, { Description: "Get filename fails", FilenameStatus: http.StatusBadRequest, ExpectedError: "unexpected HTTP status code: 400 Bad Request: ", }, { Description: "Get filename is missing body", FilenameStatus: http.StatusOK, ExpectedError: "response body is empty", }, { Description: "Get filename has newlines", FilenameStatus: http.StatusOK, FilenameBody: "bad\nfilename", ExpectedError: "invalid characters in filename", }, } tempDir, err := ioutil.TempDir("", "gutest-") require.NoError(t, err) defer func() { err = os.RemoveAll(tempDir) require.NoError(t, err) }() for _, test := range tests { t.Run(test.Description, func(t *testing.T) { server := httptest.NewServer( http.HandlerFunc( func(rw http.ResponseWriter, r *http.Request) { if r.URL.Path == "/app/update_getfilename" { rw.WriteHeader(test.FilenameStatus) _, err := rw.Write([]byte(test.FilenameBody)) require.NoError(t, err) return } if r.URL.Path == "/go-here" { rw.WriteHeader(http.StatusNotModified) return } rw.WriteHeader(http.StatusBadRequest) }, ), ) config := &Config{ AccountID: 123, DatabaseDirectory: tempDir, EditionIDs: []string{"GeoIP2-City"}, LicenseKey: "testing", LockFile: filepath.Join(tempDir, ".geoipupdate.lock"), URL: server.URL, } client := NewClient(config) actualOutput, actualError := GetFilename(config, config.EditionIDs[0], client) assert.Equal(t, test.ExpectedOutput, actualOutput, test.Description) if test.ExpectedError != "" { require.Error(t, actualError, test.Description) assert.Equal(t, test.ExpectedError, actualError.Error(), test.Description) } }) } } geoipupdate-4.6.0/pkg/geoipupdate/internal/000077500000000000000000000000001376571433200207455ustar00rootroot00000000000000geoipupdate-4.6.0/pkg/geoipupdate/internal/retry.go000066400000000000000000000010651376571433200224430ustar00rootroot00000000000000// Package internal is none of your business package internal import ( "time" ) // RetryWithBackoff calls the provided function repeatedly until it succeeds or // until the retry duration is up. func RetryWithBackoff( fn func() error, retryFor time.Duration, ) error { start := time.Now() for i := uint(0); ; i++ { err := fn() if err == nil { return nil } currentDuration := time.Since(start) waitDuration := 200 * time.Millisecond * (1 << i) if currentDuration+waitDuration > retryFor { return err } time.Sleep(waitDuration) } } geoipupdate-4.6.0/pkg/geoipupdate/internal/retry_test.go000066400000000000000000000012051376571433200234760ustar00rootroot00000000000000package internal import ( "testing" "time" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) func TestRetryWithBackoff(t *testing.T) { t.Run("never succeeds", func(t *testing.T) { var n int err := RetryWithBackoff( func() error { n++ return errors.New("foo") }, 6*time.Second, ) assert.Equal(t, 5, n) assert.Error(t, err) }) t.Run("succeeds after failures", func(t *testing.T) { var n int err := RetryWithBackoff( func() error { n++ if n < 3 { return errors.New("foo") } return nil }, 6*time.Second, ) assert.Equal(t, 3, n) assert.NoError(t, err) }) }