Rasmus Lindroth

En blogg om Linux & teknik

Korta domännamn

Istället för att skapa falska domännamn för att nå min server kom jag på att jag faktiskt kan köpa mig en domän till istället. Jag ville ha ett kort domännamn så att det går snabbt att skriva, så jag sökte på internet och hittade gamla forumtrådar där folk hade gjort listor med korta och lediga domännamn. En del av dem var redan använda, så jag bestämde mig för att skriva ett eget program som listar lediga domäner.

Lösningen jag tänkte köra med först var att skapa en lista med domäner från a.se till zzz.se och sedan titta upp om domänen finns med hjälp av WHOIS. Det skulle dock bli väldigt många uppslagningar, men då upptäckte jag att Internetstiftelsen publicerar sina zonfiler helt öppet här. Det enda som krävdes för att få ner zonfilerna var att skriva in följande i terminalen.

dig @zonedata.iis.se se AXFR > se.zone.txt
dig @zonedata.iis.se nu AXFR > nu.zone.txt

Efter det skrev jag ett program som först generarar möjliga kombinationer av domäner med bokstäverna a-z med maxlängden tre. När det är färdigt går programmet igenom zonfilen och markerar alla domäner som redan finns. Kvar är en lista med alla domäner som inte finns med i zonfilen, så för dessa domännamn görs en WHOIS-uppslagning för att se om domänen finns. Finns den inte skrivs namnet ut i terminalen.

Jag trodde faktiskt inte att det skulle finnas så många korta domännamn som det gör. Tyvärr fanns det inga lediga domännamn med endast två bokstäver, men med tre finns det 2452 .se-domämner och 7775 .nu-domäner som är lediga.

Till sist kommer programmet jag skrev ihop med Go. Det körs genom go run *.go se.zone.txt .se > se.txt eller go run *.go nu.zone.txt .nu > nu.txt om man inte kompilerar det till en binär. En liten nackdel är att domänerna inte kommer ut i bokstavsordning, men det finns det andra program som kan lösa åt en. I Vim räcker det att skriva in :sort

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"

	"github.com/likexian/whois-go"
)

func main() {
	combos := generateCombinations()

	if len(os.Args) < 3 {
		fmt.Fprintf(os.Stderr, "usage: %s file.txt .se", os.Args[0])
		os.Exit(1)
	}

	f, err := os.Open(os.Args[1])
	if err != nil {
		fmt.Fprintf(os.Stderr, "error opening file: %v", err)
		os.Exit(1)
	}
	r := bufio.NewReader(f)
	line, rerr := readLine(r)
	for rerr == nil {
		parts := strings.Split(line, "\t")
		parts = strings.Split(parts[0], " ")
		domain := parts[0]
		dp := strings.Split(domain, ".")
		if len(dp) < 3 {
			line, rerr = readLine(r)
			continue
		}
		name := ""
		for i := 0; i < len(dp)-2; i++ {
			name = name + dp[i]
		}
		if len(name) < 4 {
			combos[name] = true
		}
		line, rerr = readLine(r)
	}

	items := len(combos)
	lookups := make(chan string, items)
	results := make(chan string, items)

	for i := 1; i < 50; i++ {
		go lookUp(lookups, results)
	}

	for key, b := range combos {
		if b {
			results <- ""
			continue
		}
		lookups <- key + os.Args[2]
	}
	close(lookups)
	for i := 1; i <= items; i++ {
		r := <-results
		if r != "" {
			fmt.Println(r)
		}
	}
}

func generateCombinations() map[string]bool {
	chars := "abcdefghijklmnoprstuvwxyz"
	combos := make(map[string]bool)
	for _, a := range chars {
		combos[string(a)] = false
		for _, b := range chars {
			combos[fmt.Sprintf("%c%c", a, b)] = false
			for _, c := range chars {
				combos[fmt.Sprintf("%c%c%c", a, b, c)] = false
			}
		}
	}

	return combos
}

func readLine(r *bufio.Reader) (string, error) {
	l, _, err := r.ReadLine()
	return string(l), err
}

func lookUp(lookups <-chan string, res chan<- string) {
	for l := range lookups {
		result, err := whois.Whois(l)
		if err != nil {
			res <- ""
			continue
		}
		parts := strings.Split(result, "\n")
		if len(parts) < 2 {
			res <- ""
			continue
		}
		last := parts[len(parts)-2]
		if last != fmt.Sprintf("domain \"%s\" not found.", l) {
			res <- ""
			continue
		}
		res <- l
	}
}