<commit_before>package main

import (
	"fmt"
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/miekg/dns"
)

func dnsSetup(cfg *Config, etc *etcd.Client) chan error {
	dns.HandleFunc(".", func(w dns.ResponseWriter, req *dns.Msg) { dnsQueryServe(cfg, etc, w, req) })
	etc.CreateDir("dns", 0)
	exit := make(chan error, 1)

	go func() {
		exit <- dns.ListenAndServe("0.0.0.0:53", "tcp", nil) // TODO: should use cfg to define the listening ip/port
	}()

	go func() {
		exit <- dns.ListenAndServe("0.0.0.0:53", "udp", nil) // TODO: should use cfg to define the listening ip/port
	}()

	return exit
}

func dnsQueryServe(cfg *Config, etc *etcd.Client, w dns.ResponseWriter, req *dns.Msg) {
	q := req.Question[0]

	if req.MsgHdr.Response == true { // supposed responses sent to us are bogus
		fmt.Printf("DNS Query IS BOGUS %s %s from %s.\n", q.Name, dns.Type(q.Qtype).String(), w.RemoteAddr())
		return
	}

	fmt.Printf("DNS Query %s %s from %s.\n", q.Name, dns.Type(q.Qtype).String(), w.RemoteAddr())

	// TODO: lookup in an in-memory cache (obeying TTLs!)

	qType := dns.Type(q.Qtype).String()                              // query type
	pathParts := strings.Split(strings.TrimSuffix(q.Name, "."), ".") // breakup the queryed name
	queryPath := strings.Join(reverseSlice(pathParts), "/")          // reverse and join them with a slash delimiter
	key := strings.ToLower("/dns/" + queryPath + "/@" + qType)       // structure the lookup key
	response, err := etc.Get(strings.ToLower(key), true, true)       // do the lookup
	if err == nil && response != nil && response.Node != nil && len(response.Node.Nodes) > 0 {
		var vals *etcd.Node
		meta := make(map[string]string)
		for _, node := range response.Node.Nodes {
			nodeKey := strings.Replace(node.Key, key+"/", "", 1)
			if nodeKey == "val" && node.Dir {
				vals = node
			} else if !node.Dir {
				meta[nodeKey] = node.Value // NOTE: the keys are case-sensitive
			}
		}

		answerMsg := new(dns.Msg)
		answerMsg.Id = req.Id
		answerMsg.Response = true
		answerMsg.Authoritative = true
		answerMsg.Question = req.Question
		answerMsg.Rcode = dns.RcodeSuccess
		answerMsg.Extra = []dns.RR{}
		answerTTL := uint32(10800) // this is the default TTL = 3 hours
		gotTTL, _ := strconv.Atoi(meta["TTL"])
		if gotTTL > 0 {
			answerTTL = uint32(gotTTL)
		}

		switch qType {
		case "SOA":
			answer := new(dns.SOA)
			answer.Header().Name = q.Name
			answer.Header().Ttl = answerTTL
			answer.Header().Rrtype = dns.TypeSOA
			answer.Header().Class = dns.ClassINET
			answer.Ns = strings.TrimSuffix(meta["NS"], ".") + "."
			answer.Mbox = strings.TrimSuffix(meta["MBOX"], ".") + "."
			answer.Serial = uint32(time.Now().Unix())
			answer.Refresh = uint32(60) // only used for master->slave timing
			answer.Retry = uint32(60)   // only used for master->slave timing
			answer.Expire = uint32(60)  // only used for master->slave timing
			answer.Minttl = uint32(60)  // how long caching resolvers should cache a miss (NXDOMAIN status)
			answerMsg.Answer = append(answerMsg.Answer, answer)
		default:
			// ... for answers that have values
			if vals != nil && vals.Nodes != nil {
				for _, child := range vals.Nodes {
					if child.Expiration != nil && child.Expiration.Unix() < time.Now().Unix() {
						continue
					}
					if child.TTL > 0 && uint32(child.TTL) < answerTTL {
						answerTTL = uint32(child.TTL)
					}

					// this builds the attributes for complex types, like MX and SRV
					attr := make(map[string]string)
					if child.Nodes != nil {
						for _, attrNode := range child.Nodes {
							nodeKey := strings.Replace(attrNode.Key, child.Key+"/", "", 1)
							attr[nodeKey] = attrNode.Value
						}
					}

					switch qType {
					// FIXME: Add more RR types!
					//        http://godoc.org/github.com/miekg/dns has info as well as
					//        http://en.wikipedia.org/wiki/List_of_DNS_record_types
					case "TXT":
						answer := new(dns.TXT)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeTXT
						answer.Header().Class = dns.ClassINET
						answer.Txt = []string{child.Value}
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "A":
						answer := new(dns.A)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeA
						answer.Header().Class = dns.ClassINET
						answer.A = net.ParseIP(child.Value)
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "AAAA":
						answer := new(dns.AAAA)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeAAAA
						answer.Header().Class = dns.ClassINET
						answer.AAAA = net.ParseIP(child.Value)
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "NS":
						answer := new(dns.NS)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeNS
						answer.Header().Class = dns.ClassINET
						answer.Ns = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "CNAME":
						// FIXME: This is not being used quite correctly.  For example, we
						//        we need to ensure that we are not supplying conflicting
						//        RRs in the same space.  We need to be using the CNAME if
						//        it exists even if the client is asking for a different
						//        RR type.  We have a CNAME in this space and they ask for
						//        an A record?  Return the CNAME!  And maybe even resolve
						//        the value for them.  Or at least carry them as far down
						//        the CNAME chain as possible depending on our willingness
						//        to talk to external DNS servers for recursive queries.
						//        For now, I consider the present behavior incredibly
						//        broken.  It needs to be fixed.  See also DNAME.
						//				... http://en.wikipedia.org/wiki/CNAME_record
						answer := new(dns.CNAME)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeCNAME
						answer.Header().Class = dns.ClassINET
						answer.Target = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "DNAME":
						// FIXME: This is not being used correctly.  See the notes about
						//        fixing CNAME and then consider that DNAME takes it a
						//        big step forward and aliases an entire subtree, not just
						//        a single name in the tree.  Note that this is for pointing
						//        to subtree, not to the self-equivalent.  See the Wikipedia
						//        article about it, linked below.  See also CNAME above.
						//				... http://en.wikipedia.org/wiki/CNAME_record#DNAME_record
						answer := new(dns.DNAME)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeDNAME
						answer.Header().Class = dns.ClassINET
						answer.Target = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "PTR":
						answer := new(dns.PTR)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypePTR
						answer.Header().Class = dns.ClassINET
						answer.Ptr = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "MX":
						answer := new(dns.MX)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeMX
						answer.Header().Class = dns.ClassINET
						answer.Preference = 50 // default if not defined
						priority, err := strconv.Atoi(attr["PRIORITY"])
						if err == nil {
							answer.Preference = uint16(priority)
						}
						if target, ok := attr["TARGET"]; ok {
							answer.Mx = strings.TrimSuffix(target, ".") + "."
						} else if child.Value != "" { // allows for simplified setting
							answer.Mx = strings.TrimSuffix(child.Value, ".") + "."
						}
						// FIXME: are we supposed to be returning these in prio ordering?
						//        ... or maybe it does that for us?  or maybe it's the enduser's problem?
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "SRV":
						answer := new(dns.SRV)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeSRV
						answer.Header().Class = dns.ClassINET
						answer.Priority = 50 // default if not defined
						priority, err := strconv.Atoi(attr["PRIORITY"])
						if err == nil {
							answer.Priority = uint16(priority)
						}
						answer.Weight = 50 // default if not defined
						weight, err := strconv.Atoi(attr["WEIGHT"])
						if err == nil {
							answer.Weight = uint16(weight)
						}
						answer.Port = 50 // default if not defined
						port, err := strconv.Atoi(attr["PORT"])
						if err == nil {
							answer.Port = uint16(port)
						}
						if target, ok := attr["TARGET"]; ok {
							answer.Target = strings.TrimSuffix(target, ".") + "."
						} else if child.Value != "" { // allows for simplified setting
							targetParts := strings.Split(child.Value, ":")
							answer.Target = strings.TrimSuffix(targetParts[0], ".") + "."
							port, err := strconv.Atoi(targetParts[1])
							if err == nil {
								answer.Port = uint16(port)
							}
						}
						// FIXME: are we supposed to be returning these rando-weighted and in priority ordering?
						//        ... or maybe it does that for us?  or maybe it's the enduser's problem?
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "SSHFP":
						// TODO: implement SSHFP
						//       http://godoc.org/github.com/miekg/dns#SSHFP
						//       NOTE: we must implement DNSSEC before using this RR type
					}
				}
			}
		}

		if len(answerMsg.Answer) > 0 {
			for _, answer := range answerMsg.Answer {
				answer.Header().Ttl = answerTTL
			}
			//fmt.Printf("OUR DATA: [%+v]\n", answerMsg)
			w.WriteMsg(answerMsg)

			// TODO: cache the response locally in RAM?

			return
		}
	}

	// check to see if we host this zone; if yes, don't allow use of ext forwarders
	// ... also, check to see if we hit a DNAME so we can handle that aliasing
	allowUseForwarder := true
	{
		keyParts := strings.Split(key, "/")
		for i := len(keyParts); i > 2; i-- {
			parentKey := strings.Join(keyParts[0:i], "/")
			fmt.Printf("PARENTKEY: [%s]\n", parentKey)
			{ // test for an SOA (which tells us we have authority)
				parentKey := parentKey + "/@soa"
				response, err := etc.Get(strings.ToLower(parentKey), false, false) // do the lookup
				if err == nil && response != nil && response.Node != nil {
					fmt.Printf("PARENTKEY EXISTS\n")
					allowUseForwarder = false
					break
				}
			}
			{ // test for a DNAME which has special handling for aliasing of subdomains within
				parentKey := parentKey + "/@dname"
				response, err := etc.Get(strings.ToLower(parentKey), false, false) // do the lookup
				if err == nil && response != nil && response.Node != nil {
					fmt.Printf("DNAME EXISTS!\n")
					allowUseForwarder = false
					// FIXME!  THIS NEEDS TO HANDLE DNAME ALIASING CORRECTLY INSTEAD OF IGNORING IT...
					break
				}
			}
		}
	}

	if allowUseForwarder { // XXX: just for devtime!
		forwarders := cfg.DNSForwarders()
		if len(forwarders) == 0 {
			// we have no upstreams, so we'll just not use any
		} else if strings.TrimSpace(forwarders[0]) == "!" {
			// we've been told explicitly to not pass anything along to any upsteams
		} else {
			c := new(dns.Client)
			for _, server := range forwarders {
				c.Net = "udp"
				m, _, err := c.Exchange(req, strings.TrimSpace(server))

				if m != nil && m.MsgHdr.Truncated {
					c.Net = "tcp"
					m, _, err = c.Exchange(req, strings.TrimSpace(server))
				}

				if err != nil {
					fmt.Println(err)
				} else {
					w.WriteMsg(m)
					return // because we're done here
				}
			}
		}
	}

	// if we got here, it means we didn't find what we were looking for.
	failMsg := new(dns.Msg)
	failMsg.Id = req.Id
	failMsg.Response = true
	failMsg.Authoritative = true
	failMsg.Question = req.Question
	failMsg.Rcode = dns.RcodeNameError
	w.WriteMsg(failMsg)
	return
}

// FIXME: please support DNSSEC, verification, signing, etc...
<commit_msg>Removed erroneous comment<commit_after>package main

import (
	"fmt"
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/miekg/dns"
)

func dnsSetup(cfg *Config, etc *etcd.Client) chan error {
	dns.HandleFunc(".", func(w dns.ResponseWriter, req *dns.Msg) { dnsQueryServe(cfg, etc, w, req) })
	etc.CreateDir("dns", 0)
	exit := make(chan error, 1)

	go func() {
		exit <- dns.ListenAndServe("0.0.0.0:53", "tcp", nil) // TODO: should use cfg to define the listening ip/port
	}()

	go func() {
		exit <- dns.ListenAndServe("0.0.0.0:53", "udp", nil) // TODO: should use cfg to define the listening ip/port
	}()

	return exit
}

func dnsQueryServe(cfg *Config, etc *etcd.Client, w dns.ResponseWriter, req *dns.Msg) {
	q := req.Question[0]

	if req.MsgHdr.Response == true { // supposed responses sent to us are bogus
		fmt.Printf("DNS Query IS BOGUS %s %s from %s.\n", q.Name, dns.Type(q.Qtype).String(), w.RemoteAddr())
		return
	}

	fmt.Printf("DNS Query %s %s from %s.\n", q.Name, dns.Type(q.Qtype).String(), w.RemoteAddr())

	// TODO: lookup in an in-memory cache (obeying TTLs!)

	qType := dns.Type(q.Qtype).String()                              // query type
	pathParts := strings.Split(strings.TrimSuffix(q.Name, "."), ".") // breakup the queryed name
	queryPath := strings.Join(reverseSlice(pathParts), "/")          // reverse and join them with a slash delimiter
	key := strings.ToLower("/dns/" + queryPath + "/@" + qType)       // structure the lookup key
	response, err := etc.Get(strings.ToLower(key), true, true)       // do the lookup
	if err == nil && response != nil && response.Node != nil && len(response.Node.Nodes) > 0 {
		var vals *etcd.Node
		meta := make(map[string]string)
		for _, node := range response.Node.Nodes {
			nodeKey := strings.Replace(node.Key, key+"/", "", 1)
			if nodeKey == "val" && node.Dir {
				vals = node
			} else if !node.Dir {
				meta[nodeKey] = node.Value // NOTE: the keys are case-sensitive
			}
		}

		answerMsg := new(dns.Msg)
		answerMsg.Id = req.Id
		answerMsg.Response = true
		answerMsg.Authoritative = true
		answerMsg.Question = req.Question
		answerMsg.Rcode = dns.RcodeSuccess
		answerMsg.Extra = []dns.RR{}
		answerTTL := uint32(10800) // this is the default TTL = 3 hours
		gotTTL, _ := strconv.Atoi(meta["TTL"])
		if gotTTL > 0 {
			answerTTL = uint32(gotTTL)
		}

		switch qType {
		case "SOA":
			answer := new(dns.SOA)
			answer.Header().Name = q.Name
			answer.Header().Ttl = answerTTL
			answer.Header().Rrtype = dns.TypeSOA
			answer.Header().Class = dns.ClassINET
			answer.Ns = strings.TrimSuffix(meta["NS"], ".") + "."
			answer.Mbox = strings.TrimSuffix(meta["MBOX"], ".") + "."
			answer.Serial = uint32(time.Now().Unix())
			answer.Refresh = uint32(60) // only used for master->slave timing
			answer.Retry = uint32(60)   // only used for master->slave timing
			answer.Expire = uint32(60)  // only used for master->slave timing
			answer.Minttl = uint32(60)  // how long caching resolvers should cache a miss (NXDOMAIN status)
			answerMsg.Answer = append(answerMsg.Answer, answer)
		default:
			// ... for answers that have values
			if vals != nil && vals.Nodes != nil {
				for _, child := range vals.Nodes {
					if child.Expiration != nil && child.Expiration.Unix() < time.Now().Unix() {
						continue
					}
					if child.TTL > 0 && uint32(child.TTL) < answerTTL {
						answerTTL = uint32(child.TTL)
					}

					// this builds the attributes for complex types, like MX and SRV
					attr := make(map[string]string)
					if child.Nodes != nil {
						for _, attrNode := range child.Nodes {
							nodeKey := strings.Replace(attrNode.Key, child.Key+"/", "", 1)
							attr[nodeKey] = attrNode.Value
						}
					}

					switch qType {
					// FIXME: Add more RR types!
					//        http://godoc.org/github.com/miekg/dns has info as well as
					//        http://en.wikipedia.org/wiki/List_of_DNS_record_types
					case "TXT":
						answer := new(dns.TXT)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeTXT
						answer.Header().Class = dns.ClassINET
						answer.Txt = []string{child.Value}
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "A":
						answer := new(dns.A)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeA
						answer.Header().Class = dns.ClassINET
						answer.A = net.ParseIP(child.Value)
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "AAAA":
						answer := new(dns.AAAA)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeAAAA
						answer.Header().Class = dns.ClassINET
						answer.AAAA = net.ParseIP(child.Value)
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "NS":
						answer := new(dns.NS)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeNS
						answer.Header().Class = dns.ClassINET
						answer.Ns = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "CNAME":
						// FIXME: This is not being used quite correctly.  For example, we
						//        we need to ensure that we are not supplying conflicting
						//        RRs in the same space.  We need to be using the CNAME if
						//        it exists even if the client is asking for a different
						//        RR type.  We have a CNAME in this space and they ask for
						//        an A record?  Return the CNAME!  And maybe even resolve
						//        the value for them.  Or at least carry them as far down
						//        the CNAME chain as possible depending on our willingness
						//        to talk to external DNS servers for recursive queries.
						//        For now, I consider the present behavior incredibly
						//        broken.  It needs to be fixed.  See also DNAME.
						//				... http://en.wikipedia.org/wiki/CNAME_record
						answer := new(dns.CNAME)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeCNAME
						answer.Header().Class = dns.ClassINET
						answer.Target = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "DNAME":
						// FIXME: This is not being used correctly.  See the notes about
						//        fixing CNAME and then consider that DNAME takes it a
						//        big step forward and aliases an entire subtree, not just
						//        a single name in the tree.  Note that this is for pointing
						//        to subtree, not to the self-equivalent.  See the Wikipedia
						//        article about it, linked below.  See also CNAME above.
						//				... http://en.wikipedia.org/wiki/CNAME_record#DNAME_record
						answer := new(dns.DNAME)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeDNAME
						answer.Header().Class = dns.ClassINET
						answer.Target = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "PTR":
						answer := new(dns.PTR)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypePTR
						answer.Header().Class = dns.ClassINET
						answer.Ptr = strings.TrimSuffix(child.Value, ".") + "."
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "MX":
						answer := new(dns.MX)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeMX
						answer.Header().Class = dns.ClassINET
						answer.Preference = 50 // default if not defined
						priority, err := strconv.Atoi(attr["PRIORITY"])
						if err == nil {
							answer.Preference = uint16(priority)
						}
						if target, ok := attr["TARGET"]; ok {
							answer.Mx = strings.TrimSuffix(target, ".") + "."
						} else if child.Value != "" { // allows for simplified setting
							answer.Mx = strings.TrimSuffix(child.Value, ".") + "."
						}
						// FIXME: are we supposed to be returning these in prio ordering?
						//        ... or maybe it does that for us?  or maybe it's the enduser's problem?
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "SRV":
						answer := new(dns.SRV)
						answer.Header().Name = q.Name
						answer.Header().Rrtype = dns.TypeSRV
						answer.Header().Class = dns.ClassINET
						answer.Priority = 50 // default if not defined
						priority, err := strconv.Atoi(attr["PRIORITY"])
						if err == nil {
							answer.Priority = uint16(priority)
						}
						answer.Weight = 50 // default if not defined
						weight, err := strconv.Atoi(attr["WEIGHT"])
						if err == nil {
							answer.Weight = uint16(weight)
						}
						answer.Port = 50 // default if not defined
						port, err := strconv.Atoi(attr["PORT"])
						if err == nil {
							answer.Port = uint16(port)
						}
						if target, ok := attr["TARGET"]; ok {
							answer.Target = strings.TrimSuffix(target, ".") + "."
						} else if child.Value != "" { // allows for simplified setting
							targetParts := strings.Split(child.Value, ":")
							answer.Target = strings.TrimSuffix(targetParts[0], ".") + "."
							port, err := strconv.Atoi(targetParts[1])
							if err == nil {
								answer.Port = uint16(port)
							}
						}
						// FIXME: are we supposed to be returning these rando-weighted and in priority ordering?
						//        ... or maybe it does that for us?  or maybe it's the enduser's problem?
						answerMsg.Answer = append(answerMsg.Answer, answer)
					case "SSHFP":
						// TODO: implement SSHFP
						//       http://godoc.org/github.com/miekg/dns#SSHFP
						//       NOTE: we must implement DNSSEC before using this RR type
					}
				}
			}
		}

		if len(answerMsg.Answer) > 0 {
			for _, answer := range answerMsg.Answer {
				answer.Header().Ttl = answerTTL
			}
			//fmt.Printf("OUR DATA: [%+v]\n", answerMsg)
			w.WriteMsg(answerMsg)

			// TODO: cache the response locally in RAM?

			return
		}
	}

	// check to see if we host this zone; if yes, don't allow use of ext forwarders
	// ... also, check to see if we hit a DNAME so we can handle that aliasing
	allowUseForwarder := true
	{
		keyParts := strings.Split(key, "/")
		for i := len(keyParts); i > 2; i-- {
			parentKey := strings.Join(keyParts[0:i], "/")
			fmt.Printf("PARENTKEY: [%s]\n", parentKey)
			{ // test for an SOA (which tells us we have authority)
				parentKey := parentKey + "/@soa"
				response, err := etc.Get(strings.ToLower(parentKey), false, false) // do the lookup
				if err == nil && response != nil && response.Node != nil {
					fmt.Printf("PARENTKEY EXISTS\n")
					allowUseForwarder = false
					break
				}
			}
			{ // test for a DNAME which has special handling for aliasing of subdomains within
				parentKey := parentKey + "/@dname"
				response, err := etc.Get(strings.ToLower(parentKey), false, false) // do the lookup
				if err == nil && response != nil && response.Node != nil {
					fmt.Printf("DNAME EXISTS!\n")
					allowUseForwarder = false
					// FIXME!  THIS NEEDS TO HANDLE DNAME ALIASING CORRECTLY INSTEAD OF IGNORING IT...
					break
				}
			}
		}
	}

	if allowUseForwarder {
		forwarders := cfg.DNSForwarders()
		if len(forwarders) == 0 {
			// we have no upstreams, so we'll just not use any
		} else if strings.TrimSpace(forwarders[0]) == "!" {
			// we've been told explicitly to not pass anything along to any upsteams
		} else {
			c := new(dns.Client)
			for _, server := range forwarders {
				c.Net = "udp"
				m, _, err := c.Exchange(req, strings.TrimSpace(server))

				if m != nil && m.MsgHdr.Truncated {
					c.Net = "tcp"
					m, _, err = c.Exchange(req, strings.TrimSpace(server))
				}

				if err != nil {
					fmt.Println(err)
				} else {
					w.WriteMsg(m)
					return // because we're done here
				}
			}
		}
	}

	// if we got here, it means we didn't find what we were looking for.
	failMsg := new(dns.Msg)
	failMsg.Id = req.Id
	failMsg.Response = true
	failMsg.Authoritative = true
	failMsg.Question = req.Question
	failMsg.Rcode = dns.RcodeNameError
	w.WriteMsg(failMsg)
	return
}

// FIXME: please support DNSSEC, verification, signing, etc...
<|endoftext|>


<commit_before>package egoscale

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
)

// DNSDomain represents a domain
type DNSDomain struct {
	ID             int64  `json:"id"`
	AccountID      int64  `json:"account_id,omitempty"`
	UserID         int64  `json:"user_id,omitempty"`
	RegistrantID   int64  `json:"registrant_id,omitempty"`
	Name           string `json:"name"`
	UnicodeName    string `json:"unicode_name"`
	Token          string `json:"token"`
	State          string `json:"state"`
	Language       string `json:"language,omitempty"`
	Lockable       bool   `json:"lockable"`
	AutoRenew      bool   `json:"auto_renew"`
	WhoisProtected bool   `json:"whois_protected"`
	RecordCount    int64  `json:"record_count"`
	ServiceCount   int64  `json:"service_count"`
	ExpiresOn      string `json:"expires_on,omitempty"`
	CreatedAt      string `json:"created_at"`
	UpdatedAt      string `json:"updated_at"`
}

// DNSDomainResponse represents a domain creation response
type DNSDomainResponse struct {
	Domain *DNSDomain `json:"domain"`
}

// DNSRecord represents a DNS record
type DNSRecord struct {
	ID         int64  `json:"id,omitempty"`
	DomainID   int64  `json:"domain_id,omitempty"`
	Name       string `json:"name"`
	TTL        int    `json:"ttl,omitempty"`
	CreatedAt  string `json:"created_at,omitempty"`
	UpdatedAt  string `json:"updated_at,omitempty"`
	Content    string `json:"content"`
	RecordType string `json:"record_type"`
	Prio       int    `json:"prio,omitempty"`
}

// DNSRecordResponse represents the creation of a DNS record
type DNSRecordResponse struct {
	Record DNSRecord `json:"record"`
}

// DNSErrorResponse represents an error in the API
type DNSErrorResponse struct {
	Message string    `json:"message,omitempty"`
	Errors  *DNSError `json:"errors"`
}

// DNSError represents an error
type DNSError struct {
	Name []string `json:"name"`
}

// Error formats the DNSerror into a string
func (req *DNSErrorResponse) Error() error {
	if req.Errors != nil {
		return fmt.Errorf("DNS error: %s", strings.Join(req.Errors.Name, ", "))
	}
	return fmt.Errorf("DNS error: %s", req.Message)
}

// CreateDomain creates a DNS domain
func (exo *Client) CreateDomain(name string) (*DNSDomain, error) {
	m, err := json.Marshal(DNSDomainResponse{
		Domain: &DNSDomain{
			Name: name,
		},
	})
	if err != nil {
		return nil, err
	}

	resp, err := exo.dnsRequest("/v1/domains", string(m), "POST")
	if err != nil {
		return nil, err
	}

	var d *DNSDomainResponse
	if err := json.Unmarshal(resp, &d); err != nil {
		return nil, err
	}

	return d.Domain, nil
}

// GetDomain gets a DNS domain
func (exo *Client) GetDomain(name string) (*DNSDomain, error) {
	resp, err := exo.dnsRequest("/v1/domains/"+name, "", "GET")
	if err != nil {
		return nil, err
	}

	var d *DNSDomainResponse
	if err := json.Unmarshal(resp, &d); err != nil {
		return nil, err
	}

	return d.Domain, nil
}

// DeleteDomain delets a DNS domain
func (exo *Client) DeleteDomain(name string) error {
	_, err := exo.dnsRequest("/v1/domains/"+name, "", "DELETE")
	if err != nil {
		return err
	}

	return nil
}

// GetRecord returns a DNS record
func (exo *Client) GetRecord(domain string, recordID int64) (*DNSRecord, error) {
	id := strconv.FormatInt(recordID, 10)
	resp, err := exo.dnsRequest("/v1/domains/"+domain+"/records/"+id, "", "GET")
	if err != nil {
		return nil, err
	}

	var r DNSRecordResponse
	if err = json.Unmarshal(resp, &r); err != nil {
		return nil, err
	}

	return &(r.Record), nil
}

// GetRecords returns the DNS records
func (exo *Client) GetRecords(name string) ([]DNSRecord, error) {
	resp, err := exo.dnsRequest("/v1/domains/"+name+"/records", "", "GET")
	if err != nil {
		return nil, err
	}

	var r []DNSRecordResponse
	if err = json.Unmarshal(resp, &r); err != nil {
		return nil, err
	}

	records := make([]DNSRecord, 0, len(r))
	for _, rec := range r {
		records = append(records, rec.Record)
	}

	return records, nil
}

// CreateRecord creates a DNS record
func (exo *Client) CreateRecord(name string, rec DNSRecord) (*DNSRecord, error) {
	body, err := json.Marshal(DNSRecordResponse{
		Record: rec,
	})
	if err != nil {
		return nil, err
	}

	resp, err := exo.dnsRequest("/v1/domains/"+name+"/records", string(body), "POST")
	if err != nil {
		return nil, err
	}

	var r DNSRecordResponse
	if err = json.Unmarshal(resp, &r); err != nil {
		return nil, err
	}

	return &(r.Record), nil
}

// UpdateRecord updates a DNS record
func (exo *Client) UpdateRecord(name string, rec DNSRecord) (*DNSRecord, error) {
	body, err := json.Marshal(DNSRecordResponse{
		Record: rec,
	})
	if err != nil {
		return nil, err
	}

	id := strconv.FormatInt(rec.ID, 10)
	resp, err := exo.dnsRequest("/v1/domains/"+name+"/records/"+id, string(body), "PUT")
	if err != nil {
		return nil, err
	}

	var r DNSRecordResponse
	if err = json.Unmarshal(resp, &r); err != nil {
		return nil, err
	}

	return &(r.Record), nil
}

// DeleteRecord deletes a record
func (exo *Client) DeleteRecord(name string, recordID int64) error {
	id := strconv.FormatInt(recordID, 10)
	_, err := exo.dnsRequest("/v1/domains/"+name+"/records/"+id, "", "DELETE")

	return err
}

func (exo *Client) dnsRequest(uri string, params string, method string) (json.RawMessage, error) {
	url := exo.Endpoint + uri
	req, err := http.NewRequest(method, url, strings.NewReader(params))
	if err != nil {
		return nil, err
	}

	var hdr = make(http.Header)
	hdr.Add("X-DNS-TOKEN", exo.APIKey+":"+exo.apiSecret)
	hdr.Add("User-Agent", fmt.Sprintf("exoscale/egoscale (%v)", Version))
	hdr.Add("Accept", "application/json")
	if params != "" {
		hdr.Add("Content-Type", "application/json")
	}
	req.Header = hdr

	response, err := exo.HTTPClient.Do(req)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close() // nolint: errcheck
	b, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	if response.StatusCode >= 400 {
		var e DNSErrorResponse
		if err := json.Unmarshal(b, &e); err != nil {
			return nil, err
		}
		return nil, e.Error()
	}

	return b, nil
}
<commit_msg>dns: unify client name<commit_after>package egoscale

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
)

// DNSDomain represents a domain
type DNSDomain struct {
	ID             int64  `json:"id"`
	AccountID      int64  `json:"account_id,omitempty"`
	UserID         int64  `json:"user_id,omitempty"`
	RegistrantID   int64  `json:"registrant_id,omitempty"`
	Name           string `json:"name"`
	UnicodeName    string `json:"unicode_name"`
	Token          string `json:"token"`
	State          string `json:"state"`
	Language       string `json:"language,omitempty"`
	Lockable       bool   `json:"lockable"`
	AutoRenew      bool   `json:"auto_renew"`
	WhoisProtected bool   `json:"whois_protected"`
	RecordCount    int64  `json:"record_count"`
	ServiceCount   int64  `json:"service_count"`
	ExpiresOn      string `json:"expires_on,omitempty"`
	CreatedAt      string `json:"created_at"`
	UpdatedAt      string `json:"updated_at"`
}

// DNSDomainResponse represents a domain creation response
type DNSDomainResponse struct {
	Domain *DNSDomain `json:"domain"`
}

// DNSRecord represents a DNS record
type DNSRecord struct {
	ID         int64  `json:"id,omitempty"`
	DomainID   int64  `json:"domain_id,omitempty"`
	Name       string `json:"name"`
	TTL        int    `json:"ttl,omitempty"`
	CreatedAt  string `json:"created_at,omitempty"`
	UpdatedAt  string `json:"updated_at,omitempty"`
	Content    string `json:"content"`
	RecordType string `json:"record_type"`
	Prio       int    `json:"prio,omitempty"`
}

// DNSRecordResponse represents the creation of a DNS record
type DNSRecordResponse struct {
	Record DNSRecord `json:"record"`
}

// DNSErrorResponse represents an error in the API
type DNSErrorResponse struct {
	Message string    `json:"message,omitempty"`
	Errors  *DNSError `json:"errors"`
}

// DNSError represents an error
type DNSError struct {
	Name []string `json:"name"`
}

// Error formats the DNSerror into a string
func (req *DNSErrorResponse) Error() error {
	if req.Errors != nil {
		return fmt.Errorf("DNS error: %s", strings.Join(req.Errors.Name, ", "))
	}
	return fmt.Errorf("DNS error: %s", req.Message)
}

// CreateDomain creates a DNS domain
func (client *Client) CreateDomain(name string) (*DNSDomain, error) {
	m, err := json.Marshal(DNSDomainResponse{
		Domain: &DNSDomain{
			Name: name,
		},
	})
	if err != nil {
		return nil, err
	}

	resp, err := client.dnsRequest("/v1/domains", string(m), "POST")
	if err != nil {
		return nil, err
	}

	var d *DNSDomainResponse
	if err := json.Unmarshal(resp, &d); err != nil {
		return nil, err
	}

	return d.Domain, nil
}

// GetDomain gets a DNS domain
func (client *Client) GetDomain(name string) (*DNSDomain, error) {
	resp, err := client.dnsRequest("/v1/domains/"+name, "", "GET")
	if err != nil {
		return nil, err
	}

	var d *DNSDomainResponse
	if err := json.Unmarshal(resp, &d); err != nil {
		return nil, err
	}

	return d.Domain, nil
}

// DeleteDomain delets a DNS domain
func (client *Client) DeleteDomain(name string) error {
	_, err := client.dnsRequest("/v1/domains/"+name, "", "DELETE")
	if err != nil {
		return err
	}

	return nil
}

// GetRecord returns a DNS record
func (client *Client) GetRecord(domain string, recordID int64) (*DNSRecord, error) {
	id := strconv.FormatInt(recordID, 10)
	resp, err := client.dnsRequest("/v1/domains/"+domain+"/records/"+id, "", "GET")
	if err != nil {
		return nil, err
	}

	var r DNSRecordResponse
	if err = json.Unmarshal(resp, &r); err != nil {
		return nil, err
	}

	return &(r.Record), nil
}

// GetRecords returns the DNS records
func (client *Client) GetRecords(name string) ([]DNSRecord, error) {
	resp, err := client.dnsRequest("/v1/domains/"+name+"/records", "", "GET")
	if err != nil {
		return nil, err
	}

	var r []DNSRecordResponse
	if err = json.Unmarshal(resp, &r); err != nil {
		return nil, err
	}

	records := make([]DNSRecord, 0, len(r))
	for _, rec := range r {
		records = append(records, rec.Record)
	}

	return records, nil
}

// CreateRecord creates a DNS record
func (client *Client) CreateRecord(name string, rec DNSRecord) (*DNSRecord, error) {
	body, err := json.Marshal(DNSRecordResponse{
		Record: rec,
	})
	if err != nil {
		return nil, err
	}

	resp, err := client.dnsRequest("/v1/domains/"+name+"/records", string(body), "POST")
	if err != nil {
		return nil, err
	}

	var r DNSRecordResponse
	if err = json.Unmarshal(resp, &r); err != nil {
		return nil, err
	}

	return &(r.Record), nil
}

// UpdateRecord updates a DNS record
func (client *Client) UpdateRecord(name string, rec DNSRecord) (*DNSRecord, error) {
	body, err := json.Marshal(DNSRecordResponse{
		Record: rec,
	})
	if err != nil {
		return nil, err
	}

	id := strconv.FormatInt(rec.ID, 10)
	resp, err := client.dnsRequest("/v1/domains/"+name+"/records/"+id, string(body), "PUT")
	if err != nil {
		return nil, err
	}

	var r DNSRecordResponse
	if err = json.Unmarshal(resp, &r); err != nil {
		return nil, err
	}

	return &(r.Record), nil
}

// DeleteRecord deletes a record
func (client *Client) DeleteRecord(name string, recordID int64) error {
	id := strconv.FormatInt(recordID, 10)
	_, err := client.dnsRequest("/v1/domains/"+name+"/records/"+id, "", "DELETE")

	return err
}

func (client *Client) dnsRequest(uri string, params string, method string) (json.RawMessage, error) {
	url := client.Endpoint + uri
	req, err := http.NewRequest(method, url, strings.NewReader(params))
	if err != nil {
		return nil, err
	}

	var hdr = make(http.Header)
	hdr.Add("X-DNS-TOKEN", client.APIKey+":"+client.apiSecret)
	hdr.Add("User-Agent", fmt.Sprintf("exoscale/egoscale (%v)", Version))
	hdr.Add("Accept", "application/json")
	if params != "" {
		hdr.Add("Content-Type", "application/json")
	}
	req.Header = hdr

	response, err := client.HTTPClient.Do(req)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close() // nolint: errcheck
	b, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	if response.StatusCode >= 400 {
		var e DNSErrorResponse
		if err := json.Unmarshal(b, &e); err != nil {
			return nil, err
		}
		return nil, e.Error()
	}

	return b, nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"regexp"
	"sort"
	"strings"
	"time"

	ldns "github.com/Liamraystanley/go-ldns"
	sempool "github.com/Liamraystanley/go-sempool"
	"github.com/miekg/dns"
)

var recordTypes = [...]string{"A", "AAAA", "CNAME", "MX", "NS", "TXT"}

// ^(?:(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s{1,})?(?P<domain>(?:(?:[A-Za-z0-9_.-]{2,350}\.[A-Za-z0-9]{2,63})\s+)+)$
var reDomain = regexp.MustCompile(`^[A-Za-z0-9_.-]{2,350}\.[A-Za-z0-9]{2,63}$`)
var reRawDomain = regexp.MustCompile(`^(?:(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+)?(?P<domains>[A-Za-z0-9_.\s-]{6,})$`)
var reSpaces = regexp.MustCompile(`^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$`)
var reNewlines = regexp.MustCompile(`[\n\r]+`)

// Host represents an item to look up
type Host struct {
	Name string
	Want string
}

type DNSAnswer struct {
	Query        string
	Want         string
	Raw          []string
	Answers      []string
	Answer       string
	ResponseTime string
	Error        string
	RType        string
	IsMatch      bool
}

type DNSResults struct {
	Request  Request
	Records  Answer
	RType    string
	ScanTime string
}

type Request []*Host
type Answer []*DNSAnswer

func (ans Answer) Len() int {
	return len(ans)
}

func (ans Answer) Less(i, j int) bool {
	// if one is erronous, and one is not
	if ans[i].Error != "" && ans[j].Error == "" {
		return true
	} else if ans[i].Error == "" && ans[j].Error != "" {
		return false
	}

	if ans[i].IsMatch == ans[j].IsMatch {
		return ans[i].Query < ans[j].Query
	}

	if ans[i].IsMatch {
		return false
	}

	return true
}

func (ans Answer) Swap(i, j int) {
	ans[i], ans[j] = ans[j], ans[i]
}

func parseHosts(hosts string) (out []*Host, err error) {
	input := strings.Split(reNewlines.ReplaceAllString(reSpaces.ReplaceAllString(hosts, ""), "\n"), "\n")

	var knownHosts []string

	for i := 0; i < len(input); i++ {
		line := reRawDomain.FindStringSubmatch(reSpaces.ReplaceAllString(input[i], ""))
		if len(line) != 3 {
			return nil, errors.New("erronous input")
		}

		for _, domain := range strings.Split(line[2], " ") {
			if domain == "" {
				return nil, errors.New("erronous input")
			}

			domain = strings.Trim(domain, " ")
			if !reDomain.MatchString(domain) {
				return nil, errors.New("erronous input")
			}

			ip, host := line[1], domain

			if host == "" {
				return nil, errors.New("erronous input")
			}

			// verify it's not already within the list
			var alreadyExists bool
			for k := 0; k < len(knownHosts); k++ {
				if knownHosts[k] == host {
					alreadyExists = true
					break
				}
			}
			if alreadyExists {
				continue // skip it
			}

			// track this host to prevent duplicate checks
			knownHosts = append(knownHosts, host)

			out = append(out, &Host{Name: host, Want: ip})
		}
	}

	return out, nil
}

func fmtTime(t time.Duration) string {
	ms := float32(t.Nanoseconds()) / 1000000.0

	return fmt.Sprintf("%.2fms", ms)
}

func LookupAll(hosts []*Host, servers []string, rtype string) (*DNSResults, error) {
	if len(hosts) > conf.Limit {
		return nil, errors.New("Too many queries to process.")
	}

	if len(servers) == 0 {
		return nil, errors.New("No resolvers configured")
	}

	out := &DNSResults{}
	out.ScanTime = time.Now().Format(time.RFC3339)
	out.Request = hosts
	out.RType = rtype
	var lookupType uint16

	switch rtype {
	case "A":
		lookupType = dns.TypeA
	case "":
		lookupType = dns.TypeA
	case "AAAA":
		lookupType = dns.TypeAAAA
	case "CNAME":
		lookupType = dns.TypeCNAME
	case "MX":
		lookupType = dns.TypeMX
	case "NS":
		lookupType = dns.TypeNS
	case "TXT":
		lookupType = dns.TypeTXT
	default:
		return nil, errors.New("Invalid lookup type")
	}

	pool := sempool.New(conf.Concurrency)
	r, err := ldns.New(servers)
	if err != nil {
		return nil, err
	}

	for i := 0; i < len(hosts); i++ {
		pool.Slot()

		go func(host *Host) {
			defer pool.Free()

			result, err := r.Lookup(host.Name, lookupType)
			if err != nil {
				out.Records = append(out.Records, &DNSAnswer{
					Query: host.Name,
					Want:  host.Want,
					RType: rtype,
					Error: err.Error(),
				})
				return
			}

			ans := &DNSAnswer{
				Query:        result.Host,
				Want:         host.Want,
				RType:        result.QueryType(),
				Answer:       result.String(),
				ResponseTime: fmtTime(result.RTT),
			}

			for a := 0; a < len(result.Records); a++ {
				ans.Answers = append(ans.Answers, result.Records[a].String())

				if !ans.IsMatch && (result.Records[a].String() == ans.Want || len(ans.Want) == 0 || lookupType != dns.TypeA) {
					// currently, only A records are comparable. in the future, this should support anything,
					// though it would require the user entering this to compare.
					ans.IsMatch = true
				}
			}

			ans.Answer = result.String()

			out.Records = append(out.Records, ans)
		}(hosts[i])
	}

	pool.Wait()

	sort.Sort(out.Records)

	return out, nil
}
<commit_msg>start working on stats data<commit_after>package main

import (
	"errors"
	"fmt"
	"regexp"
	"sort"
	"strings"
	"time"

	ldns "github.com/Liamraystanley/go-ldns"
	sempool "github.com/Liamraystanley/go-sempool"
	"github.com/miekg/dns"
)

var recordTypes = [...]string{"A", "AAAA", "CNAME", "MX", "NS", "TXT"}

// ^(?:(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s{1,})?(?P<domain>(?:(?:[A-Za-z0-9_.-]{2,350}\.[A-Za-z0-9]{2,63})\s+)+)$
var reDomain = regexp.MustCompile(`^[A-Za-z0-9_.-]{2,350}\.[A-Za-z0-9]{2,63}$`)
var reRawDomain = regexp.MustCompile(`^(?:(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+)?(?P<domains>[A-Za-z0-9_.\s-]{6,})$`)
var reSpaces = regexp.MustCompile(`^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$`)
var reNewlines = regexp.MustCompile(`[\n\r]+`)

// Host represents an item to look up
type Host struct {
	Name string
	Want string
}

type DNSAnswer struct {
	Query        string
	Want         string
	Raw          []string
	Answers      []string
	ResponseTime string
	Error        string
	RType        string
	IsMatch      bool
}

func (a *DNSAnswer) String() string {
	return strings.Join(a.Answers, ", ")
}

type DNSResults struct {
	Request  Request
	Records  Answer
	RType    string
	ScanTime string
}

type DNSStats struct {
	Matched    float32
	NotMatched float32
	Erronous   float32
	AnsPercent AnsCountList
}

type AnsCountAnswer struct {
	Answer     string
	Percentage float32
	Count      int
}

type AnsCountList []*AnsCountAnswer

func (ans AnsCountList) Len() int {
	return len(ans)
}

func (ans AnsCountList) Less(i, j int) bool {
	if ans[i].Percentage > ans[j].Percentage {
		return true
	}

	return false
}

func (ans AnsCountList) Swap(i, j int) {
	ans[i], ans[j] = ans[j], ans[i]
}

func (res *DNSResults) Stats() (stats DNSStats, err error) {
	var matched, notMatched, erronous float32

	answerMap := make(map[string]int)

	for i := 0; i < len(res.Records); i++ {
		if res.Records[i].IsMatch {
			matched++
		} else {
			notMatched++
		}

		if len(res.Records[i].Error) != 0 {
			erronous++
		}

		if len(res.Records[i].Error) == 0 {
			if _, ok := answerMap[res.Records[i].String()]; !ok {
				answerMap[res.Records[i].String()] = 0
			}

			answerMap[res.Records[i].String()]++
		}
	}

	// match percentages
	stats.Matched = matched / float32(len(res.Records)) * 100
	stats.NotMatched = notMatched / float32(len(res.Records)) * 100

	// error percentages
	stats.Erronous = erronous / float32(len(res.Records)) * 100

	// generate a list of most common answers
	for ans, count := range answerMap {
		stats.AnsPercent = append(stats.AnsPercent, &AnsCountAnswer{
			Answer:     ans,
			Count:      count,
			Percentage: float32(count) / float32(len(res.Records)) * 100,
		})
	}

	sort.Sort(stats.AnsPercent)

	return stats, nil
}

type Request []*Host
type Answer []*DNSAnswer

func (ans Answer) Len() int {
	return len(ans)
}

func (ans Answer) Less(i, j int) bool {
	// if one is erronous, and one is not
	if ans[i].Error != "" && ans[j].Error == "" {
		return true
	} else if ans[i].Error == "" && ans[j].Error != "" {
		return false
	}

	if ans[i].IsMatch == ans[j].IsMatch {
		return ans[i].Query < ans[j].Query
	}

	if ans[i].IsMatch {
		return false
	}

	return true
}

func (ans Answer) Swap(i, j int) {
	ans[i], ans[j] = ans[j], ans[i]
}

func parseHosts(hosts string) (out []*Host, err error) {
	input := strings.Split(reNewlines.ReplaceAllString(reSpaces.ReplaceAllString(hosts, ""), "\n"), "\n")

	var knownHosts []string

	for i := 0; i < len(input); i++ {
		line := reRawDomain.FindStringSubmatch(reSpaces.ReplaceAllString(input[i], ""))
		if len(line) != 3 {
			return nil, errors.New("erronous input")
		}

		for _, domain := range strings.Split(line[2], " ") {
			if domain == "" {
				return nil, errors.New("erronous input")
			}

			domain = strings.Trim(domain, " ")
			if !reDomain.MatchString(domain) {
				return nil, errors.New("erronous input")
			}

			ip, host := line[1], domain

			if host == "" {
				return nil, errors.New("erronous input")
			}

			// verify it's not already within the list
			var alreadyExists bool
			for k := 0; k < len(knownHosts); k++ {
				if knownHosts[k] == host {
					alreadyExists = true
					break
				}
			}
			if alreadyExists {
				continue // skip it
			}

			// track this host to prevent duplicate checks
			knownHosts = append(knownHosts, host)

			out = append(out, &Host{Name: host, Want: ip})
		}
	}

	return out, nil
}

func fmtTime(t time.Duration) string {
	ms := float32(t.Nanoseconds()) / 1000000.0

	return fmt.Sprintf("%.2fms", ms)
}

func LookupAll(hosts []*Host, servers []string, rtype string) (*DNSResults, error) {
	if len(hosts) > conf.Limit {
		return nil, errors.New("Too many queries to process.")
	}

	if len(servers) == 0 {
		return nil, errors.New("No resolvers configured")
	}

	out := &DNSResults{}
	out.ScanTime = time.Now().Format(time.RFC3339)
	out.Request = hosts
	out.RType = rtype
	var lookupType uint16

	switch rtype {
	case "A":
		lookupType = dns.TypeA
	case "":
		lookupType = dns.TypeA
	case "AAAA":
		lookupType = dns.TypeAAAA
	case "CNAME":
		lookupType = dns.TypeCNAME
	case "MX":
		lookupType = dns.TypeMX
	case "NS":
		lookupType = dns.TypeNS
	case "TXT":
		lookupType = dns.TypeTXT
	default:
		return nil, errors.New("Invalid lookup type")
	}

	pool := sempool.New(conf.Concurrency)
	r, err := ldns.New(servers)
	if err != nil {
		return nil, err
	}

	for i := 0; i < len(hosts); i++ {
		pool.Slot()

		go func(host *Host) {
			defer pool.Free()

			result, err := r.Lookup(host.Name, lookupType)
			if err != nil {
				out.Records = append(out.Records, &DNSAnswer{
					Query: host.Name,
					Want:  host.Want,
					RType: rtype,
					Error: err.Error(),
				})
				return
			}

			ans := &DNSAnswer{
				Query:        result.Host,
				Want:         host.Want,
				RType:        result.QueryType(),
				ResponseTime: fmtTime(result.RTT),
			}

			for a := 0; a < len(result.Records); a++ {
				ans.Answers = append(ans.Answers, result.Records[a].String())

				if !ans.IsMatch && (result.Records[a].String() == ans.Want || len(ans.Want) == 0 || lookupType != dns.TypeA) {
					// currently, only A records are comparable. in the future, this should support anything,
					// though it would require the user entering this to compare.
					ans.IsMatch = true
				}
			}

			out.Records = append(out.Records, ans)
		}(hosts[i])
	}

	pool.Wait()

	sort.Sort(out.Records)

	return out, nil
}
<|endoftext|>


<commit_before>//go:generate gen/docstring.sh

/*
lf is a terminal file manager.

Source code can be found in the repository at https://github.com/gokcehan/lf.

This documentation can either be read from terminal using "lf -doc" or online
at https://godoc.org/github.com/gokcehan/lf.

Reference

The following commands and default keybindings are provided by lf.

    up                (default "k" and "<up>")
    half-up           (default "<c-u>")
    page-up           (default "<c-b>")
    down              (default "j" and "<down>")
    half-down         (default "<c-d>")
    page-down         (default "<c-f>")
    updir             (default "h" and "<left>")
    open              (default "l" and "<right>")
    quit              (default "q")
    bot               (default "G")
    top               (default "gg")
    read              (default ":")
    read-shell        (default "$")
    read-shell-wait   (default "!")
    read-shell-async  (default "&")
    search            (default "/")
    search-back       (default "?")
    toggle            (default "<space>")
    yank              (default "y")
    delete            (default "d")
    paste             (default "p")
    renew             (default "<c-l>")

The following options can be used to customize the behavior of lf.

    hidden     bool    (default off)
    preview    bool    (default on)
    scrolloff  int     (default 0)
    tabstop    int     (default 8)
    ifs        string  (default "") (not exported if empty)
    shell      string  (default "$SHELL")
    showinfo   string  (default "none")
    sortby     string  (default "name")
    ratios     string  (default "1:2:3")

The following variables are exported for shell commands.

    $f   current file
    $fs  marked file(s) separated with ':'
    $fx  current file or marked file(s) if any

The configuration file should either be located in "$XDG_CONFIG_HOME/lf/lfrc"
or "~/.config/lf/lfrc". A sample configuration file can be found at
https://github.com/gokcehan/lf/blob/master/etc/lfrc.example.

Prefixes

The following command prefixes are used by lf:

    :  read (default)
    $  read-shell
    !  read-shell-wait
    &  read-shell-async
    /  search
    ?  search-back

The same evaluator is used for the command line and the configuration file. The
difference is that prefixes are not necessary in the command line. Instead
different modes are provided to read corresponding commands. Note that by
default these modes are mapped to the prefix keys above.

Syntax

Characters from "#" to the line end are comments and ignored.

There are three special commands for configuration.

"set" is used to set an option which could be (1) bool (e.g. "set hidden", "set
nohidden", "set hidden!") (2) int (e.g. "set scrolloff 10") (3) string (e.g.
"set sortby time").

"map" is used to bind a key to a command which could be (1) built-in command
(e.g. "map gh cd ~") (2) custom command (e.g. "map D trash") (3) shell command
(e.g. "map i $less "$f"", "map u !du -h . | less"). You can delete an existing
binding by leaving the expression empty (e.g. "map gh").

"cmd" is used to define a custom command or delete an existing command by
leaving the expression empty (e.g. "cmd trash").

If there is no prefix then ":" is assumed. An explicit ":" could be provided to
group statements until a "\n" occurs. This is especially useful for "map" and
"cmd" commands. If you need multiline you can wrap statements in "{{" and "}}"
after the proper prefix.

Yank/Delete/Paste

lf uses the underlying "cp" and "mv" shell commands for file operations. For
this purpose, when you "yank" (i.e. copy) a file, it doesn't actually copy the
file on the disk, but only records its name to memory. The actual file
operation takes place when you do the "paste" in which case the "cp" command is
used. Similarly the "mv" command is used for "delete" (i.e. cut or kill)
followed by "paste". These traditional names (e.g. "yank" and "delete") are
picked instead of the other common convention (e.g. copy and cut) to resemble
the default keybinds for these operations.

Custom Commands

To wrap up let us write a shell command to move selected file(s) to trash.

A first attempt to write such a command may look like this:

    cmd trash ${{
        mkdir -p ~/.trash
        if [ -z $fs ]; then
            mv --backup=numbered "$f" $HOME/.trash
        else
            IFS=':'; mv --backup=numbered $fs $HOME/.trash
        fi
    }}

We check "$fs" to see if there are any marked files. Otherwise we just delete
the current file. Since this is such a common pattern, a separate "$fx"
variable is provided. We can use this variable to get rid of the conditional.

    cmd trash ${{
        mkdir -p ~/.trash
        IFS=':'; mv --backup=numbered $fx $HOME/.trash
    }}

The trash directory is checked each time the command is executed. We can move
it outside of the command so it would only run once at startup.

    ${{ mkdir -p ~/.trash }}

    cmd trash ${{ IFS=':'; mv --backup=numbered $fx $HOME/.trash }}

Since these are one liners, we can drop "{{" and "}}".

    $mkdir -p ~/.trash

    cmd trash $IFS=':'; mv --backup=numbered $fx $HOME/.trash

Finally note that we set "IFS" variable accordingly in the command. Instead we
could use the "ifs" option to set it for all commands (e.g. "set ifs ':'").
This could be especially useful for interactive use (e.g. "rm $fs" would simply
work). This option is not set by default as things may behave unexpectedly at
other places.

Opening Files

You can use "open-file" command to open a file. This is a special command
called by "open" when the current file is not a directory. Normally a user maps
the "open" command to a key (default "l") and customize "open-file" command as
desired. You can define it just as you would define any other command.

    cmd open-file $IFS=':'; vim $fx

It is possible to use different command types.

    cmd open-file &xdg-open "$f"

You may want to use either file extensions or mime types with "file".

    cmd open-file ${{
        case $(file --mime-type "$f" -b) in
            text/*) IFS=':'; vim $fx;;
            *) IFS=':'; for f in $fx; do xdg-open "$f" &> /dev/null & done;;
        esac
    }}

lf does not come bundled with a file opener. You can use any of the existing
file openers as you like. Possible options are "open" (for Mac OS X only),
"xdg-utils" (executable name is "xdg-open"), "libfile-mimeinfo-perl"
(executable name is "mimeopen"), "rifle" (ranger's default file opener), or
"mimeo" to name a few.
*/
package main
<commit_msg>cleanup<commit_after>//go:generate gen/docstring.sh

/*
lf is a terminal file manager.

Source code can be found in the repository at https://github.com/gokcehan/lf.

This documentation can either be read from terminal using "lf -doc" or online
at https://godoc.org/github.com/gokcehan/lf.

Reference

The following commands and default keybindings are provided by lf.

    up                (default "k" and "<up>")
    half-up           (default "<c-u>")
    page-up           (default "<c-b>")
    down              (default "j" and "<down>")
    half-down         (default "<c-d>")
    page-down         (default "<c-f>")
    updir             (default "h" and "<left>")
    open              (default "l" and "<right>")
    quit              (default "q")
    bot               (default "G")
    top               (default "gg")
    read              (default ":")
    read-shell        (default "$")
    read-shell-wait   (default "!")
    read-shell-async  (default "&")
    search            (default "/")
    search-back       (default "?")
    toggle            (default "<space>")
    yank              (default "y")
    delete            (default "d")
    paste             (default "p")
    renew             (default "<c-l>")

The following options can be used to customize the behavior of lf.

    hidden     bool    (default off)
    preview    bool    (default on)
    scrolloff  int     (default 0)
    tabstop    int     (default 8)
    ifs        string  (default "") (not exported if empty)
    shell      string  (default "$SHELL")
    showinfo   string  (default "none")
    sortby     string  (default "name")
    ratios     string  (default "1:2:3")

The following variables are exported for shell commands.

    $f   current file
    $fs  marked file(s) separated with ':'
    $fx  current file or marked file(s) if any

The configuration file should either be located in "$XDG_CONFIG_HOME/lf/lfrc"
or "~/.config/lf/lfrc". A sample configuration file can be found at
https://github.com/gokcehan/lf/blob/master/etc/lfrc.example.

Prefixes

The following command prefixes are used by lf:

    :  read (default)
    $  read-shell
    !  read-shell-wait
    &  read-shell-async
    /  search
    ?  search-back

The same evaluator is used for the command line and the configuration file. The
difference is that prefixes are not necessary in the command line. Instead
different modes are provided to read corresponding commands. Note that by
default these modes are mapped to the prefix keys above.

Syntax

Characters from "#" to the line end are comments and ignored.

There are three special commands for configuration.

"set" is used to set an option which could be (1) bool (e.g. "set hidden", "set
nohidden", "set hidden!") (2) int (e.g. "set scrolloff 10") (3) string (e.g.
"set sortby time").

"map" is used to bind a key to a command which could be (1) built-in command
(e.g. "map gh cd ~") (2) custom command (e.g. "map D trash") (3) shell command
(e.g. "map i $less "$f"", "map u !du -h . | less"). You can delete an existing
binding by leaving the expression empty (e.g. "map gh").

"cmd" is used to define a custom command or delete an existing command by
leaving the expression empty (e.g. "cmd trash").

If there is no prefix then ":" is assumed. An explicit ":" could be provided to
group statements until a "\n" occurs. This is especially useful for "map" and
"cmd" commands. If you need multiline you can wrap statements in "{{" and "}}"
after the proper prefix.

File Operations

lf uses the underlying "cp" and "mv" shell commands for file operations. For
this purpose, when you "yank" (i.e. copy) a file, it doesn't actually copy the
file on the disk, but only records its name to memory. The actual file
operation takes place when you do the "paste" in which case the "cp" command is
used. Similarly the "mv" command is used for "delete" (i.e. cut or kill)
followed by "paste". These traditional names (e.g. "yank" and "delete") are
picked instead of the other common convention (e.g. copy and cut) to resemble
the default keybinds for these operations.

Custom Commands

To wrap up let us write a shell command to move selected file(s) to trash.

A first attempt to write such a command may look like this:

    cmd trash ${{
        mkdir -p ~/.trash
        if [ -z $fs ]; then
            mv --backup=numbered "$f" $HOME/.trash
        else
            IFS=':'; mv --backup=numbered $fs $HOME/.trash
        fi
    }}

We check "$fs" to see if there are any marked files. Otherwise we just delete
the current file. Since this is such a common pattern, a separate "$fx"
variable is provided. We can use this variable to get rid of the conditional.

    cmd trash ${{
        mkdir -p ~/.trash
        IFS=':'; mv --backup=numbered $fx $HOME/.trash
    }}

The trash directory is checked each time the command is executed. We can move
it outside of the command so it would only run once at startup.

    ${{ mkdir -p ~/.trash }}

    cmd trash ${{ IFS=':'; mv --backup=numbered $fx $HOME/.trash }}

Since these are one liners, we can drop "{{" and "}}".

    $mkdir -p ~/.trash

    cmd trash $IFS=':'; mv --backup=numbered $fx $HOME/.trash

Finally note that we set "IFS" variable accordingly in the command. Instead we
could use the "ifs" option to set it for all commands (e.g. "set ifs ':'").
This could be especially useful for interactive use (e.g. "rm $fs" would simply
work). This option is not set by default as things may behave unexpectedly at
other places.

Opening Files

You can use "open-file" command to open a file. This is a special command
called by "open" when the current file is not a directory. Normally a user maps
the "open" command to a key (default "l") and customize "open-file" command as
desired. You can define it just as you would define any other command.

    cmd open-file $IFS=':'; vim $fx

It is possible to use different command types.

    cmd open-file &xdg-open "$f"

You may want to use either file extensions or mime types with "file".

    cmd open-file ${{
        case $(file --mime-type "$f" -b) in
            text/*) IFS=':'; vim $fx;;
            *) IFS=':'; for f in $fx; do xdg-open "$f" &> /dev/null & done;;
        esac
    }}

lf does not come bundled with a file opener. You can use any of the existing
file openers as you like. Possible options are "open" (for Mac OS X only),
"xdg-utils" (executable name is "xdg-open"), "libfile-mimeinfo-perl"
(executable name is "mimeopen"), "rifle" (ranger's default file opener), or
"mimeo" to name a few.
*/
package main
<|endoftext|>


<commit_before>/*
Package response is a helper for the Core.
It provides syntactic sugar that let's you easily write responses on *core.Context.

Usage

Here is the full example status, string, bytes, JSON and view responses:

	package main

	import (
		"net/http"
		"strings"

		"github.com/volatile/core"
		"github.com/volatile/response"
		"github.com/volatile/route"
	)

	func main() {
		// We set functions for views templates.
		response.ViewsFuncs(response.FuncMap{
			"toUpper": strings.ToUpper,
			"toLower": strings.ToLower,
		})

		// Status response
		route.Get("^/status$", func(c *core.Context) {
			response.Status(c, http.StatusForbidden)
		})

		// String response
		route.Get("^/string$", func(c *core.Context) {
			response.String(c, "Hello, World!")
		})

		// Bytes response
		route.Get("^/bytes$", func(c *core.Context) {
			response.Bytes(c, []byte("Hello, World!"))
		})

		// JSON response
		route.Get("^/json$", func(c *core.Context) {
			response.JSON(c, &Car{
				ID:    1,
				Brand: "Bentley",
				Model: "Continental GT",
			})
		})

		// View response
		route.Get("^/(?P<name>[A-Za-z]+)$", func(c *core.Context, params map[string]string) {
			response.View(c, "hello", params)
		})

		core.Run()
	}

	type Car struct {
		ID    int    `json:"id"`
		Brand string `json:"brand"`
		Model string `json:"model"`
	}

Views

The views templates are recursively parsed from the "views" directory, just before running the server.
The file names and extensions and the directory organization doesn't matter. All the files are parsed.

Template functions

To use functions in your views, use response.ViewsFuncs(response.FuncMap{}), like with the `html/template` standard package.
*/
package response
<commit_msg>Update GoDoc<commit_after>/*
Package response is a helper for the Core.
It provides syntactic sugar that let's you easily write responses on *core.Context.

Usage

A full example with status, string, bytes, JSON and view responses:

	package main

	import (
		"net/http"
		"strings"

		"github.com/volatile/core"
		"github.com/volatile/response"
		"github.com/volatile/route"
	)

	func main() {
		// We set functions for views templates.
		response.ViewsFuncs(response.FuncMap{
			"toUpper": strings.ToUpper,
			"toLower": strings.ToLower,
		})

		// Status response
		route.Get("^/status$", func(c *core.Context) {
			response.Status(c, http.StatusForbidden)
		})

		// String response
		route.Get("^/string$", func(c *core.Context) {
			response.String(c, "Hello, World!")
		})

		// Bytes response
		route.Get("^/bytes$", func(c *core.Context) {
			response.Bytes(c, []byte("Hello, World!"))
		})

		// JSON response
		route.Get("^/json$", func(c *core.Context) {
			response.JSON(c, &Car{
				ID:    1,
				Brand: "Bentley",
				Model: "Continental GT",
			})
		})

		// View response
		route.Get("^/(?P<name>[A-Za-z]+)$", func(c *core.Context, params map[string]string) {
			response.View(c, "hello", params)
		})

		core.Run()
	}

	type Car struct {
		ID    int    `json:"id"`
		Brand string `json:"brand"`
		Model string `json:"model"`
	}

Views

The views templates are recursively parsed from the "views" directory, just before running the server.
The file names and extensions and the directory organization doesn't matter. All the files are parsed.

Template functions

To use functions in your views, use response.ViewsFuncs(response.FuncMap{}), like with the `html/template` standard package.
*/
package response
<|endoftext|>


<commit_before>// Package grump implements a grumpy cryptosystem for asynchronous messages.
//
// Grump is an experiment in building a post-PGP, post-Snowden cryptosystem for
// asynchronously sending confidential and authentic messages of arbitrary
// size. It is an experiment, and should literally never be used in a context
// where the confidentiality or authenticity of a message has any value
// whatsoever. It is intended to be a conversation piece, and has not been
// vetted, analyzed, reviewed, or even heard of by a security professional. You
// are better off CC'ing a copy of your communications to the FBI than using
// this. No touchy.
//
//     It was on display in the bottom of a locked filing cabinet stuck in a
//     disused lavatory with a sign on the door saying "Beware of the Leopard".
//
// I swear, I will slap you.
//
// Threat Model
//
// The threat model is defined in terms of what each possible attacker can
// achieve. The list is intended to be exhaustive, i.e. if an entity can do
// something that is not listed here then that should count as a break of Grump.
//
//
// Assumptions About The User
//
// The user must act reasonably and in their best interest. They must not reveal
// their passphrase or share unencrypted copies of messages.
//
// The user must run a copy of Grump which has not been suborned.
//
//
// Assumptions About The User's Computer
//
// The user's computer computer must function correctly and not be compromised
// by malware.
//
//
// Assumptions About The User's Friends
//
// The user's friends must also act reasonably.
//
// The user's friends must also run copies of Grump which have not been
// suborned.
//
//
// Assumptions About The World
//
// The security assumptions and proofs of Curve25519
// (http://cr.yp.to/ecdh.html), Ed25519 (http://ed25519.cr.yp.to/), ChaCha20
// (http://cr.yp.to/chacha.html), Poly1305 (http://cr.yp.to/mac.html), their
// combination (http://eprint.iacr.org/2014/613.pdf), SHA-512
// (http://csrc.nist.gov/publications/nistpubs/800-107-rev1/sp800-107-rev1.pdf),
// HKDF (https://tools.ietf.org/html/rfc5869), and scrypt
// (http://www.tarsnap.com/scrypt/scrypt.pdf) must be valid.
//
//
// Threats From Global Passive Adversaries
//
// A Global Passive Adversary is an attacker who can eavesdrop on internet
// traffic globally. A GPA can:
//
//     * Identify Grump messages as Grump messages.
//     * Observe when Grump messages are sent, and to whom they are sent.
//     * Learn the upper bound of the size of a Grump message.
//     * Learn the upper bound of the number of recipients of a Grump message.
//     * Identify Grump keys, both public and private, as such.
//     * Observe when Grump keys are sent or exchanged, and with whom.
//
//
// Threats From Active Network Adversaries
//
// An attacker with a privileged network position to the user can:
//
//     * Identify Grump messages as Grump messages.
//     * Observe when Grump messages are sent, and to whom they are sent if the
//       message is sent directly (e.g., via email).
//     * Learn the upper bound of the size of a Grump message.
//     * Learn the upper bound of the number of recipients of a Grump message.
//     * Identify Grump keys, both public and private, as such.
//     * Observe when Grump keys are sent or exchanged, and with whom if the
//       keys are sent directly.
//     * Modify messages such that they are no longer valid.
//     * Block messages from being sent.
//     * Replay previously recorded messages.
//
//
// Seizure Of The User's Computer
//
// An attacker who physically seizes the user's computer (or compromises the
// user's backups) can:
//
//     * Attempt an offline dictionary/brute force attack on the user's
//       passphrase. If successful, the attacker will be able to decrypt all
//       messages encrypted with the user's public key as well as forge
//       arbitrary messages from the user.
//     * Analyze the user's collection of other people's public keys, which may
//       potentially aide traffic analysis and de-anonymization efforts.
//
//
// Compromise Of The User's Computer
//
// An attacker who physically compromises the user's computer can:
//
//     * Recover the user's passphrase, allowing them to decrypt all existing
//       messages encrypted with the user's public key as well as forge
//       arbitrary messages from the user.
//     * Analyze the user's public keys of other people's public keys, which may
//       potentially aide traffic analysis and de-anonymization efforts.
//
//
// Recipients Of A Message
//
// A valid recipient of a message can:
//
//     * Prove, to some degree, that a particular message came from a particular
//       user.
//
//
// Thirsty Randos
//
// A thirsty rando can:
//
//     * Stop playing in my mentions.
//
//
// If A Non-Recipient Attempts To Read A Message
//
// Without their public key being used to encrypt a copy of the message key, a
// non-recipient would have to brute force the 256-bit ChaCha20Poly1305 key used
// to encrypt a message key copy.
//
//
// If A Non-Recipient Attempts To Modify A Message
//
// A more crafty attacker may attempt to modify an existing message.
//
// If they modify the header, either by adding junk recipients or by removing
// valid recipients, the resulting message will be considered invalid when the
// first packet fails to decrypt due to the SHA-512 hash of the header being
// different.
//
// If they modify either the nonce or the ciphertext of a packet in the message,
// ChaCha20Poly1305 will fail to authenticate and decryption will halt.
//
// If they duplicate a packet or modify the order of the packets, the SHA-512
// hash will change and that packet and all following packets will fail to
// decrypt.
//
// Finally, the Ed25519 signature will fail to verify if any non-signature bytes
// in the message change.
//
//
// If A Recipient Attempts To Modify A Message
//
// A recipient has the message key, which allows them to forge packets for a
// message. While this would allow them to copy the header from a message and
// replace all the contents with valid ChaCha20Poly1305 ciphertexts, the Ed25519
// signature will fail to verify and decryption will halt.
//
//
// If A Rando Plays With Protobufs
//
// The sole aspect of a message which is unauthenticated is the Protocol Buffer
// and framing metadata. An attacker can craft a message which has protobuf
// frames which are up to 4GiB. Grump implementations should perform hygiene
// checks on these values, and Protocol Buffer implementations should be checked
// for robustness to these types of attacks.
//
// If a packet's frame is changed to, for example, elide data, the SHA-512 hash
// will change and the packet will become invalid.
//
//
// Design
//
// The general design is largely informed by ECIES
// (http://en.wikipedia.org/wiki/Integrated_Encryption_Scheme), in that it
// combines a key exchange algorithm, a key derivation algorithm, and an
// IND-CCA2 AEAD scheme.
//
//
// Protocol Buffers
//
// X.509 is better than nothing, but it's horrible. Grump uses Protocol Buffers,
// which has extremely broad language support
// (https://code.google.com/p/protobuf/wiki/ThirdPartyAddOns).
//
//
// Curve25519 & Ed25519
//
// These algorithms were selected because they're both fast, constant-time,
// require entropy for key generation only, use small keys, and use safe
// elliptic curves (http://safecurves.cr.yp.to).
//
//
// ChaCha20Poly1305
//
// The ChaChaPoly1305 AEAD construction was chosen because it's fast and
// constant-time.
//
//
// scrypt
//
// scrypt was chosen as a PBKDF algorithm because it's resistant to CPU-, GPU-,
// FPGA-, and ASIC-optimized dictionary attacks.
//
//
// SHA-512 and HKDF
//
// These were selected as a KDF because Ed25519 already uses SHA-512.
//
//
// Keys
//
// A Grump private key is the combination of a Curve25519 decryption key and a
// Ed25519 signing key. Both are stored on disk encrypted with ChaCha20Poly1305,
// using a key derived from your passphrase via scrypt.
//
// A Grump public key is the combination of a Curve25519 encryption key and a
// Ed25519 verifying key.
//
// No, there isn't any way to keep track of whose keys are whose.
//
//
// Messages
//
// Grump messages are a sequence of framed protobufs (little-endian, 32-bit
// frame value, then that many bytes of protobuf).
//
// A message starts with a header, which is contains the message key (a random
// 256-bit key) encrypted with the shared secret for each recipient (the
// Curve25519 ECDH shared secret, run through HKDF-SHA-512) using
// ChaCha20Poly1305. There is no meta-data, so random values can be added here
// to obscure the actual number of recipients.
//
// (To decrypt a message, one simply iterates through the recipients, trying
// each.)
//
// After the header comes a series of packets of arbitrary sizes, which are
// portions of the plaintext encrypted with ChaCha20Poly1305, using the SHA-512
// hash of all previously written bytes as the authenticated data. The last
// packet in a message is flagged as such.
//
// The final element of a message is an Ed25519 signature of the SHA-512 hash of
// every byte in the message which precedes the signature's frame.  Any attempt
// to modify the message will fail this verification.
package grump
<commit_msg>Fix godoc headers.<commit_after>// Package grump implements a grumpy cryptosystem for asynchronous messages.
//
// Grump is an experiment in building a post-PGP, post-Snowden cryptosystem for
// asynchronously sending confidential and authentic messages of arbitrary
// size. It is an experiment, and should literally never be used in a context
// where the confidentiality or authenticity of a message has any value
// whatsoever. It is intended to be a conversation piece, and has not been
// vetted, analyzed, reviewed, or even heard of by a security professional. You
// are better off CC'ing a copy of your communications to the FBI than using
// this. No touchy.
//
//     It was on display in the bottom of a locked filing cabinet stuck in a
//     disused lavatory with a sign on the door saying "Beware of the Leopard".
//
// I swear, I will slap you.
//
// Threat Model
//
// The threat model is defined in terms of what each possible attacker can
// achieve. The list is intended to be exhaustive, i.e. if an entity can do
// something that is not listed here then that should count as a break of Grump.
//
//
// Assumptions About The User
//
// The user must act reasonably and in their best interest. They must not reveal
// their passphrase or share unencrypted copies of messages.
//
// The user must run a copy of Grump which has not been suborned.
//
//
// Assumptions About The User's Computer
//
// The user's computer computer must function correctly and not be compromised
// by malware.
//
//
// Assumptions About The User's Friends
//
// The user's friends must also act reasonably.
//
// The user's friends must also run copies of Grump which have not been
// suborned.
//
//
// Assumptions About The World
//
// The security assumptions and proofs of Curve25519
// (http://cr.yp.to/ecdh.html), Ed25519 (http://ed25519.cr.yp.to/), ChaCha20
// (http://cr.yp.to/chacha.html), Poly1305 (http://cr.yp.to/mac.html), their
// combination (http://eprint.iacr.org/2014/613.pdf), SHA-512
// (http://csrc.nist.gov/publications/nistpubs/800-107-rev1/sp800-107-rev1.pdf),
// HKDF (https://tools.ietf.org/html/rfc5869), and scrypt
// (http://www.tarsnap.com/scrypt/scrypt.pdf) must be valid.
//
//
// Threats From Global Passive Adversaries
//
// A Global Passive Adversary is an attacker who can eavesdrop on internet
// traffic globally. A GPA can:
//
//     * Identify Grump messages as Grump messages.
//     * Observe when Grump messages are sent, and to whom they are sent.
//     * Learn the upper bound of the size of a Grump message.
//     * Learn the upper bound of the number of recipients of a Grump message.
//     * Identify Grump keys, both public and private, as such.
//     * Observe when Grump keys are sent or exchanged, and with whom.
//
//
// Threats From Active Network Adversaries
//
// An attacker with a privileged network position to the user can:
//
//     * Identify Grump messages as Grump messages.
//     * Observe when Grump messages are sent, and to whom they are sent if the
//       message is sent directly (e.g., via email).
//     * Learn the upper bound of the size of a Grump message.
//     * Learn the upper bound of the number of recipients of a Grump message.
//     * Identify Grump keys, both public and private, as such.
//     * Observe when Grump keys are sent or exchanged, and with whom if the
//       keys are sent directly.
//     * Modify messages such that they are no longer valid.
//     * Block messages from being sent.
//     * Replay previously recorded messages.
//
//
// Seizure Of The User's Computer
//
// An attacker who physically seizes the user's computer (or compromises the
// user's backups) can:
//
//     * Attempt an offline dictionary/brute force attack on the user's
//       passphrase. If successful, the attacker will be able to decrypt all
//       messages encrypted with the user's public key as well as forge
//       arbitrary messages from the user.
//     * Analyze the user's collection of other people's public keys, which may
//       potentially aide traffic analysis and de-anonymization efforts.
//
//
// Compromise Of The User's Computer
//
// An attacker who physically compromises the user's computer can:
//
//     * Recover the user's passphrase, allowing them to decrypt all existing
//       messages encrypted with the user's public key as well as forge
//       arbitrary messages from the user.
//     * Analyze the user's public keys of other people's public keys, which may
//       potentially aide traffic analysis and de-anonymization efforts.
//
//
// Recipients Of A Message
//
// A valid recipient of a message can:
//
//     * Prove, to some degree, that a particular message came from a particular
//       user.
//
//
// Thirsty Randos
//
// A thirsty rando can:
//
//     * Stop playing in my mentions.
//
//
// If A Non-Recipient Attempts To Read A Message
//
// Without their public key being used to encrypt a copy of the message key, a
// non-recipient would have to brute force the 256-bit ChaCha20Poly1305 key used
// to encrypt a message key copy.
//
//
// If A Non-Recipient Attempts To Modify A Message
//
// A more crafty attacker may attempt to modify an existing message.
//
// If they modify the header, either by adding junk recipients or by removing
// valid recipients, the resulting message will be considered invalid when the
// first packet fails to decrypt due to the SHA-512 hash of the header being
// different.
//
// If they modify either the nonce or the ciphertext of a packet in the message,
// ChaCha20Poly1305 will fail to authenticate and decryption will halt.
//
// If they duplicate a packet or modify the order of the packets, the SHA-512
// hash will change and that packet and all following packets will fail to
// decrypt.
//
// Finally, the Ed25519 signature will fail to verify if any non-signature bytes
// in the message change.
//
//
// If A Recipient Attempts To Modify A Message
//
// A recipient has the message key, which allows them to forge packets for a
// message. While this would allow them to copy the header from a message and
// replace all the contents with valid ChaCha20Poly1305 ciphertexts, the Ed25519
// signature will fail to verify and decryption will halt.
//
//
// If A Rando Plays With Protobufs
//
// The sole aspect of a message which is unauthenticated is the Protocol Buffer
// and framing metadata. An attacker can craft a message which has protobuf
// frames which are up to 4GiB. Grump implementations should perform hygiene
// checks on these values, and Protocol Buffer implementations should be checked
// for robustness to these types of attacks.
//
// If a packet's frame is changed to, for example, elide data, the SHA-512 hash
// will change and the packet will become invalid.
//
//
// Design
//
// The general design is largely informed by ECIES
// (http://en.wikipedia.org/wiki/Integrated_Encryption_Scheme), in that it
// combines a key exchange algorithm, a key derivation algorithm, and an
// IND-CCA2 AEAD scheme.
//
//
// Protocol Buffers
//
// X.509 is better than nothing, but it's horrible. Grump uses Protocol Buffers,
// which has extremely broad language support
// (https://code.google.com/p/protobuf/wiki/ThirdPartyAddOns).
//
//
// Curve25519 And Ed25519
//
// These algorithms were selected because they're both fast, constant-time,
// require entropy for key generation only, use small keys, and use safe
// elliptic curves (http://safecurves.cr.yp.to).
//
//
// ChaCha20Poly1305
//
// The ChaChaPoly1305 AEAD construction was chosen because it's fast and
// constant-time.
//
//
// Scrypt
//
// scrypt was chosen as a PBKDF algorithm because it's resistant to CPU-, GPU-,
// FPGA-, and ASIC-optimized dictionary attacks.
//
//
// SHA-512 and HKDF
//
// These were selected as a KDF because Ed25519 already uses SHA-512.
//
//
// Keys
//
// A Grump private key is the combination of a Curve25519 decryption key and a
// Ed25519 signing key. Both are stored on disk encrypted with ChaCha20Poly1305,
// using a key derived from your passphrase via scrypt.
//
// A Grump public key is the combination of a Curve25519 encryption key and a
// Ed25519 verifying key.
//
// No, there isn't any way to keep track of whose keys are whose.
//
//
// Messages
//
// Grump messages are a sequence of framed protobufs (little-endian, 32-bit
// frame value, then that many bytes of protobuf).
//
// A message starts with a header, which is contains the message key (a random
// 256-bit key) encrypted with the shared secret for each recipient (the
// Curve25519 ECDH shared secret, run through HKDF-SHA-512) using
// ChaCha20Poly1305. There is no meta-data, so random values can be added here
// to obscure the actual number of recipients.
//
// (To decrypt a message, one simply iterates through the recipients, trying
// each.)
//
// After the header comes a series of packets of arbitrary sizes, which are
// portions of the plaintext encrypted with ChaCha20Poly1305, using the SHA-512
// hash of all previously written bytes as the authenticated data. The last
// packet in a message is flagged as such.
//
// The final element of a message is an Ed25519 signature of the SHA-512 hash of
// every byte in the message which precedes the signature's frame.  Any attempt
// to modify the message will fail this verification.
package grump
<|endoftext|>


<commit_before>/*
Package syslog5424 implements syslog RFC 5424 with a completly API compatibility with the standard log.Logger

the simpliest way to use syslog5424 is :

	package main

	import	(
		"github.com/nathanaelle/syslog5424"
	)

	func main() {
		// create a connection the standard error
		sl_conn, _, _ := syslog5424.Dial( "stdio", "stderr:" )

		// create a syslog wrapper around the connection
		// the program is named "test-app" and you log to LogDAEMON facility at least at LogWARNING level
		syslog,_ := syslog5424.New( sl_conn, syslog5424.LogDAEMON|syslog5424.LogWARNING, "test-app" )

		// create a channel for the level LogERR
		err_channel	:= syslog.Channel( syslog5424.LogERR )

		// get a the *log.Logger for this channel with a prefix "ERR : "
		logger_err := err_channel.Logger( "ERR : " )

		// log a message through the log.Logger
		logger_err.Print( "doing some stuff" )
	}

*/
package syslog5424 // import "github.com/nathanaelle/syslog5424/v2"
<commit_msg>correction in doc<commit_after>/*

Package syslog5424 implements syslog RFC 5424 with a complete compatibility with the standard log.Logger API.

the simpliest way to use syslog5424 is :

	package main

	import	(
		"github.com/nathanaelle/syslog5424"
	)

	func main() {
		// create a connection the standard error
		sl_conn, _, _ := syslog5424.Dial( "stdio", "stderr:" )

		// create a syslog wrapper around the connection
		// the program is named "test-app" and you log to LogDAEMON facility at least at LogWARNING level
		syslog,_ := syslog5424.New( sl_conn, syslog5424.LogDAEMON|syslog5424.LogWARNING, "test-app" )

		// create a channel for the level LogERR
		err_channel	:= syslog.Channel( syslog5424.LogERR )

		// get a the *log.Logger for this channel with a prefix "ERR : "
		logger_err := err_channel.Logger( "ERR : " )

		// log a message through the log.Logger
		logger_err.Print( "doing some stuff" )
	}

*/
package syslog5424 // import "github.com/nathanaelle/syslog5424/v2"
<|endoftext|>


<commit_before>// A library to create in process topologies of goroutines connected by channels.
// Topo does boilerplate work as outlined in http://blog.golang.org/pipelines.
// You receive correctly connected input and output channels, leaving the
// message processing for you while handling the plumbing.
//
// Example Code
//
// 	package main
//
// 	import (
// 		"fmt"
// 		"sync"
//
// 		"github.com/mdmarek/topo"
// 		"github.com/mdmarek/topo/topoutil"
// 	)
//
// 	const seed = 12282
// 	const nworkers = 2
//
// 	func main() {
// 		wg := new(sync.WaitGroup)
// 		wg.Add(nworkers)
//
// 		// Create a new topo and source of streaming data from meetup.com.
// 		t := topo.New(seed)
// 		source, err := topoutil.NewMeetupSource(t)
//
// 		if err != nil {
// 			fmt.Printf("Failed to open source: %v\n", err)
// 			return
// 		}
//
// 		// Shuffles messages read from the source
// 		// to each output channel.
// 		outputs := t.Shuffle(nworkers, source)
//
// 		// Each output channel is read by one Sink, which
// 		// prints to stdout the messages it receives.
// 		for i := 0; i < nworkers; i++ {
// 			go topoutil.Sink(i, wg, outputs[i])
// 		}
//
// 		// Wait for the sinks to finish, if ever.
// 		wg.Wait()
// 	}
//<commit_msg>Go doc fix.<commit_after>// A library to create in process topologies of goroutines connected by channels.
// Topo does boilerplate work as outlined in http://blog.golang.org/pipelines.
// You receive correctly connected input and output channels, leaving the
// message processing for you while handling the plumbing.
//
// Example Code
//
// 	package main
//
// 	import (
// 		"fmt"
// 		"sync"
//
// 		"github.com/mdmarek/topo"
// 		"github.com/mdmarek/topo/topoutil"
// 	)
//
// 	const seed = 12282
// 	const nworkers = 2
//
// 	func main() {
// 		wg := new(sync.WaitGroup)
// 		wg.Add(nworkers)
//
// 		// Create a new topo and source of streaming data from meetup.com.
// 		t := topo.New(seed)
// 		source, err := topoutil.NewMeetupSource(t)
//
// 		if err != nil {
// 			fmt.Printf("Failed to open source: %v\n", err)
// 			return
// 		}
//
// 		// Shuffles messages read from the source
// 		// to each output channel.
// 		outputs := t.Shuffle(nworkers, source)
//
// 		// Each output channel is read by one Sink, which
// 		// prints to stdout the messages it receives.
// 		for i := 0; i < nworkers; i++ {
// 			go topoutil.Sink(i, wg, outputs[i])
// 		}
//
// 		// Wait for the sinks to finish, if ever.
// 		wg.Wait()
// 	}
//
package topo
<|endoftext|>


<commit_before>/*
A Redis storage backend for osin.

Installation:

	go get github.com/ShaleApps/osinredis

Usage:

	import (
			"github.com/RangelReale/osin"
			"github.com/ShaleApps/osinredis"
			"github.com/garyburd/redigo/redis"
	)

	func main() {
		pool = &redis.Pool{
			Dial: func() (redis.Conn, error) {
				conn, err := redis.Dial("tcp", ":6379")
				if err != nil {
					return nil, err
				}
				return conn, nil
			},
		}

		storage := osinredis.New(pool, "prefix")
		server := osin.NewServer(osin.NewServerConfig(), storage)
	}

*/
package osinredis
<commit_msg>Fix doc.go tabs<commit_after>/*
A Redis storage backend for osin.

Installation:

	go get github.com/ShaleApps/osinredis

Usage:

	import (
		"github.com/RangelReale/osin"
		"github.com/ShaleApps/osinredis"
		"github.com/garyburd/redigo/redis"
	)

	func main() {
		pool = &redis.Pool{
			Dial: func() (redis.Conn, error) {
				conn, err := redis.Dial("tcp", ":6379")
				if err != nil {
					return nil, err
				}
				return conn, nil
			},
		}

		storage := osinredis.New(pool, "prefix")
		server := osin.NewServer(osin.NewServerConfig(), storage)
	}

*/
package osinredis
<|endoftext|>


<commit_before>// Copyright 2017 Stratumn SAS. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Stratumn's SDK to create Indigo applications and networks.
//
// Unless otherwise noted, the source files are distributed under the Mozilla
// Public License 2.0 found in the LICENSE file.
//
// Third party dependencies included in the vendor directory are distributed
// under their respective licenses.
package stratumngo
<commit_msg>fix SDK doc<commit_after>// Copyright 2017 Stratumn SAS. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package sdk contains Stratumn tools and functionality to create Indigo
// applications and networks.
//
// Unless otherwise noted, the source files are distributed under the Mozilla
// Public License 2.0 found in the LICENSE file.
//
// Third party dependencies included in the vendor directory are distributed
// under their respective licenses.
package sdk
<|endoftext|>