diff --git a/server.go b/server.go index e36a115..079a91f 100644 --- a/server.go +++ b/server.go @@ -1,19 +1,14 @@ package main import ( - "bufio" - "bytes" + "./whois" "flag" "fmt" - "io/ioutil" "net" "os" "os/signal" "path" - "regexp" - "sort" "strconv" - "strings" "sync" "sync/atomic" "syscall" @@ -21,7 +16,7 @@ import ( ) type Server struct { - DataPath string + registry whois.Registry LastConnection time.Time SocketActivation bool stopListening int32 @@ -29,7 +24,8 @@ type Server struct { } func New(dataPath string) *Server { - return &Server{dataPath, time.Now(), false, 0, sync.WaitGroup{}} + registry := whois.Registry{dataPath} + return &Server{registry, time.Now(), false, 0, sync.WaitGroup{}} } func (s *Server) Run(listener *net.TCPListener) { @@ -63,148 +59,13 @@ func (s *Server) Shutdown() { s.activeWorkers.Wait() } -func readCidrs(path string) ([]net.IPNet, error) { - files, err := ioutil.ReadDir(path) - if err != nil { - return nil, err - } - cidrs := []net.IPNet{} - for _, f := range files { - name := strings.Replace(f.Name(), "_", "/", -1) - _, cidr, err := net.ParseCIDR(name) - if err != nil { - fmt.Fprintf(os.Stderr, "skip invalid net '%s'", f.Name()) - continue - } - i := sort.Search(len(cidrs), func(i int) bool { - c := cidrs[i] - return bytes.Compare(c.Mask, cidr.Mask) >= 0 - }) - - if i < len(cidrs) { - cidrs = append(cidrs[:i], append([]net.IPNet{*cidr}, cidrs[i:]...)...) - } else { - cidrs = append(cidrs, *cidr) - } - } - - return cidrs, nil -} - -type WhoisType struct { - Name string - Pattern *regexp.Regexp - Kind int -} - -const ( - UPPER = iota - LOWER - ROUTE - ROUTE6 -) - -var whoisTypes = []WhoisType{ - {"aut-num", regexp.MustCompile(`^AS([0123456789]+)$`), UPPER}, - {"dns", regexp.MustCompile(`.dn42$`), LOWER}, - {"person", regexp.MustCompile(`-DN42$`), UPPER}, - {"mntner", regexp.MustCompile(`-MNT$`), UPPER}, - {"schema", regexp.MustCompile(`-SCHEMA$`), UPPER}, - {"organisation", regexp.MustCompile(`ORG-`), UPPER}, - {"tinc-keyset", regexp.MustCompile(`^SET-.+-TINC$`), UPPER}, - {"tinc-key", regexp.MustCompile(`-TINC$`), UPPER}, - {"as-set", regexp.MustCompile(`^AS`), UPPER}, - {"route-set", regexp.MustCompile(`^RS-`), UPPER}, - {"inetnum", nil, ROUTE}, - {"inet6num", nil, ROUTE6}, - {"route", nil, ROUTE}, - {"route6", nil, ROUTE6}, - {"as-block", regexp.MustCompile(`\d+_\d+`), UPPER}, -} - -func parseQuery(conn *net.TCPConn) map[int]interface{} { - r := bufio.NewReader(conn) - req, e := r.ReadString('\n') - if e != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", e) - return nil - } - obj := path.Base(strings.TrimSpace(req)) - queryArgs := map[int]interface{}{ - UPPER: strings.ToUpper(obj), - LOWER: strings.ToLower(obj), - } - ip := net.ParseIP(obj) - if ip == nil { - ip, _, _ = net.ParseCIDR(strings.TrimSpace(req)) - } - if ip != nil { - if ip.To4() == nil { - queryArgs[ROUTE6] = ip - } else { - queryArgs[ROUTE] = ip.To4() - } - } - fmt.Fprintf(os.Stdout, "[%s] %s\n", conn.RemoteAddr(), obj) - return queryArgs -} - func (s *Server) handleConn(conn *net.TCPConn) { defer func() { conn.Close() s.activeWorkers.Done() }() - queryArgs := parseQuery(conn) - if queryArgs == nil { - return - } - - found := false - for _, t := range whoisTypes { - if t.Kind == ROUTE || t.Kind == ROUTE6 { - if queryArgs[t.Kind] != nil { - found = found || s.printNet(conn, t.Name, queryArgs[t.Kind].(net.IP)) - } - } else { - arg := queryArgs[t.Kind].(string) - if t.Pattern.MatchString(arg) { - s.printObject(conn, t.Name, arg) - found = true - } - } - } - - if !found { - fmt.Fprint(conn, "% 404") - } -} - -func (s *Server) printNet(conn *net.TCPConn, name string, ip net.IP) bool { - routePath := path.Join(s.DataPath, name) - cidrs, err := readCidrs(routePath) - if err != nil { - fmt.Printf("Error reading cidr from '%s'\n", routePath) - } - - found := false - for _, c := range cidrs { - if c.Contains(ip) { - obj := strings.Replace(c.String(), "/", "_", -1) - s.printObject(conn, name, obj) - found = true - } - } - return found -} - -func (s *Server) printObject(conn *net.TCPConn, objType string, obj string) { - f, err := os.Open(path.Join(s.DataPath, objType, obj)) - defer f.Close() - if err != nil && !os.IsNotExist(err) { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - } - conn.ReadFrom(f) + s.registry.HandleQuery(conn) } type options struct { diff --git a/whois/flags.go b/whois/flags.go new file mode 100644 index 0000000..b912a28 --- /dev/null +++ b/whois/flags.go @@ -0,0 +1,52 @@ +package whois + +import ( + "flag" + "strings" +) + +func shellSplit(s string) []string { + split := strings.Split(s, " ") + + var result []string + var inquote string + var block string + for _, i := range split { + if inquote == "" { + if strings.HasPrefix(i, "'") || strings.HasPrefix(i, "\"") { + inquote = string(i[0]) + block = strings.TrimPrefix(i, inquote) + " " + } else { + result = append(result, i) + } + } else { + if !strings.HasSuffix(i, inquote) { + block += i + " " + } else { + block += strings.TrimSuffix(i, inquote) + inquote = "" + result = append(result, block) + block = "" + } + } + } + + return result +} + +type Flags struct { + ServerInfo string + Args []string +} + +func parseFlags(request string) (*Flags, *flag.FlagSet, error) { + args := shellSplit(request) + set := flag.NewFlagSet("whois42d", flag.ContinueOnError) + f := Flags{} + set.StringVar(&f.ServerInfo, "q", "", "[version|sources|types] query specified server info") + if err := set.Parse(args); err != nil { + return nil, set, err + } + f.Args = set.Args() + return &f, set, nil +} diff --git a/whois/query.go b/whois/query.go new file mode 100644 index 0000000..61caaef --- /dev/null +++ b/whois/query.go @@ -0,0 +1,227 @@ +package whois + +import ( + "bufio" + "bytes" + "flag" + "fmt" + "io/ioutil" + "net" + "os" + "path" + "regexp" + "sort" + "strings" +) + +type Registry struct { + DataPath string +} + +type Type struct { + Name string + Pattern *regexp.Regexp + Kind int +} + +type Object map[int]interface{} + +const ( + UPPER = iota + LOWER + ROUTE + ROUTE6 +) + +type Query struct { + Objects []Object + Flags *Flags +} + +var whoisTypes = []Type{ + {"aut-num", regexp.MustCompile(`^AS([0123456789]+)$`), UPPER}, + {"dns", regexp.MustCompile(`.dn42$`), LOWER}, + {"person", regexp.MustCompile(`-DN42$`), UPPER}, + {"mntner", regexp.MustCompile(`-MNT$`), UPPER}, + {"schema", regexp.MustCompile(`-SCHEMA$`), UPPER}, + {"organisation", regexp.MustCompile(`ORG-`), UPPER}, + {"tinc-keyset", regexp.MustCompile(`^SET-.+-TINC$`), UPPER}, + {"tinc-key", regexp.MustCompile(`-TINC$`), UPPER}, + {"as-set", regexp.MustCompile(`^AS`), UPPER}, + {"route-set", regexp.MustCompile(`^RS-`), UPPER}, + {"inetnum", nil, ROUTE}, + {"inet6num", nil, ROUTE6}, + {"route", nil, ROUTE}, + {"route6", nil, ROUTE6}, + {"as-block", regexp.MustCompile(`\d+_\d+`), UPPER}, +} + +func (r *Registry) handleObject(conn *net.TCPConn, object Object) bool { + found := false + for _, t := range whoisTypes { + if t.Kind == ROUTE || t.Kind == ROUTE6 { + if object[t.Kind] != nil { + found = found || r.printNet(conn, t.Name, object[t.Kind].(net.IP)) + } + } else { + arg := object[t.Kind].(string) + if t.Pattern.MatchString(arg) { + r.printObject(conn, t.Name, arg) + found = true + } + } + } + return found +} + +func (r *Registry) HandleQuery(conn *net.TCPConn) { + fmt.Fprint(conn, "% This is the dn42 whois query service.\n\n") + + query := parseQuery(conn) + if query == nil { + return + } + + flags := query.Flags + if flags.ServerInfo != "" { + r.printServerInfo(conn, flags.ServerInfo) + return + } + found := false + for _, obj := range query.Objects { + if r.handleObject(conn, obj) { + found = true + } + } + + if !found { + fmt.Fprint(conn, "% 404\n") + } + fmt.Fprint(conn, "\n") +} + +func readCidrs(path string) ([]net.IPNet, error) { + files, err := ioutil.ReadDir(path) + if err != nil { + return nil, err + } + cidrs := []net.IPNet{} + for _, f := range files { + name := strings.Replace(f.Name(), "_", "/", -1) + _, cidr, err := net.ParseCIDR(name) + if err != nil { + fmt.Fprintf(os.Stderr, "skip invalid net '%s'", f.Name()) + continue + } + i := sort.Search(len(cidrs), func(i int) bool { + c := cidrs[i] + return bytes.Compare(c.Mask, cidr.Mask) >= 0 + }) + + if i < len(cidrs) { + cidrs = append(cidrs[:i], append([]net.IPNet{*cidr}, cidrs[i:]...)...) + } else { + cidrs = append(cidrs, *cidr) + } + } + + return cidrs, nil +} + +func parseObject(arg string) Object { + obj := path.Base(arg) + object := Object{ + UPPER: strings.ToUpper(obj), + LOWER: strings.ToLower(obj), + } + + ip := net.ParseIP(obj) + if ip == nil { + ip, _, _ = net.ParseCIDR(arg) + } + if ip != nil { + if ip.To4() == nil { + object[ROUTE6] = ip + } else { + object[ROUTE] = ip.To4() + } + } + return object +} + +func parseQuery(conn *net.TCPConn) *Query { + r := bufio.NewReader(conn) + req, e := r.ReadString('\n') + if e != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", e) + return nil + } + flags, flagSet, err := parseFlags(req) + if err != nil { + flagSet.SetOutput(conn) + if err != flag.ErrHelp { + fmt.Fprintf(conn, "%s", err) + } + flagSet.PrintDefaults() + return nil + } + + query := Query{} + query.Flags = flags + query.Objects = make([]Object, len(flags.Args)) + for i, arg := range flags.Args { + query.Objects[i] = parseObject(strings.TrimSpace(arg)) + } + fmt.Fprintf(os.Stdout, "[%s] %s\n", conn.RemoteAddr(), req) + return &query +} + +func (r *Registry) printServerInfo(conn *net.TCPConn, what string) { + switch what { + case "version": + fmt.Fprintf(conn, "%% whois42d v%d\n", VERSION) + case "sources": + fmt.Fprintf(conn, "DN42:3:N:0-0\n") + case "types": + for _, t := range whoisTypes { + fmt.Fprintf(conn, "%s\n", t.Name) + } + default: + fmt.Fprintf(conn, "% unknown option %s\n", what) + } +} + +func (r *Registry) printNet(conn *net.TCPConn, name string, ip net.IP) bool { + routePath := path.Join(r.DataPath, name) + cidrs, err := readCidrs(routePath) + if err != nil { + fmt.Printf("Error reading cidr from '%s'\n", routePath) + } + + found := false + for _, c := range cidrs { + if c.Contains(ip) { + obj := strings.Replace(c.String(), "/", "_", -1) + r.printObject(conn, name, obj) + found = true + } + } + return found +} + +func (r *Registry) printObject(conn *net.TCPConn, objType string, obj string) { + file := path.Join(r.DataPath, objType, obj) + + f, err := os.Open(file) + defer f.Close() + if err != nil { + if os.IsNotExist(err) { + return + } + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + return + } + fmt.Fprintf(conn, "%% Information related to '%s':\n", file[len(r.DataPath)+1:]) + conn.ReadFrom(f) + fmt.Fprint(conn, "\n") +} diff --git a/whois/version.go b/whois/version.go new file mode 100644 index 0000000..69493d4 --- /dev/null +++ b/whois/version.go @@ -0,0 +1,3 @@ +package whois + +const VERSION = 1