diff --git a/server/device/geo.go b/server/device/geo.go index 136c76d1579854cfbb587238816ce31ed903a5c8..83a66b35ae689ff9b92b85b3f9dc176809969166 100644 --- a/server/device/geo.go +++ b/server/device/geo.go @@ -1,21 +1,53 @@ package device -import "net" +import ( + "errors" + "net" -func (m *Manager) getZoneForIP(ip net.IP) (string, error) { - if m.geodb == nil { - return "", nil + "github.com/oschwald/maxminddb-golang" +) + +var ( + errGeoNotFound = errors.New("no record found") + + defaultGeoIPPaths = []string{ + "/usr/share/GeoIP/GeoIP.dat", + "/usr/share/GeoIP/GeoIPv6.dat", + } +) + +type geoIPDb struct { + readers []*maxminddb.Reader +} + +func newGeoIP(paths []string) (*geoIPDb, error) { + if len(paths) == 0 { + paths = defaultGeoIPPaths } + db := new(geoIPDb) + for _, path := range paths { + geodb, err := maxminddb.Open(path) + if err != nil { + return nil, err + } + db.readers = append(db.readers, geodb) + } + return db, nil +} + +func (db *geoIPDb) getZoneForIP(ip net.IP) (string, error) { // Only look up a single attribute (country). var record struct { Country struct { ISOCode string `maxminddb:"iso_code"` } `maxminddb:"country"` } - if err := m.geodb.Lookup(ip, &record); err != nil { - return "", err - } - return record.Country.ISOCode, nil + for _, r := range db.readers { + if err := r.Lookup(ip, &record); err == nil { + return record.Country.ISOCode, nil + } + } + return "", errGeoNotFound } diff --git a/server/device/manager.go b/server/device/manager.go index 981da2bc1e37cd594aa783f9f8d003c49affc810..c0fa0af25996477e75a504616647108b15506448 100644 --- a/server/device/manager.go +++ b/server/device/manager.go @@ -3,13 +3,14 @@ package device import ( "crypto/rand" "encoding/hex" + "log" "net" "net/http" + "strings" "git.autistici.org/id/auth" "github.com/gorilla/sessions" "github.com/mssola/user_agent" - "github.com/oschwald/maxminddb-golang" ) func randomDeviceID() string { @@ -21,7 +22,7 @@ func randomDeviceID() string { // Manager can provide DeviceInfo entries for incoming HTTP requests. type Manager struct { store sessions.Store - geodb *maxminddb.Reader + geodb *geoIPDb trustedForwarders []net.IPNet remoteAddrHeader string } @@ -29,7 +30,7 @@ type Manager struct { // Config stores options for the device info manager. type Config struct { AuthKey string `yaml:"auth_key"` - GeoIPDataFile string `yaml:"geo_ip_data"` + GeoIPDataFiles []string `yaml:"geo_ip_data_files"` TrustedForwarders []string `yaml:"trusted_forwarders"` RemoteAddrHeader string `yaml:"remote_addr_header"` } @@ -44,12 +45,9 @@ func New(config *Config) (*Manager, error) { return nil, err } - var geodb *maxminddb.Reader - if config.GeoIPDataFile != "" { - geodb, err = maxminddb.Open(config.GeoIPDataFile) - if err != nil { - return nil, err - } + var geodb *geoIPDb + if geodb, err = newGeoIP(config.GeoIPDataFiles); err != nil { + log.Printf("Warning: GeoIP disabled: %v", err) } // The remote IP header (if any) defaults to X-Forwarded-For. @@ -93,10 +91,21 @@ func (m *Manager) GetDeviceInfoFromRequest(w http.ResponseWriter, req *http.Requ Browser: browser, } - if ip := m.getIPFromRequest(req); ip != nil { - d.RemoteAddr = ip.String() - if zone, err := m.getZoneForIP(ip); err == nil { - d.RemoteZone = zone + // Special check for .onion HTTP hosts - sets the zone to + // "onion" and skips the IP-based checks. + if req.Host != "" && strings.HasSuffix(req.Host, ".onion") { + d.RemoteZone = "onion" + } else { + // If we have an IP address, we can add more information. + if ip := m.getIPFromRequest(req); ip != nil { + d.RemoteAddr = ip.String() + + // Country lookup. + if m.geodb != nil { + if zone, err := m.geodb.getZoneForIP(ip); err == nil { + d.RemoteZone = zone + } + } } }