pax_global_header00006660000000000000000000000064145473462120014522gustar00rootroot0000000000000052 comment=411fed06da31a03542cb2dd1875373ff5e78e838 geoipupdate-6.1.0/000077500000000000000000000000001454734621200140345ustar00rootroot00000000000000geoipupdate-6.1.0/.github/000077500000000000000000000000001454734621200153745ustar00rootroot00000000000000geoipupdate-6.1.0/.github/dependabot.yml000066400000000000000000000003261454734621200202250ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: daily open-pull-requests-limit: 10 - package-ecosystem: "github-actions" directory: "/" schedule: interval: daily geoipupdate-6.1.0/.github/workflows/000077500000000000000000000000001454734621200174315ustar00rootroot00000000000000geoipupdate-6.1.0/.github/workflows/codeql-analysis.yml000066400000000000000000000031241454734621200232440ustar00rootroot00000000000000name: "Code scanning - action" on: push: branches-ignore: - 'dependabot/**' pull_request: schedule: - cron: '0 11 * * 2' jobs: CodeQL-Build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 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@v3 # 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@v3 # ℹ️ 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@v3 geoipupdate-6.1.0/.github/workflows/go.yml000066400000000000000000000013561454734621200205660ustar00rootroot00000000000000name: Go on: push: pull_request: schedule: - cron: '5 10 * * SUN' jobs: build: strategy: matrix: go-version: [1.20.x, 1.21.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@v5 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v4 - name: Get dependencies run: go get -v -t -d ./... - name: Build run: go build -v ./... - name: Test run: go test -race -v ./... geoipupdate-6.1.0/.github/workflows/golangci-lint.yml000066400000000000000000000004641454734621200227070ustar00rootroot00000000000000name: golangci-lint on: push: pull_request: schedule: - cron: '5 5 * * SUN' jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: version: latest geoipupdate-6.1.0/.gitignore000066400000000000000000000000701454734621200160210ustar00rootroot00000000000000*.swp /build /cmd/geoipupdate/geoipupdate /vendor .idea geoipupdate-6.1.0/.golangci.toml000066400000000000000000000545371454734621200166100ustar00rootroot00000000000000[run] deadline = "10m" tests = true [linters] disable-all = true enable = [ "asasalint", "asciicheck", "bidichk", "bodyclose", "containedctx", "contextcheck", "depguard", # This is probably worthwhile, but there are a number of false positives # that would need to be addressed. # "dupword", "durationcheck", "errcheck", "errchkjson", "errname", "errorlint", # This doesn't seem to know about CTEs or DELETEs with RETURNING # "execinquery", "exhaustive", # We often don't initialize all of the struct fields. This is fine # generally # "exhaustruct", "exportloopref", "forbidigo", # We tried this linter but most places we do forced type asserts are # pretty safe, e.g., an atomic.Value when everything is encapsulated # in a small package. # "forcetypeassert", "goconst", "gocyclo", "gocritic", "godot", "gofumpt", "gomodguard", "gosec", "gosimple", # This only "caught" one thing, and it seemed like a reasonable use # of Han script. Generally, I don't think we want to prevent the use # of particulr scripts. The time.Local checks might be useful, but # this didn't actually catch anything of note there. # "gosmopolitan", # Similar to the exhaustive linter and I don't know that we use these # sorts of sum types # "gochecksumtype". "govet", "grouper", # Seems too opinionated or at least would require going through all the # interfaces we have. # "inamedparam" "ineffassign", "lll", # We don't use these loggers # "loggercheck", "makezero", # Maintainability Index. Seems like it could be a good idea, but a # lot of things fail and we would need to make some decisions about # what to allow. # "maintidx", "misspell", # Causes panics, e.g., when processing mmerrors # "musttag", "nakedret", "nilerr", # Perhaps too opinionated. We do have some legitimate uses of "return nil, nil" # "nilnil", "noctx", "nolintlint", # We occasionally use named returns for documentation, which is helpful. # Named returns are only really a problem when used in conjunction with # a bare return statement. I _think_ Revive's bare-return covers that # case. # "nonamedreturns", "nosprintfhostport", "perfsprint", "predeclared", "protogetter", "revive", "rowserrcheck", # https://github.com/golangci/golangci-lint/issues/287 # "safesql", "sloglint", "sqlclosecheck", "staticcheck", "stylecheck", # We have very few structs with multiple tags and for the couple we had, this # actually made it harder to read. # "tagalign", "tenv", "testifylint", "tparallel", "typecheck", "unconvert", "unparam", "unused", "usestdlibvars", "vetshadow", "wastedassign", # We don't currently wrap external errors in this module. # "wrapcheck", ] # Please note that we only use depguard for stdlib as gomodguard only # supports modules currently. See https://github.com/ryancurrah/gomodguard/issues/12 [[linters-settings.depguard.rules.main.deny]] pkg = "io/ioutil" desc = "Deprecated. Functions have been moved elsewhere." [[linters-settings.depguard.rules.main.deny]] # slices has better alternatives. pkg = "sort" desc = "Use slices instead" [linters-settings.errcheck] # Don't allow setting of error to the blank identifier. If there is a legitimate # reason, there should be a nolint with an explanation. check-blank = true exclude-functions = [ # If we are rolling back a transaction, we are often already in an error # state. '(*database/sql.Tx).Rollback', # It is reasonable to ignore errors if Cleanup fails in most cases. '(*github.com/google/renameio/v2.PendingFile).Cleanup', # We often don't care if removing a file failed (e.g., it doesn't exist) 'os.Remove', 'os.RemoveAll', ] # 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.errorlint] errorf = true asserts = true comparison = true [linters-settings.exhaustive] default-signifies-exhaustive = true [linters-settings.forbidigo] # Forbid the following identifiers forbid = [ { p = "Geoip", msg = "you should use `GeoIP`" }, { p = "^geoIP", msg = "you should use `geoip`" }, { p = "^hubSpot", msg = "you should use `hubspot`" }, { p = "Maxmind", msg = "you should use `MaxMind`" }, { p = "^maxMind", msg = "you should use `maxmind`" }, { p = "Minfraud", msg = "you should use `MinFraud`" }, { p = "^minFraud", msg = "you should use `minfraud`" }, { p = "[Uu]ser[iI][dD]", msg = "you should use `accountID` or `AccountID`" }, { p = "WithEnterpriseURLs", msg = "Use ghe.NewClient instead." }, { p = "^bigquery.NewClient", msg = "you should use mmgcloud.NewBigQueryClient instead." }, { p = "^cloudresourcemanager.NewService", msg = "you should use mmgcloud.NewCloudResourceManagerService instead." }, { p = "^compute.NewService", msg = "you should use mmgcloud.NewComputeService instead." }, { p = "^drive.NewService", msg = "you should use mmgdrive.NewGDrive instead." }, { p = "^math.Max$", msg = "you should use the max built-in instead." }, { p = "^math.Min$", msg = "you should use the min built-in instead." }, { p = "^net.ParseCIDR", msg = "you should use netip.ParsePrefix unless you really need a *net.IPNet" }, { p = "^net.ParseIP", msg = "you should use netip.ParseAddr unless you really need a net.IP" }, { p = "^pgtype.NewMap", msg = "you should use mmdatabase.NewTypeMap instead" }, { p = "^serviceusage.NewService", msg = "you should use mmgcloud.NewServiceUsageService instead." }, { p = "^sheets.NewService", msg = "you should use mmgcloud.NewSheetsService instead." }, { p = "^storage.NewClient", msg = "you should use mmgcloud.NewGStorageClient instead. This sets the HTTP client settings that we need for internal use." }, { p = "^os.IsNotExist", msg = "As per their docs, new code should use errors.Is(err, fs.ErrNotExist)." }, { p = "^os.IsExist", msg = "As per their docs, new code should use errors.Is(err, fs.ErrExist)" }, { p = "^net.LookupIP", msg = "You should use net.Resolver functions instead." }, { p = "^net.LookupCNAME", msg = "You should use net.Resolver functions instead." }, { p = "^net.LookupHost", msg = "You should use net.Resolver functions instead." }, { p = "^net.LookupPort", msg = "You should use net.Resolver functions instead." }, { p = "^net.LookupTXT", msg = "You should use net.Resolver functions instead." }, { p = "^net.LookupAddr", msg = "You should use net.Resolver functions instead." }, { p = "^net.LookupMX", msg = "You should use net.Resolver functions instead." }, { p = "^net.LookupNS", msg = "You should use net.Resolver functions instead." }, { p = "^net.LookupSRV", msg = "You should use net.Resolver functions instead." }, ] [linters-settings.gocritic] enabled-checks = [ "appendAssign", "appendCombine", "argOrder", "assignOp", "badCall", "badCond", "badLock", "badRegexp", "badSorting", "boolExprSimplify", "builtinShadow", "builtinShadowDecl", "captLocal", "caseOrder", "codegenComment", "commentedOutCode", "commentedOutImport", "commentFormatting", "defaultCaseOrder", # Revive's defer rule already captures this. This caught no extra cases. # "deferInLoop", "deferUnlambda", "deprecatedComment", "docStub", "dupArg", "dupBranchBody", "dupCase", "dupImport", "dupSubExpr", "dynamicFmtString", "elseif", "emptyDecl", "emptyFallthrough", "emptyStringTest", "equalFold", "evalOrder", "exitAfterDefer", "exposedSyncMutex", "externalErrorReassign", # Given that all of our code runs on Linux and the / separate should # work fine, this seems less important. # "filepathJoin", "flagDeref", "flagName", "hexLiteral", # This seems like it could be good, but we would need to update current # uses. It supports "--fix", but the fixing is a bit broken. # "httpNoBody", # This might be good, but we would have to revisit a lot of code. # "hugeParam", "ifElseChain", "importShadow", "indexAlloc", "initClause", "mapKey", "methodExprCall", "nestingReduce", "newDeref", "nilValReturn", "octalLiteral", "offBy1", "paramTypeCombine", "preferDecodeRune", "preferFilepathJoin", "preferFprint", "preferStringWriter", "preferWriteByte", "ptrToRefParam", "rangeExprCopy", "rangeValCopy", "redundantSprint", "regexpMust", "regexpPattern", # This might be good, but I don't think we want to encourage # significant changes to regexes as we port stuff from Perl. # "regexpSimplify", "returnAfterHttpError", "ruleguard", "singleCaseSwitch", "sliceClear", "sloppyLen", # This seems like it might also be good, but a lot of existing code # fails. # "sloppyReassign", # This complains about helper functions in tests. # "sloppyTestFuncName", "sloppyTypeAssert", "sortSlice", "sprintfQuotedString", "sqlQuery", "stringsCompare", "stringConcatSimplify", "stringXbytes", "switchTrue", "syncMapLoadAndDelete", "timeExprSimplify", "todoCommentWithoutDetail", "tooManyResultsChecker", "truncateCmp", "typeAssertChain", "typeDefFirst", "typeSwitchVar", "typeUnparen", "underef", "unlabelStmt", "unlambda", # I am not sure we would want this linter and a lot of existing # code fails. # "unnamedResult", "unnecessaryBlock", "unnecessaryDefer", "unslice", "valSwap", "weakCond", # Covered by nolintlint # "whyNoLint" "wrapperFunc", "yodaStyleExpr", ] [linters-settings.gofumpt] extra-rules = true lang-version = "1.18" [linters-settings.gomodguard] [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/avct/uasurfer"] recommendations = ["github.com/xavivars/uasurfer"] reason = "The original avct module appears abandoned." [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/BurntSushi/toml"] recommendations = ["github.com/pelletier/go-toml/v2"] reason = "This library panics frequently on invalid input." [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/pelletier/go-toml"] recommendations = ["github.com/pelletier/go-toml/v2"] reason = "This is an outdated version." [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/gofrs/uuid"] recommendations = ["github.maxmind.com/maxmind/mm_website/go/pkg/uuid"] [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/gofrs/uuid/v5"] recommendations = ["github.maxmind.com/maxmind/mm_website/go/pkg/uuid"] [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/satori/go.uuid"] recommendations = ["github.maxmind.com/maxmind/mm_website/go/pkg/uuid"] [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/google/uuid"] recommendations = ["github.maxmind.com/maxmind/mm_website/go/pkg/uuid"] [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/lib/pq"] recommendations = ["github.com/jackc/pgx"] reason = "This library is no longer actively maintained." [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/neilotoole/errgroup"] recommendations = ["golang.org/x/sync/errgroup"] reason = "This library can lead to subtle deadlocks in certain use cases." [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/pariz/gountries"] reason = "This library's data is not actively maintained. Use GeoInfo data." [linters-settings.gomodguard.blocked.modules."github.com/pkg/errors"] reason = "pkg/errors is no longer maintained." [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/RackSec/srslog"] recommendations = ["github.com/RackSec/srslog"] reason = "This library's data is not actively maintained." [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/ua-parser/uap-go/uaparser"] recommendations = ["github.com/xavivars/uasurfer"] reason = "The performance of this library is absolutely abysmal." [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."github.com/ugorji/go/codec"] recommendations = ["encoding/json", "github.com/mailru/easyjson"] reason = "This library is poorly maintained. We should default to using encoding/json and use easyjson where performance really matters." [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."io/ioutil"] [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."gotest.tools/v3/assert"] recommendations = ["github.com/stretchr/testify/assert"] reason = "Use github.com/stretchr/testify/assert" [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."golang.org/x/exp/slog"] recommendations = ["log/slog"] reason = "Use log/slog" [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."golang.org/x/exp/slices"] recommendations = ["slices"] reason = "Use slices" [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."inet.af/netaddr"] recommendations = ["go4.org/netipx"] reason = "inet.af/netaddr has been deprecated." [[linters-settings.gomodguard.blocked.modules]] [linters-settings.gomodguard.blocked.modules."k8s.io/utils/strings/slices"] recommendations = ["slices"] reason = "Use slices" [[linters-settings.gomodguard.blocked.versions]] [linters-settings.gomodguard.blocked.versions."github.com/jackc/pgconn"] reason = "Use github.com/jackc/pgx/v5" [[linters-settings.gomodguard.blocked.versions]] [linters-settings.gomodguard.blocked.versions."github.com/jackc/pgtype"] reason = "Use github.com/jackc/pgx/v5" [[linters-settings.gomodguard.blocked.versions]] [linters-settings.gomodguard.blocked.versions."github.com/jackc/pgx"] version = "< 5.0.0" reason = "Use github.com/jackc/pgx/v5" [linters-settings.gosec] excludes = [ # G104 - "Audit errors not checked." We use errcheck for this. "G104", # G306 - "Expect WriteFile permissions to be 0600 or less". "G306", # Prohibits defer (*os.File).Close, which we allow when reading from file. "G307", ] [linters-settings.govet] # This seems to be duplicate setting, but enable it for good measure. check-shadowing = true "enable-all" = true # Although it is very useful in particular cases where we are trying to # use as little memory as possible, there are even more cases where # other organizations may make more sense. disable = ["fieldalignment"] [linters-settings.govet.settings.shadow] strict = true [linters-settings.lll] line-length = 120 tab-width = 4 [linters-settings.nolintlint] allow-leading-space = false allow-unused = false allow-no-explanation = ["lll", "misspell"] require-explanation = true require-specific = true [linters-settings.revive] ignore-generated-header = true severity = "warning" # This might be nice but it is so common that it is hard # to enable. # [[linters-settings.revive.rules]] # name = "add-constant" # [[linters-settings.revive.rules]] # name = "argument-limit" [[linters-settings.revive.rules]] name = "atomic" [[linters-settings.revive.rules]] name = "bare-return" [[linters-settings.revive.rules]] name = "blank-imports" [[linters-settings.revive.rules]] name = "bool-literal-in-expr" [[linters-settings.revive.rules]] name = "call-to-gc" # [[linters-settings.revive.rules]] # name = "cognitive-complexity" [[linters-settings.revive.rules]] name = "comment-spacings" arguments = ["easyjson", "nolint"] # Probably a good rule, but we have a lot of names that # only have case differences. # [[linters-settings.revive.rules]] # name = "confusing-naming" [[linters-settings.revive.rules]] name = "confusing-results" [[linters-settings.revive.rules]] name = "constant-logical-expr" [[linters-settings.revive.rules]] name = "context-as-argument" [[linters-settings.revive.rules]] name = "context-keys-type" # [[linters-settings.revive.rules]] # name = "cyclomatic" [[linters-settings.revive.rules]] name = "datarace" [[linters-settings.revive.rules]] name = "deep-exit" [[linters-settings.revive.rules]] name = "defer" [[linters-settings.revive.rules]] name = "dot-imports" [[linters-settings.revive.rules]] name = "duplicated-imports" [[linters-settings.revive.rules]] name = "early-return" [[linters-settings.revive.rules]] name = "empty-block" [[linters-settings.revive.rules]] name = "empty-lines" [[linters-settings.revive.rules]] name = "errorf" [[linters-settings.revive.rules]] name = "error-naming" [[linters-settings.revive.rules]] name = "error-return" [[linters-settings.revive.rules]] name = "error-strings" [[linters-settings.revive.rules]] name = "exported" # [[linters-settings.revive.rules]] # name = "file-header" # We have a lot of flag parameters. This linter probably makes # a good point, but we would need some cleanup or a lot of nolints. # [[linters-settings.revive.rules]] # name = "flag-parameter" # [[linters-settings.revive.rules]] # name = "function-result-limit" [[linters-settings.revive.rules]] name = "get-return" [[linters-settings.revive.rules]] name = "identical-branches" [[linters-settings.revive.rules]] name = "if-return" [[linters-settings.revive.rules]] name = "imports-blacklist" [[linters-settings.revive.rules]] name = "import-shadowing" [[linters-settings.revive.rules]] name = "increment-decrement" [[linters-settings.revive.rules]] name = "indent-error-flow" # [[linters-settings.revive.rules]] # name = "line-length-limit" # [[linters-settings.revive.rules]] # name = "max-public-structs" [[linters-settings.revive.rules]] name = "modifies-parameter" [[linters-settings.revive.rules]] name = "modifies-value-receiver" # We frequently use nested structs, particularly in tests. # [[linters-settings.revive.rules]] # name = "nested-structs" [[linters-settings.revive.rules]] name = "optimize-operands-order" [[linters-settings.revive.rules]] name = "package-comments" [[linters-settings.revive.rules]] name = "range" [[linters-settings.revive.rules]] name = "range-val-address" [[linters-settings.revive.rules]] name = "range-val-in-closure" [[linters-settings.revive.rules]] name = "receiver-naming" [[linters-settings.revive.rules]] name = "redefines-builtin-id" [[linters-settings.revive.rules]] name = "string-of-int" [[linters-settings.revive.rules]] name = "struct-tag" [[linters-settings.revive.rules]] name = "superfluous-else" [[linters-settings.revive.rules]] name = "time-equal" [[linters-settings.revive.rules]] name = "time-naming" [[linters-settings.revive.rules]] name = "unconditional-recursion" [[linters-settings.revive.rules]] name = "unexported-naming" [[linters-settings.revive.rules]] name = "unexported-return" # This is covered elsewhere and we want to ignore some # functions such as fmt.Fprintf. # [[linters-settings.revive.rules]] # name = "unhandled-error" [[linters-settings.revive.rules]] name = "unnecessary-stmt" [[linters-settings.revive.rules]] name = "unreachable-code" [[linters-settings.revive.rules]] name = "unused-parameter" # We generally have unused receivers in tests for meeting the # requirements of an interface. # [[linters-settings.revive.rules]] # name = "unused-receiver" [[linters-settings.revive.rules]] name = "use-any" [[linters-settings.revive.rules]] name = "useless-break" [[linters-settings.revive.rules]] name = "var-declaration" [[linters-settings.revive.rules]] name = "var-naming" [[linters-settings.revive.rules]] name = "waitgroup-by-value" [linters-settings.unparam] check-exported = true [issues] exclude-use-default = false # This goes off for MD5 usage, which we use heavily [[issues.exclude-rules]] text = "weak cryptographic primitive" linters = ["gosec"] [[issues.exclude-rules]] linters = [ "bodyclose", ] # This rule doesn't really make sense for tests where we don't have an open # connection and we might be passing around the response for other reasons. path = "_test.go" [[issues.exclude-rules]] linters = [ "forbidigo", ] # This refers to a minFraud field, not the MaxMind Account ID source = "AccountUserID|Account\\.UserID" # we include both a source and text exclusion as the source exclusion # misses matches where forbidigo reports the error on the first line # of a chunk of a function call even though the use is on a later line. [[issues.exclude-rules]] linters = [ "forbidigo", ] text = "AccountUserID|Account\\.UserID" [[issues.exclude-rules]] linters = [ "gocritic", ] # For some reason the imports stuff in ruleguard doesn't work in golangci-lint. # Perhaps it has an outdated version or something path = "_test.go" text = "ruleguard: Prefer the alternative Context method instead" [[issues.exclude-rules]] linters = [ "gocritic", ] # The nolintlint linter behaves oddly with ruleguard rules source = "// *no-ruleguard" [[issues.exclude-rules]] linters = [ "govet", ] # These are usually fine to shadow and not allowing shadowing for them can # make the code unnecessarily verbose. text = 'shadow: declaration of "(ctx|err|ok)" shadows declaration' [[issues.exclude-rules]] linters = [ "contextcheck", # With recent changes to the linter, there were a lot of failures in # the tests and it wasn't clear to me that fixing them would actually # improve the readability. "goconst", "nilerr", "wrapcheck", ] path = "_test.go" [[issues.exclude-rules]] linters = [ "stylecheck", ] # ST1016 - methods on the same type should have the same receiver name. # easyjson doesn't interact well with this. text = "ST1016" [[issues.exclude-rules]] linters = [ "staticcheck", ] # SA5008: unknown JSON option "intern" - easyjson specific option. text = 'SA5008: unknown JSON option "intern"' [[issues.exclude-rules]] linters = [ "wrapcheck", ] path = "_easyjson.go" [[issues.exclude-rules]] linters = [ "gocritic", ] source = "Chmod|WriteFile" text = "octalLiteral" geoipupdate-6.1.0/.goreleaser.yml000066400000000000000000000142441454734621200167720ustar00rootroot00000000000000project_name: 'geoipupdate' archives: - id: 'archives-unix' builds: - 'geoipupdate-unix' wrap_in_directory: true files: - 'CHANGELOG.md' - 'LICENSE-APACHE' - 'LICENSE-MIT' - 'README.md' - src: 'build/unix/*' dst: '.' strip_parent: true - id: 'archives-windows' builds: - 'geoipupdate-windows' wrap_in_directory: true files: - 'CHANGELOG.md' - 'LICENSE-APACHE' - 'LICENSE-MIT' - 'README.md' - src: 'build/windows/*' dst: '.' strip_parent: true format: 'zip' builds: - id: 'geoipupdate-unix' main: './cmd/geoipupdate' binary: 'geoipupdate' goarch: - '386' - 'amd64' - 'arm' - 'arm64' goos: - 'darwin' - 'linux' - 'freebsd' - 'openbsd' ignore: - goos: freebsd goarch: arm - goos: openbsd goarch: arm - goos: freebsd goarch: arm64 - goos: openbsd goarch: arm64 hooks: post: 'make data BUILDDIR="build/unix"' env: - CGO_ENABLED=0 # This is a separate build as we want to specify different paths in the # ldflags. - id: 'geoipupdate-packages' main: './cmd/geoipupdate' binary: 'geoipupdate' goarch: - '386' - 'amd64' - 'arm' - 'arm64' goos: - 'linux' hooks: post: 'make data BUILDDIR="build/packages" 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 - id: 'geoipupdate-windows' main: './cmd/geoipupdate' binary: 'geoipupdate' goarch: - '386' - 'amd64' goos: - 'windows' hooks: post: 'make data OS=Windows_NT BUILDDIR="build/windows"' env: - CGO_ENABLED=0 dockers: - ids: - 'geoipupdate-unix' image_templates: - "maxmindinc/geoipupdate:{{ .Tag }}-amd64" - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-amd64" dockerfile: docker/Dockerfile use: buildx goarch: amd64 extra_files: - docker/entry.sh - docker/healthcheck.sh build_flag_templates: - "--platform=linux/amd64" - image_templates: - "maxmindinc/geoipupdate:{{ .Tag }}-arm64" - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-arm64" dockerfile: docker/Dockerfile use: buildx goarch: arm64 extra_files: - docker/entry.sh - docker/healthcheck.sh build_flag_templates: - "--platform=linux/arm64" - image_templates: - "maxmindinc/geoipupdate:{{ .Tag }}-arm-v6" - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-arm-v6" dockerfile: docker/Dockerfile use: buildx goarch: arm goarm: 6 extra_files: - docker/entry.sh - docker/healthcheck.sh build_flag_templates: - "--platform=linux/arm/v6" docker_manifests: - name_template: "maxmindinc/geoipupdate:{{ .Tag }}" image_templates: - "maxmindinc/geoipupdate:{{ .Tag }}-amd64" - "maxmindinc/geoipupdate:{{ .Tag }}-arm64" - "maxmindinc/geoipupdate:{{ .Tag }}-arm-v6" - name_template: "maxmindinc/geoipupdate:v{{ .Major }}" image_templates: - "maxmindinc/geoipupdate:{{ .Tag }}-amd64" - "maxmindinc/geoipupdate:{{ .Tag }}-arm64" - "maxmindinc/geoipupdate:{{ .Tag }}-arm-v6" - name_template: "maxmindinc/geoipupdate:v{{ .Major }}.{{ .Minor }}" image_templates: - "maxmindinc/geoipupdate:{{ .Tag }}-amd64" - "maxmindinc/geoipupdate:{{ .Tag }}-arm64" - "maxmindinc/geoipupdate:{{ .Tag }}-arm-v6" - name_template: "maxmindinc/geoipupdate:latest" image_templates: - "maxmindinc/geoipupdate:{{ .Tag }}-amd64" - "maxmindinc/geoipupdate:{{ .Tag }}-arm64" - "maxmindinc/geoipupdate:{{ .Tag }}-arm-v6" - name_template: "ghcr.io/maxmind/geoipupdate:{{ .Tag }}" image_templates: - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-amd64" - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-arm64" - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-arm-v6" - name_template: "ghcr.io/maxmind/geoipupdate:v{{ .Major }}" image_templates: - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-amd64" - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-arm64" - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-arm-v6" - name_template: "ghcr.io/maxmind/geoipupdate:v{{ .Major }}.{{ .Minor }}" image_templates: - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-amd64" - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-arm64" - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-arm-v6" - name_template: "ghcr.io/maxmind/geoipupdate:latest" image_templates: - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-amd64" - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-arm64" - "ghcr.io/maxmind/geoipupdate:{{ .Tag }}-arm-v6" nfpms: - builds: - 'geoipupdate-packages' vendor: 'MaxMind, Inc.' homepage: https://www.maxmind.com/ maintainer: 'MaxMind, Inc. ' description: Program to perform automatic updates of GeoIP2 and GeoLite2 binary databases. license: Apache 2.0 or MIT formats: - deb - rpm bindir: /usr/bin contents: - dst: /usr/share/GeoIP type: 'dir' - src: 'CHANGELOG.md' dst: '/usr/share/doc/geoipupdate/CHANGELOG.md' - src: 'LICENSE-APACHE' dst: '/usr/share/doc/geoipupdate/LICENSE-APACHE' - src: 'LICENSE-MIT' dst: '/usr/share/doc/geoipupdate/LICENSE-MIT' - src: 'README.md' dst: '/usr/share/doc/geoipupdate/README.md' - src: 'build/packages/GeoIP.conf' dst: '/usr/share/doc/geoipupdate/GeoIP.conf' - src: 'build/packages/GeoIP.conf.md' dst: '/usr/share/doc/geoipupdate/GeoIP.conf.md' - src: 'build/packages/geoipupdate.md' dst: '/usr/share/doc/geoipupdate/geoipupdate.md' - src: 'build/packages/GeoIP.conf.5' dst: '/usr/share/man/man5/GeoIP.conf.5' - src: 'build/packages/geoipupdate.1' dst: '/usr/share/man/man1/geoipupdate.1' - src: 'build/packages/GeoIP.conf' dst: '/etc/GeoIP.conf' type: config release: name_template: "{{.Version}}" target_commitish: "{{ .FullCommit }}" geoipupdate-6.1.0/.yamllintrc000066400000000000000000000002471454734621200162160ustar00rootroot00000000000000extends: default rules: braces: disable brackets: disable line-length: disable colons: {max-spaces-before: 0, max-spaces-after: -1} document-start: disable geoipupdate-6.1.0/CHANGELOG.md000066400000000000000000000262501454734621200156520ustar00rootroot00000000000000# CHANGELOG ## 6.1.0 (2024-01-09) * `geoipupdate` now sets the version in the `User-Agent` header to the version in the binary. While there were no issues with the version in the header, this makes sure it will match the binary. The header also now includes build information, such as OS and architecture. * White spaces in secret files `GEOIPUPDATE_ACCOUNT_ID_FILE`, `GEOIPUPDATE_LICENSE_KEY_FILE` are ignored, see #262 issue. ## 6.0.0 (2023-07-12) * `geoipupdate` now supports configuration via environment variables. Any configuration set this way will override any value from the config file, but still be overridden by any associated command line option (if any). The following new environment variables are supported: * `GEOIPUPDATE_ACCOUNT_ID` * `GEOIPUPDATE_ACCOUNT_ID_FILE` * `GEOIPUPDATE_CONF_FILE` * `GEOIPUPDATE_DB_DIR` * `GEOIPUPDATE_EDITION_IDS` * `GEOIPUPDATE_HOST` * `GEOIPUPDATE_LICENSE_KEY` * `GEOIPUPDATE_LICENSE_KEY_FILE` * `GEOIPUPDATE_LOCK_FILE` * `GEOIPUPDATE_PARALLELISM` * `GEOIPUPDATE_PRESERVE_FILE_TIMES` * `GEOIPUPDATE_PROXY` * `GEOIPUPDATE_PROXY_USER_PASSWORD` * `GEOIPUPDATE_RETRY_FOR` * `GEOIPUPDATE_VERBOSE` * Changed the signature of `NewConfig` in `pkg/geoipupdate` to no longer accept a positional config file path argument, which can now be passed in using the option from `WithConfigFile` along with the other optional parameters. * `geoipupdate` and `NewConfig` no longer require a config file to exist. * The `--stack-trace` flag has been removed. This flag has been broken since 4.11.0. ## 5.1.1 (2023-05-08) * Based on feedback, the change to use a non-root user in 5.1.0 when using the Docker image has been reverted. There are no non-Docker changes in this release. See GitHub #233. ## 5.1.0 (2023-05-05) * Fixed the Docker health-check script to use the correct time of the last update attempt. Reported by cford1080. GitHub #225. * Added new `--output` flag to print JSON to standard output describing the result of the run. * Compilation with Go versions before 1.19 is no longer supported. * When using the provided Docker images, `geoipupdate` no longer runs as root in the container. Based on pull request by Andreas Grünenfelder. GitHub #200. ## 5.0.4 (2023-04-17) * On releases 4.9.0 through 5.0.3, the incorrect commit was tagged. This release attempts to fix the release process to prevent this issue. There are no code changes to the binaries provided by MaxMind, either on the GitHub Release page or the MaxMind PPA. ## 5.0.3 (2023-04-15) * On 5.0.0 through 5.0.2, the default database directory was not being correctly set for Debian and RPM package builds. The directory `/usr/local/share/GeoIP` was being used rather than `/usr/share/GeoIP`. This build restores `/usr/share/GeoIP` as the default directory for these builds. Reported by Yvan. GitHub #222. ## 5.0.2 (2023-04-13) * "Database ... up to date" messages are now only shown if the verbose flag is set. Pull request by Adam Weinberger. GitHub #219. ## 5.0.1 (2023-04-13) * The 5.0.0 release mistakenly set the file permissions on downloaded databases to 0600. This restores the previous behavior of using 0644. Pull request by Josh Samuelson. GitHub #217 and #218. ## 5.0.0 (2023-04-12) * Redefined the `Reader` and `Writer` interface APIs in `pkg/geoipupdate/database`. This change aims to to make it easier to introduce custom implementations of these interfaces. * Changed the signature of `NewConfig` in `pkg/geoipupdate` to accept optional parameters. This change allows the introduction of new flags or config options without making breaking changes to the function's signature. * Introduced `Parallelism` as a new flag and config option to enable concurrent database updates. ## 4.11.1 (2023-03-16) * Removed extra underscore in script variables preventing the Docker secret support added in 4.11.0 from working as expected. Pull request by Moeen Mirjalili. GitHub #210. ## 4.11.0 (2023-03-15) * `github.com/pkg/errors` is no longer used to wrap errors. * Docker secrets are now supported for the MaxMind account ID and license key. Pull request by Matthew Kobayashi. GitHub #197. * The Dockerfile now has a Healthcheck that makes sure the modification date of the database directory is within the update period. * The Docker images are now published to the GitHub Container Registry, `ghcr.io`. We will likely stop publishing to Docker Hub in the near future. ## 4.10.0 (2022-09-26) * HTTPS proxies are now supported. Pull request by Jamie Thompson. GitHub #172. * An HTTP request to get the filename for the edition ID has been removed. This was previously required as the GeoIP Legacy edition IDs bore little relation to the name of the database on disk. ## 4.9.0 (2022-02-15) * The client now sets the `User-Agent` header. * The error handling has been improved. * The `goreleaser` configuration has been consolidated. There is now one checksum file for all builds. * Binaries are now built for OpenBSD and FreeBSD. Pull request by Devin Buhl. GitHub #161. * Packages for ARM are now correctly uploaded. Bug report by Service Entity. GitHub #162. ## 4.8.0 (2021-07-20) * The Docker container now supports the following new environment variables: * `GEOIPUPDATE_CONF_FILE` - The path where the configuration file will be written. The default is `/etc/GeoIP.conf`. * `GEOIPUPDATE_DB_DIR` - The directory where geoipupdate will download the databases. The default is `/usr/share/GeoIP`. Pull request by Maxence POULAIN. GitHub #143. ## 4.7.1 (2021-04-19) * The Alpine version used for the Docker image now tracks the `alpine:3` tag rather than a specific point release. * The `arm64` Docker images were not correctly generated in 4.7.0. This release corrects the issue. * This release provides an `arm/v6` Docker image. ## 4.7.0 (2021-04-16) * Go 1.13 or greater is now required. * In verbose mode, we now print a message before each HTTP request. Previously we would not print anything for retried requests. * Expected response errors no longer cause request retries. For example, we no longer retry the download request if the database subscription has lapsed. * When running with `GEOIPUPDATE_FREQUENCY` set, the Docker image will now stop when sent a SIGTERM instead of waiting for a SIGKILL. Pull request by Maxence POULAIN. GitHub #135. * Docker images are now provided for ARM64. Requested by allthesebugsv2. GitHub #136. ## 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-6.1.0/LICENSE-APACHE000066400000000000000000000261401454734621200157630ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 https://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 https://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-6.1.0/LICENSE-MIT000066400000000000000000000017771454734621200155040ustar00rootroot00000000000000Permission 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-6.1.0/Makefile000066400000000000000000000034711454734621200155010ustar00rootroot00000000000000ifndef 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-6.1.0/README.dev.md000066400000000000000000000036021454734621200160710ustar00rootroot00000000000000# Releasing * Make sure you have [`goreleaser`](https://goreleaser.com/), rpmbuild, and pandoc installed. (rpmbuild is in the Ubuntu package `rpm`). * Set release date in `CHANGELOG.md` and commit it. * Ensure you can run `docker` commands as your user (e.g., `docker images`). * Ensure Docker is set up to do cross-compilation. You can verify this with `docker buildx ls`. It should list `linux/arm64` as an available platform. If not, follow [these instructions](https://docs.docker.com/build/building/multi-platform/). * Log in to your Docker Hub account (be sure to be in our organization): `docker login`. * 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/main/README.dev.md). * Ensure you have the `dh-golang`, `golang-any`, `devscripts`, `libfile-slurp-tiny-perl`, and `libdatetime-perl` 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` Gotcha with PPA: * If you get an error from `dput` like `No host ppa:maxmind/ppa found in config`, you can create a `~/.dput.cf` with content like so: ``` [maxmind] fqdn = ppa.launchpad.net method = ftp incoming = ~maxmind/ubuntu/ppa/ login = anonymous allow_unsigned_uploads = 0 ``` Then you can run the same `dput` command but with `dput maxmind [...]` instead of `dput ppa:maxmind/ppa [...]` (I'm not sure how to make the matching work with the original command). geoipupdate-6.1.0/README.md000066400000000000000000000104331454734621200153140ustar00rootroot00000000000000# GeoIP Update The GeoIP Update program performs automatic updates of GeoIP2 and GeoLite2 binary databases. CSV databases are _not_ supported. ## 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 GeoIP Update, please refer to our [documentation](https://dev.maxmind.com/geoip/updating-databases?lang=en) for information about configuration. If you're upgrading from GeoIP Update 3.x, please see our [upgrade guide](https://dev.maxmind.com/geoip/upgrading-geoip-update?lang=en). ### 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_5.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_5.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_5.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_5.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](https://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_5.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.20+). You can get it at the [Go website](https://golang.org). The easiest way is via `go install`: $ go install github.com/maxmind/geoipupdate/v6/cmd/geoipupdate@latest This installs `geoipupdate` to `$GOPATH/bin/geoipupdate`. # Configuring Please see our [online guide](https://dev.maxmind.com/geoip/updating-databases?lang=en) 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](https://github.com/maxmind/geoipupdate/issues). # Copyright and License This software is Copyright (c) 2018 - 2023 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-6.1.0/_config.yml000066400000000000000000000000471454734621200161640ustar00rootroot00000000000000exclude: ['README.dev.md', 'vendor'] geoipupdate-6.1.0/cmd/000077500000000000000000000000001454734621200145775ustar00rootroot00000000000000geoipupdate-6.1.0/cmd/geoipupdate/000077500000000000000000000000001454734621200171055ustar00rootroot00000000000000geoipupdate-6.1.0/cmd/geoipupdate/args.go000066400000000000000000000033061454734621200203720ustar00rootroot00000000000000package main import ( "log" "os" "github.com/maxmind/geoipupdate/v6/pkg/geoipupdate/vars" flag "github.com/spf13/pflag" ) // Args are command line arguments. type Args struct { ConfigFile string DatabaseDirectory string Verbose bool Output bool Parallelism int } func getArgs() *Args { confFileDefault := vars.DefaultConfigFile if value, ok := os.LookupEnv("GEOIPUPDATE_CONF_FILE"); ok { confFileDefault = value } configFile := flag.StringP( "config-file", "f", confFileDefault, "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") verbose := flag.BoolP("verbose", "v", false, "Use verbose output") output := flag.BoolP("output", "o", false, "Output download/update results in JSON format") displayVersion := flag.BoolP("version", "V", false, "Display the version and exit") parallelism := flag.Int("parallelism", 0, "Set the number of parallel database downloads") flag.Parse() if *help { printUsage() } if *displayVersion { log.Printf("geoipupdate %s", version) //nolint: revive // deep exit from main package os.Exit(0) } if *parallelism < 0 { log.Printf("Parallelism must be a positive number") printUsage() } return &Args{ ConfigFile: *configFile, DatabaseDirectory: *databaseDirectory, Verbose: *verbose, Output: *output, Parallelism: *parallelism, } } func printUsage() { log.Printf("Usage: %s \n", os.Args[0]) flag.PrintDefaults() //nolint: revive // deep exit from main package os.Exit(1) } geoipupdate-6.1.0/cmd/geoipupdate/end_to_end_test.go000066400000000000000000000042731454734621200225770ustar00rootroot00000000000000package main import ( "bytes" "compress/gzip" "context" "crypto/md5" "encoding/hex" "io" "log" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "github.com/maxmind/geoipupdate/v6/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", hex.EncodeToString(md5Writer.Sum(nil)), ) rw.Header().Set("Last-Modified", time.Now().Format(time.RFC1123)) _, err = rw.Write(buf.Bytes()) require.NoError(t, err) return } rw.WriteHeader(http.StatusBadRequest) }, ), ) defer server.Close() tempDir := t.TempDir() config := &geoipupdate.Config{ AccountID: 123, DatabaseDirectory: tempDir, EditionIDs: []string{"GeoLite2-City", "GeoLite2-Country"}, LicenseKey: "testing", LockFile: filepath.Join(tempDir, ".geoipupdate.lock"), URL: server.URL, Parallelism: 1, } logOutput := &bytes.Buffer{} log.SetOutput(logOutput) client := geoipupdate.NewClient(config) err := client.Run(context.Background()) require.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 := os.ReadFile(filepath.Clean(path)) require.NoError(t, err, "read file") assert.Equal( t, databaseContent+" /geoip/databases/"+editionID+"/update", string(buf), "correct database", ) } } geoipupdate-6.1.0/cmd/geoipupdate/main.go000066400000000000000000000026301454734621200203610ustar00rootroot00000000000000// geoipupdate performs automatic updates of GeoIP binary databases. package main import ( "context" "log" "github.com/maxmind/geoipupdate/v6/pkg/geoipupdate" "github.com/maxmind/geoipupdate/v6/pkg/geoipupdate/vars" ) const unknownVersion = "unknown" // These values are set by build scripts. Changing the names of // the variables should be considered a breaking change. var ( version = unknownVersion defaultConfigFile string defaultDatabaseDirectory string ) func main() { log.SetFlags(0) if defaultConfigFile != "" { vars.DefaultConfigFile = defaultConfigFile } if defaultDatabaseDirectory != "" { vars.DefaultDatabaseDirectory = defaultDatabaseDirectory } args := getArgs() config, err := geoipupdate.NewConfig( geoipupdate.WithConfigFile(args.ConfigFile), geoipupdate.WithDatabaseDirectory(args.DatabaseDirectory), geoipupdate.WithParallelism(args.Parallelism), geoipupdate.WithVerbose(args.Verbose), geoipupdate.WithOutput(args.Output), ) if err != nil { log.Fatalf("Error loading configuration: %s", 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 = client.Run(context.Background()); err != nil { log.Fatalf("Error retrieving updates: %s", err) } } geoipupdate-6.1.0/cmd/geoipupdate/version.go000066400000000000000000000020461454734621200211230ustar00rootroot00000000000000//go:build go1.12 // +build go1.12 package main import ( "runtime/debug" "github.com/maxmind/geoipupdate/v6/pkg/geoipupdate/vars" ) func init() { info, ok := debug.ReadBuildInfo() if !ok { // Getting the build info failed, e.g., it was disabled on build. return } if version == unknownVersion { // This will set the version on go install ... version = info.Main.Version } var rev, time, arch, os string dirty := false for _, kv := range info.Settings { switch kv.Key { case "vcs.revision": rev = kv.Value case "vcs.time": time = kv.Value case "vcs.modified": dirty = kv.Value == "true" case "GOARCH": arch = kv.Value case "GOOS": os = kv.Value } } bi := "" if len(rev) >= 8 { bi += rev[:8] if dirty { bi += "-modified" } bi += ", " } if time != "" { bi += time + ", " } bi += os + "-" + arch version += " (" + bi + ")" // Ensure the API client version which gets used in the User-Agent matches // our version. In theory these could otherwise be out of sync. vars.Version = version } geoipupdate-6.1.0/conf/000077500000000000000000000000001454734621200147615ustar00rootroot00000000000000geoipupdate-6.1.0/conf/GeoIP.conf.default000066400000000000000000000035361454734621200202250ustar00rootroot00000000000000# Please see https://dev.maxmind.com/geoip/updating-databases?lang=en 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 # The number of parallel database downloads. # Defaults to "1". # Parallelism 1 geoipupdate-6.1.0/dev-bin/000077500000000000000000000000001454734621200153605ustar00rootroot00000000000000geoipupdate-6.1.0/dev-bin/make-man-pages.pl000077500000000000000000000022501454734621200205020ustar00rootroot00000000000000#!/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-6.1.0/dev-bin/release.sh000077500000000000000000000024021454734621200173350ustar00rootroot00000000000000#!/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" perl -pi -e "s/(?<=Version = \").+?(?=\")/$version/g" pkg/geoipupdate/vars/version.go echo $'\nRelease notes:' echo "$notes" read -p "Continue? (y/n) " ok if [ "$ok" != "y" ]; then echo "Aborting" exit 1 fi if [ -n "$(git status --porcelain)" ]; then git commit -m "Update for $tag" -a fi git push echo "Creating tag $tag" message="$version $notes" git tag -a -m "$message" "$tag" git push # goreleaser's `--clean' should clear out `dist', but it didn't work for me. rm -rf dist goreleaser release --clean -f .goreleaser.yml --release-notes <(echo "$notes") geoipupdate-6.1.0/doc/000077500000000000000000000000001454734621200146015ustar00rootroot00000000000000geoipupdate-6.1.0/doc/GeoIP.conf.md000066400000000000000000000067201454734621200170170ustar00rootroot00000000000000# NAME GeoIP.conf - Configuration file for geoipupdate # SYNOPSIS This file allows you to configure your `geoipupdate` program to download GeoIP2 and GeoLite2 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`. This can be overridden at run time by either the `GEOIPUPDATE_ACCOUNT_ID` or the `GEOIPUPDATE_ACCOUNT_ID_FILE` environment variables. `LicenseKey` : Your case-sensitive MaxMind license key. This can be overridden at run time by either the `GEOIPUPDATE_LICENSE_KEY` or `GEOIPUPDATE_LICENSE_KEY_FILE` environment variables. `EditionIDs` : List of space-separated database edition IDs. Edition IDs may consist of letters, digits, and dashes. For example, `GeoIP2-City` would download the GeoIP2 City database (`GeoIP2-City`). This can be overridden at run time by the `GEOIPUPDATE_EDITION_IDS` environment variable. 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 `GEOIPUPDATE_DB_DIR` environment variable or the `-d` command line argument. `Host` : The host name of the server to use. The default is `updates.maxmind.com`. This can be overridden at run time by the `GEOIPUPDATE_HOST` environment variable. `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. This can be overridden at run time by the `GEOIPUPDATE_PROXY` environment variable. `ProxyUserPassword` : The proxy user name and password, separated by a colon. For instance, `username:password`. This can be overridden at run time by the `GEOIPUPDATE_PROXY_USER_PASSWORD` environment variable. `PreserveFileTimes` : Whether to preserve modification times of files downloaded from the server. This option is either `0` or `1`. The default is `0`. This can be overridden at run time by the `GEOIPUPDATE_PRESERVE_FILE_TIMES` environment variable. `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`. This can be overridden at run time by the `GEOIPUPDATE_LOCK_FILE` environment variable. `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). This can be overridden at run time by the `GEOIPUPDATE_RETRY_FOR` environment variable. `Parallelism` : The maximum number of parallel database downloads. The default is 1, which means that databases will be downloaded sequentially. This can be overridden at run time by the `GEOIPUPDATE_PARALLELISM` environment variable or the `--parallelism` command line argument. ## Deprecated settings: The following are deprecated and will be ignored if present: `Protocol` `SkipPeerVerification` `SkipHostnameVerification` # SEE ALSO `geoipupdate`(1) geoipupdate-6.1.0/doc/docker.md000066400000000000000000000101471454734621200163750ustar00rootroot00000000000000# Docker ## Image information The image is available on [ghcr.io](https://github.com/maxmind/geoipupdate/pkgs/container/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_EDITION_IDS` - List of space-separated database edition IDs. Edition IDs may consist of letters, digits, and dashes. For example, `GeoIP2-City` would download the GeoIP2 City database (`GeoIP2-City`). One of: * `GEOIPUPDATE_ACCOUNT_ID` - Your MaxMind account ID. * `GEOIPUPDATE_ACCOUNT_ID_FILE` - A file containing your MaxMind account ID. One of: * `GEOIPUPDATE_LICENSE_KEY` - Your case-sensitive MaxMind license key. * `GEOIPUPDATE_LICENSE_KEY_FILE` - A file containing your case-sensitive MaxMind license key. 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. * `GEOIPUPDATE_CONF_FILE` - The path of a configuration file to be used by `geoipupdate`. * `GEOIPUPDATE_DB_DIR` - The directory where geoipupdate will download the databases. The default is `/usr/share/GeoIP`. 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: ```sh docker run --env-file -v :/usr/share/GeoIP ghcr.io/maxmind/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: ```yaml version: '3' services: geoipupdate: container_name: geoipupdate image: ghcr.io/maxmind/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 ``` You may also pass your MaxMind account ID and license key as secrets, for example: ```yaml version: '3' services: geoipupdate: container_name: geoipupdate image: ghcr.io/maxmind/geoipupdate restart: unless-stopped environment: - 'GEOIPUPDATE_ACCOUNT_ID_FILE=/run/secrets/GEOIPUPDATE_ACCOUNT_ID' - 'GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY' - 'GEOIPUPDATE_EDITION_IDS=GeoLite2-ASN GeoLite2-City GeoLite2-Country' - GEOIPUPDATE_FREQUENCY=72 networks: - geoipupdate volumes: - 'geoipupdate_data:/usr/share/GeoIP' secrets: - GEOIPUPDATE_ACCOUNT_ID - GEOIPUPDATE_LICENSE_KEY networks: geoipupdate: volumes: geoipupdate_data: driver: local secrets: GEOIPUPDATE_ACCOUNT_ID: file: ./secrets/GEOIPUPDATE_ACCOUNT_ID.txt GEOIPUPDATE_LICENSE_KEY: file: ./secrets/GEOIPUPDATE_LICENSE_KEY.txt ``` 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-6.1.0/doc/geoipupdate.md000066400000000000000000000047061454734621200174400ustar00rootroot00000000000000# NAME geoipupdate - GeoIP2 and GeoLite2 Update Program # SYNOPSIS **geoipupdate** [-Vvh] [-f *CONFIG_FILE*] [-d *TARGET_DIRECTORY*] # DESCRIPTION `geoipupdate` automatically updates GeoIP2 and GeoLite2 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 the `DatabaseDirectory` value from the configuration file and the `GEOIPUPDATE_DB_DIR` environment variable. `-f`, `--config-file` : The configuration file to use. See `GeoIP.conf` and its documentation for more information. This is optional. It defaults to the environment variable `GEOIPUPDATE_CONF_FILE` if it is set, or CONFFILE otherwise. `--parallelism` : Set the number of parallel database downloads. `-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. If provided, it overrides any `GEOIPUPDATE_VERBOSE` environment variable. `-o`, `--output` : Output download/update results in JSON format. # 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. Alternatively, set the `GEOIPUPDATE_PROXY` or `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-2023 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 databases or to sign up for a subscription. # SEE ALSO `GeoIP.conf`(5) geoipupdate-6.1.0/docker/000077500000000000000000000000001454734621200153035ustar00rootroot00000000000000geoipupdate-6.1.0/docker/Dockerfile000066400000000000000000000005251454734621200172770ustar00rootroot00000000000000FROM alpine:3 RUN apk update && apk add jq COPY geoipupdate /usr/bin/geoipupdate COPY docker/entry.sh /usr/bin/entry.sh COPY docker/healthcheck.sh /usr/bin/healthcheck.sh ENTRYPOINT ["/usr/bin/entry.sh"] HEALTHCHECK --interval=10s --timeout=10s CMD [ "/usr/bin/healthcheck.sh" ] VOLUME [ "/usr/share/GeoIP" ] WORKDIR /var/lib/geoipupdate geoipupdate-6.1.0/docker/entry.sh000077500000000000000000000024701454734621200170060ustar00rootroot00000000000000#!/bin/sh set -e # SIGTERM-handler term_handler() { if [ $pid -ne 0 ]; then kill -SIGTERM "$pid" wait "$pid" fi exit 143; # 128 + 15 -- SIGTERM } trap 'kill ${!}; term_handler' SIGTERM pid=0 database_dir=/usr/share/GeoIP log_dir="/tmp/geoipupdate" log_file="$log_dir/.healthcheck" flags="--output" frequency=$((GEOIPUPDATE_FREQUENCY * 60 * 60)) export GEOIPUPDATE_CONF_FILE="" if [ -z "$GEOIPUPDATE_DB_DIR" ]; then export GEOIPUPDATE_DB_DIR="$database_dir" fi if [ -z "$GEOIPUPDATE_ACCOUNT_ID" ] && [ -z "$GEOIPUPDATE_ACCOUNT_ID_FILE" ]; then echo "ERROR: You must set the environment variable GEOIPUPDATE_ACCOUNT_ID or GEOIPUPDATE_ACCOUNT_ID_FILE!" exit 1 fi if [ -z "$GEOIPUPDATE_LICENSE_KEY" ] && [ -z "$GEOIPUPDATE_LICENSE_KEY_FILE" ]; then echo "ERROR: You must set the environment variable GEOIPUPDATE_LICENSE_KEY or GEOIPUPDATE_LICENSE_KEY_FILE!" exit 1 fi if [ -z "$GEOIPUPDATE_EDITION_IDS" ]; then echo "ERROR: You must set the environment variable GEOIPUPDATE_EDITION_IDS!" exit 1 fi mkdir -p $log_dir while true; do echo "# STATE: Running geoipupdate" /usr/bin/geoipupdate $flags 1>$log_file if [ "$frequency" -eq 0 ]; then break fi echo "# STATE: Sleeping for $GEOIPUPDATE_FREQUENCY hours" sleep "$frequency" & pid=$! wait $! done geoipupdate-6.1.0/docker/healthcheck.sh000077500000000000000000000012531454734621200201060ustar00rootroot00000000000000#!/bin/sh set -e # 2 minutes are added to the update frequency threshold to make room for slower starts. cutoff_duration=$(($GEOIPUPDATE_FREQUENCY * 60 * 60 + 120)) current_time=$(date +%s) cutoff_date=$(($current_time - $cutoff_duration)) log_file="/tmp/geoipupdate/.healthcheck" editions=$(cat "$log_file" | jq -r '.[] | select(.checked_at > '$cutoff_date') | .edition_id') checked_editions=$(echo "$editions" | wc -l) desired_editions=$(echo "$GEOIPUPDATE_EDITION_IDS" | awk -F' ' '{print NF}') if [ "$checked_editions" != "$desired_editions" ]; then echo "healtcheck editions number $checked_editions is less than the desired editions number $desired_editions" exit 1 fi geoipupdate-6.1.0/go.mod000066400000000000000000000011171454734621200151420ustar00rootroot00000000000000module github.com/maxmind/geoipupdate/v6 go 1.20 require ( github.com/cenkalti/backoff/v4 v4.2.1 github.com/gofrs/flock v0.8.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 golang.org/x/sync v0.6.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.16.0 // indirect gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) geoipupdate-6.1.0/go.sum000066400000000000000000000045731454734621200152000ustar00rootroot00000000000000github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/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/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= geoipupdate-6.1.0/pkg/000077500000000000000000000000001454734621200146155ustar00rootroot00000000000000geoipupdate-6.1.0/pkg/geoipupdate/000077500000000000000000000000001454734621200171235ustar00rootroot00000000000000geoipupdate-6.1.0/pkg/geoipupdate/config.go000066400000000000000000000306621454734621200207260ustar00rootroot00000000000000package geoipupdate import ( "bufio" "errors" "fmt" "net/url" "os" "path/filepath" "regexp" "strconv" "strings" "time" "github.com/maxmind/geoipupdate/v6/pkg/geoipupdate/vars" ) // Config is a parsed configuration file. type Config struct { // AccountID is the account ID. AccountID int // confFile is the path to any configuration file used when // potentially populating Config fields. configFile string // DatabaseDirectory is where database files are going to be // stored. DatabaseDirectory string // EditionIDs are the database editions to be updated. EditionIDs []string // LicenseKey is the license attached to the account. LicenseKey string // LockFile is the path of a lock file that ensures that only one // geoipupdate process can run at a time. LockFile string // PreserveFileTimes sets whether database modification times // are preserved across downloads. PreserveFileTimes bool // Parallelism defines the number of concurrent downloads that // can be triggered at the same time. It defaults to 1, which // wouldn't change the existing behaviour of downloading files // sequentially. Parallelism int // Proxy is host name or IP address of a proxy server. Proxy *url.URL // proxyURL is the host value of Proxy proxyURL string // proxyUserInfo is the userinfo value of Proxy proxyUserInfo string // RetryFor is the retry timeout for HTTP requests. It defaults // to 5 minutes. RetryFor time.Duration // URL points to maxmind servers. URL string // Verbose turns on debug statements. Verbose bool // Output turns on sending the download/update result to stdout as JSON. Output bool } // Option is a function type that modifies a configuration object. // It is used to define functions that override a config with // values set as command line arguments. type Option func(f *Config) error // WithParallelism returns an Option that sets the Parallelism // value of a config. func WithParallelism(i int) Option { return func(c *Config) error { if i < 0 { return fmt.Errorf("parallelism can't be negative, got '%d'", i) } if i > 0 { c.Parallelism = i } return nil } } // WithDatabaseDirectory returns an Option that sets the DatabaseDirectory // value of a config. func WithDatabaseDirectory(dir string) Option { return func(c *Config) error { if dir != "" { c.DatabaseDirectory = filepath.Clean(dir) } return nil } } // WithVerbose returns an Option that sets the Verbose // value of a config. func WithVerbose(val bool) Option { return func(c *Config) error { c.Verbose = val return nil } } // WithOutput returns an Option that sets the Output // value of a config. func WithOutput(val bool) Option { return func(c *Config) error { c.Output = val return nil } } // WithConfigFile returns an Option that sets the configuration // file to be used. func WithConfigFile(file string) Option { return func(c *Config) error { if file != "" { c.configFile = filepath.Clean(file) } return nil } } // NewConfig creates a new configuration and populates it based on an optional // config file pointed to by an option set with WithConfigFile, then by various // environment variables, and then finally by flag overrides provided by // flagOptions. Values from the later override the former. func NewConfig( flagOptions ...Option, ) (*Config, error) { // config defaults config := &Config{ URL: "https://updates.maxmind.com", DatabaseDirectory: filepath.Clean(vars.DefaultDatabaseDirectory), RetryFor: 5 * time.Minute, Parallelism: 1, } // Potentially populate config.configFilePath. We will rerun this function // again later to ensure the flag values override env variables. err := setConfigFromFlags(config, flagOptions...) if err != nil { return nil, err } // Override config with values from the config file. if confFile := config.configFile; confFile != "" { err = setConfigFromFile(config, confFile) if err != nil { return nil, err } } // Override config with values from environment variables. err = setConfigFromEnv(config) if err != nil { return nil, err } // Override config with values from option flags. err = setConfigFromFlags(config, flagOptions...) if err != nil { return nil, err } // Set config values that depend on other config values. For instance // proxyURL may have been set by the default config, and proxyUserInfo // by config file. Both of these values need to be combined to create // the public Proxy field that is a *url.URL. config.Proxy, err = parseProxy(config.proxyURL, config.proxyUserInfo) if err != nil { return nil, err } if config.LockFile == "" { config.LockFile = filepath.Join(config.DatabaseDirectory, ".geoipupdate.lock") } // Validate config values now that all config sources have been considered and // any value that may need to be created from other values has been set. err = validateConfig(config) if err != nil { return nil, err } // Reset values that were only needed to communicate information between // config overrides. config.configFile = "" config.proxyURL = "" config.proxyUserInfo = "" return config, nil } // setConfigFromFile sets Config fields based on the configuration file. func setConfigFromFile(config *Config, path string) error { fh, err := os.Open(filepath.Clean(path)) if err != nil { return fmt.Errorf("error opening file: %w", err) } defer fh.Close() scanner := bufio.NewScanner(fh) lineNumber := 0 keysSeen := map[string]struct{}{} for scanner.Scan() { lineNumber++ line := strings.TrimSpace(scanner.Text()) if line == "" || line[0] == '#' { continue } fields := strings.Fields(line) if len(fields) < 2 { return fmt.Errorf("invalid format on line %d", lineNumber) } key := fields[0] value := strings.Join(fields[1:], " ") if _, ok := keysSeen[key]; ok { return fmt.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 fmt.Errorf("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": config.URL = "https://" + value case "LicenseKey": config.LicenseKey = value case "LockFile": config.LockFile = filepath.Clean(value) case "PreserveFileTimes": if value != "0" && value != "1" { return errors.New("`PreserveFileTimes' must be 0 or 1") } config.PreserveFileTimes = value == "1" case "Proxy": config.proxyURL = value case "ProxyUserPassword": config.proxyUserInfo = value case "Protocol", "SkipHostnameVerification", "SkipPeerVerification": // Deprecated. case "RetryFor": dur, err := time.ParseDuration(value) if err != nil || dur < 0 { return fmt.Errorf("'%s' is not a valid duration", value) } config.RetryFor = dur case "Parallelism": parallelism, err := strconv.Atoi(value) if err != nil { return fmt.Errorf("'%s' is not a valid parallelism value: %w", value, err) } if parallelism <= 0 { return fmt.Errorf("parallelism should be greater than 0, got '%d'", parallelism) } config.Parallelism = parallelism default: return fmt.Errorf("unknown option on line %d", lineNumber) } } if err := scanner.Err(); err != nil { return fmt.Errorf("reading file: %w", err) } return nil } // setConfigFromEnv sets Config fields based on environment variables. func setConfigFromEnv(config *Config) error { if value, ok := os.LookupEnv("GEOIPUPDATE_ACCOUNT_ID"); ok { var err error config.AccountID, err = strconv.Atoi(value) if err != nil { return fmt.Errorf("invalid account ID format") } } if value := os.Getenv("GEOIPUPDATE_ACCOUNT_ID_FILE"); value != "" { var err error accountID, err := os.ReadFile(filepath.Clean(value)) if err != nil { return fmt.Errorf("failed to open GEOIPUPDATE_ACCOUNT_ID_FILE: %w", err) } config.AccountID, err = strconv.Atoi(strings.TrimSpace(string(accountID))) if err != nil { return fmt.Errorf("invalid account ID format") } } if value, ok := os.LookupEnv("GEOIPUPDATE_DB_DIR"); ok { config.DatabaseDirectory = value } if value, ok := os.LookupEnv("GEOIPUPDATE_EDITION_IDS"); ok { config.EditionIDs = strings.Fields(value) } if value, ok := os.LookupEnv("GEOIPUPDATE_HOST"); ok { config.URL = "https://" + value } if value, ok := os.LookupEnv("GEOIPUPDATE_LICENSE_KEY"); ok { config.LicenseKey = value } if value := os.Getenv("GEOIPUPDATE_LICENSE_KEY_FILE"); value != "" { var err error licenseKey, err := os.ReadFile(filepath.Clean(value)) if err != nil { return fmt.Errorf("failed to open GEOIPUPDATE_LICENSE_KEY_FILE: %w", err) } config.LicenseKey = strings.TrimSpace(string(licenseKey)) } if value, ok := os.LookupEnv("GEOIPUPDATE_LOCK_FILE"); ok { config.LockFile = value } if value, ok := os.LookupEnv("GEOIPUPDATE_PARALLELISM"); ok { parallelism, err := strconv.Atoi(value) if err != nil { return fmt.Errorf("'%s' is not a valid parallelism value: %w", value, err) } if parallelism <= 0 { return fmt.Errorf("parallelism should be greater than 0, got '%d'", parallelism) } config.Parallelism = parallelism } if value, ok := os.LookupEnv("GEOIPUPDATE_PRESERVE_FILE_TIMES"); ok { if value != "0" && value != "1" { return errors.New("`PreserveFileTimes' must be 0 or 1") } config.PreserveFileTimes = value == "1" } if value, ok := os.LookupEnv("GEOIPUPDATE_PROXY"); ok { config.proxyURL = value } if value, ok := os.LookupEnv("GEOIPUPDATE_PROXY_USER_PASSWORD"); ok { config.proxyUserInfo = value } if value, ok := os.LookupEnv("GEOIPUPDATE_RETRY_FOR"); ok { dur, err := time.ParseDuration(value) if err != nil || dur < 0 { return fmt.Errorf("'%s' is not a valid duration", value) } config.RetryFor = dur } if value, ok := os.LookupEnv("GEOIPUPDATE_VERBOSE"); ok { config.Verbose = value != "" } return nil } // setConfigFromFlags sets Config fields based on option flags. func setConfigFromFlags(config *Config, flagOptions ...Option) error { for _, option := range flagOptions { if err := option(config); err != nil { return fmt.Errorf("error applying flag to config: %w", err) } } return nil } func validateConfig(config *Config) error { // 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 errors.New("geoipupdate requires a valid AccountID and LicenseKey combination") } if len(config.EditionIDs) == 0 { return fmt.Errorf("the `EditionIDs` option is required") } if config.AccountID == 0 { return fmt.Errorf("the `AccountID` option is required") } if config.LicenseKey == "" { return fmt.Errorf("the `LicenseKey` option is required") } return 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 } proxyURL := proxy // If no scheme is provided, use http. matches := schemeRE.FindStringSubmatch(proxyURL) if matches == nil { proxyURL = "http://" + proxyURL } else { scheme := strings.ToLower(matches[1]) // The http package only supports http, https, and socks5. if scheme != "http" && scheme != "https" && scheme != "socks5" { return nil, fmt.Errorf("unsupported proxy type: %s", scheme) } } // Now that we have a scheme, we should be able to parse. u, err := url.Parse(proxyURL) if err != nil { return nil, fmt.Errorf("parsing proxy URL: %w", err) } 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-6.1.0/pkg/geoipupdate/config_test.go000066400000000000000000000724671454734621200217760ustar00rootroot00000000000000package geoipupdate import ( "fmt" "net/url" "os" "path/filepath" "strings" "testing" "time" "github.com/maxmind/geoipupdate/v6/pkg/geoipupdate/vars" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewConfig(t *testing.T) { tests := []struct { Description string Input string Env map[string]string Flags []Option Output *Config Err string }{ { Description: "Default config", Input: `# Please see https://dev.maxmind.com/geoip/updating-databases?lang=en 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 # The number of parallel database downloads. # Defaults to "1". # Parallelism 1 `, Output: &Config{ AccountID: 42, LicenseKey: "000000000001", DatabaseDirectory: filepath.Clean(vars.DefaultDatabaseDirectory), EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, LockFile: filepath.Clean(filepath.Join(vars.DefaultDatabaseDirectory, ".geoipupdate.lock")), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, Parallelism: 1, }, }, { Description: "Default config, old names", Input: `# Please see https://dev.maxmind.com/geoip/updating-databases?lang=en 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(vars.DefaultDatabaseDirectory), EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, LockFile: filepath.Clean(filepath.Join(vars.DefaultDatabaseDirectory, ".geoipupdate.lock")), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, Parallelism: 1, }, }, { Description: "Everything populated", Input: `# Please see https://dev.maxmind.com/geoip/updating-databases?lang=en 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 Parallelism 3 `, Output: &Config{ AccountID: 1234, DatabaseDirectory: filepath.Clean("/home"), 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", }, proxyURL: "", proxyUserInfo: "", PreserveFileTimes: true, URL: "https://updates.example.com", RetryFor: 10 * time.Minute, Parallelism: 3, }, }, { 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`, }, { 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: "Parallelism should be a number", Input: `AccountID 42 LicenseKey 000000000001 Parallelism a`, Err: "'a' is not a valid parallelism value: strconv.Atoi: parsing \"a\": invalid syntax", }, { Description: "Parallelism should be a positive number", Input: `AccountID 42 LicenseKey 000000000001 Parallelism 0`, Err: "parallelism should be greater than 0, got '0'", }, { Description: "Parallelism overridden by flag", Input: `AccountID 999999 LicenseKey abcd EditionIDs GeoIP2-City Parallelism 2`, Flags: []Option{WithParallelism(4)}, Output: &Config{ AccountID: 999999, DatabaseDirectory: filepath.Clean(vars.DefaultDatabaseDirectory), EditionIDs: []string{"GeoIP2-City"}, LicenseKey: "abcd", LockFile: filepath.Clean(filepath.Join(vars.DefaultDatabaseDirectory, ".geoipupdate.lock")), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, Parallelism: 4, }, }, { Description: "DatabaseDirectory overridden by flag", Input: `AccountID 999999 LicenseKey abcd EditionIDs GeoIP2-City`, Flags: []Option{WithDatabaseDirectory("/tmp")}, 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, Parallelism: 1, }, }, { 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(vars.DefaultDatabaseDirectory), EditionIDs: []string{"GeoIP2-City"}, LicenseKey: "abcd", LockFile: filepath.Clean(filepath.Join(vars.DefaultDatabaseDirectory, ".geoipupdate.lock")), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, Parallelism: 1, }, }, { Description: "Deprecated options", Input: `AccountID 123 LicenseKey abcd EditionIDs GeoIP2-City Protocol http SkipHostnameVerification 1 SkipPeerVerification 1 `, Output: &Config{ AccountID: 123, DatabaseDirectory: filepath.Clean(vars.DefaultDatabaseDirectory), EditionIDs: []string{"GeoIP2-City"}, LicenseKey: "abcd", LockFile: filepath.Clean(filepath.Join(vars.DefaultDatabaseDirectory, ".geoipupdate.lock")), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, Parallelism: 1, }, }, { Description: "CRLF line ending works", Input: "AccountID 123\r\nLicenseKey 123\r\nEditionIDs GeoIP2-City\r\n", Output: &Config{ AccountID: 123, DatabaseDirectory: filepath.Clean(vars.DefaultDatabaseDirectory), EditionIDs: []string{"GeoIP2-City"}, LicenseKey: "123", LockFile: filepath.Clean(filepath.Join(vars.DefaultDatabaseDirectory, ".geoipupdate.lock")), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, Parallelism: 1, }, }, { Description: "CR line ending does not work", Input: "AccountID 0\rLicenseKey 123\rEditionIDs GeoIP2-City\r", Err: `invalid account ID format`, }, { 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(vars.DefaultDatabaseDirectory), EditionIDs: []string{"GeoLite2-City", "GeoLite2-Country"}, LicenseKey: "456", LockFile: filepath.Clean(filepath.Join(vars.DefaultDatabaseDirectory, ".geoipupdate.lock")), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, Parallelism: 1, }, }, { 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(vars.DefaultDatabaseDirectory), EditionIDs: []string{"GeoLite2-City", "GeoLite2-Country"}, LicenseKey: "456", LockFile: filepath.Clean(filepath.Join(vars.DefaultDatabaseDirectory, ".geoipupdate.lock")), URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, Parallelism: 1, }, }, { Description: "Config flags override env vars override config file", Input: "AccountID\t\t123\nLicenseKey\t\t456\nParallelism\t\t1\n", Env: map[string]string{ "GEOIPUPDATE_DB_DIR": "/tmp/db", "GEOIPUPDATE_EDITION_IDS": "GeoLite2-Country GeoLite2-City", "GEOIPUPDATE_HOST": "updates.maxmind.com", "GEOIPUPDATE_LICENSE_KEY": "000000000001", "GEOIPUPDATE_LOCK_FILE": "/tmp/lock", "GEOIPUPDATE_PARALLELISM": "2", "GEOIPUPDATE_PRESERVE_FILE_TIMES": "1", "GEOIPUPDATE_PROXY": "127.0.0.1:8888", "GEOIPUPDATE_PROXY_USER_PASSWORD": "username:password", "GEOIPUPDATE_RETRY_FOR": "1m", "GEOIPUPDATE_VERBOSE": "1", }, Flags: []Option{WithParallelism(3)}, Output: &Config{ AccountID: 123, DatabaseDirectory: "/tmp/db", EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, LicenseKey: "000000000001", LockFile: "/tmp/lock", Parallelism: 3, PreserveFileTimes: true, Proxy: &url.URL{ Scheme: "http", User: url.UserPassword("username", "password"), Host: "127.0.0.1:8888", }, RetryFor: 1 * time.Minute, URL: "https://updates.maxmind.com", Verbose: true, }, }, } for _, test := range tests { t.Run(test.Description, func(t *testing.T) { withEnvVars(t, test.Env, func() { tempName := filepath.Join(t.TempDir(), "/GeoIP-test.conf") require.NoError(t, os.WriteFile(tempName, []byte(test.Input), 0o600)) testFlags := append([]Option{WithConfigFile(tempName)}, test.Flags...) config, err := NewConfig(testFlags...) if test.Err == "" { require.NoError(t, err, test.Description) } else { require.EqualError(t, err, test.Err, test.Description) } assert.Equal(t, test.Output, config, test.Description) }) }) } } func TestSetConfigFromFile(t *testing.T) { tests := []struct { Description string Input string Expected Config Err string }{ { Description: "All config file related variables", Input: `AccountID 1 DatabaseDirectory /tmp/db EditionIDs GeoLite2-Country GeoLite2-City Host updates.maxmind.com LicenseKey 000000000001 LockFile /tmp/lock Parallelism 2 PreserveFileTimes 1 Proxy 127.0.0.1:8888 ProxyUserPassword username:password RetryFor 1m `, Expected: Config{ AccountID: 1, DatabaseDirectory: filepath.Clean("/tmp/db"), EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, LicenseKey: "000000000001", LockFile: filepath.Clean("/tmp/lock"), Parallelism: 2, PreserveFileTimes: true, proxyURL: "127.0.0.1:8888", proxyUserInfo: "username:password", RetryFor: 1 * time.Minute, URL: "https://updates.maxmind.com", }, }, { Description: "Empty config", Input: "", Expected: Config{}, }, { Description: "Invalid account ID", Input: "AccountID 1a", Err: `invalid account ID format`, }, { Description: "Invalid PreserveFileTimes", Input: "PreserveFileTimes 1a", Err: "`PreserveFileTimes' must be 0 or 1", }, { Description: "RetryFor needs a unit", Input: "RetryFor 5", Err: "'5' is not a valid duration", }, { Description: "RetryFor needs to be non-negative", Input: "RetryFor -5m", Err: "'-5m' is not a valid duration", }, { Description: "Parallelism should be a number", Input: "Parallelism a", Err: "'a' is not a valid parallelism value: strconv.Atoi: parsing \"a\": invalid syntax", }, { Description: "Parallelism should be a positive number", Input: "Parallelism 0", Err: "parallelism should be greater than 0, got '0'", }, } for _, test := range tests { t.Run(test.Description, func(t *testing.T) { tempName := filepath.Join(t.TempDir(), "/GeoIP-test.conf") require.NoError(t, os.WriteFile(tempName, []byte(test.Input), 0o600)) var config Config err := setConfigFromFile(&config, tempName) if test.Err == "" { require.NoError(t, err, test.Description) } else { require.EqualError(t, err, test.Err, test.Description) } assert.Equal(t, test.Expected, config, test.Description) }) } } func TestSetConfigFromEnv(t *testing.T) { tests := []struct { Description string AccountIDFileContents string LicenseKeyFileContents string Env map[string]string Expected Config Err string }{ { Description: "All config related environment variables", Env: map[string]string{ "GEOIPUPDATE_ACCOUNT_ID": "1", "GEOIPUPDATE_ACCOUNT_ID_FILE": "", "GEOIPUPDATE_DB_DIR": "/tmp/db", "GEOIPUPDATE_EDITION_IDS": "GeoLite2-Country GeoLite2-City", "GEOIPUPDATE_HOST": "updates.maxmind.com", "GEOIPUPDATE_LICENSE_KEY": "000000000001", "GEOIPUPDATE_LICENSE_KEY_FILE": "", "GEOIPUPDATE_LOCK_FILE": "/tmp/lock", "GEOIPUPDATE_PARALLELISM": "2", "GEOIPUPDATE_PRESERVE_FILE_TIMES": "1", "GEOIPUPDATE_PROXY": "127.0.0.1:8888", "GEOIPUPDATE_PROXY_USER_PASSWORD": "username:password", "GEOIPUPDATE_RETRY_FOR": "1m", "GEOIPUPDATE_VERBOSE": "1", }, Expected: Config{ AccountID: 1, DatabaseDirectory: "/tmp/db", EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, LicenseKey: "000000000001", LockFile: "/tmp/lock", Parallelism: 2, PreserveFileTimes: true, proxyURL: "127.0.0.1:8888", proxyUserInfo: "username:password", RetryFor: 1 * time.Minute, URL: "https://updates.maxmind.com", Verbose: true, }, }, { Description: "ACCOUNT_ID_FILE and LICENSE_KEY_FILE override", AccountIDFileContents: "2", LicenseKeyFileContents: "000000000002", Env: map[string]string{ "GEOIPUPDATE_ACCOUNT_ID": "1", "GEOIPUPDATE_ACCOUNT_ID_FILE": filepath.Join(t.TempDir(), "accountIDFile"), "GEOIPUPDATE_DB_DIR": "/tmp/db", "GEOIPUPDATE_EDITION_IDS": "GeoLite2-Country GeoLite2-City", "GEOIPUPDATE_HOST": "updates.maxmind.com", "GEOIPUPDATE_LICENSE_KEY": "000000000001", "GEOIPUPDATE_LICENSE_KEY_FILE": filepath.Join(t.TempDir(), "licenseKeyFile"), "GEOIPUPDATE_LOCK_FILE": "/tmp/lock", "GEOIPUPDATE_PARALLELISM": "2", "GEOIPUPDATE_PRESERVE_FILE_TIMES": "1", "GEOIPUPDATE_PROXY": "127.0.0.1:8888", "GEOIPUPDATE_PROXY_USER_PASSWORD": "username:password", "GEOIPUPDATE_RETRY_FOR": "1m", "GEOIPUPDATE_VERBOSE": "1", }, Expected: Config{ AccountID: 2, DatabaseDirectory: "/tmp/db", EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, LicenseKey: "000000000002", LockFile: "/tmp/lock", Parallelism: 2, PreserveFileTimes: true, proxyURL: "127.0.0.1:8888", proxyUserInfo: "username:password", RetryFor: 1 * time.Minute, URL: "https://updates.maxmind.com", Verbose: true, }, }, { Description: "Clean up ACCOUNT_ID_FILE and LICENSE_KEY_FILE", AccountIDFileContents: "\n\n2\t\n", LicenseKeyFileContents: "\n000000000002\t\n\n", Env: map[string]string{ "GEOIPUPDATE_ACCOUNT_ID": "1", "GEOIPUPDATE_ACCOUNT_ID_FILE": filepath.Join(t.TempDir(), "accountIDFile"), "GEOIPUPDATE_DB_DIR": "/tmp/db", "GEOIPUPDATE_EDITION_IDS": "GeoLite2-Country GeoLite2-City", "GEOIPUPDATE_HOST": "updates.maxmind.com", "GEOIPUPDATE_LICENSE_KEY": "000000000001", "GEOIPUPDATE_LICENSE_KEY_FILE": filepath.Join(t.TempDir(), "licenseKeyFile"), "GEOIPUPDATE_LOCK_FILE": "/tmp/lock", "GEOIPUPDATE_PARALLELISM": "2", "GEOIPUPDATE_PRESERVE_FILE_TIMES": "1", "GEOIPUPDATE_PROXY": "127.0.0.1:8888", "GEOIPUPDATE_PROXY_USER_PASSWORD": "username:password", "GEOIPUPDATE_RETRY_FOR": "1m", "GEOIPUPDATE_VERBOSE": "1", }, Expected: Config{ AccountID: 2, DatabaseDirectory: "/tmp/db", EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, LicenseKey: "000000000002", LockFile: "/tmp/lock", Parallelism: 2, PreserveFileTimes: true, proxyURL: "127.0.0.1:8888", proxyUserInfo: "username:password", RetryFor: time.Minute, URL: "https://updates.maxmind.com", Verbose: true, }, }, { Description: "Empty config", Env: map[string]string{}, Expected: Config{}, }, { Description: "Invalid account ID", Env: map[string]string{ "GEOIPUPDATE_ACCOUNT_ID": "1a", }, Err: `invalid account ID format`, }, { Description: "Invalid PreserveFileTimes", Env: map[string]string{ "GEOIPUPDATE_PRESERVE_FILE_TIMES": "1a", }, Err: "`PreserveFileTimes' must be 0 or 1", }, { Description: "RetryFor needs a unit", Env: map[string]string{ "GEOIPUPDATE_RETRY_FOR": "5", }, Err: "'5' is not a valid duration", }, { Description: "RetryFor needs to be non-negative", Env: map[string]string{ "GEOIPUPDATE_RETRY_FOR": "-5m", }, Err: "'-5m' is not a valid duration", }, { Description: "Parallelism should be a number", Env: map[string]string{ "GEOIPUPDATE_PARALLELISM": "a", }, Err: "'a' is not a valid parallelism value: strconv.Atoi: parsing \"a\": invalid syntax", }, { Description: "Parallelism should be a positive number", Env: map[string]string{ "GEOIPUPDATE_PARALLELISM": "0", }, Err: "parallelism should be greater than 0, got '0'", }, } for _, test := range tests { t.Run(test.Description, func(t *testing.T) { accountIDFile := test.Env["GEOIPUPDATE_ACCOUNT_ID_FILE"] licenseKeyFile := test.Env["GEOIPUPDATE_LICENSE_KEY_FILE"] if test.AccountIDFileContents != "" { require.NoError(t, os.WriteFile(accountIDFile, []byte(test.AccountIDFileContents), 0o600)) } if test.LicenseKeyFileContents != "" { require.NoError(t, os.WriteFile(licenseKeyFile, []byte(test.LicenseKeyFileContents), 0o600)) } withEnvVars(t, test.Env, func() { var config Config err := setConfigFromEnv(&config) if test.Err == "" { require.NoError(t, err, test.Description) } else { require.EqualError(t, err, test.Err, test.Description) } assert.Equal(t, test.Expected, config, test.Description) }) }) } } func TestSetConfigFromFlags(t *testing.T) { tests := []struct { Description string Flags []Option Expected Config Err string }{ { Description: "All option flag related config set", Flags: []Option{ WithDatabaseDirectory("/tmp/db"), WithOutput(true), WithParallelism(2), WithVerbose(true), }, Expected: Config{ DatabaseDirectory: filepath.Clean("/tmp/db"), Output: true, Parallelism: 2, Verbose: true, }, }, { Description: "Empty config", Flags: []Option{}, Expected: Config{}, }, { Description: "Parallelism should be a positive number", Flags: []Option{WithParallelism(-1)}, Err: "error applying flag to config: parallelism can't be negative, got '-1'", }, } for _, test := range tests { t.Run(test.Description, func(t *testing.T) { var config Config err := setConfigFromFlags(&config, test.Flags...) if test.Err == "" { require.NoError(t, err, test.Description) } else { require.EqualError(t, err, test.Err, test.Description) } assert.Equal(t, test.Expected, config, test.Description) }) } } func TestValidateConfig(t *testing.T) { tests := []struct { Description string Config Config Err string }{ { Description: "Basic config", Config: Config{ AccountID: 42, LicenseKey: "000000000001", DatabaseDirectory: "/tmp/db", EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, LockFile: "/tmp/lock", URL: "https://updates.maxmind.com", RetryFor: 5 * time.Minute, Parallelism: 1, }, Err: "", }, { Description: "EditionIDs required", Config: Config{}, Err: "the `EditionIDs` option is required", }, { Description: "AccountID required", Config: Config{ EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, }, Err: "the `AccountID` option is required", }, { Description: "LicenseKey required", Config: Config{ AccountID: 42, EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, }, Err: "the `LicenseKey` option is required", }, { Description: "Valid AccountID + LicenseKey combination", Config: Config{ AccountID: 999999, LicenseKey: "000000000000", }, Err: "geoipupdate requires a valid AccountID and LicenseKey combination", }, } for _, test := range tests { t.Run(test.Description, func(t *testing.T) { config := test.Config err := validateConfig(&config) if test.Err == "" { require.NoError(t, err, test.Description) } else { require.EqualError(t, err, test.Err, 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 != "" { require.EqualError(t, err, test.Err) assert.Nil(t, output) } else { require.NoError(t, err) assert.Equal(t, test.Output, output.String()) } }, ) } } func withEnvVars(t *testing.T, newEnvVars map[string]string, f func()) { origEnv := os.Environ() for key, val := range newEnvVars { err := os.Setenv(key, val) require.NoError(t, err) } // Execute the test f() // Clean the environment os.Clearenv() // Reset the original environment variables for _, pair := range origEnv { parts := strings.SplitN(pair, "=", 2) err := os.Setenv(parts[0], parts[1]) require.NoError(t, err) } } geoipupdate-6.1.0/pkg/geoipupdate/database/000077500000000000000000000000001454734621200206675ustar00rootroot00000000000000geoipupdate-6.1.0/pkg/geoipupdate/database/http_reader.go000066400000000000000000000123531454734621200235230ustar00rootroot00000000000000// Package database provides an abstraction over getting and writing a // database file. package database import ( "compress/gzip" "context" "errors" "fmt" "io" "log" "net/http" "net/url" "strconv" "time" "github.com/cenkalti/backoff/v4" "github.com/maxmind/geoipupdate/v6/pkg/geoipupdate/internal" "github.com/maxmind/geoipupdate/v6/pkg/geoipupdate/vars" ) const urlFormat = "%s/geoip/databases/%s/update?db_md5=%s" // HTTPReader is a Reader that uses an HTTP client to retrieve // databases. type HTTPReader struct { // client is an http client responsible of fetching database updates. client *http.Client // path is the request path. path string // accountID is used for request auth. accountID int // licenseKey is used for request auth. licenseKey string // retryFor sets the timeout for when a request can no longuer be retried. retryFor time.Duration // verbose turns on/off debug logs. verbose bool } // NewHTTPReader creates a Reader that downloads database updates via // HTTP. func NewHTTPReader( proxy *url.URL, path string, accountID int, licenseKey string, retryFor time.Duration, verbose bool, ) Reader { transport := http.DefaultTransport if proxy != nil { proxyFunc := http.ProxyURL(proxy) transport.(*http.Transport).Proxy = proxyFunc } return &HTTPReader{ client: &http.Client{Transport: transport}, path: path, accountID: accountID, licenseKey: licenseKey, retryFor: retryFor, verbose: verbose, } } // Read attempts to fetch database updates for a specific editionID. // It takes an editionID and it's previously downloaded hash if available // as arguments and returns a ReadResult struct as a response. // It's the responsibility of the Writer to close the io.ReadCloser // included in the response after consumption. func (r *HTTPReader) Read(ctx context.Context, editionID, hash string) (*ReadResult, error) { var result *ReadResult var err error // RetryFor value of 0 means that no retries should be performed. // Max zero retries has to be set to achieve that // because the backoff never stops if MaxElapsedTime is zero. exp := backoff.NewExponentialBackOff() exp.MaxElapsedTime = r.retryFor b := backoff.BackOff(exp) if exp.MaxElapsedTime == 0 { b = backoff.WithMaxRetries(exp, 0) } err = backoff.RetryNotify( func() error { result, err = r.get(ctx, editionID, hash) if err == nil { return nil } var httpErr internal.HTTPError if errors.As(err, &httpErr) && httpErr.StatusCode >= 400 && httpErr.StatusCode < 500 { return backoff.Permanent(err) } return err }, b, func(err error, d time.Duration) { if r.verbose { log.Printf("Couldn't download %s, retrying in %v: %v", editionID, d, err) } }, ) if err != nil { return nil, fmt.Errorf("getting update for %s: %w", editionID, err) } return result, nil } // get makes an http request to fetch updates for a specific editionID if any. func (r *HTTPReader) get( ctx context.Context, editionID string, hash string, ) (result *ReadResult, err error) { requestURL := fmt.Sprintf( urlFormat, r.path, url.PathEscape(editionID), url.QueryEscape(hash), ) if r.verbose { log.Printf("Requesting updates for %s: %s", editionID, requestURL) } req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil) if err != nil { return nil, fmt.Errorf("creating request: %w", err) } req.Header.Add("User-Agent", "geoipupdate/"+vars.Version) req.SetBasicAuth(strconv.Itoa(r.accountID), r.licenseKey) response, err := r.client.Do(req) if err != nil { return nil, fmt.Errorf("performing HTTP request: %w", err) } // It is safe to close the response body reader as it wouldn't be // consumed in case this function returns an error. defer func() { if err != nil { response.Body.Close() } }() switch response.StatusCode { case http.StatusNotModified: if r.verbose { log.Printf("No new updates available for %s", editionID) } return &ReadResult{EditionID: editionID, OldHash: hash, NewHash: hash}, nil case http.StatusOK: default: //nolint:errcheck // we are already returning an error. buf, _ := io.ReadAll(io.LimitReader(response.Body, 256)) httpErr := internal.HTTPError{ Body: string(buf), StatusCode: response.StatusCode, } return nil, fmt.Errorf("unexpected HTTP status code: %w", httpErr) } newHash := response.Header.Get("X-Database-MD5") if newHash == "" { return nil, errors.New("no X-Database-MD5 header found") } modifiedAt, err := parseTime(response.Header.Get("Last-Modified")) if err != nil { return nil, fmt.Errorf("reading Last-Modified header: %w", err) } gzReader, err := gzip.NewReader(response.Body) if err != nil { return nil, fmt.Errorf("encountered an error creating GZIP reader: %w", err) } if r.verbose { log.Printf("Updates available for %s", editionID) } return &ReadResult{ reader: gzReader, EditionID: editionID, OldHash: hash, NewHash: newHash, ModifiedAt: modifiedAt, }, nil } // parseTime parses a string representation of a time into time.Time according to the // RFC1123 format. func parseTime(s string) (time.Time, error) { t, err := time.ParseInLocation(time.RFC1123, s, time.UTC) if err != nil { return time.Time{}, fmt.Errorf("parsing time: %w", err) } return t, nil } geoipupdate-6.1.0/pkg/geoipupdate/database/http_reader_test.go000066400000000000000000000115341454734621200245620ustar00rootroot00000000000000package database import ( "bytes" "compress/gzip" "context" "io" "net/http" "net/http/httptest" "testing" "time" "github.com/stretchr/testify/require" ) // TestHTTPReader tests the functionality of the HTTPReader.Read method. func TestHTTPReader(t *testing.T) { testTime := time.Date(2023, 4, 10, 12, 47, 31, 0, time.UTC) tests := []struct { description string checkErr func(require.TestingT, error, ...interface{}) //nolint:revive // support older versions requestEdition string requestHash string responseStatus int responseBody string responseHash string responseTime string result *ReadResult }{ { description: "success", checkErr: require.NoError, requestEdition: "GeoIP2-City", requestHash: "fbe1786bfd80e1db9dc42ddaff868f38", responseStatus: http.StatusOK, responseBody: "database content", responseHash: "cfa36ddc8279b5483a5aa25e9a6151f4", responseTime: testTime.Format(time.RFC1123), result: &ReadResult{ reader: getReader(t, "database content"), EditionID: "GeoIP2-City", OldHash: "fbe1786bfd80e1db9dc42ddaff868f38", NewHash: "cfa36ddc8279b5483a5aa25e9a6151f4", ModifiedAt: testTime, }, }, { description: "no new update", checkErr: require.NoError, requestEdition: "GeoIP2-City", requestHash: "fbe1786bfd80e1db9dc42ddaff868f38", responseStatus: http.StatusNotModified, responseBody: "", responseHash: "", responseTime: "", result: &ReadResult{ reader: nil, EditionID: "GeoIP2-City", OldHash: "fbe1786bfd80e1db9dc42ddaff868f38", NewHash: "fbe1786bfd80e1db9dc42ddaff868f38", ModifiedAt: time.Time{}, }, }, { description: "bad request", checkErr: require.Error, requestEdition: "GeoIP2-City", requestHash: "fbe1786bfd80e1db9dc42ddaff868f38", responseStatus: http.StatusBadRequest, responseBody: "", responseHash: "", responseTime: "", }, { description: "missing hash header", checkErr: require.Error, requestEdition: "GeoIP2-City", requestHash: "fbe1786bfd80e1db9dc42ddaff868f38", responseStatus: http.StatusOK, responseBody: "database content", responseHash: "", responseTime: testTime.Format(time.RFC1123), }, { description: "modified time header wrong format", checkErr: require.Error, requestEdition: "GeoIP2-City", requestHash: "fbe1786bfd80e1db9dc42ddaff868f38", responseStatus: http.StatusOK, responseBody: "database content", responseHash: "fbe1786bfd80e1db9dc42ddaff868f38", responseTime: testTime.Format(time.Kitchen), }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { server := httptest.NewServer( http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { if test.responseStatus != http.StatusOK { w.WriteHeader(test.responseStatus) return } w.Header().Set("X-Database-MD5", test.responseHash) w.Header().Set("Last-Modified", test.responseTime) buf := &bytes.Buffer{} gzWriter := gzip.NewWriter(buf) _, err := gzWriter.Write([]byte(test.responseBody)) require.NoError(t, err) require.NoError(t, gzWriter.Flush()) require.NoError(t, gzWriter.Close()) _, err = w.Write(buf.Bytes()) require.NoError(t, err) }, ), ) defer server.Close() reader := NewHTTPReader( nil, // request proxy. server.URL, // fixed, as the server is mocked above. 10, // fixed, as it's not valuable for the purpose of the test. "license", // fixed, as it's not valuable for the purpose of the test. 0, // zero means no retries. false, // verbose ) result, err := reader.Read(context.Background(), test.requestEdition, test.requestHash) test.checkErr(t, err) if err == nil { require.Equal(t, result.EditionID, test.result.EditionID) require.Equal(t, result.OldHash, test.result.OldHash) require.Equal(t, result.NewHash, test.result.NewHash) require.Equal(t, result.ModifiedAt, test.result.ModifiedAt) if test.result.reader != nil && result.reader != nil { defer result.reader.Close() defer test.result.reader.Close() resultDatabase, err := io.ReadAll(test.result.reader) require.NoError(t, err) expectedDatabase, err := io.ReadAll(result.reader) require.NoError(t, err) require.Equal(t, expectedDatabase, resultDatabase) } } }) } } //nolint:unparam // complains that it always receives the same string to encode. ridiculous. func getReader(t *testing.T, s string) io.ReadCloser { var buf bytes.Buffer gz := gzip.NewWriter(&buf) _, err := gz.Write([]byte(s)) require.NoError(t, err) require.NoError(t, gz.Close()) require.NoError(t, gz.Flush()) r, err := gzip.NewReader(&buf) require.NoError(t, err) return r } geoipupdate-6.1.0/pkg/geoipupdate/database/local_file_writer.go000066400000000000000000000155471454734621200247170ustar00rootroot00000000000000package database import ( "crypto/md5" "encoding/hex" "errors" "fmt" "hash" "io" "log" "os" "path/filepath" "strings" "time" ) const ( extension = ".mmdb" tempExtension = ".temporary" ) // LocalFileWriter is a database.Writer that stores the database to the // local file system. type LocalFileWriter struct { dir string preserveFileTime bool verbose bool } // NewLocalFileWriter create a LocalFileWriter. func NewLocalFileWriter( databaseDir string, preserveFileTime bool, verbose bool, ) (*LocalFileWriter, error) { err := os.MkdirAll(filepath.Dir(databaseDir), 0o750) if err != nil { return nil, fmt.Errorf("creating database directory: %w", err) } return &LocalFileWriter{ dir: databaseDir, preserveFileTime: preserveFileTime, verbose: verbose, }, nil } // Write writes the result struct returned by a Reader to a database file. func (w *LocalFileWriter) Write(result *ReadResult) error { // exit early if we've got the latest database version. if strings.EqualFold(result.OldHash, result.NewHash) { if w.verbose { log.Printf("Database %s up to date", result.EditionID) } return nil } defer func() { if err := result.reader.Close(); err != nil { log.Printf("closing reader for %s: %+v", result.EditionID, err) } }() databaseFilePath := w.getFilePath(result.EditionID) // write the Reader's result into a temporary file. fw, err := newFileWriter(databaseFilePath + tempExtension) if err != nil { return fmt.Errorf("setting up database writer for %s: %w", result.EditionID, err) } defer func() { if err := fw.close(); err != nil { log.Printf("closing file writer: %+v", err) } }() if err := fw.write(result.reader); err != nil { return fmt.Errorf("writing to the temp file for %s: %w", result.EditionID, err) } // make sure the hash of the temp file matches the expected hash. if err := fw.validateHash(result.NewHash); err != nil { return fmt.Errorf("validating hash for %s: %w", result.EditionID, err) } // move the temoporary database file into it's final location and // sync the directory. if err := fw.syncAndRename(databaseFilePath); err != nil { return fmt.Errorf("renaming temp file: %w", err) } // sync database directory. if err := syncDir(filepath.Dir(databaseFilePath)); err != nil { return fmt.Errorf("syncing database directory: %w", err) } // check if we need to set the file's modified at time if w.preserveFileTime { if err := setModifiedAtTime(databaseFilePath, result.ModifiedAt); err != nil { return err } } if w.verbose { log.Printf("Database %s successfully updated: %+v", result.EditionID, result.NewHash) } return nil } // GetHash returns the hash of the current database file. func (w *LocalFileWriter) GetHash(editionID string) (string, error) { databaseFilePath := w.getFilePath(editionID) //nolint:gosec // we really need to read this file. database, err := os.Open(databaseFilePath) if err != nil { if errors.Is(err, os.ErrNotExist) { if w.verbose { log.Print("Database does not exist, returning zeroed hash") } return ZeroMD5, nil } return "", fmt.Errorf("opening database: %w", err) } defer func() { if err := database.Close(); err != nil { log.Println(fmt.Errorf("closing database: %w", err)) } }() md5Hash := md5.New() if _, err := io.Copy(md5Hash, database); err != nil { return "", fmt.Errorf("calculating database hash: %w", err) } result := byteToString(md5Hash.Sum(nil)) if w.verbose { log.Printf("Calculated MD5 sum for %s: %s", databaseFilePath, result) } return result, nil } // getFilePath construct the file path for a database edition. func (w *LocalFileWriter) getFilePath(editionID string) string { return filepath.Join(w.dir, editionID) + extension } // fileWriter is used to write the content of a Reader's response // into a file. type fileWriter struct { // file is used for writing the Reader's response. file *os.File // md5Writer is used to verify the integrity of the received data. md5Writer hash.Hash } // newFileWriter initializes a new fileWriter struct. func newFileWriter(path string) (*fileWriter, error) { // prepare temp file for initial writing. //nolint:gosec // we really need to read this file. file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644) if err != nil { return nil, fmt.Errorf("creating temporary file at %s: %w", path, err) } return &fileWriter{ file: file, md5Writer: md5.New(), }, nil } // close closes and deletes the file. func (w *fileWriter) close() error { if err := w.file.Close(); err != nil { var perr *os.PathError if !errors.As(err, &perr) || !errors.Is(perr.Err, os.ErrClosed) { return fmt.Errorf("closing temporary file: %w", err) } } err := os.Remove(w.file.Name()) if err != nil && !errors.Is(err, os.ErrNotExist) { return fmt.Errorf("removing temporary file: %w", err) } return nil } // write writes the content of reader to the file. func (w *fileWriter) write(r io.Reader) error { writer := io.MultiWriter(w.md5Writer, w.file) if _, err := io.Copy(writer, r); err != nil { return fmt.Errorf("writing database: %w", err) } return nil } // validateHash validates the hash of the file against a known value. func (w *fileWriter) validateHash(h string) error { tempFileHash := byteToString(w.md5Writer.Sum(nil)) if !strings.EqualFold(h, tempFileHash) { return fmt.Errorf("md5 of new database (%s) does not match expected md5 (%s)", tempFileHash, h) } return nil } // syncAndRename syncs the content of the file to storage and renames it. func (w *fileWriter) syncAndRename(name string) error { if err := w.file.Sync(); err != nil { return fmt.Errorf("syncing temporary file: %w", err) } if err := w.file.Close(); err != nil { return fmt.Errorf("closing temporary file: %w", err) } if err := os.Rename(w.file.Name(), name); err != nil { return fmt.Errorf("moving database into place: %w", err) } return nil } // syncDir syncs the content of a directory to storage. func syncDir(path string) error { // fsync the directory. https://austingroupbugs.net/view.php?id=672 //nolint:gosec // we really need to read this file. d, err := os.Open(path) if err != nil { return fmt.Errorf("opening database directory %s: %w", path, err) } defer func() { if err := d.Close(); err != nil { log.Printf("closing directory %s: %+v", path, err) } }() // We ignore Sync errors as they primarily happen on file systems that do // not support sync. //nolint:errcheck // See above. _ = d.Sync() return nil } // setModifiedAtTime sets the times for a database file to a certain value. func setModifiedAtTime(path string, t time.Time) error { if err := os.Chtimes(path, t, t); err != nil { return fmt.Errorf("setting times on file %s: %w", path, err) } return nil } // byteToString returns the base16 representation of a byte array. func byteToString(b []byte) string { return hex.EncodeToString(b) } geoipupdate-6.1.0/pkg/geoipupdate/database/local_file_writer_test.go000066400000000000000000000064561454734621200257550ustar00rootroot00000000000000package database import ( "os" "testing" "time" "github.com/stretchr/testify/require" ) // TestLocalFileWriterWrite tests functionality of the LocalFileWriter.Write method. func TestLocalFileWriterWrite(t *testing.T) { testTime := time.Date(2023, 4, 10, 12, 47, 31, 0, time.UTC) tests := []struct { description string //nolint:revive // support older versions checkErr func(require.TestingT, error, ...interface{}) preserveFileTime bool //nolint:revive // support older versions checkTime func(require.TestingT, interface{}, interface{}, ...interface{}) result *ReadResult }{ { description: "success", checkErr: require.NoError, preserveFileTime: true, checkTime: require.Equal, result: &ReadResult{ reader: getReader(t, "database content"), EditionID: "GeoIP2-City", OldHash: "", NewHash: "cfa36ddc8279b5483a5aa25e9a6151f4", ModifiedAt: testTime, }, }, { description: "hash does not match", checkErr: require.Error, preserveFileTime: true, checkTime: require.Equal, result: &ReadResult{ reader: getReader(t, "database content"), EditionID: "GeoIP2-City", OldHash: "", NewHash: "badhash", ModifiedAt: testTime, }, }, { description: "hash case does not matter", checkErr: require.NoError, preserveFileTime: true, checkTime: require.Equal, result: &ReadResult{ reader: getReader(t, "database content"), EditionID: "GeoIP2-City", OldHash: "", NewHash: "cfa36ddc8279b5483a5aa25e9a6151f4", ModifiedAt: testTime, }, }, { description: "do not preserve file modification time", checkErr: require.NoError, preserveFileTime: false, checkTime: require.NotEqual, result: &ReadResult{ reader: getReader(t, "database content"), EditionID: "GeoIP2-City", OldHash: "", NewHash: "CFA36DDC8279B5483A5AA25E9A6151F4", ModifiedAt: testTime, }, }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { tempDir := t.TempDir() defer test.result.reader.Close() fw, err := NewLocalFileWriter(tempDir, test.preserveFileTime, false) require.NoError(t, err) err = fw.Write(test.result) test.checkErr(t, err) if err == nil { database, err := os.Stat(fw.getFilePath(test.result.EditionID)) require.NoError(t, err) test.checkTime(t, database.ModTime().UTC(), testTime) } }) } } // TestLocalFileWriterGetHash tests functionality of the LocalFileWriter.GetHash method. func TestLocalFileWriterGetHash(t *testing.T) { result := &ReadResult{ reader: getReader(t, "database content"), EditionID: "GeoIP2-City", OldHash: "", NewHash: "cfa36ddc8279b5483a5aa25e9a6151f4", ModifiedAt: time.Time{}, } tempDir := t.TempDir() defer result.reader.Close() fw, err := NewLocalFileWriter(tempDir, false, false) require.NoError(t, err) err = fw.Write(result) require.NoError(t, err) // returns the correct hash for an existing database. hash, err := fw.GetHash(result.EditionID) require.NoError(t, err) require.Equal(t, hash, result.NewHash) // returns a zero hash for a non existing edition. hash, err = fw.GetHash("NewEdition") require.NoError(t, err) require.Equal(t, ZeroMD5, hash) } geoipupdate-6.1.0/pkg/geoipupdate/database/reader.go000066400000000000000000000034671454734621200224720ustar00rootroot00000000000000package database import ( "context" "encoding/json" "fmt" "io" "time" ) // Reader provides an interface for retrieving a database update and copying it // into place. type Reader interface { Read(context.Context, string, string) (*ReadResult, error) } // ReadResult is the struct returned by a Reader's Get method. type ReadResult struct { reader io.ReadCloser EditionID string `json:"edition_id"` OldHash string `json:"old_hash"` NewHash string `json:"new_hash"` ModifiedAt time.Time `json:"modified_at"` CheckedAt time.Time `json:"checked_at"` } // MarshalJSON is a custom json marshaler that strips out zero time fields. func (r ReadResult) MarshalJSON() ([]byte, error) { type partialResult ReadResult s := &struct { partialResult ModifiedAt int64 `json:"modified_at,omitempty"` CheckedAt int64 `json:"checked_at,omitempty"` }{ partialResult: partialResult(r), ModifiedAt: 0, CheckedAt: 0, } if !r.ModifiedAt.IsZero() { s.ModifiedAt = r.ModifiedAt.Unix() } if !r.CheckedAt.IsZero() { s.CheckedAt = r.CheckedAt.Unix() } res, err := json.Marshal(s) if err != nil { return nil, fmt.Errorf("marshaling ReadResult: %w", err) } return res, nil } // UnmarshalJSON is a custom json unmarshaler that converts timestamps to go // time fields. func (r *ReadResult) UnmarshalJSON(data []byte) error { type partialResult ReadResult s := &struct { partialResult ModifiedAt int64 `json:"modified_at,omitempty"` CheckedAt int64 `json:"checked_at,omitempty"` }{} err := json.Unmarshal(data, &s) if err != nil { return fmt.Errorf("unmarshaling json into ReadResult: %w", err) } result := ReadResult(s.partialResult) result.ModifiedAt = time.Unix(s.ModifiedAt, 0).In(time.UTC) result.CheckedAt = time.Unix(s.CheckedAt, 0).In(time.UTC) *r = result return nil } geoipupdate-6.1.0/pkg/geoipupdate/database/writer.go000066400000000000000000000005121454734621200225300ustar00rootroot00000000000000package database // 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 { Write(*ReadResult) error GetHash(editionID string) (string, error) } geoipupdate-6.1.0/pkg/geoipupdate/geoip_updater.go000066400000000000000000000055441454734621200223110ustar00rootroot00000000000000// Package geoipupdate provides a library for using MaxMind's GeoIP Update // service. package geoipupdate import ( "context" "encoding/json" "fmt" "log" "os" "sync" "time" "github.com/maxmind/geoipupdate/v6/pkg/geoipupdate/database" "github.com/maxmind/geoipupdate/v6/pkg/geoipupdate/internal" ) // Client uses config data to initiate a download or update // process for GeoIP databases. type Client struct { config *Config getReader func() (database.Reader, error) getWriter func() (database.Writer, error) output *log.Logger } // NewClient initialized a new Client struct. func NewClient(config *Config) *Client { getReader := func() (database.Reader, error) { return database.NewHTTPReader( config.Proxy, config.URL, config.AccountID, config.LicenseKey, config.RetryFor, config.Verbose, ), nil } getWriter := func() (database.Writer, error) { return database.NewLocalFileWriter( config.DatabaseDirectory, config.PreserveFileTimes, config.Verbose, ) } return &Client{ config: config, getReader: getReader, getWriter: getWriter, output: log.New(os.Stdout, "", 0), } } // Run starts the download or update process. func (c *Client) Run(ctx context.Context) error { fileLock, err := internal.NewFileLock(c.config.LockFile, c.config.Verbose) if err != nil { return fmt.Errorf("initializing file lock: %w", err) } if err := fileLock.Acquire(); err != nil { return fmt.Errorf("acquiring file lock: %w", err) } defer func() { if err := fileLock.Release(); err != nil { log.Printf("releasing file lock: %s", err) } }() jobProcessor := internal.NewJobProcessor(ctx, c.config.Parallelism) reader, err := c.getReader() if err != nil { return fmt.Errorf("initializing database reader: %w", err) } writer, err := c.getWriter() if err != nil { return fmt.Errorf("initializing database writer: %w", err) } var editions []database.ReadResult var mu sync.Mutex for _, editionID := range c.config.EditionIDs { editionID := editionID processFunc := func(ctx context.Context) error { editionHash, err := writer.GetHash(editionID) if err != nil { return err } edition, err := reader.Read(ctx, editionID, editionHash) if err != nil { return err } if err := writer.Write(edition); err != nil { return err } edition.CheckedAt = time.Now().In(time.UTC) mu.Lock() editions = append(editions, *edition) mu.Unlock() return nil } jobProcessor.Add(processFunc) } // Run blocks until all jobs are processed or exits early after // the first encountered error. if err := jobProcessor.Run(ctx); err != nil { return fmt.Errorf("running the job processor: %w", err) } if c.config.Output { result, err := json.Marshal(editions) if err != nil { return fmt.Errorf("marshaling result log: %w", err) } c.output.Print(string(result)) } return nil } geoipupdate-6.1.0/pkg/geoipupdate/geoip_updater_test.go000066400000000000000000000053031454734621200233410ustar00rootroot00000000000000package geoipupdate import ( "bytes" "context" "encoding/json" "errors" "log" "path/filepath" "testing" "time" "github.com/maxmind/geoipupdate/v6/pkg/geoipupdate/database" "github.com/stretchr/testify/require" ) // TestClientOutput makes sure that the client outputs the result of it's // operation to stdout in json format. func TestClientOutput(t *testing.T) { now := time.Now().Truncate(time.Second).In(time.UTC) testTime := time.Date(2023, 4, 27, 12, 4, 48, 0, time.UTC) databases := []database.ReadResult{ { EditionID: "GeoLite2-City", OldHash: "A", NewHash: "B", ModifiedAt: testTime, }, { EditionID: "GeoIP2-Country", OldHash: "C", NewHash: "D", ModifiedAt: testTime, }, } tempDir := t.TempDir() config := &Config{ EditionIDs: []string{"GeoLite2-City", "GeoLite2-Country"}, LockFile: filepath.Join(tempDir, ".geoipupdate.lock"), Output: true, Parallelism: 1, } // capture the output of the `output` logger. logOutput := &bytes.Buffer{} // create a fake client with a mocked database reader and writer. c := &Client{ config: config, getReader: func() (database.Reader, error) { return &mockReader{i: 0, result: databases}, nil }, getWriter: func() (database.Writer, error) { return &mockWriter{}, nil }, output: log.New(logOutput, "", 0), } // run the client err := c.Run(context.Background()) require.NoError(t, err) // make sure the expected output matches the input. var outputDatabases []database.ReadResult err = json.Unmarshal(logOutput.Bytes(), &outputDatabases) require.NoError(t, err) require.Equal(t, len(outputDatabases), len(databases)) for i := 0; i < len(databases); i++ { require.Equal(t, databases[i].EditionID, outputDatabases[i].EditionID) require.Equal(t, databases[i].OldHash, outputDatabases[i].OldHash) require.Equal(t, databases[i].NewHash, outputDatabases[i].NewHash) require.Equal(t, databases[i].ModifiedAt, outputDatabases[i].ModifiedAt) // comparing time wasn't supported with require in older go versions. if !afterOrEqual(outputDatabases[i].CheckedAt, now) { t.Errorf("database %s was not updated", outputDatabases[i].EditionID) } } } type mockReader struct { i int result []database.ReadResult } func (mr *mockReader) Read(_ context.Context, _, _ string) (*database.ReadResult, error) { if mr.i >= len(mr.result) { return nil, errors.New("out of bounds") } res := mr.result[mr.i] mr.i++ return &res, nil } type mockWriter struct{} func (w *mockWriter) Write(_ *database.ReadResult) error { return nil } func (w mockWriter) GetHash(_ string) (string, error) { return "", nil } func afterOrEqual(t1, t2 time.Time) bool { return t1.After(t2) || t1.Equal(t2) } geoipupdate-6.1.0/pkg/geoipupdate/internal/000077500000000000000000000000001454734621200207375ustar00rootroot00000000000000geoipupdate-6.1.0/pkg/geoipupdate/internal/errors.go000066400000000000000000000005041454734621200226010ustar00rootroot00000000000000// Package internal provides internal structures. package internal import ( "fmt" ) // HTTPError is an error from performing an HTTP request. type HTTPError struct { Body string StatusCode int } func (h HTTPError) Error() string { return fmt.Sprintf("received HTTP status code: %d: %s", h.StatusCode, h.Body) } geoipupdate-6.1.0/pkg/geoipupdate/internal/file_lock.go000066400000000000000000000027071454734621200232230ustar00rootroot00000000000000package internal import ( "fmt" "log" "os" "path/filepath" "github.com/gofrs/flock" ) // FileLock provides a file lock mechanism based on flock. type FileLock struct { lock *flock.Flock verbose bool } // NewFileLock creates a new instance of FileLock. func NewFileLock(path string, verbose bool) (*FileLock, error) { err := os.MkdirAll(filepath.Dir(path), 0o750) if err != nil { return nil, fmt.Errorf("creating lock file directory: %w", err) } if verbose { log.Printf("Initializing file lock at %s", path) } return &FileLock{ lock: flock.New(path), verbose: verbose, }, nil } // Release unlocks the file lock. func (f *FileLock) Release() error { if err := f.lock.Unlock(); err != nil { return fmt.Errorf("releasing file lock at %s: %w", f.lock.Path(), err) } if f.verbose { log.Printf("Lock file %s successfully released", f.lock.Path()) } return nil } // Acquire tries to acquire a file lock. // It is possible for multiple goroutines within the same process // to acquire the same lock, so acquireLock is not thread safe in // that sense, but protects access across different processes. func (f *FileLock) Acquire() error { ok, err := f.lock.TryLock() if err != nil { return fmt.Errorf("acquiring file lock at %s: %w", f.lock.Path(), err) } if !ok { return fmt.Errorf("lock %s already acquired by another process", f.lock.Path()) } if f.verbose { log.Printf("Acquired lock file at %s", f.lock.Path()) } return nil } geoipupdate-6.1.0/pkg/geoipupdate/internal/file_lock_test.go000066400000000000000000000016001454734621200242510ustar00rootroot00000000000000package internal import ( "path/filepath" "testing" "github.com/stretchr/testify/require" ) // TestAcquireFileLock tests that a lock can be acquired multile times // within a same process. func TestAcquireFileLock(t *testing.T) { tempDir := t.TempDir() fl, err := NewFileLock(filepath.Join(tempDir, ".geoipupdate.lock"), false) require.NoError(t, err) defer func() { err := fl.Release() require.NoError(t, err) }() // acquire lock err = fl.Acquire() require.NoError(t, err) require.True(t, fl.lock.Locked()) // acquiring lock a second time within the same process // should succeed err = fl.Acquire() require.NoError(t, err) require.True(t, fl.lock.Locked()) // release lock err = fl.Release() require.NoError(t, err) require.False(t, fl.lock.Locked()) // acquire a released lock err = fl.Acquire() require.NoError(t, err) require.True(t, fl.lock.Locked()) } geoipupdate-6.1.0/pkg/geoipupdate/internal/job_processor.go000066400000000000000000000034141454734621200241410ustar00rootroot00000000000000package internal import ( "context" "fmt" "sync" "golang.org/x/sync/errgroup" ) // JobProcessor runs jobs with a set number of workers. type JobProcessor struct { // sync.Mutex prevents adding new jobs while the processor is running. mu sync.Mutex // processor is used to parallelize and limit the number of workers // processing read requests. processor *errgroup.Group // jobs defines the jobs to be processed. jobs []func(context.Context) error // cancel cancels the context and stops the processing of the queue. cancel context.CancelFunc } // NewJobProcessor inits a new JobProcessor struct. func NewJobProcessor(ctx context.Context, workers int) *JobProcessor { processor, _ := errgroup.WithContext(ctx) processor.SetLimit(workers) return &JobProcessor{ processor: processor, jobs: []func(context.Context) error{}, } } // Add queues a job for processing. func (j *JobProcessor) Add(job func(context.Context) error) { j.mu.Lock() defer j.mu.Unlock() j.jobs = append(j.jobs, job) } // Run processes the job queue and returns the first error encountered, if any. func (j *JobProcessor) Run(ctx context.Context) error { j.mu.Lock() defer j.mu.Unlock() ctx, j.cancel = context.WithCancel(ctx) for _, job := range j.jobs { job := job j.processor.Go(func() error { if err := ctx.Err(); err != nil { return fmt.Errorf("processing cancelled: %w", err) } return job(ctx) }) } return j.Wait() } // Wait waits for all jobs to finish processing and returns the first // error encountered, if any. func (j *JobProcessor) Wait() error { if err := j.processor.Wait(); err != nil { return fmt.Errorf("running job: %w", err) } return nil } // Stop cancels all queued jobs. func (j *JobProcessor) Stop() { if j.cancel != nil { j.cancel() } } geoipupdate-6.1.0/pkg/geoipupdate/internal/job_processor_test.go000066400000000000000000000056011454734621200252000ustar00rootroot00000000000000package internal import ( "context" "sync" "testing" "time" "github.com/stretchr/testify/require" ) // TestJobQueueRun tests the parallel job queue functionality // and ensures that the maximum number of allowed goroutines does not exceed the value // set in the config. func TestJobQueueRun(t *testing.T) { simulatedJobDuration := 5 * time.Millisecond jobsNumber := 10 tests := []struct { Description string Parallelism int }{{ Description: "sequential jobs", Parallelism: 1, }, { Description: "parallel jobs", Parallelism: 3, }} for _, test := range tests { t.Run(test.Description, func(t *testing.T) { doneCh := make(chan struct{}) var lock sync.Mutex runningGoroutines := 0 maxConcurrentGoroutines := 0 // A mock processor function that is used to gather data // about the number of goroutines called. processorFunc := func(_ context.Context) error { lock.Lock() runningGoroutines++ if runningGoroutines > maxConcurrentGoroutines { maxConcurrentGoroutines = runningGoroutines } lock.Unlock() time.Sleep(simulatedJobDuration) lock.Lock() runningGoroutines-- lock.Unlock() return nil } ctx := context.Background() jobProcessor := NewJobProcessor(ctx, test.Parallelism) for i := 0; i < jobsNumber; i++ { jobProcessor.Add(processorFunc) } // Execute run in a goroutine so that we can exit early if the test // hangs or takes too long to execute. go func() { err := jobProcessor.Run(ctx) require.NoError(t, err) close(doneCh) }() // Wait for run to complete or timeout after a certain duration select { case <-doneCh: case <-time.After(1000 * time.Millisecond): t.Errorf("Timeout waiting for function completion") } // The maximum number of parallel downloads executed should not exceed // the number defined in the configuration. require.Equal(t, maxConcurrentGoroutines, test.Parallelism) }) } } // TestJobQueueStop cancels a job queue and makes sure queued // jobs are not processed. func TestJobQueueStop(t *testing.T) { doneCh := make(chan struct{}) processedJobs := 0 maxProcessedJobs := 5 ctx := context.Background() jobProcessor := NewJobProcessor(ctx, 1) processorFunc := func(_ context.Context) error { processedJobs++ if processedJobs == maxProcessedJobs { jobProcessor.Stop() } return nil } for i := 0; i < 10; i++ { jobProcessor.Add(processorFunc) } // Execute run in a goroutine so that we can exit early if the test // hangs or takes too long to execute. go func() { err := jobProcessor.Run(ctx) require.ErrorContains(t, err, "processing cancelled") close(doneCh) }() // Wait for run to complete or timeout after a certain duration select { case <-doneCh: case <-time.After(1000 * time.Millisecond): t.Errorf("Timeout waiting for function completion") } require.Equal(t, processedJobs, maxProcessedJobs) } geoipupdate-6.1.0/pkg/geoipupdate/vars/000077500000000000000000000000001454734621200200765ustar00rootroot00000000000000geoipupdate-6.1.0/pkg/geoipupdate/vars/defaults_notwin.go000066400000000000000000000007001454734621200236270ustar00rootroot00000000000000//go:build !windows // +build !windows package vars 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-6.1.0/pkg/geoipupdate/vars/defaults_windows.go000066400000000000000000000005651454734621200240140ustar00rootroot00000000000000package vars 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-6.1.0/pkg/geoipupdate/vars/version.go000066400000000000000000000002211454734621200221050ustar00rootroot00000000000000// Package vars holds random vars, consts, and defaults. package vars // Version defines the current geoipupdate version. var Version = "6.1.0"