<commit_before>package nameserver

import (
	"bytes"
	"fmt"
	"math/rand"
	"net"
	"sort"
	"strings"
	"time"

	"github.com/miekg/dns"

	"github.com/weaveworks/weave/net/address"
)

const (
	topDomain        = "."
	reverseDNSdomain = "in-addr.arpa."
	etcResolvConf    = "/etc/resolv.conf"
	udpBuffSize      = uint16(4096)
	minUDPSize       = 512

	DefaultListenAddress = "0.0.0.0:53"
	DefaultTTL           = 1
	DefaultClientTimeout = 5 * time.Second
)

type DNSServer struct {
	ns      *Nameserver
	domain  string
	ttl     uint32
	address string

	servers   []*dns.Server
	upstream  *dns.ClientConfig
	tcpClient *dns.Client
	udpClient *dns.Client
}

func NewDNSServer(ns *Nameserver, domain string, address string, ttl uint32, clientTimeout time.Duration) (*DNSServer, error) {

	s := &DNSServer{
		ns:        ns,
		domain:    dns.Fqdn(domain),
		ttl:       ttl,
		address:   address,
		tcpClient: &dns.Client{Net: "tcp", ReadTimeout: clientTimeout},
		udpClient: &dns.Client{Net: "udp", ReadTimeout: clientTimeout, UDPSize: udpBuffSize},
	}
	var err error
	if s.upstream, err = dns.ClientConfigFromFile(etcResolvConf); err != nil {
		return nil, err
	}

	err = s.listen(address)
	return s, err
}

func (d *DNSServer) String() string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "WeaveDNS (%s)\n", d.ns.ourName)
	fmt.Fprintf(&buf, "  listening on %s, for domain %s\n", d.address, d.domain)
	fmt.Fprintf(&buf, "  response ttl %d\n", d.ttl)
	return buf.String()
}

func (d *DNSServer) listen(address string) error {
	udpListener, err := net.ListenPacket("udp", address)
	if err != nil {
		return err
	}
	udpServer := &dns.Server{PacketConn: udpListener, Handler: d.createMux(d.udpClient, minUDPSize)}

	tcpListener, err := net.Listen("tcp", address)
	if err != nil {
		udpServer.Shutdown()
		return err
	}
	tcpServer := &dns.Server{Listener: tcpListener, Handler: d.createMux(d.tcpClient, -1)}

	d.servers = []*dns.Server{udpServer, tcpServer}
	return nil
}

func (d *DNSServer) ActivateAndServe() {
	for _, server := range d.servers {
		go func(server *dns.Server) {
			server.ActivateAndServe()
		}(server)
	}
}

func (d *DNSServer) Stop() error {
	for _, server := range d.servers {
		if err := server.Shutdown(); err != nil {
			return err
		}
	}
	return nil
}

func (d *DNSServer) errorResponse(r *dns.Msg, code int, w dns.ResponseWriter) {
	m := dns.Msg{}
	m.SetReply(r)
	m.RecursionAvailable = true
	m.Rcode = code

	d.ns.debugf("error response: %+v", m)
	if err := w.WriteMsg(&m); err != nil {
		d.ns.infof("error responding: %v", err)
	}
}

type handler struct {
	*DNSServer
	maxResponseSize int
	client          *dns.Client
}

func (d *DNSServer) createMux(client *dns.Client, defaultMaxResponseSize int) *dns.ServeMux {
	m := dns.NewServeMux()
	h := &handler{
		DNSServer:       d,
		maxResponseSize: defaultMaxResponseSize,
		client:          client,
	}
	m.HandleFunc(d.domain, h.handleLocal)
	m.HandleFunc(reverseDNSdomain, h.handleReverse)
	m.HandleFunc(topDomain, h.handleRecursive)
	return m
}

func (h *handler) handleLocal(w dns.ResponseWriter, req *dns.Msg) {
	h.ns.debugf("local request: %+v", *req)
	if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypeA {
		h.errorResponse(req, dns.RcodeNameError, w)
		return
	}

	hostname := dns.Fqdn(req.Question[0].Name)
	if strings.Count(hostname, ".") == 1 {
		hostname = hostname + h.domain
	}

	addrs := h.ns.Lookup(hostname)
	if len(addrs) == 0 {
		h.errorResponse(req, dns.RcodeNameError, w)
		return
	}

	header := dns.RR_Header{
		Name:   req.Question[0].Name,
		Rrtype: dns.TypeA,
		Class:  dns.ClassINET,
		Ttl:    h.ttl,
	}
	answers := make([]dns.RR, len(addrs))
	for i, addr := range addrs {
		ip := addr.IP4()
		answers[i] = &dns.A{Hdr: header, A: ip}
	}
	shuffleAnswers(&answers)

	response := h.makeResponse(req, answers)
	h.ns.debugf("response: %+v", response)
	if err := w.WriteMsg(response); err != nil {
		h.ns.infof("error responding: %v", err)
	}
}

func (h *handler) handleReverse(w dns.ResponseWriter, req *dns.Msg) {
	h.ns.debugf("reverse request: %+v", *req)
	if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypePTR {
		h.errorResponse(req, dns.RcodeNameError, w)
		return
	}

	ipStr := strings.TrimSuffix(req.Question[0].Name, "."+reverseDNSdomain)
	ip, err := address.ParseIP(ipStr)
	if err != nil {
		h.errorResponse(req, dns.RcodeNameError, w)
		return
	}

	hostname, err := h.ns.ReverseLookup(ip.Reverse())
	if err != nil {
		h.handleRecursive(w, req)
		return
	}

	header := dns.RR_Header{
		Name:   req.Question[0].Name,
		Rrtype: dns.TypePTR,
		Class:  dns.ClassINET,
		Ttl:    h.ttl,
	}
	answers := []dns.RR{&dns.PTR{
		Hdr: header,
		Ptr: hostname,
	}}

	response := h.makeResponse(req, answers)
	h.ns.debugf("response: %+v", response)
	if err := w.WriteMsg(response); err != nil {
		h.ns.infof("error responding: %v", err)
	}
}

func (h *handler) handleRecursive(w dns.ResponseWriter, req *dns.Msg) {
	h.ns.debugf("recursive request: %+v", *req)

	// Resolve unqualified names locally
	if len(req.Question) == 1 && req.Question[0].Qtype == dns.TypeA {
		hostname := dns.Fqdn(req.Question[0].Name)
		if strings.Count(hostname, ".") == 1 {
			h.handleLocal(w, req)
			return
		}
	}

	for _, server := range h.upstream.Servers {
		reqCopy := req.Copy()
		reqCopy.Id = dns.Id()
		response, _, err := h.client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, h.upstream.Port))
		if err != nil || response == nil {
			h.ns.debugf("error trying %s: %v", server, err)
			continue
		}
		h.ns.debugf("response: %+v", response)
		response.Id = req.Id
		if response.Len() > h.getMaxResponseSize(req) {
			response.Compress = true
		}
		if err := w.WriteMsg(response); err != nil {
			h.ns.infof("error responding: %v", err)
		}
		return
	}

	h.errorResponse(req, dns.RcodeServerFailure, w)
}

func (h *handler) makeResponse(req *dns.Msg, answers []dns.RR) *dns.Msg {
	response := &dns.Msg{}
	response.SetReply(req)
	response.RecursionAvailable = true
	response.Authoritative = true
	response.Answer = answers

	maxSize := h.getMaxResponseSize(req)
	if len(response.Answer) <= 1 || maxSize <= 0 {
		return response
	}

	// search for smallest i that is too big
	i := sort.Search(len(response.Answer), func(i int) bool {
		// return true if too big
		response.Answer = answers[:i+1]
		return response.Len() > maxSize
	})
	if i == len(answers) {
		response.Answer = answers
		return response
	}

	response.Answer = answers[:i]
	response.Truncated = true
	return response
}

func (h *handler) getMaxResponseSize(req *dns.Msg) int {
	if opt := req.IsEdns0(); opt != nil {
		return int(opt.UDPSize())
	}
	return h.maxResponseSize
}

func shuffleAnswers(answers *[]dns.RR) {
	if len(*answers) <= 1 {
		return
	}

	for i := range *answers {
		j := rand.Intn(i + 1)
		(*answers)[i], (*answers)[j] = (*answers)[j], (*answers)[i]
	}
}
<commit_msg>simplify response truncation<commit_after>package nameserver

import (
	"bytes"
	"fmt"
	"math/rand"
	"net"
	"sort"
	"strings"
	"time"

	"github.com/miekg/dns"

	"github.com/weaveworks/weave/net/address"
)

const (
	topDomain        = "."
	reverseDNSdomain = "in-addr.arpa."
	etcResolvConf    = "/etc/resolv.conf"
	udpBuffSize      = uint16(4096)
	minUDPSize       = 512

	DefaultListenAddress = "0.0.0.0:53"
	DefaultTTL           = 1
	DefaultClientTimeout = 5 * time.Second
)

type DNSServer struct {
	ns      *Nameserver
	domain  string
	ttl     uint32
	address string

	servers   []*dns.Server
	upstream  *dns.ClientConfig
	tcpClient *dns.Client
	udpClient *dns.Client
}

func NewDNSServer(ns *Nameserver, domain string, address string, ttl uint32, clientTimeout time.Duration) (*DNSServer, error) {

	s := &DNSServer{
		ns:        ns,
		domain:    dns.Fqdn(domain),
		ttl:       ttl,
		address:   address,
		tcpClient: &dns.Client{Net: "tcp", ReadTimeout: clientTimeout},
		udpClient: &dns.Client{Net: "udp", ReadTimeout: clientTimeout, UDPSize: udpBuffSize},
	}
	var err error
	if s.upstream, err = dns.ClientConfigFromFile(etcResolvConf); err != nil {
		return nil, err
	}

	err = s.listen(address)
	return s, err
}

func (d *DNSServer) String() string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "WeaveDNS (%s)\n", d.ns.ourName)
	fmt.Fprintf(&buf, "  listening on %s, for domain %s\n", d.address, d.domain)
	fmt.Fprintf(&buf, "  response ttl %d\n", d.ttl)
	return buf.String()
}

func (d *DNSServer) listen(address string) error {
	udpListener, err := net.ListenPacket("udp", address)
	if err != nil {
		return err
	}
	udpServer := &dns.Server{PacketConn: udpListener, Handler: d.createMux(d.udpClient, minUDPSize)}

	tcpListener, err := net.Listen("tcp", address)
	if err != nil {
		udpServer.Shutdown()
		return err
	}
	tcpServer := &dns.Server{Listener: tcpListener, Handler: d.createMux(d.tcpClient, -1)}

	d.servers = []*dns.Server{udpServer, tcpServer}
	return nil
}

func (d *DNSServer) ActivateAndServe() {
	for _, server := range d.servers {
		go func(server *dns.Server) {
			server.ActivateAndServe()
		}(server)
	}
}

func (d *DNSServer) Stop() error {
	for _, server := range d.servers {
		if err := server.Shutdown(); err != nil {
			return err
		}
	}
	return nil
}

func (d *DNSServer) errorResponse(r *dns.Msg, code int, w dns.ResponseWriter) {
	m := dns.Msg{}
	m.SetReply(r)
	m.RecursionAvailable = true
	m.Rcode = code

	d.ns.debugf("error response: %+v", m)
	if err := w.WriteMsg(&m); err != nil {
		d.ns.infof("error responding: %v", err)
	}
}

type handler struct {
	*DNSServer
	maxResponseSize int
	client          *dns.Client
}

func (d *DNSServer) createMux(client *dns.Client, defaultMaxResponseSize int) *dns.ServeMux {
	m := dns.NewServeMux()
	h := &handler{
		DNSServer:       d,
		maxResponseSize: defaultMaxResponseSize,
		client:          client,
	}
	m.HandleFunc(d.domain, h.handleLocal)
	m.HandleFunc(reverseDNSdomain, h.handleReverse)
	m.HandleFunc(topDomain, h.handleRecursive)
	return m
}

func (h *handler) handleLocal(w dns.ResponseWriter, req *dns.Msg) {
	h.ns.debugf("local request: %+v", *req)
	if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypeA {
		h.errorResponse(req, dns.RcodeNameError, w)
		return
	}

	hostname := dns.Fqdn(req.Question[0].Name)
	if strings.Count(hostname, ".") == 1 {
		hostname = hostname + h.domain
	}

	addrs := h.ns.Lookup(hostname)
	if len(addrs) == 0 {
		h.errorResponse(req, dns.RcodeNameError, w)
		return
	}

	header := dns.RR_Header{
		Name:   req.Question[0].Name,
		Rrtype: dns.TypeA,
		Class:  dns.ClassINET,
		Ttl:    h.ttl,
	}
	answers := make([]dns.RR, len(addrs))
	for i, addr := range addrs {
		ip := addr.IP4()
		answers[i] = &dns.A{Hdr: header, A: ip}
	}
	shuffleAnswers(&answers)

	response := h.makeResponse(req, answers)
	h.ns.debugf("response: %+v", response)
	if err := w.WriteMsg(response); err != nil {
		h.ns.infof("error responding: %v", err)
	}
}

func (h *handler) handleReverse(w dns.ResponseWriter, req *dns.Msg) {
	h.ns.debugf("reverse request: %+v", *req)
	if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypePTR {
		h.errorResponse(req, dns.RcodeNameError, w)
		return
	}

	ipStr := strings.TrimSuffix(req.Question[0].Name, "."+reverseDNSdomain)
	ip, err := address.ParseIP(ipStr)
	if err != nil {
		h.errorResponse(req, dns.RcodeNameError, w)
		return
	}

	hostname, err := h.ns.ReverseLookup(ip.Reverse())
	if err != nil {
		h.handleRecursive(w, req)
		return
	}

	header := dns.RR_Header{
		Name:   req.Question[0].Name,
		Rrtype: dns.TypePTR,
		Class:  dns.ClassINET,
		Ttl:    h.ttl,
	}
	answers := []dns.RR{&dns.PTR{
		Hdr: header,
		Ptr: hostname,
	}}

	response := h.makeResponse(req, answers)
	h.ns.debugf("response: %+v", response)
	if err := w.WriteMsg(response); err != nil {
		h.ns.infof("error responding: %v", err)
	}
}

func (h *handler) handleRecursive(w dns.ResponseWriter, req *dns.Msg) {
	h.ns.debugf("recursive request: %+v", *req)

	// Resolve unqualified names locally
	if len(req.Question) == 1 && req.Question[0].Qtype == dns.TypeA {
		hostname := dns.Fqdn(req.Question[0].Name)
		if strings.Count(hostname, ".") == 1 {
			h.handleLocal(w, req)
			return
		}
	}

	for _, server := range h.upstream.Servers {
		reqCopy := req.Copy()
		reqCopy.Id = dns.Id()
		response, _, err := h.client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, h.upstream.Port))
		if err != nil || response == nil {
			h.ns.debugf("error trying %s: %v", server, err)
			continue
		}
		h.ns.debugf("response: %+v", response)
		response.Id = req.Id
		if response.Len() > h.getMaxResponseSize(req) {
			response.Compress = true
		}
		if err := w.WriteMsg(response); err != nil {
			h.ns.infof("error responding: %v", err)
		}
		return
	}

	h.errorResponse(req, dns.RcodeServerFailure, w)
}

func (h *handler) makeResponse(req *dns.Msg, answers []dns.RR) *dns.Msg {
	response := &dns.Msg{}
	response.SetReply(req)
	response.RecursionAvailable = true
	response.Authoritative = true

	maxSize := h.getMaxResponseSize(req)
	if len(answers) <= 1 || maxSize <= 0 {
		response.Answer = answers
		return response
	}

	// search for smallest i that is too big
	i := sort.Search(len(answers), func(i int) bool {
		// return true if too big
		response.Answer = answers[:i+1]
		return response.Len() > maxSize
	})

	response.Answer = answers[:i]
	if i < len(answers) {
		response.Truncated = true
	}
	return response
}

func (h *handler) getMaxResponseSize(req *dns.Msg) int {
	if opt := req.IsEdns0(); opt != nil {
		return int(opt.UDPSize())
	}
	return h.maxResponseSize
}

func shuffleAnswers(answers *[]dns.RR) {
	if len(*answers) <= 1 {
		return
	}

	for i := range *answers {
		j := rand.Intn(i + 1)
		(*answers)[i], (*answers)[j] = (*answers)[j], (*answers)[i]
	}
}
<|endoftext|>


<commit_before>package nameserver

import (
	"bytes"
	"fmt"
	"math/rand"
	"net"
	"sort"
	"strings"
	"time"

	"github.com/miekg/dns"

	"github.com/weaveworks/weave/net/address"
)

const (
	topDomain        = "."
	reverseDNSdomain = "in-addr.arpa."
	etcResolvConf    = "/etc/resolv.conf"
	udpBuffSize      = uint16(4096)
	minUDPSize       = 512

	DefaultListenAddress = "0.0.0.0:53"
	DefaultTTL           = 1
	DefaultClientTimeout = 5 * time.Second
)

type DNSServer struct {
	ns      *Nameserver
	domain  string
	ttl     uint32
	address string

	servers   []*dns.Server
	upstream  *dns.ClientConfig
	tcpClient *dns.Client
	udpClient *dns.Client
}

func NewDNSServer(ns *Nameserver, domain string, address string, ttl uint32,
	clientTimeout time.Duration) (*DNSServer, error) {

	s := &DNSServer{
		ns:        ns,
		domain:    dns.Fqdn(domain),
		ttl:       ttl,
		address:   address,
		tcpClient: &dns.Client{Net: "tcp", ReadTimeout: clientTimeout},
		udpClient: &dns.Client{Net: "udp", ReadTimeout: clientTimeout, UDPSize: udpBuffSize},
	}
	var err error
	if s.upstream, err = dns.ClientConfigFromFile(etcResolvConf); err != nil {
		return nil, err
	}

	err = s.listen(address)
	return s, err
}

func (d *DNSServer) String() string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "WeaveDNS (%s)\n", d.ns.ourName)
	fmt.Fprintf(&buf, "  listening on %s, for domain %s\n", d.address, d.domain)
	fmt.Fprintf(&buf, "  response ttl %d\n", d.ttl)
	return buf.String()
}

func (d *DNSServer) listen(address string) error {
	udpListener, err := net.ListenPacket("udp", address)
	if err != nil {
		return err
	}
	udpServer := &dns.Server{PacketConn: udpListener, Handler: d.createMux(d.udpClient, minUDPSize)}

	tcpListener, err := net.Listen("tcp", address)
	if err != nil {
		udpServer.Shutdown()
		return err
	}
	tcpServer := &dns.Server{Listener: tcpListener, Handler: d.createMux(d.tcpClient, -1)}

	d.servers = []*dns.Server{udpServer, tcpServer}
	return nil
}

func (d *DNSServer) ActivateAndServe() {
	for _, server := range d.servers {
		go func(server *dns.Server) {
			server.ActivateAndServe()
		}(server)
	}
}

func (d *DNSServer) Stop() error {
	for _, server := range d.servers {
		if err := server.Shutdown(); err != nil {
			return err
		}
	}
	return nil
}

func (d *DNSServer) errorResponse(r *dns.Msg, code int, w dns.ResponseWriter) {
	m := dns.Msg{}
	m.SetReply(r)
	m.RecursionAvailable = true
	m.Rcode = code

	d.ns.debugf("error response: %+v", m)
	if err := w.WriteMsg(&m); err != nil {
		d.ns.infof("error responding: %v", err)
	}
}

func (d *DNSServer) createMux(client *dns.Client, defaultMaxResponseSize int) *dns.ServeMux {
	m := dns.NewServeMux()
	m.HandleFunc(d.domain, d.handleLocal(defaultMaxResponseSize))
	m.HandleFunc(reverseDNSdomain, d.handleReverse(client, defaultMaxResponseSize))
	m.HandleFunc(topDomain, d.handleRecursive(client, defaultMaxResponseSize))
	return m
}

func (d *DNSServer) handleLocal(defaultMaxResponseSize int) func(dns.ResponseWriter, *dns.Msg) {
	return func(w dns.ResponseWriter, req *dns.Msg) {
		d.ns.debugf("local request: %+v", *req)
		if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypeA {
			d.errorResponse(req, dns.RcodeNameError, w)
			return
		}

		hostname := dns.Fqdn(req.Question[0].Name)
		if strings.Count(hostname, ".") == 1 {
			hostname = hostname + d.domain
		}

		addrs := d.ns.Lookup(hostname)

		response := dns.Msg{}
		response.RecursionAvailable = true
		response.Authoritative = true
		response.SetReply(req)
		response.Answer = make([]dns.RR, len(addrs))

		header := dns.RR_Header{
			Name:   req.Question[0].Name,
			Rrtype: dns.TypeA,
			Class:  dns.ClassINET,
			Ttl:    d.ttl,
		}

		for i, addr := range addrs {
			ip := addr.IP4()
			response.Answer[i] = &dns.A{Hdr: header, A: ip}
		}

		shuffleAnswers(&response.Answer)
		maxResponseSize := getMaxResponseSize(req, defaultMaxResponseSize)
		truncateResponse(&response, maxResponseSize)

		d.ns.debugf("response: %+v", response)
		if err := w.WriteMsg(&response); err != nil {
			d.ns.infof("error responding: %v", err)
		}
	}
}

func (d *DNSServer) handleReverse(client *dns.Client, defaultMaxResponseSize int) func(dns.ResponseWriter, *dns.Msg) {
	return func(w dns.ResponseWriter, req *dns.Msg) {
		d.ns.debugf("reverse request: %+v", *req)
		if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypePTR {
			d.errorResponse(req, dns.RcodeNameError, w)
			return
		}

		ipStr := strings.TrimSuffix(req.Question[0].Name, "."+reverseDNSdomain)
		ip, err := address.ParseIP(ipStr)
		if err != nil {
			d.errorResponse(req, dns.RcodeNameError, w)
			return
		}

		hostname, err := d.ns.ReverseLookup(ip.Reverse())
		if err != nil {
			d.handleRecursive(client, defaultMaxResponseSize)(w, req)
			return
		}

		response := dns.Msg{}
		response.RecursionAvailable = true
		response.Authoritative = true
		response.SetReply(req)

		header := dns.RR_Header{
			Name:   req.Question[0].Name,
			Rrtype: dns.TypePTR,
			Class:  dns.ClassINET,
			Ttl:    d.ttl,
		}

		response.Answer = []dns.RR{&dns.PTR{
			Hdr: header,
			Ptr: hostname,
		}}

		maxResponseSize := getMaxResponseSize(req, defaultMaxResponseSize)
		truncateResponse(&response, maxResponseSize)

		d.ns.debugf("response: %+v", response)
		if err := w.WriteMsg(&response); err != nil {
			d.ns.infof("error responding: %v", err)
		}
	}
}

func (d *DNSServer) handleRecursive(client *dns.Client, defaultMaxResponseSize int) func(dns.ResponseWriter, *dns.Msg) {
	return func(w dns.ResponseWriter, req *dns.Msg) {
		d.ns.debugf("recursive request: %+v", *req)

		// Resolve unqualified names locally
		if len(req.Question) == 1 && req.Question[0].Qtype == dns.TypeA {
			hostname := dns.Fqdn(req.Question[0].Name)
			if strings.Count(hostname, ".") == 1 {
				d.handleLocal(defaultMaxResponseSize)(w, req)
				return
			}
		}

		for _, server := range d.upstream.Servers {
			reqCopy := req.Copy()
			reqCopy.Id = dns.Id()
			response, _, err := client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, d.upstream.Port))
			if err != nil || response == nil {
				d.ns.debugf("error trying %s: %v", server, err)
				continue
			}
			if response.Rcode != dns.RcodeSuccess && !response.Authoritative {
				d.ns.debugf("non-authoritative error trying %s: %v", server, response.Rcode)
				continue
			}
			d.ns.debugf("response: %+v", response)
			response.SetReply(req)
			if err := w.WriteMsg(response); err != nil {
				d.ns.infof("error responding: %v", err)
			}
			return
		}

		d.errorResponse(req, dns.RcodeServerFailure, w)
	}
}

func shuffleAnswers(answers *[]dns.RR) {
	if len(*answers) <= 1 {
		return
	}

	for i := range *answers {
		j := rand.Intn(i + 1)
		(*answers)[i], (*answers)[j] = (*answers)[j], (*answers)[i]
	}
}

func truncateResponse(response *dns.Msg, maxSize int) {
	if len(response.Answer) <= 1 || maxSize <= 0 {
		return
	}

	// take a copy of answers, as we're going to mutate response
	answers := response.Answer

	// search for smallest i that is too big
	i := sort.Search(len(response.Answer), func(i int) bool {
		// return true if too big
		response.Answer = answers[:i+1]
		return response.Len() > maxSize
	})
	if i == len(answers) {
		response.Answer = answers
		return
	}

	response.Answer = answers[:i]
	response.Truncated = true
}

func getMaxResponseSize(req *dns.Msg, defaultMaxResponseSize int) int {
	if opt := req.IsEdns0(); opt != nil {
		return int(opt.UDPSize())
	}
	return defaultMaxResponseSize
}
<commit_msg>cosmetic<commit_after>package nameserver

import (
	"bytes"
	"fmt"
	"math/rand"
	"net"
	"sort"
	"strings"
	"time"

	"github.com/miekg/dns"

	"github.com/weaveworks/weave/net/address"
)

const (
	topDomain        = "."
	reverseDNSdomain = "in-addr.arpa."
	etcResolvConf    = "/etc/resolv.conf"
	udpBuffSize      = uint16(4096)
	minUDPSize       = 512

	DefaultListenAddress = "0.0.0.0:53"
	DefaultTTL           = 1
	DefaultClientTimeout = 5 * time.Second
)

type DNSServer struct {
	ns      *Nameserver
	domain  string
	ttl     uint32
	address string

	servers   []*dns.Server
	upstream  *dns.ClientConfig
	tcpClient *dns.Client
	udpClient *dns.Client
}

func NewDNSServer(ns *Nameserver, domain string, address string, ttl uint32, clientTimeout time.Duration) (*DNSServer, error) {

	s := &DNSServer{
		ns:        ns,
		domain:    dns.Fqdn(domain),
		ttl:       ttl,
		address:   address,
		tcpClient: &dns.Client{Net: "tcp", ReadTimeout: clientTimeout},
		udpClient: &dns.Client{Net: "udp", ReadTimeout: clientTimeout, UDPSize: udpBuffSize},
	}
	var err error
	if s.upstream, err = dns.ClientConfigFromFile(etcResolvConf); err != nil {
		return nil, err
	}

	err = s.listen(address)
	return s, err
}

func (d *DNSServer) String() string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "WeaveDNS (%s)\n", d.ns.ourName)
	fmt.Fprintf(&buf, "  listening on %s, for domain %s\n", d.address, d.domain)
	fmt.Fprintf(&buf, "  response ttl %d\n", d.ttl)
	return buf.String()
}

func (d *DNSServer) listen(address string) error {
	udpListener, err := net.ListenPacket("udp", address)
	if err != nil {
		return err
	}
	udpServer := &dns.Server{PacketConn: udpListener, Handler: d.createMux(d.udpClient, minUDPSize)}

	tcpListener, err := net.Listen("tcp", address)
	if err != nil {
		udpServer.Shutdown()
		return err
	}
	tcpServer := &dns.Server{Listener: tcpListener, Handler: d.createMux(d.tcpClient, -1)}

	d.servers = []*dns.Server{udpServer, tcpServer}
	return nil
}

func (d *DNSServer) ActivateAndServe() {
	for _, server := range d.servers {
		go func(server *dns.Server) {
			server.ActivateAndServe()
		}(server)
	}
}

func (d *DNSServer) Stop() error {
	for _, server := range d.servers {
		if err := server.Shutdown(); err != nil {
			return err
		}
	}
	return nil
}

func (d *DNSServer) errorResponse(r *dns.Msg, code int, w dns.ResponseWriter) {
	m := dns.Msg{}
	m.SetReply(r)
	m.RecursionAvailable = true
	m.Rcode = code

	d.ns.debugf("error response: %+v", m)
	if err := w.WriteMsg(&m); err != nil {
		d.ns.infof("error responding: %v", err)
	}
}

func (d *DNSServer) createMux(client *dns.Client, defaultMaxResponseSize int) *dns.ServeMux {
	m := dns.NewServeMux()
	m.HandleFunc(d.domain, d.handleLocal(defaultMaxResponseSize))
	m.HandleFunc(reverseDNSdomain, d.handleReverse(client, defaultMaxResponseSize))
	m.HandleFunc(topDomain, d.handleRecursive(client, defaultMaxResponseSize))
	return m
}

func (d *DNSServer) handleLocal(defaultMaxResponseSize int) func(dns.ResponseWriter, *dns.Msg) {
	return func(w dns.ResponseWriter, req *dns.Msg) {
		d.ns.debugf("local request: %+v", *req)
		if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypeA {
			d.errorResponse(req, dns.RcodeNameError, w)
			return
		}

		hostname := dns.Fqdn(req.Question[0].Name)
		if strings.Count(hostname, ".") == 1 {
			hostname = hostname + d.domain
		}

		addrs := d.ns.Lookup(hostname)

		response := dns.Msg{}
		response.RecursionAvailable = true
		response.Authoritative = true
		response.SetReply(req)
		response.Answer = make([]dns.RR, len(addrs))

		header := dns.RR_Header{
			Name:   req.Question[0].Name,
			Rrtype: dns.TypeA,
			Class:  dns.ClassINET,
			Ttl:    d.ttl,
		}

		for i, addr := range addrs {
			ip := addr.IP4()
			response.Answer[i] = &dns.A{Hdr: header, A: ip}
		}

		shuffleAnswers(&response.Answer)
		maxResponseSize := getMaxResponseSize(req, defaultMaxResponseSize)
		truncateResponse(&response, maxResponseSize)

		d.ns.debugf("response: %+v", response)
		if err := w.WriteMsg(&response); err != nil {
			d.ns.infof("error responding: %v", err)
		}
	}
}

func (d *DNSServer) handleReverse(client *dns.Client, defaultMaxResponseSize int) func(dns.ResponseWriter, *dns.Msg) {
	return func(w dns.ResponseWriter, req *dns.Msg) {
		d.ns.debugf("reverse request: %+v", *req)
		if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypePTR {
			d.errorResponse(req, dns.RcodeNameError, w)
			return
		}

		ipStr := strings.TrimSuffix(req.Question[0].Name, "."+reverseDNSdomain)
		ip, err := address.ParseIP(ipStr)
		if err != nil {
			d.errorResponse(req, dns.RcodeNameError, w)
			return
		}

		hostname, err := d.ns.ReverseLookup(ip.Reverse())
		if err != nil {
			d.handleRecursive(client, defaultMaxResponseSize)(w, req)
			return
		}

		response := dns.Msg{}
		response.RecursionAvailable = true
		response.Authoritative = true
		response.SetReply(req)

		header := dns.RR_Header{
			Name:   req.Question[0].Name,
			Rrtype: dns.TypePTR,
			Class:  dns.ClassINET,
			Ttl:    d.ttl,
		}

		response.Answer = []dns.RR{&dns.PTR{
			Hdr: header,
			Ptr: hostname,
		}}

		maxResponseSize := getMaxResponseSize(req, defaultMaxResponseSize)
		truncateResponse(&response, maxResponseSize)

		d.ns.debugf("response: %+v", response)
		if err := w.WriteMsg(&response); err != nil {
			d.ns.infof("error responding: %v", err)
		}
	}
}

func (d *DNSServer) handleRecursive(client *dns.Client, defaultMaxResponseSize int) func(dns.ResponseWriter, *dns.Msg) {
	return func(w dns.ResponseWriter, req *dns.Msg) {
		d.ns.debugf("recursive request: %+v", *req)

		// Resolve unqualified names locally
		if len(req.Question) == 1 && req.Question[0].Qtype == dns.TypeA {
			hostname := dns.Fqdn(req.Question[0].Name)
			if strings.Count(hostname, ".") == 1 {
				d.handleLocal(defaultMaxResponseSize)(w, req)
				return
			}
		}

		for _, server := range d.upstream.Servers {
			reqCopy := req.Copy()
			reqCopy.Id = dns.Id()
			response, _, err := client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, d.upstream.Port))
			if err != nil || response == nil {
				d.ns.debugf("error trying %s: %v", server, err)
				continue
			}
			if response.Rcode != dns.RcodeSuccess && !response.Authoritative {
				d.ns.debugf("non-authoritative error trying %s: %v", server, response.Rcode)
				continue
			}
			d.ns.debugf("response: %+v", response)
			response.SetReply(req)
			if err := w.WriteMsg(response); err != nil {
				d.ns.infof("error responding: %v", err)
			}
			return
		}

		d.errorResponse(req, dns.RcodeServerFailure, w)
	}
}

func shuffleAnswers(answers *[]dns.RR) {
	if len(*answers) <= 1 {
		return
	}

	for i := range *answers {
		j := rand.Intn(i + 1)
		(*answers)[i], (*answers)[j] = (*answers)[j], (*answers)[i]
	}
}

func truncateResponse(response *dns.Msg, maxSize int) {
	if len(response.Answer) <= 1 || maxSize <= 0 {
		return
	}

	// take a copy of answers, as we're going to mutate response
	answers := response.Answer

	// search for smallest i that is too big
	i := sort.Search(len(response.Answer), func(i int) bool {
		// return true if too big
		response.Answer = answers[:i+1]
		return response.Len() > maxSize
	})
	if i == len(answers) {
		response.Answer = answers
		return
	}

	response.Answer = answers[:i]
	response.Truncated = true
}

func getMaxResponseSize(req *dns.Msg, defaultMaxResponseSize int) int {
	if opt := req.IsEdns0(); opt != nil {
		return int(opt.UDPSize())
	}
	return defaultMaxResponseSize
}
<|endoftext|>


<commit_before>package net

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/binary"
	"errors"
	"golang.org/x/crypto/hkdf"
	libp2p "gx/ipfs/QmUWER4r4qMvaCnX5zREcfyiWN7cXN9g3a7fkRqNz8qWPP/go-libp2p-crypto"
	"io"
)

const (
	// The version of the encryption algorithm used. Currently only 1 is supported
	CiphertextVersion = 1

	// Length of the serialized version in bytes
	CiphertextVersionBytes = 4

	// Length of the secret key used to generate the AES and MAC keys in bytes
	SecretKeyBytes = 32

	// Length of the AES key in bytes
	AESKeyBytes = 32

	// Length of the MAC key in bytes
	MacKeyBytes = 32

	// Length of the RSA encrypted secret key ciphertext in bytes
	EncryptedSecretKeyBytes = 512

	// Length of the MAC in bytes
	MacBytes = 32
)

var (
	// The ciphertext cannot be shorter than CiphertextVersionBytes + EncryptedSecretKeyBytes + aes.BlockSize + MacKeyBytes
	ErrShortCiphertext = errors.New("Ciphertext is too short")

	// The HMAC included in the ciphertext is invalid
	ErrInvalidHmac = errors.New("Invalid Hmac")

	// Satic salt used in the hdkf
	Salt = []byte("OpenBazaar Encryption Algorithm")
)

func Encrypt(pubKey libp2p.PubKey, plaintext []byte) ([]byte, error) {

	// Encrypt random secret key with RSA pubkey
	secretKey := make([]byte, SecretKeyBytes)
	rand.Read(secretKey)

	encKey, err := pubKey.Encrypt(secretKey)
	if err != nil {
		return nil, err
	}

	// Derive MAC and AES keys from the secret key using hkdf
	hash := sha256.New

	hkdf := hkdf.New(hash, secretKey, Salt, nil)

	aesKey := make([]byte, AESKeyBytes)
	_, err = io.ReadFull(hkdf, aesKey)
	if err != nil {
		return nil, err
	}
	macKey := make([]byte, AESKeyBytes)
	_, err = io.ReadFull(hkdf, macKey)
	if err != nil {
		return nil, err
	}

	// Encrypt message with the AES key
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return nil, err
	}

	/* The IV needs to be unique, but not secure. Therefore it is common to
	   include it at the beginning of the ciphertext. */
	ciphertext := make([]byte, aes.BlockSize+len(plaintext))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return nil, err
	}

	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)

	// Create the HMAC
	mac := hmac.New(sha256.New, macKey)
	mac.Write(ciphertext)
	messageMac := mac.Sum(nil)

	// Prepend the ciphertext with the encrypted secret key
	ciphertext = append(encKey, ciphertext...)

	// Prepend version
	version := make([]byte, CiphertextVersionBytes)
	binary.BigEndian.PutUint32(version, uint32(CiphertextVersion))
	ciphertext = append(version, ciphertext...)

	// Append the MAC
	ciphertext = append(ciphertext, messageMac...)
	return ciphertext, nil
}

func Decrypt(privKey libp2p.PrivKey, ciphertext []byte) ([]byte, error) {
	version := getCipherTextVersion(ciphertext)
	if version == CiphertextVersion {
		return decryptV1(privKey, ciphertext)
	} else {
		return nil, errors.New("Unknown ciphertext version")
	}
}

func decryptV1(privKey libp2p.PrivKey, ciphertext []byte) ([]byte, error) {
	if len(ciphertext) < CiphertextVersionBytes+EncryptedSecretKeyBytes+aes.BlockSize+MacKeyBytes {
		return nil, ErrShortCiphertext
	}

	// Decrypt the secret key using the RSA private key
	secretKey, err := privKey.Decrypt(ciphertext[CiphertextVersionBytes : CiphertextVersionBytes+EncryptedSecretKeyBytes])
	if err != nil {
		return nil, err
	}

	// Derive the AES and MAC keys from the secret key using hdkf
	hash := sha256.New

	hkdf := hkdf.New(hash, secretKey, Salt, nil)

	aesKey := make([]byte, AESKeyBytes)
	_, err = io.ReadFull(hkdf, aesKey)
	if err != nil {
		return nil, err
	}
	macKey := make([]byte, MacKeyBytes)
	_, err = io.ReadFull(hkdf, macKey)
	if err != nil {
		return nil, err
	}

	// Calculate the HMAC and verify it is correct
	mac := hmac.New(sha256.New, macKey)
	mac.Write(ciphertext[CiphertextVersionBytes+EncryptedSecretKeyBytes : len(ciphertext)-MacBytes])
	messageMac := mac.Sum(nil)
	if !hmac.Equal(messageMac, ciphertext[len(ciphertext)-MacBytes:]) {
		return nil, ErrInvalidHmac
	}

	// Decrypt the AES ciphertext
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return nil, err
	}
	ciphertext = ciphertext[CiphertextVersionBytes+EncryptedSecretKeyBytes : len(ciphertext)-MacBytes]
	if len(ciphertext) < aes.BlockSize {
		return nil, err
	}
	iv := ciphertext[:aes.BlockSize]
	ciphertext = ciphertext[aes.BlockSize:]

	stream := cipher.NewCFBDecrypter(block, iv)

	// XORKeyStream can work in-place if the two arguments are the same
	stream.XORKeyStream(ciphertext, ciphertext)
	plaintext := ciphertext
	return plaintext, nil
}

func getCipherTextVersion(ciphertext []byte) uint32 {
	return binary.BigEndian.Uint32(ciphertext[:CiphertextVersionBytes])
}
<commit_msg>fix: mac key now uses MacKeyBytes<commit_after>package net

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/binary"
	"errors"
	"golang.org/x/crypto/hkdf"
	libp2p "gx/ipfs/QmUWER4r4qMvaCnX5zREcfyiWN7cXN9g3a7fkRqNz8qWPP/go-libp2p-crypto"
	"io"
)

const (
	// The version of the encryption algorithm used. Currently only 1 is supported
	CiphertextVersion = 1

	// Length of the serialized version in bytes
	CiphertextVersionBytes = 4

	// Length of the secret key used to generate the AES and MAC keys in bytes
	SecretKeyBytes = 32

	// Length of the AES key in bytes
	AESKeyBytes = 32

	// Length of the MAC key in bytes
	MacKeyBytes = 32

	// Length of the RSA encrypted secret key ciphertext in bytes
	EncryptedSecretKeyBytes = 512

	// Length of the MAC in bytes
	MacBytes = 32
)

var (
	// The ciphertext cannot be shorter than CiphertextVersionBytes + EncryptedSecretKeyBytes + aes.BlockSize + MacKeyBytes
	ErrShortCiphertext = errors.New("Ciphertext is too short")

	// The HMAC included in the ciphertext is invalid
	ErrInvalidHmac = errors.New("Invalid Hmac")

	// Satic salt used in the hdkf
	Salt = []byte("OpenBazaar Encryption Algorithm")
)

func Encrypt(pubKey libp2p.PubKey, plaintext []byte) ([]byte, error) {

	// Encrypt random secret key with RSA pubkey
	secretKey := make([]byte, SecretKeyBytes)
	rand.Read(secretKey)

	encKey, err := pubKey.Encrypt(secretKey)
	if err != nil {
		return nil, err
	}

	// Derive MAC and AES keys from the secret key using hkdf
	hash := sha256.New

	hkdf := hkdf.New(hash, secretKey, Salt, nil)

	aesKey := make([]byte, AESKeyBytes)
	_, err = io.ReadFull(hkdf, aesKey)
	if err != nil {
		return nil, err
	}
	macKey := make([]byte, MacKeyBytes)
	_, err = io.ReadFull(hkdf, macKey)
	if err != nil {
		return nil, err
	}

	// Encrypt message with the AES key
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return nil, err
	}

	/* The IV needs to be unique, but not secure. Therefore it is common to
	   include it at the beginning of the ciphertext. */
	ciphertext := make([]byte, aes.BlockSize+len(plaintext))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return nil, err
	}

	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)

	// Create the HMAC
	mac := hmac.New(sha256.New, macKey)
	mac.Write(ciphertext)
	messageMac := mac.Sum(nil)

	// Prepend the ciphertext with the encrypted secret key
	ciphertext = append(encKey, ciphertext...)

	// Prepend version
	version := make([]byte, CiphertextVersionBytes)
	binary.BigEndian.PutUint32(version, uint32(CiphertextVersion))
	ciphertext = append(version, ciphertext...)

	// Append the MAC
	ciphertext = append(ciphertext, messageMac...)
	return ciphertext, nil
}

func Decrypt(privKey libp2p.PrivKey, ciphertext []byte) ([]byte, error) {
	version := getCipherTextVersion(ciphertext)
	if version == CiphertextVersion {
		return decryptV1(privKey, ciphertext)
	} else {
		return nil, errors.New("Unknown ciphertext version")
	}
}

func decryptV1(privKey libp2p.PrivKey, ciphertext []byte) ([]byte, error) {
	if len(ciphertext) < CiphertextVersionBytes+EncryptedSecretKeyBytes+aes.BlockSize+MacKeyBytes {
		return nil, ErrShortCiphertext
	}

	// Decrypt the secret key using the RSA private key
	secretKey, err := privKey.Decrypt(ciphertext[CiphertextVersionBytes : CiphertextVersionBytes+EncryptedSecretKeyBytes])
	if err != nil {
		return nil, err
	}

	// Derive the AES and MAC keys from the secret key using hdkf
	hash := sha256.New

	hkdf := hkdf.New(hash, secretKey, Salt, nil)

	aesKey := make([]byte, AESKeyBytes)
	_, err = io.ReadFull(hkdf, aesKey)
	if err != nil {
		return nil, err
	}
	macKey := make([]byte, MacKeyBytes)
	_, err = io.ReadFull(hkdf, macKey)
	if err != nil {
		return nil, err
	}

	// Calculate the HMAC and verify it is correct
	mac := hmac.New(sha256.New, macKey)
	mac.Write(ciphertext[CiphertextVersionBytes+EncryptedSecretKeyBytes : len(ciphertext)-MacBytes])
	messageMac := mac.Sum(nil)
	if !hmac.Equal(messageMac, ciphertext[len(ciphertext)-MacBytes:]) {
		return nil, ErrInvalidHmac
	}

	// Decrypt the AES ciphertext
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return nil, err
	}
	ciphertext = ciphertext[CiphertextVersionBytes+EncryptedSecretKeyBytes : len(ciphertext)-MacBytes]
	if len(ciphertext) < aes.BlockSize {
		return nil, err
	}
	iv := ciphertext[:aes.BlockSize]
	ciphertext = ciphertext[aes.BlockSize:]

	stream := cipher.NewCFBDecrypter(block, iv)

	// XORKeyStream can work in-place if the two arguments are the same
	stream.XORKeyStream(ciphertext, ciphertext)
	plaintext := ciphertext
	return plaintext, nil
}

func getCipherTextVersion(ciphertext []byte) uint32 {
	return binary.BigEndian.Uint32(ciphertext[:CiphertextVersionBytes])
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/gorilla/mux"

	"github.com/lxc/lxd/client"
	agentAPI "github.com/lxc/lxd/lxd-agent/api"
	"github.com/lxc/lxd/lxd/daemon"
	"github.com/lxc/lxd/lxd/device/config"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/logger"
)

// DevLxdServer creates an http.Server capable of handling requests against the
// /dev/lxd Unix socket endpoint created inside VMs.
func devLxdServer(d *Daemon) *http.Server {
	return &http.Server{
		Handler: devLxdAPI(d),
	}
}

type devLxdResponse struct {
	content any
	code    int
	ctype   string
}

func okResponse(ct any, ctype string) *devLxdResponse {
	return &devLxdResponse{ct, http.StatusOK, ctype}
}

type devLxdHandler struct {
	path string

	/*
	 * This API will have to be changed slightly when we decide to support
	 * websocket events upgrading, but since we don't have events on the
	 * server side right now either, I went the simple route to avoid
	 * needless noise.
	 */
	f func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse
}

func getVsockClient(d *Daemon) (lxd.InstanceServer, error) {
	// Try connecting to LXD server.
	client, err := getClient(int(d.serverCID), int(d.serverPort), d.serverCertificate)
	if err != nil {
		return nil, err
	}

	server, err := lxd.ConnectLXDHTTP(nil, client)
	if err != nil {
		return nil, err
	}

	return server, nil
}

var devlxdConfigGet = devLxdHandler{"/1.0/config", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
	client, err := getVsockClient(d)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	defer client.Disconnect()

	resp, _, err := client.RawQuery("GET", "/1.0/config", nil, "")
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	var config []string

	err = resp.MetadataAsStruct(&config)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	filtered := []string{}
	for _, k := range config {
		if strings.HasPrefix(k, "/1.0/config/user.") || strings.HasPrefix(k, "/1.0/config/cloud-init.") {
			filtered = append(filtered, k)
		}
	}
	return okResponse(filtered, "json")
}}

var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
	key, err := url.PathUnescape(mux.Vars(r)["key"])
	if err != nil {
		return &devLxdResponse{"bad request", http.StatusBadRequest, "raw"}
	}

	if !strings.HasPrefix(key, "user.") && !strings.HasPrefix(key, "cloud-init.") {
		return &devLxdResponse{"not authorized", http.StatusForbidden, "raw"}
	}

	client, err := getVsockClient(d)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	defer client.Disconnect()

	resp, _, err := client.RawQuery("GET", fmt.Sprintf("/1.0/config/%s", key), nil, "")
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	var value string

	err = resp.MetadataAsStruct(&value)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	return okResponse(value, "raw")
}}

var devlxdMetadataGet = devLxdHandler{"/1.0/meta-data", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
	client, err := getVsockClient(d)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	defer client.Disconnect()

	resp, _, err := client.RawQuery("GET", "/1.0/meta-data", nil, "")
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	var metaData string

	err = resp.MetadataAsStruct(&metaData)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	return okResponse(metaData, "raw")
}}

var devLxdEventsGet = devLxdHandler{"/1.0/events", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
	err := eventsGet(d, r).Render(w)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	return okResponse("", "raw")
}}

var devlxdAPIGet = devLxdHandler{"/1.0", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
	client, err := getVsockClient(d)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	defer client.Disconnect()

	resp, _, err := client.RawQuery("GET", "/1.0", nil, "")
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	var instanceData agentAPI.DevLXDGet

	err = resp.MetadataAsStruct(&instanceData)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	return okResponse(instanceData, "json")
}}

var devlxdDevicesGet = devLxdHandler{"/1.0/devices", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
	client, err := getVsockClient(d)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	defer client.Disconnect()

	resp, _, err := client.RawQuery("GET", "/1.0/devices", nil, "")
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	var devices config.Devices

	err = resp.MetadataAsStruct(&devices)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	return okResponse(devices, "json")
}}

var handlers = []devLxdHandler{
	{"/", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
		return okResponse([]string{"/1.0"}, "json")
	}},
	devlxdAPIGet,
	devlxdConfigGet,
	devlxdConfigKeyGet,
	devlxdMetadataGet,
	devLxdEventsGet,
	devlxdDevicesGet,
}

func hoistReq(f func(*Daemon, http.ResponseWriter, *http.Request) *devLxdResponse, d *Daemon) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		resp := f(d, w, r)
		if resp.code != http.StatusOK {
			http.Error(w, fmt.Sprintf("%s", resp.content), resp.code)
		} else if resp.ctype == "json" {
			w.Header().Set("Content-Type", "application/json")

			var debugLogger logger.Logger
			if daemon.Debug {
				debugLogger = logger.Logger(logger.Log)
			}

			_ = util.WriteJSON(w, resp.content, debugLogger)
		} else if resp.ctype != "websocket" {
			w.Header().Set("Content-Type", "application/octet-stream")
			_, _ = fmt.Fprint(w, resp.content.(string))
		}
	}
}

func devLxdAPI(d *Daemon) http.Handler {
	m := mux.NewRouter()
	m.UseEncodedPath() // Allow encoded values in path segments.

	for _, handler := range handlers {
		m.HandleFunc(handler.path, hoistReq(handler.f, d))
	}

	return m
}

// Create a new net.Listener bound to the unix socket of the devlxd endpoint.
func createDevLxdlListener(dir string) (net.Listener, error) {
	path := filepath.Join(dir, "lxd", "sock")

	err := os.MkdirAll(filepath.Dir(path), 0755)
	if err != nil {
		return nil, err
	}

	// If this socket exists, that means a previous LXD instance died and
	// didn't clean up. We assume that such LXD instance is actually dead
	// if we get this far, since localCreateListener() tries to connect to
	// the actual lxd socket to make sure that it is actually dead. So, it
	// is safe to remove it here without any checks.
	//
	// Also, it would be nice to SO_REUSEADDR here so we don't have to
	// delete the socket, but we can't:
	//   http://stackoverflow.com/questions/15716302/so-reuseaddr-and-af-unix
	//
	// Note that this will force clients to reconnect when LXD is restarted.
	err = socketUnixRemoveStale(path)
	if err != nil {
		return nil, err
	}

	listener, err := socketUnixListen(path)
	if err != nil {
		return nil, err
	}

	err = socketUnixSetPermissions(path, 0600)
	if err != nil {
		_ = listener.Close()
		return nil, err
	}

	return listener, nil
}

// Remove any stale socket file at the given path.
func socketUnixRemoveStale(path string) error {
	// If there's no socket file at all, there's nothing to do.
	if !shared.PathExists(path) {
		return nil
	}

	logger.Debugf("Detected stale unix socket, deleting")
	err := os.Remove(path)
	if err != nil {
		return fmt.Errorf("could not delete stale local socket: %w", err)
	}

	return nil
}

// Change the file mode of the given unix socket file.
func socketUnixSetPermissions(path string, mode os.FileMode) error {
	err := os.Chmod(path, mode)
	if err != nil {
		return fmt.Errorf("cannot set permissions on local socket: %w", err)
	}

	return nil
}

// Bind to the given unix socket path.
func socketUnixListen(path string) (net.Listener, error) {
	addr, err := net.ResolveUnixAddr("unix", path)
	if err != nil {
		return nil, fmt.Errorf("cannot resolve socket address: %w", err)
	}

	listener, err := net.ListenUnix("unix", addr)
	if err != nil {
		return nil, fmt.Errorf("cannot bind socket: %w", err)
	}

	return listener, err
}
<commit_msg>lxd-agent: Handle PATCH /1.0 in devlxd<commit_after>package main

import (
	"fmt"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/gorilla/mux"

	"github.com/lxc/lxd/client"
	agentAPI "github.com/lxc/lxd/lxd-agent/api"
	"github.com/lxc/lxd/lxd/daemon"
	"github.com/lxc/lxd/lxd/device/config"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/logger"
)

// DevLxdServer creates an http.Server capable of handling requests against the
// /dev/lxd Unix socket endpoint created inside VMs.
func devLxdServer(d *Daemon) *http.Server {
	return &http.Server{
		Handler: devLxdAPI(d),
	}
}

type devLxdResponse struct {
	content any
	code    int
	ctype   string
}

func okResponse(ct any, ctype string) *devLxdResponse {
	return &devLxdResponse{ct, http.StatusOK, ctype}
}

type devLxdHandler struct {
	path string

	/*
	 * This API will have to be changed slightly when we decide to support
	 * websocket events upgrading, but since we don't have events on the
	 * server side right now either, I went the simple route to avoid
	 * needless noise.
	 */
	f func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse
}

func getVsockClient(d *Daemon) (lxd.InstanceServer, error) {
	// Try connecting to LXD server.
	client, err := getClient(int(d.serverCID), int(d.serverPort), d.serverCertificate)
	if err != nil {
		return nil, err
	}

	server, err := lxd.ConnectLXDHTTP(nil, client)
	if err != nil {
		return nil, err
	}

	return server, nil
}

var devlxdConfigGet = devLxdHandler{"/1.0/config", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
	client, err := getVsockClient(d)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	defer client.Disconnect()

	resp, _, err := client.RawQuery("GET", "/1.0/config", nil, "")
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	var config []string

	err = resp.MetadataAsStruct(&config)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	filtered := []string{}
	for _, k := range config {
		if strings.HasPrefix(k, "/1.0/config/user.") || strings.HasPrefix(k, "/1.0/config/cloud-init.") {
			filtered = append(filtered, k)
		}
	}
	return okResponse(filtered, "json")
}}

var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
	key, err := url.PathUnescape(mux.Vars(r)["key"])
	if err != nil {
		return &devLxdResponse{"bad request", http.StatusBadRequest, "raw"}
	}

	if !strings.HasPrefix(key, "user.") && !strings.HasPrefix(key, "cloud-init.") {
		return &devLxdResponse{"not authorized", http.StatusForbidden, "raw"}
	}

	client, err := getVsockClient(d)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	defer client.Disconnect()

	resp, _, err := client.RawQuery("GET", fmt.Sprintf("/1.0/config/%s", key), nil, "")
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	var value string

	err = resp.MetadataAsStruct(&value)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	return okResponse(value, "raw")
}}

var devlxdMetadataGet = devLxdHandler{"/1.0/meta-data", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
	client, err := getVsockClient(d)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	defer client.Disconnect()

	resp, _, err := client.RawQuery("GET", "/1.0/meta-data", nil, "")
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	var metaData string

	err = resp.MetadataAsStruct(&metaData)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	return okResponse(metaData, "raw")
}}

var devLxdEventsGet = devLxdHandler{"/1.0/events", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
	err := eventsGet(d, r).Render(w)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	return okResponse("", "raw")
}}

var devlxdAPIGet = devLxdHandler{"/1.0", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
	client, err := getVsockClient(d)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	defer client.Disconnect()

	if r.Method == "GET" {
		resp, _, err := client.RawQuery(r.Method, "/1.0", nil, "")
		if err != nil {
			return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
		}

		var instanceData agentAPI.DevLXDGet

		err = resp.MetadataAsStruct(&instanceData)
		if err != nil {
			return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
		}

		return okResponse(instanceData, "json")
	} else if r.Method == "PATCH" {
		_, _, err := client.RawQuery(r.Method, "/1.0", r.Body, "")
		if err != nil {
			return &devLxdResponse{err.Error(), http.StatusInternalServerError, "raw"}
		}

		return okResponse("", "raw")
	}

	return &devLxdResponse{fmt.Sprintf("method %q not allowed", r.Method), http.StatusBadRequest, "raw"}
}}

var devlxdDevicesGet = devLxdHandler{"/1.0/devices", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
	client, err := getVsockClient(d)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	defer client.Disconnect()

	resp, _, err := client.RawQuery("GET", "/1.0/devices", nil, "")
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	var devices config.Devices

	err = resp.MetadataAsStruct(&devices)
	if err != nil {
		return &devLxdResponse{"internal server error", http.StatusInternalServerError, "raw"}
	}

	return okResponse(devices, "json")
}}

var handlers = []devLxdHandler{
	{"/", func(d *Daemon, w http.ResponseWriter, r *http.Request) *devLxdResponse {
		return okResponse([]string{"/1.0"}, "json")
	}},
	devlxdAPIGet,
	devlxdConfigGet,
	devlxdConfigKeyGet,
	devlxdMetadataGet,
	devLxdEventsGet,
	devlxdDevicesGet,
}

func hoistReq(f func(*Daemon, http.ResponseWriter, *http.Request) *devLxdResponse, d *Daemon) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		resp := f(d, w, r)
		if resp.code != http.StatusOK {
			http.Error(w, fmt.Sprintf("%s", resp.content), resp.code)
		} else if resp.ctype == "json" {
			w.Header().Set("Content-Type", "application/json")

			var debugLogger logger.Logger
			if daemon.Debug {
				debugLogger = logger.Logger(logger.Log)
			}

			_ = util.WriteJSON(w, resp.content, debugLogger)
		} else if resp.ctype != "websocket" {
			w.Header().Set("Content-Type", "application/octet-stream")
			_, _ = fmt.Fprint(w, resp.content.(string))
		}
	}
}

func devLxdAPI(d *Daemon) http.Handler {
	m := mux.NewRouter()
	m.UseEncodedPath() // Allow encoded values in path segments.

	for _, handler := range handlers {
		m.HandleFunc(handler.path, hoistReq(handler.f, d))
	}

	return m
}

// Create a new net.Listener bound to the unix socket of the devlxd endpoint.
func createDevLxdlListener(dir string) (net.Listener, error) {
	path := filepath.Join(dir, "lxd", "sock")

	err := os.MkdirAll(filepath.Dir(path), 0755)
	if err != nil {
		return nil, err
	}

	// If this socket exists, that means a previous LXD instance died and
	// didn't clean up. We assume that such LXD instance is actually dead
	// if we get this far, since localCreateListener() tries to connect to
	// the actual lxd socket to make sure that it is actually dead. So, it
	// is safe to remove it here without any checks.
	//
	// Also, it would be nice to SO_REUSEADDR here so we don't have to
	// delete the socket, but we can't:
	//   http://stackoverflow.com/questions/15716302/so-reuseaddr-and-af-unix
	//
	// Note that this will force clients to reconnect when LXD is restarted.
	err = socketUnixRemoveStale(path)
	if err != nil {
		return nil, err
	}

	listener, err := socketUnixListen(path)
	if err != nil {
		return nil, err
	}

	err = socketUnixSetPermissions(path, 0600)
	if err != nil {
		_ = listener.Close()
		return nil, err
	}

	return listener, nil
}

// Remove any stale socket file at the given path.
func socketUnixRemoveStale(path string) error {
	// If there's no socket file at all, there's nothing to do.
	if !shared.PathExists(path) {
		return nil
	}

	logger.Debugf("Detected stale unix socket, deleting")
	err := os.Remove(path)
	if err != nil {
		return fmt.Errorf("could not delete stale local socket: %w", err)
	}

	return nil
}

// Change the file mode of the given unix socket file.
func socketUnixSetPermissions(path string, mode os.FileMode) error {
	err := os.Chmod(path, mode)
	if err != nil {
		return fmt.Errorf("cannot set permissions on local socket: %w", err)
	}

	return nil
}

// Bind to the given unix socket path.
func socketUnixListen(path string) (net.Listener, error) {
	addr, err := net.ResolveUnixAddr("unix", path)
	if err != nil {
		return nil, fmt.Errorf("cannot resolve socket address: %w", err)
	}

	listener, err := net.ListenUnix("unix", addr)
	if err != nil {
		return nil, fmt.Errorf("cannot bind socket: %w", err)
	}

	return listener, err
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package model

import (
	"fmt"

	"k8s.io/kops/nodeup/pkg/distros"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks"

	"github.com/golang/glog"
)

// KubectlBuilder install kubectl
type KubectlBuilder struct {
	*NodeupModelContext
}

var _ fi.ModelBuilder = &KubectlBuilder{}

// Build is responsible for mananging the kubectl on the nodes
func (b *KubectlBuilder) Build(c *fi.ModelBuilderContext) error {
	if !b.IsMaster {
		return nil
	}

	{
		// TODO: Extract to common function?
		assetName := "kubectl"
		assetPath := ""
		asset, err := b.Assets.Find(assetName, assetPath)
		if err != nil {
			return fmt.Errorf("error trying to locate asset %q: %v", assetName, err)
		}
		if asset == nil {
			return fmt.Errorf("unable to locate asset %q", assetName)
		}

		t := &nodetasks.File{
			Path:     b.KubectlPath() + "/" + assetName,
			Contents: asset,
			Type:     nodetasks.FileType_File,
			Mode:     s("0755"),
		}
		c.AddTask(t)
	}

	{
		kubeconfig, err := b.buildPKIKubeconfig("kubecfg")
		if err != nil {
			return err
		}

		t := &nodetasks.File{
			Path:     "/var/lib/kubectl/kubeconfig",
			Contents: fi.NewStringResource(kubeconfig),
			Type:     nodetasks.FileType_File,
			Mode:     s("0400"),
		}
		c.AddTask(t)

		switch b.Distribution {
		case distros.DistributionJessie, distros.DistributionDebian9:
			c.AddTask(&nodetasks.File{
				Path:  "/home/admin/.kube/",
				Type:  nodetasks.FileType_Directory,
				Mode:  s("0700"),
				Owner: s("admin"),
				Group: s("admin"),
			})

			c.AddTask(&nodetasks.File{
				Path:     "/home/admin/.kube/config",
				Contents: fi.NewStringResource(kubeconfig),
				Type:     nodetasks.FileType_File,
				Mode:     s("0400"),
				Owner:    s("admin"),
				Group:    s("admin"),
			})

		default:
			glog.Warningf("Unknown distro; won't write kubeconfig to homedir %s", b.Distribution)
		}
	}

	return nil
}
<commit_msg>fix typo in comment: mananging<commit_after>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package model

import (
	"fmt"

	"k8s.io/kops/nodeup/pkg/distros"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks"

	"github.com/golang/glog"
)

// KubectlBuilder install kubectl
type KubectlBuilder struct {
	*NodeupModelContext
}

var _ fi.ModelBuilder = &KubectlBuilder{}

// Build is responsible for managing the kubectl on the nodes
func (b *KubectlBuilder) Build(c *fi.ModelBuilderContext) error {
	if !b.IsMaster {
		return nil
	}

	{
		// TODO: Extract to common function?
		assetName := "kubectl"
		assetPath := ""
		asset, err := b.Assets.Find(assetName, assetPath)
		if err != nil {
			return fmt.Errorf("error trying to locate asset %q: %v", assetName, err)
		}
		if asset == nil {
			return fmt.Errorf("unable to locate asset %q", assetName)
		}

		t := &nodetasks.File{
			Path:     b.KubectlPath() + "/" + assetName,
			Contents: asset,
			Type:     nodetasks.FileType_File,
			Mode:     s("0755"),
		}
		c.AddTask(t)
	}

	{
		kubeconfig, err := b.buildPKIKubeconfig("kubecfg")
		if err != nil {
			return err
		}

		t := &nodetasks.File{
			Path:     "/var/lib/kubectl/kubeconfig",
			Contents: fi.NewStringResource(kubeconfig),
			Type:     nodetasks.FileType_File,
			Mode:     s("0400"),
		}
		c.AddTask(t)

		switch b.Distribution {
		case distros.DistributionJessie, distros.DistributionDebian9:
			c.AddTask(&nodetasks.File{
				Path:  "/home/admin/.kube/",
				Type:  nodetasks.FileType_Directory,
				Mode:  s("0700"),
				Owner: s("admin"),
				Group: s("admin"),
			})

			c.AddTask(&nodetasks.File{
				Path:     "/home/admin/.kube/config",
				Contents: fi.NewStringResource(kubeconfig),
				Type:     nodetasks.FileType_File,
				Mode:     s("0400"),
				Owner:    s("admin"),
				Group:    s("admin"),
			})

		default:
			glog.Warningf("Unknown distro; won't write kubeconfig to homedir %s", b.Distribution)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package certinel

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"errors"
	"log"
	"strings"
	"time"

	"github.com/miekg/dns"
)

var (
	ErrNoPeerCertificate = errors.New("peer did not present certificate for domain")
	ErrExpired           = errors.New("certificate expired")
	ErrNotYetValid       = errors.New("certificate not yet valid")
	ErrInvalidHostname   = errors.New("invalid hostname")
	ErrNoCertificate     = errors.New("certificate serial not found")
)

type Domain struct {
	Domain string `json:"domain"`
	Port   string `json:"port"`
	cert   *x509.Certificate
}

type Status struct {
	Duration int64     `json:"check_duration"`
	Valid    bool      `json:"valid"`
	Err      string    `json:"error"`
	Time     time.Time `json:"last_check"`
}

type Subject struct {
	CommonName         string   `json:"cn"`
	Country            []string `json:"c"`
	Organization       []string `json:"o"`
	OrganizationalUnit []string `json:"ou"`
}

type Certificate struct {
	NotBefore      time.Time `json:"not_before"`
	NotAfter       time.Time `json:"not_after"`
	Issuer         Subject   `json:"issuer"`
	Subject        Subject   `json:"subject"`
	SerialNumber   string    `json:"serial"`
	AlternateNames []string  `json:"alternate_names"`
}

func convertCert(cert *x509.Certificate) *Certificate {
	result := &Certificate{
		NotBefore:      cert.NotBefore.UTC(),
		NotAfter:       cert.NotAfter.UTC(),
		SerialNumber:   cert.SerialNumber.String(),
		AlternateNames: cert.DNSNames,
	}

	result.Subject = Subject{
		CommonName:         cert.Subject.CommonName,
		Country:            cert.Subject.Country,
		Organization:       cert.Subject.Organization,
		OrganizationalUnit: cert.Subject.OrganizationalUnit,
	}

	result.Issuer = Subject{
		CommonName:         cert.Issuer.CommonName,
		Country:            cert.Issuer.Country,
		Organization:       cert.Issuer.Organization,
		OrganizationalUnit: cert.Issuer.OrganizationalUnit,
	}

	return result
}

// reverse a hostname (example.com => com.example.). This will provide a better
// form for sorting (www.example.com and api.example.com will be close together
// when reversed)
func ReverseHost(hostname string) (string, error) {
	if _, ok := dns.IsDomainName(hostname); ok {
		labels := dns.SplitDomainName(hostname)

		for i, j := 0, len(labels)-1; i < j; i, j = i+1, j-1 {
			labels[i], labels[j] = labels[j], labels[i]
		}

		return strings.Join(labels, "."), nil
	} else {
		return "", ErrInvalidHostname
	}

	return "", ErrInvalidHostname
}

func (d *Domain) GetCertificate() (*x509.Certificate, error) {
	conn, err := tls.Dial("tcp", d.Domain+":"+d.Port, nil)
	if err != nil {
		return nil, err
	}

	if err := conn.Handshake(); err != nil {
		return nil, err
	}

	state := conn.ConnectionState()
	for _, cert := range state.PeerCertificates {
		if ok := cert.VerifyHostname(d.Domain); ok == nil {
			return cert, nil
		}
	}

	return nil, ErrNoPeerCertificate
}

func (d *Domain) Check() error {
	cert, err := d.GetCertificate()
	if err != nil {
		return nil
	}
	d.cert = cert

	now := time.Now().UTC()
	if !now.Before(cert.NotAfter) {
		return ErrExpired
	}

	if !now.After(cert.NotBefore) {
		return ErrNotYetValid
	}

	return nil
}

func (d *Domain) Store(status *Status) error {
	rhost, err := ReverseHost(d.Domain)
	if err != nil {
		return err
	}

	store := GetStore()
	bucket := []string{"domains", rhost + ":" + d.Port}
	if err := store.Create(bucket); err != nil {
		return err
	}

	if d.cert != nil {
		id := d.cert.NotBefore.Format(time.RFC3339)
		data := base64.StdEncoding.EncodeToString(d.cert.Raw)
		if err := store.Set(bucket, "cert~"+d.cert.SerialNumber.String(), data); err != nil {
			return err
		}

		if err := store.Set(bucket, "history~"+id, d.cert.SerialNumber.String()); err != nil {
			return err
		}

		if err := store.Set(bucket, "history~~", "-- LIST STOP --"); err != nil {
			return err
		}

		if err := store.Set(bucket, "current", d.cert.SerialNumber.String()); err != nil {
			return err
		}

		if status != nil {
			data, err := json.Marshal(status)
			if err != nil {
				return err
			}
			if err := store.Set(bucket, "status", string(data)); err != nil {
				return err
			}
		}
	}

	return nil
}

func (d *Domain) Status() (*Status, error) {
	rhost, err := ReverseHost(d.Domain)
	if err != nil {
		return nil, err
	}

	store := GetStore()
	bucket := []string{"domains", rhost + ":" + d.Port}
	value, err := store.Get(bucket, "status")
	if err != nil {
		return nil, err
	}

	data := &Status{}
	if err := json.Unmarshal([]byte(value), data); err != nil {
		return nil, err
	}

	return data, nil
}

func (d *Domain) Delete() error {
	rhost, err := ReverseHost(d.Domain)
	if err != nil {
		return err
	}

	store := GetStore()
	return store.Remove([]string{"domains"}, rhost+":"+d.Port)
}

func (d *Domain) CertList() (string, []string, error) {
	rhost, err := ReverseHost(d.Domain)
	if err != nil {
		return "", nil, err
	}

	store := GetStore()
	bucket := []string{"domains", rhost + ":" + d.Port}
	current, err := store.Get(bucket, "current")
	if err != nil {
		return "", nil, err
	}

	his := store.Scan(bucket, "history~", true, 51)
	history := make([]string, 0)
	for kv := range his {
		if len(kv.Value) > 0 {
			history = append(history, kv.Value)
		}
	}

	return current, history[1:], nil
}

func (d *Domain) LoadCertificate(serial string) (*Certificate, error) {
	rhost, err := ReverseHost(d.Domain)
	if err != nil {
		return nil, err
	}

	store := GetStore()
	bucket := []string{"domains", rhost + ":" + d.Port}
	raw, err := store.Get(bucket, "cert~"+serial)
	if err != nil {
		return nil, err
	}

	if len(raw) == 0 {
		return nil, ErrNoCertificate
	}

	data, err := base64.StdEncoding.DecodeString(raw)
	if err != nil {
		return nil, err
	}

	cert, err := x509.ParseCertificate(data)
	if err != nil {
		return nil, err
	}

	return convertCert(cert), nil
}

func CheckDomain(domain, port string) {
	ticker := time.NewTicker(time.Minute * 5)
	log.Printf("starting domain checker for \"%s:%s\"\n", domain, port)

	for {
		d := &Domain{
			Domain: domain,
			Port:   port,
		}

		start := time.Now()
		status := &Status{Time: start}
		err := d.Check()
		if err != nil {
			log.Printf("checking domain \"%s:%s\": %s\n", domain, port, err.Error())
			status.Valid = false
			status.Err = err.Error()
		} else {
			now := time.Now().UTC().Unix()
			validity := int((d.cert.NotAfter.Unix() - now) / 86400)
			log.Printf("checking domain \"%s:%s\": certificate is valid for %d days", domain, port, validity)
			status.Valid = true
		}
		status.Duration = int64(time.Since(start) / time.Millisecond)

		// store latest check and certificate
		d.Store(status)

		// wait for 5 minutes
		<-ticker.C
	}
}

func GetDomains() []*Domain {
	result := make([]*Domain, 0)
	store := GetStore()
	for kv := range store.Scan([]string{"domains"}, "", false, 0) {
		splitter := strings.Split(kv.Key, ":")
		if len(splitter) != 2 {
			continue
		}

		domain, err := ReverseHost(splitter[0])
		if err != nil {
			continue
		}

		result = append(result, &Domain{Domain: domain, Port: splitter[1]})
	}

	return result
}

func StartDomainChecker() {
	for _, d := range GetDomains() {
		go CheckDomain(d.Domain, d.Port)
	}
}
<commit_msg>more information in the certificate<commit_after>package certinel

import (
	"crypto/sha1"
	"crypto/sha256"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"errors"
	"log"
	"strings"
	"time"

	"github.com/miekg/dns"
)

var (
	ErrNoPeerCertificate = errors.New("peer did not present certificate for domain")
	ErrExpired           = errors.New("certificate expired")
	ErrNotYetValid       = errors.New("certificate not yet valid")
	ErrInvalidHostname   = errors.New("invalid hostname")
	ErrNoCertificate     = errors.New("certificate serial not found")
)

type Domain struct {
	Domain string `json:"domain"`
	Port   string `json:"port"`
	cert   *x509.Certificate
}

type Status struct {
	Duration int64     `json:"check_duration"`
	Valid    bool      `json:"valid"`
	Err      string    `json:"error"`
	Time     time.Time `json:"last_check"`
}

type Subject struct {
	CommonName         string   `json:"cn"`
	Country            []string `json:"c,omitempty"`
	Organization       []string `json:"o,omitempty"`
	OrganizationalUnit []string `json:"ou,omitempty"`
}

type Signature struct {
	Algorithm int    `json:"algorithm"`
	Value     string `json:"value"`
}

type Certificate struct {
	NotBefore      time.Time         `json:"not_before"`
	NotAfter       time.Time         `json:"not_after"`
	Issuer         Subject           `json:"issuer"`
	Subject        Subject           `json:"subject"`
	SerialNumber   string            `json:"serial"`
	AlternateNames []string          `json:"alternate_names,omitempty"`
	Signature      Signature         `json:"signature"`
	Fingerprints   map[string]string `json:"fingerprints"`
}

func toHexString(data []byte) string {
	result := make([]string, len(data))
	for i := 0; i < len(data); i += 1 {
		result[i] = hex.EncodeToString(data[i : i+1])
	}
	return strings.Join(result, ":")
}

func convertCert(cert *x509.Certificate) *Certificate {
	result := &Certificate{
		NotBefore:      cert.NotBefore.UTC(),
		NotAfter:       cert.NotAfter.UTC(),
		SerialNumber:   toHexString(cert.SerialNumber.Bytes()),
		AlternateNames: cert.DNSNames,
		Fingerprints:   make(map[string]string),
	}

	result.Signature = Signature{
		Value:     toHexString(cert.Signature),
		Algorithm: int(cert.SignatureAlgorithm),
	}

	result.Subject = Subject{
		CommonName:         cert.Subject.CommonName,
		Country:            cert.Subject.Country,
		Organization:       cert.Subject.Organization,
		OrganizationalUnit: cert.Subject.OrganizationalUnit,
	}

	result.Issuer = Subject{
		CommonName:         cert.Issuer.CommonName,
		Country:            cert.Issuer.Country,
		Organization:       cert.Issuer.Organization,
		OrganizationalUnit: cert.Issuer.OrganizationalUnit,
	}

	s256 := sha256.New()
	s256.Write(cert.Raw)
	result.Fingerprints["sha256"] = toHexString(s256.Sum(nil))

	s1 := sha1.New()
	s1.Write(cert.Raw)
	result.Fingerprints["sha1"] = toHexString(s1.Sum(nil))

	return result
}

// reverse a hostname (example.com => com.example.). This will provide a better
// form for sorting (www.example.com and api.example.com will be close together
// when reversed)
func ReverseHost(hostname string) (string, error) {
	if _, ok := dns.IsDomainName(hostname); ok {
		labels := dns.SplitDomainName(hostname)

		for i, j := 0, len(labels)-1; i < j; i, j = i+1, j-1 {
			labels[i], labels[j] = labels[j], labels[i]
		}

		return strings.Join(labels, "."), nil
	} else {
		return "", ErrInvalidHostname
	}

	return "", ErrInvalidHostname
}

func (d *Domain) GetCertificate() (*x509.Certificate, error) {
	conn, err := tls.Dial("tcp", d.Domain+":"+d.Port, nil)
	if err != nil {
		return nil, err
	}

	if err := conn.Handshake(); err != nil {
		return nil, err
	}

	state := conn.ConnectionState()
	for _, cert := range state.PeerCertificates {
		if ok := cert.VerifyHostname(d.Domain); ok == nil {
			return cert, nil
		}
	}

	return nil, ErrNoPeerCertificate
}

func (d *Domain) Check() error {
	cert, err := d.GetCertificate()
	if err != nil {
		return nil
	}
	d.cert = cert

	now := time.Now().UTC()
	if !now.Before(cert.NotAfter) {
		return ErrExpired
	}

	if !now.After(cert.NotBefore) {
		return ErrNotYetValid
	}

	return nil
}

func (d *Domain) Store(status *Status) error {
	rhost, err := ReverseHost(d.Domain)
	if err != nil {
		return err
	}

	store := GetStore()
	bucket := []string{"domains", rhost + ":" + d.Port}
	if err := store.Create(bucket); err != nil {
		return err
	}

	if d.cert != nil {
		id := d.cert.NotBefore.Format(time.RFC3339)
		data := base64.StdEncoding.EncodeToString(d.cert.Raw)
		if err := store.Set(bucket, "cert~"+d.cert.SerialNumber.String(), data); err != nil {
			return err
		}

		if err := store.Set(bucket, "history~"+id, d.cert.SerialNumber.String()); err != nil {
			return err
		}

		if err := store.Set(bucket, "history~~", "-- LIST STOP --"); err != nil {
			return err
		}

		if err := store.Set(bucket, "current", d.cert.SerialNumber.String()); err != nil {
			return err
		}

		if status != nil {
			data, err := json.Marshal(status)
			if err != nil {
				return err
			}
			if err := store.Set(bucket, "status", string(data)); err != nil {
				return err
			}
		}
	}

	return nil
}

func (d *Domain) Status() (*Status, error) {
	rhost, err := ReverseHost(d.Domain)
	if err != nil {
		return nil, err
	}

	store := GetStore()
	bucket := []string{"domains", rhost + ":" + d.Port}
	value, err := store.Get(bucket, "status")
	if err != nil {
		return nil, err
	}

	data := &Status{}
	if err := json.Unmarshal([]byte(value), data); err != nil {
		return nil, err
	}

	return data, nil
}

func (d *Domain) Delete() error {
	rhost, err := ReverseHost(d.Domain)
	if err != nil {
		return err
	}

	store := GetStore()
	return store.Remove([]string{"domains"}, rhost+":"+d.Port)
}

func (d *Domain) CertList() (string, []string, error) {
	rhost, err := ReverseHost(d.Domain)
	if err != nil {
		return "", nil, err
	}

	store := GetStore()
	bucket := []string{"domains", rhost + ":" + d.Port}
	current, err := store.Get(bucket, "current")
	if err != nil {
		return "", nil, err
	}

	his := store.Scan(bucket, "history~", true, 51)
	history := make([]string, 0)
	for kv := range his {
		if len(kv.Value) > 0 {
			history = append(history, kv.Value)
		}
	}

	return current, history[1:], nil
}

func (d *Domain) LoadCertificate(serial string) (*Certificate, error) {
	rhost, err := ReverseHost(d.Domain)
	if err != nil {
		return nil, err
	}

	store := GetStore()
	bucket := []string{"domains", rhost + ":" + d.Port}
	raw, err := store.Get(bucket, "cert~"+serial)
	if err != nil {
		return nil, err
	}

	if len(raw) == 0 {
		return nil, ErrNoCertificate
	}

	data, err := base64.StdEncoding.DecodeString(raw)
	if err != nil {
		return nil, err
	}

	cert, err := x509.ParseCertificate(data)
	if err != nil {
		return nil, err
	}

	return convertCert(cert), nil
}

func CheckDomain(domain, port string) {
	ticker := time.NewTicker(time.Minute * 5)
	log.Printf("starting domain checker for \"%s:%s\"\n", domain, port)

	for {
		d := &Domain{
			Domain: domain,
			Port:   port,
		}

		start := time.Now()
		status := &Status{Time: start}
		err := d.Check()
		if err != nil {
			log.Printf("checking domain \"%s:%s\": %s\n", domain, port, err.Error())
			status.Valid = false
			status.Err = err.Error()
		} else {
			now := time.Now().UTC().Unix()
			validity := int((d.cert.NotAfter.Unix() - now) / 86400)
			log.Printf("checking domain \"%s:%s\": certificate is valid for %d days", domain, port, validity)
			status.Valid = true
		}
		status.Duration = int64(time.Since(start) / time.Millisecond)

		// store latest check and certificate
		d.Store(status)

		// wait for 5 minutes
		<-ticker.C
	}
}

func GetDomains() []*Domain {
	result := make([]*Domain, 0)
	store := GetStore()
	for kv := range store.Scan([]string{"domains"}, "", false, 0) {
		splitter := strings.Split(kv.Key, ":")
		if len(splitter) != 2 {
			continue
		}

		domain, err := ReverseHost(splitter[0])
		if err != nil {
			continue
		}

		result = append(result, &Domain{Domain: domain, Port: splitter[1]})
	}

	return result
}

func StartDomainChecker() {
	for _, d := range GetDomains() {
		go CheckDomain(d.Domain, d.Port)
	}
}
<|endoftext|>


<commit_before>package pluginaction_test

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"

	. "code.cloudfoundry.org/cli/actor/pluginaction"
	"code.cloudfoundry.org/cli/actor/pluginaction/pluginactionfakes"
	"code.cloudfoundry.org/cli/util/configv3"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("UninstallPlugin", func() {
	var (
		actor                 Actor
		binaryPath            string
		fakeConfig            *pluginactionfakes.FakeConfig
		fakePluginUninstaller *pluginactionfakes.FakePluginUninstaller
		pluginHome            string
		err                   error
	)

	BeforeEach(func() {
		pluginHome, err = ioutil.TempDir("", "")
		Expect(err).ToNot(HaveOccurred())

		binaryPath = filepath.Join(pluginHome, "banana-faceman")
		err = ioutil.WriteFile(binaryPath, nil, 0600)
		Expect(err).ToNot(HaveOccurred())

		fakePluginUninstaller = new(pluginactionfakes.FakePluginUninstaller)
		fakeConfig = new(pluginactionfakes.FakeConfig)
		actor = NewActor(fakeConfig, nil)

	})

	AfterEach(func() {
		os.RemoveAll(pluginHome)
	})

	Context("when the plugin does not exist", func() {
		BeforeEach(func() {
			fakeConfig.GetPluginReturns(configv3.Plugin{}, false)
		})

		It("returns a PluginNotFoundError", func() {
			err := actor.UninstallPlugin(fakePluginUninstaller, "some-non-existent-plugin")
			Expect(err).To(MatchError(PluginNotFoundError{Name: "some-non-existent-plugin"}))
		})
	})

	Context("when the plugin exists", func() {
		BeforeEach(func() {
			fakeConfig.GetPluginReturns(configv3.Plugin{
				Name:     "some-plugin",
				Location: binaryPath,
			}, true)
		})

		Context("when no errors are encountered", func() {
			It("runs the plugin cleanup, deletes the binary and removes the plugin config", func() {
				err := actor.UninstallPlugin(fakePluginUninstaller, "some-plugin")
				Expect(err).ToNot(HaveOccurred())

				Expect(fakeConfig.GetPluginCallCount()).To(Equal(1))
				Expect(fakeConfig.GetPluginArgsForCall(0)).To(Equal("some-plugin"))

				Expect(fakePluginUninstaller.UninstallCallCount()).To(Equal(1))
				Expect(fakePluginUninstaller.UninstallArgsForCall(0)).To(Equal(binaryPath))

				_, err = os.Stat(binaryPath)
				Expect(os.IsNotExist(err)).To(BeTrue())

				Expect(fakeConfig.RemovePluginCallCount()).To(Equal(1))
				Expect(fakeConfig.RemovePluginArgsForCall(0)).To(Equal("some-plugin"))

				Expect(fakeConfig.WritePluginConfigCallCount()).To(Equal(1))
			})
		})

		Context("when the plugin uninstaller returns an error", func() {
			var expectedErr error

			BeforeEach(func() {
				expectedErr = errors.New("some error")
				fakePluginUninstaller.UninstallReturns(expectedErr)
			})

			It("returns the error and does not delete the binary or remove the plugin config", func() {
				err := actor.UninstallPlugin(fakePluginUninstaller, "some-plugin")
				Expect(err).To(MatchError(expectedErr))

				_, err = os.Stat(binaryPath)
				Expect(err).ToNot(HaveOccurred())

				Expect(fakeConfig.RemovePluginCallCount()).To(Equal(0))
			})
		})

		Context("when deleting the plugin binary returns a 'file does not exist' error", func() {
			BeforeEach(func() {
				err = os.Remove(binaryPath)
				Expect(err).ToNot(HaveOccurred())
			})

			It("does not return the error and removes the plugin config", func() {
				err := actor.UninstallPlugin(fakePluginUninstaller, "some-plugin")
				Expect(err).ToNot(HaveOccurred())

				Expect(fakeConfig.RemovePluginCallCount()).To(Equal(1))
			})
		})

		Context("when deleting the plugin binary returns any other error", func() {
			BeforeEach(func() {
				err = os.Remove(binaryPath)
				Expect(err).ToNot(HaveOccurred())
				err = os.Mkdir(binaryPath, 0700)
				Expect(err).ToNot(HaveOccurred())
				err = ioutil.WriteFile(filepath.Join(binaryPath, "foooooo"), nil, 0500)
				Expect(err).ToNot(HaveOccurred())
			})

			It("does not return the error and removes the plugin config", func() {
				err := actor.UninstallPlugin(fakePluginUninstaller, "some-plugin")
				_, ok := err.(*os.PathError)
				Expect(ok).To(BeTrue())

				Expect(fakeConfig.RemovePluginCallCount()).To(Equal(0))
				Expect(fakeConfig.WritePluginConfigCallCount()).To(Equal(0))
			})
		})

		Context("when writing the config returns an error", func() {
			var expectedErr error

			BeforeEach(func() {
				expectedErr = errors.New("some plugin config write error")

				fakeConfig.WritePluginConfigReturns(expectedErr)
			})

			It("returns the error", func() {
				err := actor.UninstallPlugin(fakePluginUninstaller, "some-plugin")
				Expect(err).To(MatchError(expectedErr))
			})
		})
	})
})
<commit_msg>add another level of nesting for plugin actor test<commit_after>package pluginaction_test

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"

	. "code.cloudfoundry.org/cli/actor/pluginaction"
	"code.cloudfoundry.org/cli/actor/pluginaction/pluginactionfakes"
	"code.cloudfoundry.org/cli/util/configv3"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Plugin actor", func() {
	var (
		actor      Actor
		fakeConfig *pluginactionfakes.FakeConfig
	)

	BeforeEach(func() {
		fakeConfig = new(pluginactionfakes.FakeConfig)
		actor = NewActor(fakeConfig, nil)
	})

	Describe("UninstallPlugin", func() {
		var (
			binaryPath            string
			fakePluginUninstaller *pluginactionfakes.FakePluginUninstaller
			pluginHome            string
			err                   error
		)

		BeforeEach(func() {
			pluginHome, err = ioutil.TempDir("", "")
			Expect(err).ToNot(HaveOccurred())

			binaryPath = filepath.Join(pluginHome, "banana-faceman")
			err = ioutil.WriteFile(binaryPath, nil, 0600)
			Expect(err).ToNot(HaveOccurred())

			fakePluginUninstaller = new(pluginactionfakes.FakePluginUninstaller)
		})

		AfterEach(func() {
			os.RemoveAll(pluginHome)
		})

		Context("when the plugin does not exist", func() {
			BeforeEach(func() {
				fakeConfig.GetPluginReturns(configv3.Plugin{}, false)
			})

			It("returns a PluginNotFoundError", func() {
				err := actor.UninstallPlugin(fakePluginUninstaller, "some-non-existent-plugin")
				Expect(err).To(MatchError(PluginNotFoundError{Name: "some-non-existent-plugin"}))
			})
		})

		Context("when the plugin exists", func() {
			BeforeEach(func() {
				fakeConfig.GetPluginReturns(configv3.Plugin{
					Name:     "some-plugin",
					Location: binaryPath,
				}, true)
			})

			Context("when no errors are encountered", func() {
				It("runs the plugin cleanup, deletes the binary and removes the plugin config", func() {
					err := actor.UninstallPlugin(fakePluginUninstaller, "some-plugin")
					Expect(err).ToNot(HaveOccurred())

					Expect(fakeConfig.GetPluginCallCount()).To(Equal(1))
					Expect(fakeConfig.GetPluginArgsForCall(0)).To(Equal("some-plugin"))

					Expect(fakePluginUninstaller.UninstallCallCount()).To(Equal(1))
					Expect(fakePluginUninstaller.UninstallArgsForCall(0)).To(Equal(binaryPath))

					_, err = os.Stat(binaryPath)
					Expect(os.IsNotExist(err)).To(BeTrue())

					Expect(fakeConfig.RemovePluginCallCount()).To(Equal(1))
					Expect(fakeConfig.RemovePluginArgsForCall(0)).To(Equal("some-plugin"))

					Expect(fakeConfig.WritePluginConfigCallCount()).To(Equal(1))
				})
			})

			Context("when the plugin uninstaller returns an error", func() {
				var expectedErr error

				BeforeEach(func() {
					expectedErr = errors.New("some error")
					fakePluginUninstaller.UninstallReturns(expectedErr)
				})

				It("returns the error and does not delete the binary or remove the plugin config", func() {
					err := actor.UninstallPlugin(fakePluginUninstaller, "some-plugin")
					Expect(err).To(MatchError(expectedErr))

					_, err = os.Stat(binaryPath)
					Expect(err).ToNot(HaveOccurred())

					Expect(fakeConfig.RemovePluginCallCount()).To(Equal(0))
				})
			})

			Context("when deleting the plugin binary returns a 'file does not exist' error", func() {
				BeforeEach(func() {
					err = os.Remove(binaryPath)
					Expect(err).ToNot(HaveOccurred())
				})

				It("does not return the error and removes the plugin config", func() {
					err := actor.UninstallPlugin(fakePluginUninstaller, "some-plugin")
					Expect(err).ToNot(HaveOccurred())

					Expect(fakeConfig.RemovePluginCallCount()).To(Equal(1))
				})
			})

			Context("when deleting the plugin binary returns any other error", func() {
				BeforeEach(func() {
					err = os.Remove(binaryPath)
					Expect(err).ToNot(HaveOccurred())
					err = os.Mkdir(binaryPath, 0700)
					Expect(err).ToNot(HaveOccurred())
					err = ioutil.WriteFile(filepath.Join(binaryPath, "foooooo"), nil, 0500)
					Expect(err).ToNot(HaveOccurred())
				})

				It("does not return the error and removes the plugin config", func() {
					err := actor.UninstallPlugin(fakePluginUninstaller, "some-plugin")
					_, ok := err.(*os.PathError)
					Expect(ok).To(BeTrue())

					Expect(fakeConfig.RemovePluginCallCount()).To(Equal(0))
					Expect(fakeConfig.WritePluginConfigCallCount()).To(Equal(0))
				})
			})

			Context("when writing the config returns an error", func() {
				var expectedErr error

				BeforeEach(func() {
					expectedErr = errors.New("some plugin config write error")

					fakeConfig.WritePluginConfigReturns(expectedErr)
				})

				It("returns the error", func() {
					err := actor.UninstallPlugin(fakePluginUninstaller, "some-plugin")
					Expect(err).To(MatchError(expectedErr))
				})
			})
		})
	})
})
<|endoftext|>