<commit_before>package rpcd

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/Symantec/Dominator/lib/hash"
	"github.com/Symantec/Dominator/lib/triggers"
	"github.com/Symantec/Dominator/proto/sub"
	"os"
	"os/exec"
	"path"
	"strings"
	"time"
)

func (t *rpcType) Update(request sub.UpdateRequest,
	reply *sub.UpdateResponse) error {
	rwLock.Lock()
	defer rwLock.Unlock()
	fs := fileSystemHistory.FileSystem()
	if fs == nil {
		return errors.New("No file-system history yet")
	}
	logger.Printf("Update()\n")
	if fetchInProgress {
		logger.Println("Error: fetch already in progress")
		return errors.New("fetch already in progress")
	}
	if updateInProgress {
		logger.Println("Error: update progress")
		return errors.New("update in progress")
	}
	updateInProgress = true
	go doUpdate(request, fs.RootDirectoryName())
	return nil
}

func doUpdate(request sub.UpdateRequest, rootDirectoryName string) {
	defer clearUpdateInProgress()
	startTime := time.Now()
	var oldTriggers triggers.Triggers
	file, err := os.Open(oldTriggersFilename)
	if err == nil {
		decoder := json.NewDecoder(file)
		var trig triggers.Triggers
		err = decoder.Decode(&trig.Triggers)
		file.Close()
		if err == nil {
			oldTriggers = trig
		} else {
			logger.Printf("Error decoding old triggers: %s", err.Error())
		}
	}
	if len(oldTriggers.Triggers) > 0 {
		processMakeInodes(request.InodesToMake, rootDirectoryName,
			request.MultiplyUsedObjects, &oldTriggers, false)
		processHardlinksToMake(request.HardlinksToMake, rootDirectoryName,
			&oldTriggers, false)
		processDeletes(request.PathsToDelete, rootDirectoryName, &oldTriggers,
			false)
		processMakeDirectories(request.DirectoriesToMake, rootDirectoryName,
			&oldTriggers, false)
		processChangeDirectories(request.DirectoriesToChange, rootDirectoryName,
			&oldTriggers, false)
		processChangeInodes(request.InodesToChange, rootDirectoryName,
			&oldTriggers, false)
		matchedOldTriggers := oldTriggers.GetMatchedTriggers()
		runTriggers(matchedOldTriggers, "stop")
	}
	processFilesToCopyToCache(request.FilesToCopyToCache, rootDirectoryName)
	processMakeInodes(request.InodesToMake, rootDirectoryName,
		request.MultiplyUsedObjects, request.Triggers, true)
	processHardlinksToMake(request.HardlinksToMake, rootDirectoryName,
		request.Triggers, true)
	processDeletes(request.PathsToDelete, rootDirectoryName, request.Triggers,
		true)
	processMakeDirectories(request.DirectoriesToMake, rootDirectoryName,
		request.Triggers, true)
	processChangeDirectories(request.DirectoriesToChange, rootDirectoryName,
		request.Triggers, true)
	processChangeInodes(request.InodesToChange, rootDirectoryName,
		request.Triggers, true)
	matchedNewTriggers := request.Triggers.GetMatchedTriggers()
	file, err = os.Create(oldTriggersFilename)
	if err == nil {
		b, err := json.Marshal(request.Triggers.Triggers)
		if err == nil {
			var out bytes.Buffer
			json.Indent(&out, b, "", "    ")
			out.WriteTo(file)
		} else {
			logger.Printf("Error marshaling triggers: %s", err.Error())
		}
		file.Close()
	}
	runTriggers(matchedNewTriggers, "start")
	timeTaken := time.Since(startTime)
	logger.Printf("Update() completed in %s\n", timeTaken)
	// TODO(rgooch): Remove debugging hack and implement.
	time.Sleep(time.Second * 15)
	logger.Printf("Post-Update() debugging sleep complete\n")
}

func clearUpdateInProgress() {
	rwLock.Lock()
	defer rwLock.Unlock()
	updateInProgress = false
}

func processFilesToCopyToCache(filesToCopyToCache []sub.FileToCopyToCache,
	rootDirectoryName string) {
	for _, fileToCopy := range filesToCopyToCache {
		logger.Printf("Copy: %s to cache\n", fileToCopy.Name)
		// TODO(rgooch): Implement.
	}
}

func processMakeInodes(inodesToMake []sub.Inode, rootDirectoryName string,
	multiplyUsedObjects map[hash.Hash]uint64, triggers *triggers.Triggers,
	takeAction bool) {
	for _, inode := range inodesToMake {
		fullPathname := path.Join(rootDirectoryName, inode.Name)
		triggers.Match(inode.Name)
		if takeAction {
			logger.Printf("Make inode: %s\n", fullPathname)
			// TODO(rgooch): Implement.
		}
	}
}

func processHardlinksToMake(hardlinksToMake []sub.Hardlink,
	rootDirectoryName string, triggers *triggers.Triggers, takeAction bool) {
	for _, hardlink := range hardlinksToMake {
		triggers.Match(hardlink.NewLink)
		if takeAction {
			logger.Printf("Link: %s => %s\n", hardlink.NewLink, hardlink.Target)
			// TODO(rgooch): Implement.
			// err := os.Link(path.Join(rootDirectoryName, hardlink.Target),
			//	path.Join(rootDirectoryName, hardlink.NewLink))
		}
	}
}

func processDeletes(pathsToDelete []string, rootDirectoryName string,
	triggers *triggers.Triggers, takeAction bool) {
	for _, pathname := range pathsToDelete {
		fullPathname := path.Join(rootDirectoryName, pathname)
		triggers.Match(pathname)
		if takeAction {
			logger.Printf("Delete: %s\n", fullPathname)
			// TODO(rgooch): Implement.
		}
	}
}

func processMakeDirectories(directoriesToMake []sub.Directory,
	rootDirectoryName string, triggers *triggers.Triggers, takeAction bool) {
	for _, newdir := range directoriesToMake {
		if skipPath(newdir.Name) {
			continue
		}
		fullPathname := path.Join(rootDirectoryName, newdir.Name)
		triggers.Match(newdir.Name)
		if takeAction {
			logger.Printf("Mkdir: %s\n", fullPathname)
			// TODO(rgooch): Implement.
		}
	}
}

func processChangeDirectories(directoriesToChange []sub.Directory,
	rootDirectoryName string, triggers *triggers.Triggers, takeAction bool) {
	for _, directory := range directoriesToChange {
		if takeAction {
			logger.Printf("Change directory: %s\n", directory.Name)
			// TODO(rgooch): Implement.
		}
	}
}

func processChangeInodes(inodesToChange []sub.Inode,
	rootDirectoryName string, triggers *triggers.Triggers, takeAction bool) {
	for _, inode := range inodesToChange {
		if takeAction {
			logger.Printf("Change inode: %s\n", inode.Name)
			// TODO(rgooch): Implement.
		}
	}
}

func skipPath(pathname string) bool {
	if scannerConfiguration.ScanFilter.Match(pathname) {
		return true
	}
	if pathname == "/.subd" {
		return true
	}
	if strings.HasPrefix(pathname, "/.subd/") {
		return true
	}
	return false
}

func runTriggers(triggers []*triggers.Trigger, action string) {
	// For "start" action, if there is a reboot trigger, just do that one.
	if action == "start" {
		for _, trigger := range triggers {
			if trigger.Service == "reboot" {
				logger.Print("Rebooting")
				// TODO(rgooch): Remove debugging output.
				cmd := exec.Command("echo", "reboot")
				cmd.Stdout = os.Stdout
				if err := cmd.Run(); err != nil {
					logger.Print(err)
				}
				return
			}
		}
	}
	ppid := fmt.Sprint(os.Getppid())
	for _, trigger := range triggers {
		if trigger.Service == "reboot" && action == "stop" {
			continue
		}
		logger.Printf("Action: service %s %s\n", trigger.Service, action)
		// TODO(rgooch): Remove debugging output.
		cmd := exec.Command("run-in-mntns", ppid, "echo", "service", action,
			trigger.Service)
		cmd.Stdout = os.Stdout
		if err := cmd.Run(); err != nil {
			logger.Print(err)
		}
		// TODO(rgooch): Implement.
	}
}
<commit_msg>Move copy to cache before stop triggers in subd.Update() RPC.<commit_after>package rpcd

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/Symantec/Dominator/lib/hash"
	"github.com/Symantec/Dominator/lib/triggers"
	"github.com/Symantec/Dominator/proto/sub"
	"os"
	"os/exec"
	"path"
	"strings"
	"time"
)

func (t *rpcType) Update(request sub.UpdateRequest,
	reply *sub.UpdateResponse) error {
	rwLock.Lock()
	defer rwLock.Unlock()
	fs := fileSystemHistory.FileSystem()
	if fs == nil {
		return errors.New("No file-system history yet")
	}
	logger.Printf("Update()\n")
	if fetchInProgress {
		logger.Println("Error: fetch already in progress")
		return errors.New("fetch already in progress")
	}
	if updateInProgress {
		logger.Println("Error: update progress")
		return errors.New("update in progress")
	}
	updateInProgress = true
	go doUpdate(request, fs.RootDirectoryName())
	return nil
}

func doUpdate(request sub.UpdateRequest, rootDirectoryName string) {
	defer clearUpdateInProgress()
	startTime := time.Now()
	var oldTriggers triggers.Triggers
	file, err := os.Open(oldTriggersFilename)
	if err == nil {
		decoder := json.NewDecoder(file)
		var trig triggers.Triggers
		err = decoder.Decode(&trig.Triggers)
		file.Close()
		if err == nil {
			oldTriggers = trig
		} else {
			logger.Printf("Error decoding old triggers: %s", err.Error())
		}
	}
	processFilesToCopyToCache(request.FilesToCopyToCache, rootDirectoryName)
	if len(oldTriggers.Triggers) > 0 {
		processMakeInodes(request.InodesToMake, rootDirectoryName,
			request.MultiplyUsedObjects, &oldTriggers, false)
		processHardlinksToMake(request.HardlinksToMake, rootDirectoryName,
			&oldTriggers, false)
		processDeletes(request.PathsToDelete, rootDirectoryName, &oldTriggers,
			false)
		processMakeDirectories(request.DirectoriesToMake, rootDirectoryName,
			&oldTriggers, false)
		processChangeDirectories(request.DirectoriesToChange, rootDirectoryName,
			&oldTriggers, false)
		processChangeInodes(request.InodesToChange, rootDirectoryName,
			&oldTriggers, false)
		matchedOldTriggers := oldTriggers.GetMatchedTriggers()
		runTriggers(matchedOldTriggers, "stop")
	}
	processMakeInodes(request.InodesToMake, rootDirectoryName,
		request.MultiplyUsedObjects, request.Triggers, true)
	processHardlinksToMake(request.HardlinksToMake, rootDirectoryName,
		request.Triggers, true)
	processDeletes(request.PathsToDelete, rootDirectoryName, request.Triggers,
		true)
	processMakeDirectories(request.DirectoriesToMake, rootDirectoryName,
		request.Triggers, true)
	processChangeDirectories(request.DirectoriesToChange, rootDirectoryName,
		request.Triggers, true)
	processChangeInodes(request.InodesToChange, rootDirectoryName,
		request.Triggers, true)
	matchedNewTriggers := request.Triggers.GetMatchedTriggers()
	file, err = os.Create(oldTriggersFilename)
	if err == nil {
		b, err := json.Marshal(request.Triggers.Triggers)
		if err == nil {
			var out bytes.Buffer
			json.Indent(&out, b, "", "    ")
			out.WriteTo(file)
		} else {
			logger.Printf("Error marshaling triggers: %s", err.Error())
		}
		file.Close()
	}
	runTriggers(matchedNewTriggers, "start")
	timeTaken := time.Since(startTime)
	logger.Printf("Update() completed in %s\n", timeTaken)
	// TODO(rgooch): Remove debugging hack and implement.
	time.Sleep(time.Second * 15)
	logger.Printf("Post-Update() debugging sleep complete\n")
}

func clearUpdateInProgress() {
	rwLock.Lock()
	defer rwLock.Unlock()
	updateInProgress = false
}

func processFilesToCopyToCache(filesToCopyToCache []sub.FileToCopyToCache,
	rootDirectoryName string) {
	for _, fileToCopy := range filesToCopyToCache {
		logger.Printf("Copy: %s to cache\n", fileToCopy.Name)
		// TODO(rgooch): Implement.
	}
}

func processMakeInodes(inodesToMake []sub.Inode, rootDirectoryName string,
	multiplyUsedObjects map[hash.Hash]uint64, triggers *triggers.Triggers,
	takeAction bool) {
	for _, inode := range inodesToMake {
		fullPathname := path.Join(rootDirectoryName, inode.Name)
		triggers.Match(inode.Name)
		if takeAction {
			logger.Printf("Make inode: %s\n", fullPathname)
			// TODO(rgooch): Implement.
		}
	}
}

func processHardlinksToMake(hardlinksToMake []sub.Hardlink,
	rootDirectoryName string, triggers *triggers.Triggers, takeAction bool) {
	for _, hardlink := range hardlinksToMake {
		triggers.Match(hardlink.NewLink)
		if takeAction {
			logger.Printf("Link: %s => %s\n", hardlink.NewLink, hardlink.Target)
			// TODO(rgooch): Implement.
			// err := os.Link(path.Join(rootDirectoryName, hardlink.Target),
			//	path.Join(rootDirectoryName, hardlink.NewLink))
		}
	}
}

func processDeletes(pathsToDelete []string, rootDirectoryName string,
	triggers *triggers.Triggers, takeAction bool) {
	for _, pathname := range pathsToDelete {
		fullPathname := path.Join(rootDirectoryName, pathname)
		triggers.Match(pathname)
		if takeAction {
			logger.Printf("Delete: %s\n", fullPathname)
			// TODO(rgooch): Implement.
		}
	}
}

func processMakeDirectories(directoriesToMake []sub.Directory,
	rootDirectoryName string, triggers *triggers.Triggers, takeAction bool) {
	for _, newdir := range directoriesToMake {
		if skipPath(newdir.Name) {
			continue
		}
		fullPathname := path.Join(rootDirectoryName, newdir.Name)
		triggers.Match(newdir.Name)
		if takeAction {
			logger.Printf("Mkdir: %s\n", fullPathname)
			// TODO(rgooch): Implement.
		}
	}
}

func processChangeDirectories(directoriesToChange []sub.Directory,
	rootDirectoryName string, triggers *triggers.Triggers, takeAction bool) {
	for _, directory := range directoriesToChange {
		if takeAction {
			logger.Printf("Change directory: %s\n", directory.Name)
			// TODO(rgooch): Implement.
		}
	}
}

func processChangeInodes(inodesToChange []sub.Inode,
	rootDirectoryName string, triggers *triggers.Triggers, takeAction bool) {
	for _, inode := range inodesToChange {
		if takeAction {
			logger.Printf("Change inode: %s\n", inode.Name)
			// TODO(rgooch): Implement.
		}
	}
}

func skipPath(pathname string) bool {
	if scannerConfiguration.ScanFilter.Match(pathname) {
		return true
	}
	if pathname == "/.subd" {
		return true
	}
	if strings.HasPrefix(pathname, "/.subd/") {
		return true
	}
	return false
}

func runTriggers(triggers []*triggers.Trigger, action string) {
	// For "start" action, if there is a reboot trigger, just do that one.
	if action == "start" {
		for _, trigger := range triggers {
			if trigger.Service == "reboot" {
				logger.Print("Rebooting")
				// TODO(rgooch): Remove debugging output.
				cmd := exec.Command("echo", "reboot")
				cmd.Stdout = os.Stdout
				if err := cmd.Run(); err != nil {
					logger.Print(err)
				}
				return
			}
		}
	}
	ppid := fmt.Sprint(os.Getppid())
	for _, trigger := range triggers {
		if trigger.Service == "reboot" && action == "stop" {
			continue
		}
		logger.Printf("Action: service %s %s\n", trigger.Service, action)
		// TODO(rgooch): Remove debugging output.
		cmd := exec.Command("run-in-mntns", ppid, "echo", "service", action,
			trigger.Service)
		cmd.Stdout = os.Stdout
		if err := cmd.Run(); err != nil {
			logger.Print(err)
		}
		// TODO(rgooch): Implement.
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package grpcauth

import (
	"crypto/tls"
	"math/big"
	"math/rand"
	"net"
	"strings"
	"sync"
	"time"

	gContext "golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"

	"upspin.io/bind"
	"upspin.io/errors"
	"upspin.io/log"
	"upspin.io/upspin"
	"upspin.io/upspin/proto"
)

// GRPCCommon is an interface that all GRPC services implement for authentication and ping as part of upspin.Service.
type GRPCCommon interface {
	// Ping is the GRPC call for Ping.
	Ping(ctx gContext.Context, in *proto.PingRequest, opts ...grpc.CallOption) (*proto.PingResponse, error)
	// Configure is the GRPC call for Configure.
	Configure(ctx gContext.Context, in *proto.ConfigureRequest, opts ...grpc.CallOption) (*proto.ConfigureResponse, error)
}

// AuthClientService is a partial Service that uses GRPC as transport and implements Authentication.
type AuthClientService struct {
	grpcCommon GRPCCommon
	grpcConn   *grpc.ClientConn
	context    upspin.Context

	keepAliveInterval time.Duration // interval of keep-alive packets.
	closeKeepAlive    chan bool     // channel used to tell the keep-alive routine to exit.

	mu               sync.Mutex // protects the field below.
	authToken        string
	lastTokenRefresh time.Time
	lastNetActivity  time.Time // last known time of some network activity.
}

// SecurityLevel defines the security required of a GRPC connection.
type SecurityLevel int

const (
	// Secure as the security argument to NewGRPCClient requires TLS connections using CA certificates.
	Secure = SecurityLevel(iota + 1)

	// InsecureAllowingSelfSignedCertificates as the security argument to NewGRPCClient requires TLS connections
	// but allows self signed certificates.
	InsecureAllowingSelfSignedCertificates

	// NoSecurity as the security argument to NewGRPCClient requires connections with no authentication or encryption.
	NoSecurity

	// KeepAliveInterval is a suggested interval between keep-alive ping requests to the server.
	// A value of 0 means keep-alives are disabled. Google Cloud Platform (GCP) times out connections
	// every 10 minutes so a smaller values are recommended for talking to servers on GCP.
	KeepAliveInterval = 5 * time.Minute
)

// To be safe, we refresh the token 1 hour ahead of time.
var tokenFreshnessDuration = authTokenDuration - time.Hour

// NewGRPCClient returns new GRPC client connected to a GRPC server at a net address.
// The address is expected to be a raw network address with port number, as in domain.com:5580. However, for convenience,
// it is optionally accepted for the time being to use one of the following prefixes:
// https://, http://, grpc://. This may change in the future.
// A keep alive interval indicates the amount of time to send ping requests to the server. A duration of 0 disables
// keep-alive packets.
// The security level specifies the expected security guarantees of the connection.
func NewGRPCClient(context upspin.Context, netAddr upspin.NetAddr, keepAliveInterval time.Duration, security SecurityLevel) (*AuthClientService, error) {
	const op = "auth/grpcauth.NewGRPCClient"
	if keepAliveInterval != 0 && keepAliveInterval < time.Minute {
		log.Info.Printf("Keep-alive interval too short. You may overload the server and be throttled")
	}
	addr := string(netAddr)
	isHTTP := strings.HasPrefix(addr, "http://")
	isHTTPS := strings.HasPrefix(addr, "https://")
	isGRPC := strings.HasPrefix(addr, "grpc://")
	skip := 0
	switch {
	case isHTTP, isGRPC:
		skip = 7
	case isHTTPS:
		skip = 8
	}
	opts := []grpc.DialOption{
		grpc.WithBlock(),
		grpc.WithDialer(dialWithKeepAlive),
		grpc.WithTimeout(3 * time.Second),
	}
	switch security {
	case NoSecurity:
		// Only allow insecure connections to the loop back network.
		if !isLocal(addr[skip:]) {
			return nil, errors.E(op, netAddr, errors.IO, errors.Str("insecure dial to non-loopback destination"))
		}
		opts = append(opts, grpc.WithInsecure())
	case Secure:
		opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: false})))
	case InsecureAllowingSelfSignedCertificates:
		opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})))
	default:
		return nil, errors.E(op, errors.Invalid, errors.Errorf("invalid security level to NewGRPCClient: %v", security))
	}
	conn, err := grpc.Dial(addr[skip:], opts...)
	if err != nil {
		return nil, err
	}
	ac := &AuthClientService{
		grpcConn:          conn,
		context:           context,
		keepAliveInterval: keepAliveInterval,
		closeKeepAlive:    make(chan bool, 1),
	}
	if keepAliveInterval != 0 {
		go ac.keepAlive()
	}
	return ac, nil
}

func isLocal(addr string) bool {
	host, _, err := net.SplitHostPort(addr)
	if err != nil {
		return false
	}
	ips, err := net.LookupIP(host)
	if err != nil {
		return false
	}
	for _, ip := range ips {
		if !ip.IsLoopback() {
			return false
		}
	}
	return true
}

// keepAlive loops forever pinging the server every keepAliveInterval. It skips pings if there has been network
// activity more recently than the keep alive interval. It must run on a separate go routine.
func (ac *AuthClientService) keepAlive() {
	sleepFor := ac.keepAliveInterval
	for {
		select {
		case <-time.After(sleepFor):
			lastIdleness := time.Since(ac.lastActivity())
			if lastIdleness < ac.keepAliveInterval {
				sleepFor = ac.keepAliveInterval - lastIdleness
				log.Debug.Printf("New ping in %v", sleepFor)
				continue
			}
			sleepFor = ac.keepAliveInterval
			if !ac.Ping() {
				log.Error.Printf("grpcauth: keepAlive: ping failed")
			}
			log.Debug.Printf("grpcAuth: keepAlive: ping okay")
			ac.setLastActivity()
		case <-ac.closeKeepAlive:
			log.Debug.Printf("grpcauth: keepAlive: exiting keep alive routine")
			return
		}
	}
}

// lastActivity reports the time of the last known network activity.
func (ac *AuthClientService) lastActivity() time.Time {
	ac.mu.Lock()
	defer ac.mu.Unlock()
	return ac.lastNetActivity
}

// setLastActivity sets the current time as the last known network acitivity. This is useful
// when using application pings, to prevent unnecessarily frequent pings.
func (ac *AuthClientService) setLastActivity() {
	ac.mu.Lock()
	ac.lastNetActivity = time.Now()
	ac.mu.Unlock()
}

// SetService sets the underlying RPC service which was obtained with proto.NewSERVICENAMEClient, where SERVICENAME is
// the RPC service definition from the proto file.
func (ac *AuthClientService) SetService(common GRPCCommon) {
	ac.grpcCommon = common
}

// GRPCConn returns the grpc client connection used to dial the server.
func (ac *AuthClientService) GRPCConn() *grpc.ClientConn {
	return ac.grpcConn
}

func dialWithKeepAlive(target string, timeout time.Duration) (net.Conn, error) {
	c, err := net.DialTimeout("tcp", target, timeout)
	if err != nil {
		return nil, err
	}
	if tc, ok := c.(*net.TCPConn); ok {
		if err := tc.SetKeepAlive(true); err != nil {
			return nil, err
		}
		if err := tc.SetKeepAlivePeriod(KeepAliveInterval); err != nil {
			return nil, err
		}
	}
	return c, nil
}

// Ping implements upspin.Service.
func (ac *AuthClientService) Ping() bool {
	seq := rand.Int31()
	req := &proto.PingRequest{
		PingSequence: seq,
	}
	gctx, cancel := gContext.WithTimeout(gContext.Background(), 3*time.Second)
	defer cancel()
	resp, err := ac.grpcCommon.Ping(gctx, req)
	if err != nil {
		log.Printf("Ping error: %s", err)
	}
	ac.setLastActivity()
	return err == nil && resp.PingSequence == seq
}

func (ac *AuthClientService) isAuthTokenExpired() bool {
	ac.mu.Lock()
	defer ac.mu.Unlock()
	return ac.authToken == "" || ac.lastTokenRefresh.Add(tokenFreshnessDuration).Before(time.Now())
}

// NewAuthContext sets up a gContext, GRPC CallOption, and finishAuth function
// for authenticating GRPC requests. If a request token is available, it puts
// that token in the context as GRPC metadata. If the request token is not
// available or has expired, it puts authentication request data in the
// context, and sets up a GRPC Call Option and finishAuth function for retrieving
// the authentication response from the GRPC response headers.
//
// Example usage:
// 	ctx, callOpt, finishAuth, err := ac.NewAuthContext()
// 	// handle err
// 	req := &proto.RequestMessage{ ... }
// 	resp, err := c.grpcClient.DoATrump(ctx, req, callOpt)
// 	err = finishAuth(err)
// 	// handle err
func (ac *AuthClientService) NewAuthContext() (ctx gContext.Context, opt grpc.CallOption, finishAuth func(error) error, err error) {
	const op = "auth/grpcauth.AuthClientService"

	ctx = gContext.Background()

	var header metadata.MD
	opt = grpc.Header(&header)

	if !ac.isAuthTokenExpired() {
		ac.mu.Lock()
		token := ac.authToken
		ac.mu.Unlock()
		ctx = metadata.NewContext(ctx, metadata.Pairs(authTokenKey, token))
		finishAuth = func(err error) error {
			ac.setLastActivity()
			return err
		}
		return
	}

	user := string(ac.context.UserName())
	now := time.Now().UTC().Format(time.ANSIC) // to discourage signature replay
	sig, err := ac.context.Factotum().UserSign([]byte(user + " Authenticate " + now))
	if err != nil {
		return nil, nil, nil, errors.E(op, err)
	}
	ctx = metadata.NewContext(ctx, metadata.MD{authRequestKey: []string{
		user,
		now,
		sig.R.String(),
		sig.S.String(),
	}})
	finishAuth = func(err error) error {
		ac.setLastActivity()
		if err != nil {
			return err
		}

		token, ok := header[authTokenKey]
		if !ok || len(token) != 1 {
			return errors.Str("no auth token in response header")
		}
		now := time.Now()

		ac.mu.Lock()
		defer ac.mu.Unlock()
		ac.authToken = token[0]
		ac.lastTokenRefresh = now
		return nil
	}
	return
}

// Close implements upspin.Service.
func (ac *AuthClientService) Close() {
	select { // prevents blocking if Close is called more than once.
	case ac.closeKeepAlive <- true:
		close(ac.closeKeepAlive)
	default:
	}
	// The only error returned is ErrClientConnClosing, meaning something else has already caused it to close.
	_ = ac.grpcConn.Close() // explicitly ignore the error as there's nothing we can do.
}

// ConfigureProxy uses the Configure command to tell the proxy the endpoint it is proxying for and
// to ensure that the proxy is running as our upspin user identity.
func (ac *AuthClientService) ConfigureProxy(ctx upspin.Context, e upspin.Endpoint) (upspin.UserName, error) {
	const op = "auth/grpcauth.ConfigureProxy"

	gCtx, callOpt, finishAuth, err := ac.NewAuthContext()
	if err != nil {
		return "", errors.E(op, err)
	}

	token, err := generateRandomToken()
	if err != nil {
		return "", errors.E(op, err)
	}
	req := &proto.ConfigureRequest{
		Options: []string{"authenticate=" + token, "endpoint=" + e.String()},
	}
	resp, err := ac.grpcCommon.Configure(gCtx, req, callOpt)
	err = finishAuth(err)
	if err != nil {
		return "", errors.E(op, err)
	}

	// Get user's public keys.
	keyServer, err := bind.KeyServer(ctx, ctx.KeyEndpoint())
	if err != nil {
		return "", errors.E(op, err)
	}
	u, err := keyServer.Lookup(upspin.UserName(resp.UserName))
	if err != nil {
		return "", errors.E(op, err)
	}
	key := u.PublicKey

	// Parse signature
	var rs, ss big.Int
	_, ok := rs.SetString(resp.Signature.R, 10)
	if !ok {
		return "", errors.E(op, errors.Str("bad signature"))
	}
	_, ok = ss.SetString(resp.Signature.S, 10)
	if !ok {
		return "", errors.E(op, errors.Str("bad signature"))
	}

	// Validate signature.
	err = verifySignature(key, []byte(resp.UserName+" Authenticate "+token), &rs, &ss)
	if err != nil {
		return "", errors.E(op, err)
	}
	return upspin.UserName(resp.UserName), nil
}
<commit_msg>auth/grpcauth: invalidate authentication on dialing<commit_after>// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package grpcauth

import (
	"crypto/tls"
	"math/big"
	"math/rand"
	"net"
	"strings"
	"sync"
	"time"

	gContext "golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"

	"upspin.io/bind"
	"upspin.io/errors"
	"upspin.io/log"
	"upspin.io/upspin"
	"upspin.io/upspin/proto"
)

// GRPCCommon is an interface that all GRPC services implement for authentication and ping as part of upspin.Service.
type GRPCCommon interface {
	// Ping is the GRPC call for Ping.
	Ping(ctx gContext.Context, in *proto.PingRequest, opts ...grpc.CallOption) (*proto.PingResponse, error)
	// Configure is the GRPC call for Configure.
	Configure(ctx gContext.Context, in *proto.ConfigureRequest, opts ...grpc.CallOption) (*proto.ConfigureResponse, error)
}

// AuthClientService is a partial Service that uses GRPC as transport and implements Authentication.
type AuthClientService struct {
	grpcCommon GRPCCommon
	grpcConn   *grpc.ClientConn
	context    upspin.Context

	keepAliveInterval time.Duration // interval of keep-alive packets.
	closeKeepAlive    chan bool     // channel used to tell the keep-alive routine to exit.

	mu               sync.Mutex // protects the field below.
	authToken        string
	lastTokenRefresh time.Time
	lastNetActivity  time.Time // last known time of some network activity.
}

// SecurityLevel defines the security required of a GRPC connection.
type SecurityLevel int

const (
	// Secure as the security argument to NewGRPCClient requires TLS connections using CA certificates.
	Secure = SecurityLevel(iota + 1)

	// InsecureAllowingSelfSignedCertificates as the security argument to NewGRPCClient requires TLS connections
	// but allows self signed certificates.
	InsecureAllowingSelfSignedCertificates

	// NoSecurity as the security argument to NewGRPCClient requires connections with no authentication or encryption.
	NoSecurity

	// KeepAliveInterval is a suggested interval between keep-alive ping requests to the server.
	// A value of 0 means keep-alives are disabled. Google Cloud Platform (GCP) times out connections
	// every 10 minutes so a smaller values are recommended for talking to servers on GCP.
	KeepAliveInterval = 5 * time.Minute
)

// To be safe, we refresh the token 1 hour ahead of time.
var tokenFreshnessDuration = authTokenDuration - time.Hour

// NewGRPCClient returns new GRPC client connected to a GRPC server at a net address.
// The address is expected to be a raw network address with port number, as in domain.com:5580. However, for convenience,
// it is optionally accepted for the time being to use one of the following prefixes:
// https://, http://, grpc://. This may change in the future.
// A keep alive interval indicates the amount of time to send ping requests to the server. A duration of 0 disables
// keep-alive packets.
// The security level specifies the expected security guarantees of the connection.
func NewGRPCClient(context upspin.Context, netAddr upspin.NetAddr, keepAliveInterval time.Duration, security SecurityLevel) (*AuthClientService, error) {
	const op = "auth/grpcauth.NewGRPCClient"
	if keepAliveInterval != 0 && keepAliveInterval < time.Minute {
		log.Info.Printf("Keep-alive interval too short. You may overload the server and be throttled")
	}
	addr := string(netAddr)
	isHTTP := strings.HasPrefix(addr, "http://")
	isHTTPS := strings.HasPrefix(addr, "https://")
	isGRPC := strings.HasPrefix(addr, "grpc://")
	skip := 0
	switch {
	case isHTTP, isGRPC:
		skip = 7
	case isHTTPS:
		skip = 8
	}
	ac := &AuthClientService{
		context:           context,
		keepAliveInterval: keepAliveInterval,
		closeKeepAlive:    make(chan bool, 1),
	}
	opts := []grpc.DialOption{
		grpc.WithBlock(),
		grpc.WithDialer(ac.dialWithKeepAlive),
		grpc.WithTimeout(3 * time.Second),
	}
	switch security {
	case NoSecurity:
		// Only allow insecure connections to the loop back network.
		if !isLocal(addr[skip:]) {
			return nil, errors.E(op, netAddr, errors.IO, errors.Str("insecure dial to non-loopback destination"))
		}
		opts = append(opts, grpc.WithInsecure())
	case Secure:
		opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: false})))
	case InsecureAllowingSelfSignedCertificates:
		opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})))
	default:
		return nil, errors.E(op, errors.Invalid, errors.Errorf("invalid security level to NewGRPCClient: %v", security))
	}
	var err error
	ac.grpcConn, err = grpc.Dial(addr[skip:], opts...)
	if err != nil {
		return nil, err
	}
	if keepAliveInterval != 0 {
		go ac.keepAlive()
	}
	return ac, nil
}

func isLocal(addr string) bool {
	host, _, err := net.SplitHostPort(addr)
	if err != nil {
		return false
	}
	ips, err := net.LookupIP(host)
	if err != nil {
		return false
	}
	for _, ip := range ips {
		if !ip.IsLoopback() {
			return false
		}
	}
	return true
}

// keepAlive loops forever pinging the server every keepAliveInterval. It skips pings if there has been network
// activity more recently than the keep alive interval. It must run on a separate go routine.
func (ac *AuthClientService) keepAlive() {
	sleepFor := ac.keepAliveInterval
	for {
		select {
		case <-time.After(sleepFor):
			lastIdleness := time.Since(ac.lastActivity())
			if lastIdleness < ac.keepAliveInterval {
				sleepFor = ac.keepAliveInterval - lastIdleness
				log.Debug.Printf("New ping in %v", sleepFor)
				continue
			}
			sleepFor = ac.keepAliveInterval
			if !ac.Ping() {
				log.Error.Printf("grpcauth: keepAlive: ping failed")
			}
			log.Debug.Printf("grpcAuth: keepAlive: ping okay")
			ac.setLastActivity()
		case <-ac.closeKeepAlive:
			log.Debug.Printf("grpcauth: keepAlive: exiting keep alive routine")
			return
		}
	}
}

// lastActivity reports the time of the last known network activity.
func (ac *AuthClientService) lastActivity() time.Time {
	ac.mu.Lock()
	defer ac.mu.Unlock()
	return ac.lastNetActivity
}

// setLastActivity sets the current time as the last known network acitivity. This is useful
// when using application pings, to prevent unnecessarily frequent pings.
func (ac *AuthClientService) setLastActivity() {
	ac.mu.Lock()
	ac.lastNetActivity = time.Now()
	ac.mu.Unlock()
}

// SetService sets the underlying RPC service which was obtained with proto.NewSERVICENAMEClient, where SERVICENAME is
// the RPC service definition from the proto file.
func (ac *AuthClientService) SetService(common GRPCCommon) {
	ac.grpcCommon = common
}

// GRPCConn returns the grpc client connection used to dial the server.
func (ac *AuthClientService) GRPCConn() *grpc.ClientConn {
	return ac.grpcConn
}

func (ac *AuthClientService) dialWithKeepAlive(target string, timeout time.Duration) (net.Conn, error) {
	// Invalidate auth token.
	ac.mu.Lock()
	ac.authToken = ""
	ac.mu.Unlock()

	c, err := net.DialTimeout("tcp", target, timeout)
	if err != nil {
		return nil, err
	}
	if tc, ok := c.(*net.TCPConn); ok {
		if err := tc.SetKeepAlive(true); err != nil {
			return nil, err
		}
		if err := tc.SetKeepAlivePeriod(KeepAliveInterval); err != nil {
			return nil, err
		}
	}
	return c, nil
}

// Ping implements upspin.Service.
func (ac *AuthClientService) Ping() bool {
	seq := rand.Int31()
	req := &proto.PingRequest{
		PingSequence: seq,
	}
	gctx, cancel := gContext.WithTimeout(gContext.Background(), 3*time.Second)
	defer cancel()
	resp, err := ac.grpcCommon.Ping(gctx, req)
	if err != nil {
		log.Printf("Ping error: %s", err)
	}
	ac.setLastActivity()
	return err == nil && resp.PingSequence == seq
}

func (ac *AuthClientService) isAuthTokenExpired() bool {
	ac.mu.Lock()
	defer ac.mu.Unlock()
	return ac.authToken == "" || ac.lastTokenRefresh.Add(tokenFreshnessDuration).Before(time.Now())
}

// NewAuthContext sets up a gContext, GRPC CallOption, and finishAuth function
// for authenticating GRPC requests. If a request token is available, it puts
// that token in the context as GRPC metadata. If the request token is not
// available or has expired, it puts authentication request data in the
// context, and sets up a GRPC Call Option and finishAuth function for retrieving
// the authentication response from the GRPC response headers.
//
// Example usage:
// 	ctx, callOpt, finishAuth, err := ac.NewAuthContext()
// 	// handle err
// 	req := &proto.RequestMessage{ ... }
// 	resp, err := c.grpcClient.DoATrump(ctx, req, callOpt)
// 	err = finishAuth(err)
// 	// handle err
func (ac *AuthClientService) NewAuthContext() (ctx gContext.Context, opt grpc.CallOption, finishAuth func(error) error, err error) {
	const op = "auth/grpcauth.AuthClientService"

	ctx = gContext.Background()

	var header metadata.MD
	opt = grpc.Header(&header)

	if !ac.isAuthTokenExpired() {
		ac.mu.Lock()
		token := ac.authToken
		ac.mu.Unlock()
		ctx = metadata.NewContext(ctx, metadata.Pairs(authTokenKey, token))
		finishAuth = func(err error) error {
			ac.setLastActivity()
			return err
		}
		return
	}

	user := string(ac.context.UserName())
	now := time.Now().UTC().Format(time.ANSIC) // to discourage signature replay
	sig, err := ac.context.Factotum().UserSign([]byte(user + " Authenticate " + now))
	if err != nil {
		return nil, nil, nil, errors.E(op, err)
	}
	ctx = metadata.NewContext(ctx, metadata.MD{authRequestKey: []string{
		user,
		now,
		sig.R.String(),
		sig.S.String(),
	}})
	finishAuth = func(err error) error {
		ac.setLastActivity()
		if err != nil {
			return err
		}

		token, ok := header[authTokenKey]
		if !ok || len(token) != 1 {
			return errors.Str("no auth token in response header")
		}
		now := time.Now()

		ac.mu.Lock()
		defer ac.mu.Unlock()
		ac.authToken = token[0]
		ac.lastTokenRefresh = now
		return nil
	}
	return
}

// Close implements upspin.Service.
func (ac *AuthClientService) Close() {
	select { // prevents blocking if Close is called more than once.
	case ac.closeKeepAlive <- true:
		close(ac.closeKeepAlive)
	default:
	}
	// The only error returned is ErrClientConnClosing, meaning something else has already caused it to close.
	_ = ac.grpcConn.Close() // explicitly ignore the error as there's nothing we can do.
}

// ConfigureProxy uses the Configure command to tell the proxy the endpoint it is proxying for and
// to ensure that the proxy is running as our upspin user identity.
func (ac *AuthClientService) ConfigureProxy(ctx upspin.Context, e upspin.Endpoint) (upspin.UserName, error) {
	const op = "auth/grpcauth.ConfigureProxy"

	gCtx, callOpt, finishAuth, err := ac.NewAuthContext()
	if err != nil {
		return "", errors.E(op, err)
	}

	token, err := generateRandomToken()
	if err != nil {
		return "", errors.E(op, err)
	}
	req := &proto.ConfigureRequest{
		Options: []string{"authenticate=" + token, "endpoint=" + e.String()},
	}
	resp, err := ac.grpcCommon.Configure(gCtx, req, callOpt)
	err = finishAuth(err)
	if err != nil {
		return "", errors.E(op, err)
	}

	// Get user's public keys.
	keyServer, err := bind.KeyServer(ctx, ctx.KeyEndpoint())
	if err != nil {
		return "", errors.E(op, err)
	}
	u, err := keyServer.Lookup(upspin.UserName(resp.UserName))
	if err != nil {
		return "", errors.E(op, err)
	}
	key := u.PublicKey

	// Parse signature
	var rs, ss big.Int
	_, ok := rs.SetString(resp.Signature.R, 10)
	if !ok {
		return "", errors.E(op, errors.Str("bad signature"))
	}
	_, ok = ss.SetString(resp.Signature.S, 10)
	if !ok {
		return "", errors.E(op, errors.Str("bad signature"))
	}

	// Validate signature.
	err = verifySignature(key, []byte(resp.UserName+" Authenticate "+token), &rs, &ss)
	if err != nil {
		return "", errors.E(op, err)
	}
	return upspin.UserName(resp.UserName), nil
}
<|endoftext|>


<commit_before>// Package scanner provides a recursive file scanner that is useful for
// efficiently processing relatively static datasets.
package scanner

import (
	"io/ioutil"
	"os"
	"strings"
	"sync"
)

// Scanner recursively scans a directory for files.
type Scanner struct {

	// Buffer is length of the files channel buffer.
	Buffer int

	// errs is the channel that errors are sent to.
	errs chan error

	// files is the challen that discovered files are sent to.
	files chan string

	// path is the directory being scanned.
	path string

	// sep is
	separator string
}

// New returns a new Scanner instance.
func New(path string) *Scanner {
	return &Scanner{
		Buffer:    1,
		errs:      make(chan error),
		path:      path,
		separator: string(os.PathSeparator),
	}
}

// Scan recursively scans the directory and sends the files and errors to
// the passed Handler in goroutines.
func (s *Scanner) Scan(h Handler) {
	var wg sync.WaitGroup
	s.files = make(chan string, s.Buffer)

	go func() {
		s.scan(s.path)
		close(s.files)
		close(s.errs)
	}()

	if h != nil {
		wg.Add(2)

		go func() {
			defer wg.Done()
			for err := range s.errs {
				h.HandleError(err)
			}
		}()

		go func() {
			defer wg.Done()
			for f := range s.files {
				h.Handle(f)
			}
		}()
	}

	wg.Wait()
}

// scan recursively scans path and sends the discovered files and errors to
// the built-in channels.
func (s *Scanner) scan(path string) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		s.errs <- err
		return
	}

	basedir := strings.TrimRight(path, s.separator)
	for _, f := range files {
		file := strings.Join([]string{basedir, f.Name()}, s.separator)
		if f.IsDir() {
			s.scan(file)
		} else {
			s.files <- file
		}
	}
}

// Handler is an interface for handlers that process scanned files.
type Handler interface {
	Handle(string)
	HandleError(error)
}

// MemoryHandler returns a new memoryHandler instance.
func MemoryHandler() *memoryHandler {
	return &memoryHandler{}
}

// memoryHandler stores the scanned files and errors in slices.
type memoryHandler struct {
	Files  []string
	Errors []error
}

// Handle stores file in a struct.
func (h *memoryHandler) Handle(file string) {
	h.Files = append(h.Files, file)
}

// HandleError stores err in a struct.
func (h *memoryHandler) HandleError(err error) {
	h.Errors = append(h.Errors, err)
}
<commit_msg>fixed comment<commit_after>// Package scanner provides a recursive file scanner that is useful for
// efficiently processing relatively static datasets.
package scanner

import (
	"io/ioutil"
	"os"
	"strings"
	"sync"
)

// Scanner recursively scans a directory for files.
type Scanner struct {

	// Buffer is length of the files channel buffer.
	Buffer int

	// errs is the channel that errors are sent to.
	errs chan error

	// files is the challen that discovered files are sent to.
	files chan string

	// path is the directory being scanned.
	path string

	// sep is the OS's path separator represented as a string.
	separator string
}

// New returns a new Scanner instance.
func New(path string) *Scanner {
	return &Scanner{
		Buffer:    1,
		errs:      make(chan error),
		path:      path,
		separator: string(os.PathSeparator),
	}
}

// Scan recursively scans the directory and sends the files and errors to
// the passed Handler in goroutines.
func (s *Scanner) Scan(h Handler) {
	var wg sync.WaitGroup
	s.files = make(chan string, s.Buffer)

	go func() {
		s.scan(s.path)
		close(s.files)
		close(s.errs)
	}()

	if h != nil {
		wg.Add(2)

		go func() {
			defer wg.Done()
			for err := range s.errs {
				h.HandleError(err)
			}
		}()

		go func() {
			defer wg.Done()
			for f := range s.files {
				h.Handle(f)
			}
		}()
	}

	wg.Wait()
}

// scan recursively scans path and sends the discovered files and errors to
// the built-in channels.
func (s *Scanner) scan(path string) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		s.errs <- err
		return
	}

	basedir := strings.TrimRight(path, s.separator)
	for _, f := range files {
		file := strings.Join([]string{basedir, f.Name()}, s.separator)
		if f.IsDir() {
			s.scan(file)
		} else {
			s.files <- file
		}
	}
}

// Handler is an interface for handlers that process scanned files.
type Handler interface {
	Handle(string)
	HandleError(error)
}

// MemoryHandler returns a new memoryHandler instance.
func MemoryHandler() *memoryHandler {
	return &memoryHandler{}
}

// memoryHandler stores the scanned files and errors in slices.
type memoryHandler struct {
	Files  []string
	Errors []error
}

// Handle stores file in a struct.
func (h *memoryHandler) Handle(file string) {
	h.Files = append(h.Files, file)
}

// HandleError stores err in a struct.
func (h *memoryHandler) HandleError(err error) {
	h.Errors = append(h.Errors, err)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
	"os/exec"
)

type (
	Protocol struct {
		XMLName    xml.Name    `xml:"protocol"`
		Name       string      `xml:"name,attr"`
		Copyright  string      `xml:"copyright"`
		Interfaces []Interface `xml:"interface"`
	}

	Description struct {
		XMLName     xml.Name `xml:"description"`
		Summary     string   `xml:"summary,attr"`
		Description string   `xml:"description"`
	}

	Interface struct {
		XMLName     xml.Name    `xml:"interface"`
		Name        string      `xml:"name,attr"`
		Version     int         `xml:"version,attr"`
		Since       int         `xml:"since,attr"` // maybe in future versions
		Description Description `xml:"description"`
		Requests    []Request   `xml:"request"`
		Events      []Event     `xml:"event"`
		Enums       []Enum      `xml:"enum"`
	}

	Request struct {
		XMLName     xml.Name    `xml:"request"`
		Name        string      `xml:"name,attr"`
		Type        string      `xml:"type,attr"`
		Since       int         `xml:"since,attr"`
		Description Description `xml:"description"`
		Args        []Arg       `xml:"arg"`
	}

	Arg struct {
		XMLName   xml.Name `xml:"arg"`
		Name      string   `xml:"name,attr"`
		Type      string   `xml:"type,attr"`
		Interface string   `xml:"interface,attr"`
		Enum      string   `xml:"enum,attr"`
		AllowNull bool     `xml:"allow-null,attr"`
		Summary   string   `xml:"summary,attr"`
	}

	Event struct {
		XMLName     xml.Name    `xml:"event"`
		Name        string      `xml:"name,attr"`
		Since       int         `xml:"since,attr"`
		Description Description `xml:"description"`
		Args        []Arg       `xml:"arg"`
	}

	Enum struct {
		XMLName     xml.Name    `xml:"enum"`
		Name        string      `xml:"name,attr"`
		BitField    bool        `xml:"bitfield,attr"`
		Description Description `xml:"description"`
		Entries     []Entry     `xml:"entry"`
	}

	Entry struct {
		XMLName xml.Name `xml:"entry"`
		Name    string   `xml:"name,attr"`
		Value   string   `xml:"value,attr"`
		Summary string   `xml:"summary,attr"`
	}
)

var (
	wlTypes map[string]string = map[string]string{
		"int":    "int32",
		"uint":   "uint32",
		"string": "string",
		"fd":     "uintptr",
		"fixed":  "float32",
		"array":  "[]int32",
	}

	wlNames        map[string]string
	constBuffer    bytes.Buffer
	ifaceBuffer    bytes.Buffer
	reqCodesBuffer bytes.Buffer
)

func main() {
	xmlFilePath, err := filepath.Abs("wayland.xml")
	if err != nil {
		log.Fatalf("Cannot find wayland.xml: %s",err)
	}

	xmlFile, err := os.Open(xmlFilePath)
	if err != nil {
		log.Fatal(err)
	}

	defer xmlFile.Close()

	var protocol Protocol
	if err := xml.NewDecoder(xmlFile).Decode(&protocol); err != nil {
		log.Fatal(err)
	}

	wlNames = make(map[string]string)

	constBuffer.WriteString("package wl")

	for _, iface := range protocol.Interfaces {
		//required for arg type's determine
		caseAndRegister(iface.Name)
	}

	reqCodesBuffer.WriteString("\n//Interface Request Codes\n") // request codes
	reqCodesBuffer.WriteString("\nconst (\n")                   // request codes

	for _, iface := range protocol.Interfaces {
		eventBuffer, eventNames := interfaceEvents(iface)
		eventBuffer.WriteTo(&ifaceBuffer)

		interfaceTypes(iface, eventNames)
		interfaceConstructor(iface, eventNames)
		interfaceRequests(iface)
		interfaceEnums(iface)
	}

	reqCodesBuffer.WriteString(")") // request codes end

	if _,err := os.Stat("client.go"); os.IsNotExist(err) {
		file , err := os.Create("client.go")
		if err != nil {
			log.Fatalf("Cannot create file: %s",err)
		}

		constBuffer.WriteTo(file)
		reqCodesBuffer.WriteTo(file)
		ifaceBuffer.WriteTo(file)

		file.Close()
		goex , err := exec.LookPath("go")
		if err != nil {
			log.Println("go executable cannot found run \"go fmt client.go\" yourself")
			return
		} else {
			cmd := exec.Command(goex,"fmt","client.go")
			err := cmd.Run()
			if err != nil {
				log.Fatal(err)
			}
		}
	} else {
		fmt.Println("client.go file is exist in current directory!")
	}
}

// register names to map
func caseAndRegister(wlName string) string {
	var orj string = wlName
	wlName = CamelCase(wlName)
	wlNames[orj] = wlName
	return wlName
}

func enumArgName(ifaceName, enumName string) string {
	if strings.Index(enumName, ".") == -1 {
		return ifaceName + CamelCase(enumName)
	} else {
		parts := strings.Split(enumName, ".")
		if len(parts) != 2 {
			log.Fatal("enum args must be \"interface.enum\" format")
		}
		return CamelCase(parts[0]) + CamelCase(parts[1])
	}
}

func CamelCase(wlName string) string {
	if strings.HasPrefix(wlName, "wl_") {
		wlName = strings.TrimPrefix(wlName, "wl_")
	}

	// replace all "_" chars to " " chars
	wlName = strings.Replace(wlName, "_", " ", -1)

	// Capitalize first chars
	wlName = strings.Title(wlName)

	// remove all spaces
	wlName = strings.Replace(wlName, " ", "", -1)

	return wlName
}

func interfaceConstructor(iface Interface, eventNames []string) {
	ifaceName := wlNames[iface.Name]

	// interface constructor
	ifaceBuffer.WriteString(fmt.Sprintf("\nfunc New%s(conn *Connection) *%s {\n", ifaceName, ifaceName))
	ifaceBuffer.WriteString(fmt.Sprintf("ret := new(%s)\n", ifaceName))
	for _, evName := range eventNames {
		ifaceBuffer.WriteString(fmt.Sprintf("ret.%s = make(chan %s)\n", evName+"Chan", ifaceName+evName+"Event"))
	}
	ifaceBuffer.WriteString("conn.Register(ret)\n")
	ifaceBuffer.WriteString("return ret\n")
	ifaceBuffer.WriteString("}\n")
}

func interfaceTypes(iface Interface, eventNames []string) {
	ifaceName := wlNames[iface.Name]
	// interface type definition
	ifaceBuffer.WriteString(fmt.Sprintf("\ntype %s struct {\n", ifaceName))
	ifaceBuffer.WriteString("BaseProxy\n")
	for _, evName := range eventNames {
		ifaceBuffer.WriteString(fmt.Sprintf("%s chan %s\n", evName+"Chan", ifaceName+evName+"Event"))
	}
	ifaceBuffer.WriteString("}\n")
}

func interfaceRequests(iface Interface) {
	ifaceName := wlNames[iface.Name]

	// interface method definitions (requests)
	// order used for request identification
	for order, req := range iface.Requests {
		reqName := CamelCase(req.Name)
		reqCodeName := strings.ToTitle(fmt.Sprintf("_%s_%s", ifaceName, reqName)) // first _ for not export constant
		reqCodesBuffer.WriteString(fmt.Sprintf("%s = %d\n", reqCodeName, order))

		ifaceBuffer.WriteString(fmt.Sprintf("\nfunc (p *%s) %s(", ifaceName, reqName))
		// get args buffer
		requestArgs(ifaceName, req).WriteTo(&ifaceBuffer)

		ifaceBuffer.WriteString(")") // close the args

		// get returns buffer
		requestRets(req).WriteTo(&ifaceBuffer)
		ifaceBuffer.WriteString("{\n")

		// get method body
		requestBody(req, reqCodeName).WriteTo(&ifaceBuffer)

		ifaceBuffer.WriteString("\n}\n")
	}
}

func interfaceEnums(iface Interface) {
	ifaceName := wlNames[iface.Name]

	// Enums - Constants
	for _, enum := range iface.Enums {
		enumName := caseAndRegister(enum.Name)
		constTypeName := ifaceName + enumName
		constBuffer.WriteString(fmt.Sprintf("\ntype %s uint32\n", constTypeName)) // enums are uint
		constBuffer.WriteString("const (\n")
		for _, entry := range enum.Entries {
			entryName := caseAndRegister(entry.Name)
			constName := ifaceName + enumName + entryName
			constBuffer.WriteString(fmt.Sprintf("%s %s = %s\n", constName, constTypeName, entry.Value))
		}
		constBuffer.WriteString(")\n")
	}
}

func interfaceEvents(iface Interface) (bytes.Buffer, []string) {
	var (
		eventBuffer bytes.Buffer
		eventNames  []string
		ifaceName   = wlNames[iface.Name]
	)

	// Event struct types
	for _, event := range iface.Events {
		eventName := caseAndRegister(event.Name)
		typeName := ifaceName + eventName + "Event"
		eventBuffer.WriteString(fmt.Sprintf("\ntype %s struct {\n", typeName))
		for _, arg := range event.Args {
			if t, ok := wlTypes[arg.Type]; ok { // if basic type
				if arg.Type == "uint" && arg.Enum != "" { // enum type
					enumTypeName := ifaceName + CamelCase(arg.Enum)
					eventBuffer.WriteString(fmt.Sprintf("%s %s\n", CamelCase(arg.Name), enumTypeName))
				} else {
					eventBuffer.WriteString(fmt.Sprintf("%s %s\n", CamelCase(arg.Name), t))
				}
			} else { // interface type
				if (arg.Type == "object" || arg.Type == "new_id") && arg.Interface != "" {
					t = "*" + wlNames[arg.Interface]
				} else {
					t = "Proxy"
				}
				eventBuffer.WriteString(fmt.Sprintf("%s %s\n", CamelCase(arg.Name), t))
			}
		}

		eventNames = append(eventNames, eventName)
		eventBuffer.WriteString("}\n")
	}

	return eventBuffer, eventNames
}

func requestArgs(ifaceName string, req Request) *bytes.Buffer {
	var (
		args       []string
		argsBuffer bytes.Buffer
	)

	for _, arg := range req.Args {
		// special type, for example registry.bind
		if arg.Type == "new_id" {
			if arg.Interface == "" {
				args = append(args, "iface string")
				args = append(args, "version uint32")
				args = append(args, fmt.Sprintf("%s Proxy", arg.Name))
			} else {
				continue
			}
		} else if arg.Type == "object" && arg.Interface != "" {
			argTypeName := wlNames[arg.Interface]
			args = append(args, fmt.Sprintf("%s *%s", arg.Name, argTypeName))
		} else if arg.Type == "uint" && arg.Enum != "" {
			args = append(args, fmt.Sprintf("%s %s", arg.Name, enumArgName(ifaceName, arg.Enum)))
		} else {
			args = append(args, fmt.Sprintf("%s %s", arg.Name, wlTypes[arg.Type]))
		}
	}

	for i, arg := range args {
		if i > 0 {
			argsBuffer.WriteString(",")
		}
		argsBuffer.WriteString(arg)
	}

	return &argsBuffer
}

func requestRets(req Request) *bytes.Buffer {
	var (
		rets       []string
		retsBuffer bytes.Buffer
	)

	for _, arg := range req.Args {
		if arg.Type == "new_id" && arg.Interface != "" {
			retTypeName := wlNames[arg.Interface]
			rets = append(rets, fmt.Sprintf("*%s", retTypeName))
		}
	}

	// all request have an error return
	rets = append(rets, " error")

	if len(rets) > 1 {
		retsBuffer.WriteString("(")
	}

	for i, ret := range rets {
		if i > 0 {
			retsBuffer.WriteString(",")
		}
		retsBuffer.WriteString(ret)
	}

	if len(rets) > 1 {
		retsBuffer.WriteString(")")
	}

	return &retsBuffer
}

func requestBody(req Request, reqCodeName string) *bytes.Buffer {
	var (
		params       []string
		bodyBuffer   bytes.Buffer
		paramsBuffer bytes.Buffer
		hasRetType   string
	)

	for _, arg := range req.Args {
		if arg.Type == "new_id" {
			if arg.Interface != "" {
				retTypeName := wlNames[arg.Interface]
				bodyBuffer.WriteString(fmt.Sprintf("ret := New%s(p.Connection())\n", retTypeName))
				params = append(params, "Proxy(ret)")
				hasRetType = "ret,"
			} else {
				params = append(params, "iface")
				params = append(params, "version")
				params = append(params, arg.Name)
			}
		} else {
			params = append(params, arg.Name)
		}
	}

	for _, param := range params {
		paramsBuffer.WriteString(fmt.Sprintf(",%s", param))
	}

	bodyBuffer.WriteString(fmt.Sprintf("return %s p.Connection().SendRequest(p,%s%s)", hasRetType, reqCodeName, paramsBuffer.String()))

	return &bodyBuffer
}
<commit_msg>go fmt code<commit_after>package main

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

type (
	Protocol struct {
		XMLName    xml.Name    `xml:"protocol"`
		Name       string      `xml:"name,attr"`
		Copyright  string      `xml:"copyright"`
		Interfaces []Interface `xml:"interface"`
	}

	Description struct {
		XMLName     xml.Name `xml:"description"`
		Summary     string   `xml:"summary,attr"`
		Description string   `xml:"description"`
	}

	Interface struct {
		XMLName     xml.Name    `xml:"interface"`
		Name        string      `xml:"name,attr"`
		Version     int         `xml:"version,attr"`
		Since       int         `xml:"since,attr"` // maybe in future versions
		Description Description `xml:"description"`
		Requests    []Request   `xml:"request"`
		Events      []Event     `xml:"event"`
		Enums       []Enum      `xml:"enum"`
	}

	Request struct {
		XMLName     xml.Name    `xml:"request"`
		Name        string      `xml:"name,attr"`
		Type        string      `xml:"type,attr"`
		Since       int         `xml:"since,attr"`
		Description Description `xml:"description"`
		Args        []Arg       `xml:"arg"`
	}

	Arg struct {
		XMLName   xml.Name `xml:"arg"`
		Name      string   `xml:"name,attr"`
		Type      string   `xml:"type,attr"`
		Interface string   `xml:"interface,attr"`
		Enum      string   `xml:"enum,attr"`
		AllowNull bool     `xml:"allow-null,attr"`
		Summary   string   `xml:"summary,attr"`
	}

	Event struct {
		XMLName     xml.Name    `xml:"event"`
		Name        string      `xml:"name,attr"`
		Since       int         `xml:"since,attr"`
		Description Description `xml:"description"`
		Args        []Arg       `xml:"arg"`
	}

	Enum struct {
		XMLName     xml.Name    `xml:"enum"`
		Name        string      `xml:"name,attr"`
		BitField    bool        `xml:"bitfield,attr"`
		Description Description `xml:"description"`
		Entries     []Entry     `xml:"entry"`
	}

	Entry struct {
		XMLName xml.Name `xml:"entry"`
		Name    string   `xml:"name,attr"`
		Value   string   `xml:"value,attr"`
		Summary string   `xml:"summary,attr"`
	}
)

var (
	wlTypes map[string]string = map[string]string{
		"int":    "int32",
		"uint":   "uint32",
		"string": "string",
		"fd":     "uintptr",
		"fixed":  "float32",
		"array":  "[]int32",
	}

	wlNames        map[string]string
	constBuffer    bytes.Buffer
	ifaceBuffer    bytes.Buffer
	reqCodesBuffer bytes.Buffer
)

func main() {
	xmlFilePath, err := filepath.Abs("wayland.xml")
	if err != nil {
		log.Fatalf("Cannot find wayland.xml: %s", err)
	}

	xmlFile, err := os.Open(xmlFilePath)
	if err != nil {
		log.Fatal(err)
	}

	defer xmlFile.Close()

	var protocol Protocol
	if err := xml.NewDecoder(xmlFile).Decode(&protocol); err != nil {
		log.Fatal(err)
	}

	wlNames = make(map[string]string)

	constBuffer.WriteString("package wl")

	for _, iface := range protocol.Interfaces {
		//required for arg type's determine
		caseAndRegister(iface.Name)
	}

	reqCodesBuffer.WriteString("\n//Interface Request Codes\n") // request codes
	reqCodesBuffer.WriteString("\nconst (\n")                   // request codes

	for _, iface := range protocol.Interfaces {
		eventBuffer, eventNames := interfaceEvents(iface)
		eventBuffer.WriteTo(&ifaceBuffer)

		interfaceTypes(iface, eventNames)
		interfaceConstructor(iface, eventNames)
		interfaceRequests(iface)
		interfaceEnums(iface)
	}

	reqCodesBuffer.WriteString(")") // request codes end

	if _, err := os.Stat("client.go"); os.IsNotExist(err) {
		file, err := os.Create("client.go")
		if err != nil {
			log.Fatalf("Cannot create file: %s", err)
		}

		constBuffer.WriteTo(file)
		reqCodesBuffer.WriteTo(file)
		ifaceBuffer.WriteTo(file)

		file.Close()
		goex, err := exec.LookPath("go")
		if err != nil {
			log.Println("go executable cannot found run \"go fmt client.go\" yourself")
			return
		} else {
			cmd := exec.Command(goex, "fmt", "client.go")
			err := cmd.Run()
			if err != nil {
				log.Fatal(err)
			}
		}
	} else {
		fmt.Println("client.go file is exist in current directory!")
	}
}

// register names to map
func caseAndRegister(wlName string) string {
	var orj string = wlName
	wlName = CamelCase(wlName)
	wlNames[orj] = wlName
	return wlName
}

func enumArgName(ifaceName, enumName string) string {
	if strings.Index(enumName, ".") == -1 {
		return ifaceName + CamelCase(enumName)
	} else {
		parts := strings.Split(enumName, ".")
		if len(parts) != 2 {
			log.Fatal("enum args must be \"interface.enum\" format")
		}
		return CamelCase(parts[0]) + CamelCase(parts[1])
	}
}

func CamelCase(wlName string) string {
	if strings.HasPrefix(wlName, "wl_") {
		wlName = strings.TrimPrefix(wlName, "wl_")
	}

	// replace all "_" chars to " " chars
	wlName = strings.Replace(wlName, "_", " ", -1)

	// Capitalize first chars
	wlName = strings.Title(wlName)

	// remove all spaces
	wlName = strings.Replace(wlName, " ", "", -1)

	return wlName
}

func interfaceConstructor(iface Interface, eventNames []string) {
	ifaceName := wlNames[iface.Name]

	// interface constructor
	ifaceBuffer.WriteString(fmt.Sprintf("\nfunc New%s(conn *Connection) *%s {\n", ifaceName, ifaceName))
	ifaceBuffer.WriteString(fmt.Sprintf("ret := new(%s)\n", ifaceName))
	for _, evName := range eventNames {
		ifaceBuffer.WriteString(fmt.Sprintf("ret.%s = make(chan %s)\n", evName+"Chan", ifaceName+evName+"Event"))
	}
	ifaceBuffer.WriteString("conn.Register(ret)\n")
	ifaceBuffer.WriteString("return ret\n")
	ifaceBuffer.WriteString("}\n")
}

func interfaceTypes(iface Interface, eventNames []string) {
	ifaceName := wlNames[iface.Name]
	// interface type definition
	ifaceBuffer.WriteString(fmt.Sprintf("\ntype %s struct {\n", ifaceName))
	ifaceBuffer.WriteString("BaseProxy\n")
	for _, evName := range eventNames {
		ifaceBuffer.WriteString(fmt.Sprintf("%s chan %s\n", evName+"Chan", ifaceName+evName+"Event"))
	}
	ifaceBuffer.WriteString("}\n")
}

func interfaceRequests(iface Interface) {
	ifaceName := wlNames[iface.Name]

	// interface method definitions (requests)
	// order used for request identification
	for order, req := range iface.Requests {
		reqName := CamelCase(req.Name)
		reqCodeName := strings.ToTitle(fmt.Sprintf("_%s_%s", ifaceName, reqName)) // first _ for not export constant
		reqCodesBuffer.WriteString(fmt.Sprintf("%s = %d\n", reqCodeName, order))

		ifaceBuffer.WriteString(fmt.Sprintf("\nfunc (p *%s) %s(", ifaceName, reqName))
		// get args buffer
		requestArgs(ifaceName, req).WriteTo(&ifaceBuffer)

		ifaceBuffer.WriteString(")") // close the args

		// get returns buffer
		requestRets(req).WriteTo(&ifaceBuffer)
		ifaceBuffer.WriteString("{\n")

		// get method body
		requestBody(req, reqCodeName).WriteTo(&ifaceBuffer)

		ifaceBuffer.WriteString("\n}\n")
	}
}

func interfaceEnums(iface Interface) {
	ifaceName := wlNames[iface.Name]

	// Enums - Constants
	for _, enum := range iface.Enums {
		enumName := caseAndRegister(enum.Name)
		constTypeName := ifaceName + enumName
		constBuffer.WriteString(fmt.Sprintf("\ntype %s uint32\n", constTypeName)) // enums are uint
		constBuffer.WriteString("const (\n")
		for _, entry := range enum.Entries {
			entryName := caseAndRegister(entry.Name)
			constName := ifaceName + enumName + entryName
			constBuffer.WriteString(fmt.Sprintf("%s %s = %s\n", constName, constTypeName, entry.Value))
		}
		constBuffer.WriteString(")\n")
	}
}

func interfaceEvents(iface Interface) (bytes.Buffer, []string) {
	var (
		eventBuffer bytes.Buffer
		eventNames  []string
		ifaceName   = wlNames[iface.Name]
	)

	// Event struct types
	for _, event := range iface.Events {
		eventName := caseAndRegister(event.Name)
		typeName := ifaceName + eventName + "Event"
		eventBuffer.WriteString(fmt.Sprintf("\ntype %s struct {\n", typeName))
		for _, arg := range event.Args {
			if t, ok := wlTypes[arg.Type]; ok { // if basic type
				if arg.Type == "uint" && arg.Enum != "" { // enum type
					enumTypeName := ifaceName + CamelCase(arg.Enum)
					eventBuffer.WriteString(fmt.Sprintf("%s %s\n", CamelCase(arg.Name), enumTypeName))
				} else {
					eventBuffer.WriteString(fmt.Sprintf("%s %s\n", CamelCase(arg.Name), t))
				}
			} else { // interface type
				if (arg.Type == "object" || arg.Type == "new_id") && arg.Interface != "" {
					t = "*" + wlNames[arg.Interface]
				} else {
					t = "Proxy"
				}
				eventBuffer.WriteString(fmt.Sprintf("%s %s\n", CamelCase(arg.Name), t))
			}
		}

		eventNames = append(eventNames, eventName)
		eventBuffer.WriteString("}\n")
	}

	return eventBuffer, eventNames
}

func requestArgs(ifaceName string, req Request) *bytes.Buffer {
	var (
		args       []string
		argsBuffer bytes.Buffer
	)

	for _, arg := range req.Args {
		// special type, for example registry.bind
		if arg.Type == "new_id" {
			if arg.Interface == "" {
				args = append(args, "iface string")
				args = append(args, "version uint32")
				args = append(args, fmt.Sprintf("%s Proxy", arg.Name))
			} else {
				continue
			}
		} else if arg.Type == "object" && arg.Interface != "" {
			argTypeName := wlNames[arg.Interface]
			args = append(args, fmt.Sprintf("%s *%s", arg.Name, argTypeName))
		} else if arg.Type == "uint" && arg.Enum != "" {
			args = append(args, fmt.Sprintf("%s %s", arg.Name, enumArgName(ifaceName, arg.Enum)))
		} else {
			args = append(args, fmt.Sprintf("%s %s", arg.Name, wlTypes[arg.Type]))
		}
	}

	for i, arg := range args {
		if i > 0 {
			argsBuffer.WriteString(",")
		}
		argsBuffer.WriteString(arg)
	}

	return &argsBuffer
}

func requestRets(req Request) *bytes.Buffer {
	var (
		rets       []string
		retsBuffer bytes.Buffer
	)

	for _, arg := range req.Args {
		if arg.Type == "new_id" && arg.Interface != "" {
			retTypeName := wlNames[arg.Interface]
			rets = append(rets, fmt.Sprintf("*%s", retTypeName))
		}
	}

	// all request have an error return
	rets = append(rets, " error")

	if len(rets) > 1 {
		retsBuffer.WriteString("(")
	}

	for i, ret := range rets {
		if i > 0 {
			retsBuffer.WriteString(",")
		}
		retsBuffer.WriteString(ret)
	}

	if len(rets) > 1 {
		retsBuffer.WriteString(")")
	}

	return &retsBuffer
}

func requestBody(req Request, reqCodeName string) *bytes.Buffer {
	var (
		params       []string
		bodyBuffer   bytes.Buffer
		paramsBuffer bytes.Buffer
		hasRetType   string
	)

	for _, arg := range req.Args {
		if arg.Type == "new_id" {
			if arg.Interface != "" {
				retTypeName := wlNames[arg.Interface]
				bodyBuffer.WriteString(fmt.Sprintf("ret := New%s(p.Connection())\n", retTypeName))
				params = append(params, "Proxy(ret)")
				hasRetType = "ret,"
			} else {
				params = append(params, "iface")
				params = append(params, "version")
				params = append(params, arg.Name)
			}
		} else {
			params = append(params, arg.Name)
		}
	}

	for _, param := range params {
		paramsBuffer.WriteString(fmt.Sprintf(",%s", param))
	}

	bodyBuffer.WriteString(fmt.Sprintf("return %s p.Connection().SendRequest(p,%s%s)", hasRetType, reqCodeName, paramsBuffer.String()))

	return &bodyBuffer
}
<|endoftext|>


<commit_before>package smd

import (
	"fmt"
	"math"
	"time"

	"github.com/soniakeys/meeus/julian"
	"github.com/soniakeys/meeus/planetposition"
)

const (
	// AU is one astronomical unit in kilometers.
	AU = 149598000
)

// CelestialObject defines a celestial object.
// Note: globe and elements may be nil; does not support satellites yet.
type CelestialObject struct {
	Name   string
	Radius float64
	a      float64
	μ      float64
	tilt   float64 // Axial tilt
	incl   float64 // Ecliptic inclination
	SOI    float64 // With respect to the Sun
	J2     float64
	PP     *planetposition.V87Planet
}

// String implements the Stringer interface.
func (c CelestialObject) String() string {
	return c.Name + " body"
}

// Equals returns whether the provided celestial object is the same.
func (c *CelestialObject) Equals(b CelestialObject) bool {
	return c.Name == b.Name && c.Radius == b.Radius && c.a == b.a && c.μ == b.μ && c.SOI == b.SOI && c.J2 == b.J2
}

// HelioOrbit returns the heliocentric position and velocity of this planet at a given time in equatorial coordinates.
// Note that the whole file is loaded. In fact, if we don't, then whoever is the first to call this function will
// set the Epoch at which the ephemeris are available, and that sucks.
func (c *CelestialObject) HelioOrbit(dt time.Time) Orbit {
	if c.Name == "Sun" {
		return *NewOrbitFromRV([]float64{0, 0, 0}, []float64{0, 0, 0}, *c)
	}
	if c.PP == nil {
		// Load the planet.
		var vsopPosition int
		switch c.Name {
		case "Venus":
			vsopPosition = 2
			break
		case "Earth":
			vsopPosition = 3
			break
		case "Mars":
			vsopPosition = 4
			break
		default:
			panic(fmt.Errorf("unknown object: %s", c.Name))
		}
		planet, err := planetposition.LoadPlanet(vsopPosition - 1)
		if err != nil {
			panic(fmt.Errorf("could not load planet number %d: %s", vsopPosition, err))
		}
		c.PP = planet
	}
	l, b, r := c.PP.Position2000(julian.TimeToJD(dt))
	r *= AU
	v := math.Sqrt(2*Sun.μ/r - Sun.μ/c.a)
	// Get the Cartesian coordinates from L,B,R.
	R, V := make([]float64, 3), make([]float64, 3)
	sB, cB := math.Sincos(b)
	sL, cL := math.Sincos(l)
	R[0] = r * cB * cL
	R[1] = r * cB * sL
	R[2] = r * sB
	// Let's find the direction of the velocity vector.
	vDir := cross(R, []float64{0, 0, -1})
	for i := 0; i < 3; i++ {
		V[i] = v * vDir[i] / norm(vDir)
	}
	// Correct axial tilt
	R = MxV33(R1(Deg2rad(-c.tilt)), R)
	V = MxV33(R1(Deg2rad(-c.tilt)), V)

	// Correct ecliptic inclination
	R = MxV33(R1(Deg2rad(c.incl)), R)
	V = MxV33(R1(Deg2rad(c.incl)), V)

	return *NewOrbitFromRV(R, V, Sun)
}

/* Definitions */

// Sun is our closest star.
var Sun = CelestialObject{"Sun", 695700, -1, 1.32712440018 * 1e11, 0.0, 0.0, -1, -1, nil}

// Earth is home.
var Earth = CelestialObject{"Earth", 6378.1363, 149598023, 3.986004415 * 1e5, 23.4, 0.00005, 924645.0, 0.0010826269, nil}

// Mars is the vacation place.
var Mars = CelestialObject{"Mars", 3397.2, 227939186, 4.305 * 1e4, 25.19, 1.85, 576000, 0.001964, nil}
<commit_msg>Fix breaking changes in meeus<commit_after>package smd

import (
	"fmt"
	"math"
	"time"

	"github.com/soniakeys/meeus/julian"
	"github.com/soniakeys/meeus/planetposition"
)

const (
	// AU is one astronomical unit in kilometers.
	AU = 149598000
)

// CelestialObject defines a celestial object.
// Note: globe and elements may be nil; does not support satellites yet.
type CelestialObject struct {
	Name   string
	Radius float64
	a      float64
	μ      float64
	tilt   float64 // Axial tilt
	incl   float64 // Ecliptic inclination
	SOI    float64 // With respect to the Sun
	J2     float64
	PP     *planetposition.V87Planet
}

// String implements the Stringer interface.
func (c CelestialObject) String() string {
	return c.Name + " body"
}

// Equals returns whether the provided celestial object is the same.
func (c *CelestialObject) Equals(b CelestialObject) bool {
	return c.Name == b.Name && c.Radius == b.Radius && c.a == b.a && c.μ == b.μ && c.SOI == b.SOI && c.J2 == b.J2
}

// HelioOrbit returns the heliocentric position and velocity of this planet at a given time in equatorial coordinates.
// Note that the whole file is loaded. In fact, if we don't, then whoever is the first to call this function will
// set the Epoch at which the ephemeris are available, and that sucks.
func (c *CelestialObject) HelioOrbit(dt time.Time) Orbit {
	if c.Name == "Sun" {
		return *NewOrbitFromRV([]float64{0, 0, 0}, []float64{0, 0, 0}, *c)
	}
	if c.PP == nil {
		// Load the planet.
		var vsopPosition int
		switch c.Name {
		case "Venus":
			vsopPosition = 2
			break
		case "Earth":
			vsopPosition = 3
			break
		case "Mars":
			vsopPosition = 4
			break
		default:
			panic(fmt.Errorf("unknown object: %s", c.Name))
		}
		planet, err := planetposition.LoadPlanet(vsopPosition - 1)
		if err != nil {
			panic(fmt.Errorf("could not load planet number %d: %s", vsopPosition, err))
		}
		c.PP = planet
	}
	l, b, r := c.PP.Position2000(julian.TimeToJD(dt))
	r *= AU
	v := math.Sqrt(2*Sun.μ/r - Sun.μ/c.a)
	// Get the Cartesian coordinates from L,B,R.
	R, V := make([]float64, 3), make([]float64, 3)
	sB, cB := math.Sincos(b.Rad())
	sL, cL := math.Sincos(l.Rad())
	R[0] = r * cB * cL
	R[1] = r * cB * sL
	R[2] = r * sB
	// Let's find the direction of the velocity vector.
	vDir := cross(R, []float64{0, 0, -1})
	for i := 0; i < 3; i++ {
		V[i] = v * vDir[i] / norm(vDir)
	}
	// Correct axial tilt
	R = MxV33(R1(Deg2rad(-c.tilt)), R)
	V = MxV33(R1(Deg2rad(-c.tilt)), V)

	// Correct ecliptic inclination
	R = MxV33(R1(Deg2rad(c.incl)), R)
	V = MxV33(R1(Deg2rad(c.incl)), V)

	return *NewOrbitFromRV(R, V, Sun)
}

/* Definitions */

// Sun is our closest star.
var Sun = CelestialObject{"Sun", 695700, -1, 1.32712440018 * 1e11, 0.0, 0.0, -1, -1, nil}

// Earth is home.
var Earth = CelestialObject{"Earth", 6378.1363, 149598023, 3.986004415 * 1e5, 23.4, 0.00005, 924645.0, 0.0010826269, nil}

// Mars is the vacation place.
var Mars = CelestialObject{"Mars", 3397.2, 227939186, 4.305 * 1e4, 25.19, 1.85, 576000, 0.001964, nil}
<|endoftext|>


<commit_before>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/elazarl/goproxy"

	"bufio"
	"flag"
	"fmt"
	"net"
	"net/http"
	"os"
	"regexp"
)

const DefaultPort = ":8500"

// modes
const VirtualizeMode = "virtualize"
const SynthesizeMode = "synthesize"
const ModifyMode = "modify"
const CaptureMode = "capture"

// orPanic - wrapper for logging errors
func orPanic(err error) {
	if err != nil {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Panic("Got error.")
	}
}

func main() {
	// Output to stderr instead of stdout, could also be a file.
	log.SetOutput(os.Stderr)
	log.SetFormatter(&log.TextFormatter{})

	// getting proxy configuration
	verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
	// modes
	capture := flag.Bool("capture", false, "should proxy capture requests")
	synthesize := flag.Bool("synthesize", false, "should proxy capture requests")
	modify := flag.Bool("modify", false, "should proxy only modify requests")

	destination := flag.String("destination", ".", "destination URI to catch")
	middleware := flag.String("middleware", "", "should proxy use middleware")
	flag.Parse()

	if *verbose {
		// Only log the warning severity or above.
		log.SetLevel(log.DebugLevel)
	}

	// getting settings
	initSettings()

	// overriding default middleware setting
	AppConfig.middleware = *middleware

	// setting default mode
	mode := VirtualizeMode

	if *capture {
		mode = CaptureMode
		// checking whether user supplied other modes
		if *synthesize == true || *modify == true {
			log.Fatal("Two or more modes supplied, check your flags")
		}
	} else if *synthesize {
		mode = SynthesizeMode

		if AppConfig.middleware == "" {
			log.Fatal("Synthesize mode chosen although middleware not supplied")
		}

		if *capture == true || *modify == true {
			log.Fatal("Two or more modes supplied, check your flags")
		}
	} else if *modify {
		mode = ModifyMode

		if AppConfig.middleware == "" {
			log.Fatal("Modify mode chosen although middleware not supplied")
		}

		if *capture == true || *synthesize == true {
			log.Fatal("Two or more modes supplied, check your flags")
		}
	}

	// overriding default settings
	AppConfig.mode = mode

	// overriding destination
	AppConfig.destination = *destination

	// getting default database
	port := os.Getenv("ProxyPort")
	if port == "" {
		port = DefaultPort
	} else {
		port = fmt.Sprintf(":%s", port)
	}

	redisPool := getRedisPool()
	defer redisPool.Close()

	cache := Cache{pool: redisPool,
		prefix: AppConfig.cachePrefix}

	// getting connections
	d := DBClient{
		cache: cache,
		http:  &http.Client{},
	}

	// creating proxy
	proxy := goproxy.NewProxyHttpServer()

	proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
		HandleConnect(goproxy.AlwaysMitm)

	// enable curl -p for all hosts on port 80
	proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile(*destination))).
		HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {
		defer func() {
			if e := recover(); e != nil {
				ctx.Logf("error connecting to remote: %v", e)
				client.Write([]byte("HTTP/1.1 500 Cannot reach destination\r\n\r\n"))
			}
			client.Close()
		}()
		clientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client))
		remote, err := net.Dial("tcp", req.URL.Host)
		orPanic(err)
		remoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote))
		for {
			req, err := http.ReadRequest(clientBuf.Reader)
			orPanic(err)
			orPanic(req.Write(remoteBuf))
			orPanic(remoteBuf.Flush())
			resp, err := http.ReadResponse(remoteBuf.Reader, req)

			orPanic(err)
			orPanic(resp.Write(clientBuf.Writer))
			orPanic(clientBuf.Flush())
		}
	})

	// processing connections
	proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile(*destination))).DoFunc(
		func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
			return d.processRequest(r)
		})

	go d.startAdminInterface()

	proxy.Verbose = *verbose
	// proxy starting message
	log.WithFields(log.Fields{
		"RedisAddress": AppConfig.redisAddress,
		"Destination":  *destination,
		"ProxyPort":    port,
		"Mode":         AppConfig.mode,
	}).Info("Proxy is starting...")

	log.Warn(http.ListenAndServe(port, proxy))
}

func getDB() *bolt.DB {
	db, err := bolt.Open("my.db", 0600, nil)
	if err != nil {
		log.Fatal(err)
	}

	return db
}

// processRequest - processes incoming requests and based on proxy state (record/playback)
// returns HTTP response.
func (d *DBClient) processRequest(req *http.Request) (*http.Request, *http.Response) {

	if AppConfig.mode == CaptureMode {
		log.Info("*** Capture ***")
		newResponse, err := d.captureRequest(req)
		if err != nil {
			// something bad happened, passing through
			return req, nil
		} else {
			// discarding original requests and returns supplied response
			return req, newResponse
		}

	} else if AppConfig.mode == SynthesizeMode {
		log.Info("*** Sinthesize ***")
		response := synthesizeResponse(req, AppConfig.middleware)
		return req, response

	} else if AppConfig.mode == ModifyMode {
		log.Info("*** Modify ***")
		response, err := d.modifyRequestResponse(req, AppConfig.middleware)

		if err != nil {
			log.WithFields(log.Fields{
				"error":      err.Error(),
				"middleware": AppConfig.middleware,
			}).Error("Got error when performing request modification")
			return req, nil
		}

		// returning modified response
		return req, response

	} else {
		log.Info("*** Virtualize ***")
		newResponse := d.getResponse(req)
		return req, newResponse

	}
}
<commit_msg>db init<commit_after>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/boltdb/bolt"
	"github.com/elazarl/goproxy"

	"bufio"
	"flag"
	"fmt"
	"net"
	"net/http"
	"os"
	"regexp"
)

const DefaultPort = ":8500"

// modes
const VirtualizeMode = "virtualize"
const SynthesizeMode = "synthesize"
const ModifyMode = "modify"
const CaptureMode = "capture"

// orPanic - wrapper for logging errors
func orPanic(err error) {
	if err != nil {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Panic("Got error.")
	}
}

func main() {
	// Output to stderr instead of stdout, could also be a file.
	log.SetOutput(os.Stderr)
	log.SetFormatter(&log.TextFormatter{})

	// getting proxy configuration
	verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
	// modes
	capture := flag.Bool("capture", false, "should proxy capture requests")
	synthesize := flag.Bool("synthesize", false, "should proxy capture requests")
	modify := flag.Bool("modify", false, "should proxy only modify requests")

	destination := flag.String("destination", ".", "destination URI to catch")
	middleware := flag.String("middleware", "", "should proxy use middleware")
	flag.Parse()

	if *verbose {
		// Only log the warning severity or above.
		log.SetLevel(log.DebugLevel)
	}

	// getting settings
	initSettings()

	// overriding default middleware setting
	AppConfig.middleware = *middleware

	// setting default mode
	mode := VirtualizeMode

	if *capture {
		mode = CaptureMode
		// checking whether user supplied other modes
		if *synthesize == true || *modify == true {
			log.Fatal("Two or more modes supplied, check your flags")
		}
	} else if *synthesize {
		mode = SynthesizeMode

		if AppConfig.middleware == "" {
			log.Fatal("Synthesize mode chosen although middleware not supplied")
		}

		if *capture == true || *modify == true {
			log.Fatal("Two or more modes supplied, check your flags")
		}
	} else if *modify {
		mode = ModifyMode

		if AppConfig.middleware == "" {
			log.Fatal("Modify mode chosen although middleware not supplied")
		}

		if *capture == true || *synthesize == true {
			log.Fatal("Two or more modes supplied, check your flags")
		}
	}

	// overriding default settings
	AppConfig.mode = mode

	// overriding destination
	AppConfig.destination = *destination

	// getting default database
	port := os.Getenv("ProxyPort")
	if port == "" {
		port = DefaultPort
	} else {
		port = fmt.Sprintf(":%s", port)
	}

	redisPool := getRedisPool()
	defer redisPool.Close()

	// getting boltDB
	db := getDB()
	defer db.Close()

	cache := Cache{
		pool:           redisPool,
		prefix:         AppConfig.cachePrefix,
		db:             db,
		requestsBucket: []byte(requestsBucketName),
	}

	// getting connections
	d := DBClient{
		cache: cache,
		http:  &http.Client{},
	}

	// creating proxy
	proxy := goproxy.NewProxyHttpServer()

	proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
		HandleConnect(goproxy.AlwaysMitm)

	// enable curl -p for all hosts on port 80
	proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile(*destination))).
		HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {
		defer func() {
			if e := recover(); e != nil {
				ctx.Logf("error connecting to remote: %v", e)
				client.Write([]byte("HTTP/1.1 500 Cannot reach destination\r\n\r\n"))
			}
			client.Close()
		}()
		clientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client))
		remote, err := net.Dial("tcp", req.URL.Host)
		orPanic(err)
		remoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote))
		for {
			req, err := http.ReadRequest(clientBuf.Reader)
			orPanic(err)
			orPanic(req.Write(remoteBuf))
			orPanic(remoteBuf.Flush())
			resp, err := http.ReadResponse(remoteBuf.Reader, req)

			orPanic(err)
			orPanic(resp.Write(clientBuf.Writer))
			orPanic(clientBuf.Flush())
		}
	})

	// processing connections
	proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile(*destination))).DoFunc(
		func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
			return d.processRequest(r)
		})

	go d.startAdminInterface()

	proxy.Verbose = *verbose
	// proxy starting message
	log.WithFields(log.Fields{
		"RedisAddress": AppConfig.redisAddress,
		"Destination":  *destination,
		"ProxyPort":    port,
		"Mode":         AppConfig.mode,
	}).Info("Proxy is starting...")

	log.Warn(http.ListenAndServe(port, proxy))
}

func getDB() *bolt.DB {
	db, err := bolt.Open("my.db", 0600, nil)
	if err != nil {
		log.Fatal(err)
	}

	return db
}

// processRequest - processes incoming requests and based on proxy state (record/playback)
// returns HTTP response.
func (d *DBClient) processRequest(req *http.Request) (*http.Request, *http.Response) {

	if AppConfig.mode == CaptureMode {
		log.Info("*** Capture ***")
		newResponse, err := d.captureRequest(req)
		if err != nil {
			// something bad happened, passing through
			return req, nil
		} else {
			// discarding original requests and returns supplied response
			return req, newResponse
		}

	} else if AppConfig.mode == SynthesizeMode {
		log.Info("*** Sinthesize ***")
		response := synthesizeResponse(req, AppConfig.middleware)
		return req, response

	} else if AppConfig.mode == ModifyMode {
		log.Info("*** Modify ***")
		response, err := d.modifyRequestResponse(req, AppConfig.middleware)

		if err != nil {
			log.WithFields(log.Fields{
				"error":      err.Error(),
				"middleware": AppConfig.middleware,
			}).Error("Got error when performing request modification")
			return req, nil
		}

		// returning modified response
		return req, response

	} else {
		log.Info("*** Virtualize ***")
		newResponse := d.getResponse(req)
		return req, newResponse

	}
}
<|endoftext|>


<commit_before>package restserver

/*
Original version copied from: github.com/bitly/oauth2_proxy

MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

import (
	"crypto/sha1"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/base64"
	"encoding/csv"
	"log"
	"os"
	"os/signal"
	"regexp"
	"sync"
	"syscall"
	"time"

	"golang.org/x/crypto/bcrypt"
)

// CheckInterval represents how often we check for changes in htpasswd file.
const CheckInterval = 30 * time.Second

// PasswordCacheDuration represents how long authentication credentials are
// cached in memory after they were successfully verified. This allows avoiding
// repeatedly verifying the same authentication credentials.
const PasswordCacheDuration = time.Minute

// Lookup passwords in a htpasswd file.  The entries must have been created with -s for SHA encryption.

type cacheEntry struct {
	expiry   time.Time
	verifier []byte
}

// HtpasswdFile is a map for usernames to passwords.
type HtpasswdFile struct {
	mutex    sync.Mutex
	path     string
	stat     os.FileInfo
	throttle chan struct{}
	users    map[string]string
	cache    map[string]cacheEntry
}

// NewHtpasswdFromFile reads the users and passwords from a htpasswd file and returns them.  If an error is encountered,
// it is returned, together with a nil-Pointer for the HtpasswdFile.
func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGHUP)
	stat, err := os.Stat(path)
	if err != nil {
		return nil, err
	}

	h := &HtpasswdFile{
		mutex:    sync.Mutex{},
		path:     path,
		stat:     stat,
		throttle: make(chan struct{}),
		cache:    make(map[string]cacheEntry),
	}

	if err := h.Reload(); err != nil {
		return nil, err
	}

	// Start a goroutine that limits reload checks to once per CheckInterval
	go h.throttleTimer()
	go h.expiryTimer()

	go func() {
		for range c {
			err := h.Reload()
			if err == nil {
				log.Printf("Reloaded htpasswd file")
			} else {
				log.Printf("Could not reload htpasswd file: %v", err)
			}
		}
	}()

	return h, nil
}

// throttleTimer sends at most one message per CheckInterval to throttle file change checks.
func (h *HtpasswdFile) throttleTimer() {
	var check struct{}
	for {
		time.Sleep(CheckInterval)
		h.throttle <- check
	}
}

func (h *HtpasswdFile) expiryTimer() {
	for {
		time.Sleep(5 * time.Second)
		now := time.Now()
		h.mutex.Lock()
		var zeros [sha256.Size]byte
		// try to wipe expired cache entries
		for user, entry := range h.cache {
			if entry.expiry.After(now) {
				copy(entry.verifier, zeros[:])
				delete(h.cache, user)
			}
		}
		h.mutex.Unlock()
	}
}

var validUsernameRegexp = regexp.MustCompile(`^[\p{L}\d@._-]+$`)

// Reload reloads the htpasswd file. If the reload fails, the Users map is not changed and the error is returned.
func (h *HtpasswdFile) Reload() error {
	r, err := os.Open(h.path)
	if err != nil {
		return err
	}

	cr := csv.NewReader(r)
	cr.Comma = ':'
	cr.Comment = '#'
	cr.TrimLeadingSpace = true

	records, err := cr.ReadAll()
	if err != nil {
		_ = r.Close()
		return err
	}
	users := make(map[string]string)
	for _, record := range records {
		if !validUsernameRegexp.MatchString(record[0]) {
			log.Printf("Ignoring invalid username %q in htpasswd, consists of characters other than letters, numbers, '_', '-', '.' and '@'", record[0])
			continue
		}
		users[record[0]] = record[1]
	}

	// Replace the Users map
	h.mutex.Lock()
	var zeros [sha256.Size]byte
	// try to wipe the old cache entries
	for _, entry := range h.cache {
		copy(entry.verifier, zeros[:])
	}
	h.cache = make(map[string]cacheEntry)

	h.users = users
	h.mutex.Unlock()

	_ = r.Close()
	return nil
}

// ReloadCheck checks at most once per CheckInterval if the file changed and will reload the file if it did.
// It logs errors and successful reloads, and returns an error if any was encountered.
func (h *HtpasswdFile) ReloadCheck() error {
	select {
	case <-h.throttle:
		stat, err := os.Stat(h.path)
		if err != nil {
			log.Printf("Could not stat htpasswd file: %v", err)
			return err
		}

		reload := false

		h.mutex.Lock()
		if stat.ModTime() != h.stat.ModTime() || stat.Size() != h.stat.Size() {
			reload = true
			h.stat = stat
		}
		h.mutex.Unlock()

		if reload {
			err := h.Reload()
			if err == nil {
				log.Printf("Reloaded htpasswd file")
			} else {
				log.Printf("Could not reload htpasswd file: %v", err)
				return err
			}
		}
	default:
		// No need to check
	}
	return nil
}

// Validate returns true if password matches the stored password for user.  If no password for user is stored, or the
// password is wrong, false is returned.
func (h *HtpasswdFile) Validate(user string, password string) bool {
	_ = h.ReloadCheck()

	hash := sha256.New()
	// hash.Write can never fail
	_, _ = hash.Write([]byte(user))
	_, _ = hash.Write([]byte(":"))
	_, _ = hash.Write([]byte(password))

	h.mutex.Lock()
	// avoid race conditions with cache replacements
	cache := h.cache
	realPassword, exists := h.users[user]
	entry, cacheExists := h.cache[user]
	h.mutex.Unlock()

	if !exists {
		return false
	}

	if cacheExists && subtle.ConstantTimeCompare(entry.verifier, hash.Sum(nil)) == 1 {
		h.mutex.Lock()
		// repurpose mutex to prevent concurrent cache updates
		// extend cache entry
		cache[user] = cacheEntry{
			verifier: entry.verifier,
			expiry:   time.Now().Add(PasswordCacheDuration),
		}
		h.mutex.Unlock()
		return true
	}

	var shaRe = regexp.MustCompile(`^{SHA}`)
	var bcrRe = regexp.MustCompile(`^\$2b\$|^\$2a\$|^\$2y\$`)

	isValid := false

	switch {
	case shaRe.MatchString(realPassword):
		d := sha1.New()
		_, _ = d.Write([]byte(password))
		if realPassword[5:] == base64.StdEncoding.EncodeToString(d.Sum(nil)) {
			isValid = true
		}
	case bcrRe.MatchString(realPassword):
		err := bcrypt.CompareHashAndPassword([]byte(realPassword), []byte(password))
		if err == nil {
			isValid = true
		}
	}

	if !isValid {
		log.Printf("Invalid htpasswd entry for %s.", user)
		return false
	}

	h.mutex.Lock()
	// repurpose mutex to prevent concurrent cache updates
	cache[user] = cacheEntry{
		verifier: hash.Sum(nil),
		expiry:   time.Now().Add(PasswordCacheDuration),
	}
	h.mutex.Unlock()

	return true
}
<commit_msg>use constant time comparison for sha1 password hash<commit_after>package restserver

/*
Original version copied from: github.com/bitly/oauth2_proxy

MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

import (
	"crypto/sha1"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/base64"
	"encoding/csv"
	"log"
	"os"
	"os/signal"
	"regexp"
	"sync"
	"syscall"
	"time"

	"golang.org/x/crypto/bcrypt"
)

// CheckInterval represents how often we check for changes in htpasswd file.
const CheckInterval = 30 * time.Second

// PasswordCacheDuration represents how long authentication credentials are
// cached in memory after they were successfully verified. This allows avoiding
// repeatedly verifying the same authentication credentials.
const PasswordCacheDuration = time.Minute

// Lookup passwords in a htpasswd file.  The entries must have been created with -s for SHA encryption.

type cacheEntry struct {
	expiry   time.Time
	verifier []byte
}

// HtpasswdFile is a map for usernames to passwords.
type HtpasswdFile struct {
	mutex    sync.Mutex
	path     string
	stat     os.FileInfo
	throttle chan struct{}
	users    map[string]string
	cache    map[string]cacheEntry
}

// NewHtpasswdFromFile reads the users and passwords from a htpasswd file and returns them.  If an error is encountered,
// it is returned, together with a nil-Pointer for the HtpasswdFile.
func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGHUP)
	stat, err := os.Stat(path)
	if err != nil {
		return nil, err
	}

	h := &HtpasswdFile{
		mutex:    sync.Mutex{},
		path:     path,
		stat:     stat,
		throttle: make(chan struct{}),
		cache:    make(map[string]cacheEntry),
	}

	if err := h.Reload(); err != nil {
		return nil, err
	}

	// Start a goroutine that limits reload checks to once per CheckInterval
	go h.throttleTimer()
	go h.expiryTimer()

	go func() {
		for range c {
			err := h.Reload()
			if err == nil {
				log.Printf("Reloaded htpasswd file")
			} else {
				log.Printf("Could not reload htpasswd file: %v", err)
			}
		}
	}()

	return h, nil
}

// throttleTimer sends at most one message per CheckInterval to throttle file change checks.
func (h *HtpasswdFile) throttleTimer() {
	var check struct{}
	for {
		time.Sleep(CheckInterval)
		h.throttle <- check
	}
}

func (h *HtpasswdFile) expiryTimer() {
	for {
		time.Sleep(5 * time.Second)
		now := time.Now()
		h.mutex.Lock()
		var zeros [sha256.Size]byte
		// try to wipe expired cache entries
		for user, entry := range h.cache {
			if entry.expiry.After(now) {
				copy(entry.verifier, zeros[:])
				delete(h.cache, user)
			}
		}
		h.mutex.Unlock()
	}
}

var validUsernameRegexp = regexp.MustCompile(`^[\p{L}\d@._-]+$`)

// Reload reloads the htpasswd file. If the reload fails, the Users map is not changed and the error is returned.
func (h *HtpasswdFile) Reload() error {
	r, err := os.Open(h.path)
	if err != nil {
		return err
	}

	cr := csv.NewReader(r)
	cr.Comma = ':'
	cr.Comment = '#'
	cr.TrimLeadingSpace = true

	records, err := cr.ReadAll()
	if err != nil {
		_ = r.Close()
		return err
	}
	users := make(map[string]string)
	for _, record := range records {
		if !validUsernameRegexp.MatchString(record[0]) {
			log.Printf("Ignoring invalid username %q in htpasswd, consists of characters other than letters, numbers, '_', '-', '.' and '@'", record[0])
			continue
		}
		users[record[0]] = record[1]
	}

	// Replace the Users map
	h.mutex.Lock()
	var zeros [sha256.Size]byte
	// try to wipe the old cache entries
	for _, entry := range h.cache {
		copy(entry.verifier, zeros[:])
	}
	h.cache = make(map[string]cacheEntry)

	h.users = users
	h.mutex.Unlock()

	_ = r.Close()
	return nil
}

// ReloadCheck checks at most once per CheckInterval if the file changed and will reload the file if it did.
// It logs errors and successful reloads, and returns an error if any was encountered.
func (h *HtpasswdFile) ReloadCheck() error {
	select {
	case <-h.throttle:
		stat, err := os.Stat(h.path)
		if err != nil {
			log.Printf("Could not stat htpasswd file: %v", err)
			return err
		}

		reload := false

		h.mutex.Lock()
		if stat.ModTime() != h.stat.ModTime() || stat.Size() != h.stat.Size() {
			reload = true
			h.stat = stat
		}
		h.mutex.Unlock()

		if reload {
			err := h.Reload()
			if err == nil {
				log.Printf("Reloaded htpasswd file")
			} else {
				log.Printf("Could not reload htpasswd file: %v", err)
				return err
			}
		}
	default:
		// No need to check
	}
	return nil
}

// Validate returns true if password matches the stored password for user.  If no password for user is stored, or the
// password is wrong, false is returned.
func (h *HtpasswdFile) Validate(user string, password string) bool {
	_ = h.ReloadCheck()

	hash := sha256.New()
	// hash.Write can never fail
	_, _ = hash.Write([]byte(user))
	_, _ = hash.Write([]byte(":"))
	_, _ = hash.Write([]byte(password))

	h.mutex.Lock()
	// avoid race conditions with cache replacements
	cache := h.cache
	realPassword, exists := h.users[user]
	entry, cacheExists := h.cache[user]
	h.mutex.Unlock()

	if !exists {
		return false
	}

	if cacheExists && subtle.ConstantTimeCompare(entry.verifier, hash.Sum(nil)) == 1 {
		h.mutex.Lock()
		// repurpose mutex to prevent concurrent cache updates
		// extend cache entry
		cache[user] = cacheEntry{
			verifier: entry.verifier,
			expiry:   time.Now().Add(PasswordCacheDuration),
		}
		h.mutex.Unlock()
		return true
	}

	var shaRe = regexp.MustCompile(`^{SHA}`)
	var bcrRe = regexp.MustCompile(`^\$2b\$|^\$2a\$|^\$2y\$`)

	isValid := false

	switch {
	case shaRe.MatchString(realPassword):
		d := sha1.New()
		_, _ = d.Write([]byte(password))
		if subtle.ConstantTimeCompare([]byte(realPassword[5:]), []byte(base64.StdEncoding.EncodeToString(d.Sum(nil)))) == 1 {
			isValid = true
		}
	case bcrRe.MatchString(realPassword):
		err := bcrypt.CompareHashAndPassword([]byte(realPassword), []byte(password))
		if err == nil {
			isValid = true
		}
	}

	if !isValid {
		log.Printf("Invalid htpasswd entry for %s.", user)
		return false
	}

	h.mutex.Lock()
	// repurpose mutex to prevent concurrent cache updates
	cache[user] = cacheEntry{
		verifier: hash.Sum(nil),
		expiry:   time.Now().Add(PasswordCacheDuration),
	}
	h.mutex.Unlock()

	return true
}
<|endoftext|>