diff --git a/cmd/gochan/main.go b/cmd/gochan/main.go index 6d198cfa..dde6b476 100644 --- a/cmd/gochan/main.go +++ b/cmd/gochan/main.go @@ -30,6 +30,7 @@ func main() { gcsql.Close() gcutil.CloseLog() gcplugin.ClosePlugins() + posting.CloseGeoipDB() }() fmt.Printf("Starting gochan v%s\n", versionStr) @@ -53,7 +54,10 @@ func main() { gcutil.LogFatal().Err(err).Msg("Failed to connect to the database") } events.TriggerEvent("db-connected") - fmt.Println("Connected to database") + gcutil.LogInfo(). + Str("dbType", systemCritical.DBtype). + Msg("Connected to database") + if err = gcsql.CheckAndInitializeDatabase(systemCritical.DBtype); err != nil { fmt.Println("Failed to initialize the database:", err.Error()) gcutil.LogFatal().Err(err).Msg("Failed to initialize the database") @@ -61,8 +65,9 @@ func main() { events.TriggerEvent("db-initialized") parseCommandLine() serverutil.InitMinifier() - + posting.InitGeoIP() posting.InitCaptcha() + if err = gctemplates.InitTemplates(); err != nil { fmt.Println("Failed initializing templates:", err.Error()) gcutil.LogFatal().Err(err).Send() diff --git a/examples/configs/gochan.example.json b/examples/configs/gochan.example.json index 08086a0e..8a8314fb 100644 --- a/examples/configs/gochan.example.json +++ b/examples/configs/gochan.example.json @@ -96,7 +96,9 @@ "SiteKey": "your site key goes here (if you want a captcha, make sure to replace '_Captcha' with 'Captcha'", "AccountSecret": "your account secret key goes here" }, - "EnableGeoIP": true, + "EnableGeoIP": false, + "GeoIPDBType": "", + "GeoIPDBType_info": "valid GeoIPDBType values are '', 'none', 'legacy', and 'geoip2'. '' and 'none' are treated as the same. 'geoip2' is used for MaxMind's GeoIP2 and GeoLite2 .mmdb databases. 'legacy' will eventually support the legacy GeoIP databases", "_EnableGeoIP_info": "set GeoIPDBlocation to cf to use Cloudflare's GeoIP", "GeoIPDBlocation": "/usr/share/GeoIP/GeoIP.dat", "MaxRecentPosts": 12, diff --git a/go.mod b/go.mod index 3ba716da..d96729b6 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/tdewolff/parse v2.3.4+incompatible // indirect diff --git a/go.sum b/go.sum index a8e819ab..5a7e6da7 100755 --- a/go.sum +++ b/go.sum @@ -106,6 +106,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= +github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/pkg/config/config.go b/pkg/config/config.go index 14e673e1..6d39d73b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -192,9 +192,14 @@ type SiteConfig struct { MinifyHTML bool MinifyJS bool GeoIPDBlocation string + GeoIPDBType string Captcha CaptchaConfig } +func (sc *SiteConfig) validGeoIPType() bool { + return sc.GeoIPDBType == "" || sc.GeoIPDBType == "legacy" || sc.GeoIPDBType == "geoip2" +} + type CaptchaConfig struct { Type string OnlyNeededForThreads bool diff --git a/pkg/config/util.go b/pkg/config/util.go index f08bef39..cf545b23 100644 --- a/pkg/config/util.go +++ b/pkg/config/util.go @@ -185,13 +185,23 @@ func InitConfig(versionStr string) { cfg.WebRoot += "/" } - if cfg.EnableGeoIP { - if _, err = os.Stat(cfg.GeoIPDBlocation); err != nil { - gcutil.LogError(err). + if !cfg.validGeoIPType() { + gcutil.LogFatal().Caller(). + Str("GeoIPDBType", cfg.GeoIPDBType). + Msg("Invalid GeoIPDBType value, valid values are '', 'none', 'legacy', or 'geoip2'") + } + + if cfg.GeoIPDBType != "" && cfg.GeoIPDBlocation != "" { + if _, err = os.Stat(cfg.GeoIPDBlocation); os.IsNotExist(err) { + gcutil.LogWarning(). Str("location", cfg.GeoIPDBlocation). - Msg("Unable to load GeoIP file location set in gochan.json, disabling GeoIP") + Msg("Unable to load GeoIP database location set in gochan.json, disabling GeoIP") + cfg.EnableGeoIP = false + } else if err != nil { + gcutil.LogFatal().Err(err). + Str("location", cfg.GeoIPDBlocation). + Msg("Unable to load GeoIP database location set in gochan.json") } - cfg.EnableGeoIP = false } _, zoneOffset := time.Now().Zone() diff --git a/pkg/posting/captcha.go b/pkg/posting/captcha.go index 3cecd621..9a21aa96 100644 --- a/pkg/posting/captcha.go +++ b/pkg/posting/captcha.go @@ -42,8 +42,6 @@ func InitCaptcha() { } } if !typeIsValid { - fmt.Printf("Unrecognized Captcha.Type value in configuration: %q, valid values: %v\n", - captchaCfg.Type, validCaptchaTypes) gcutil.LogFatal(). Str("captchaType", captchaCfg.Type). Msg("Unsupported captcha type set in configuration") diff --git a/pkg/posting/geoip.go b/pkg/posting/geoip.go new file mode 100644 index 00000000..37fbeda0 --- /dev/null +++ b/pkg/posting/geoip.go @@ -0,0 +1,56 @@ +package posting + +import ( + "errors" + "net" + + "github.com/gochan-org/gochan/pkg/config" + "github.com/gochan-org/gochan/pkg/gcsql" + "github.com/gochan-org/gochan/pkg/gcutil" + maxminddb "github.com/oschwald/maxminddb-golang" +) + +var ( + mmdb *maxminddb.Reader + ErrInvalidIP = errors.New("invalid IP address") +) + +type mmdbRecord struct { + Country struct { + ISOCode string `maxminddb:"iso_code"` + Names map[string]string `maxminddb:"names"` + } `maxminddb:"country"` +} + +func InitGeoIP() { + dbLocation := config.GetSiteConfig().GeoIPDBlocation + if dbLocation == "" { + return + } + var err error + mmdb, err = maxminddb.Open(dbLocation) + if err != nil { + gcutil.LogFatal().Err(err).Send() + } +} + +func LookupCountry(post *gcsql.Post, board string) (abbr string, name string, err error) { + boardCfg := config.GetBoardConfig(board) + if !boardCfg.EnableGeoIP || mmdb == nil { + return "", "", nil + } + ip := net.ParseIP(post.IP) + if ip == nil { + return "", "", ErrInvalidIP + } + var record mmdbRecord + err = mmdb.Lookup(ip, &record) + return record.Country.ISOCode, record.Country.Names["en"], err +} + +func CloseGeoipDB() error { + if mmdb == nil { + return nil + } + return mmdb.Close() +}