<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
package timedrift

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"mig"
	"net"
	"os"
	"time"
)

// init is called by the Go runtime at startup. We use this function to
// register the module in a global array of available modules, so the
// agent knows we exist
func init() {
	mig.RegisterModule("timedrift", func() interface{} {
		return new(Runner)
	}, false)
}

// Runner gives access to the exported functions and structs of the module
type Runner struct {
	Parameters params
	Results    results
}

type results struct {
	FoundAnything bool        `json:"foundanything"`
	Success       bool        `json:"success"`
	Elements      checkedtime `json:"elements"`
	Statistics    statistics  `json:"statistics"`
	Errors        []string    `json:"errors"`
}

// a simple parameters structure, the format is arbitrary
type params struct {
	Drift string `json:"drift"`
}

type checkedtime struct {
	HasCheckedDrift bool     `json:"hascheckeddrift"`
	IsWithinDrift   bool     `json:"iswithindrift,omitempty"`
	Drifts          []string `json:"drifts,omitempty"`
	LocalTime       string   `json:"localtime"`
}

type statistics struct {
	ExecTime string     `json:"exectime"`
	NtpStats []ntpstats `json:"ntpstats,omitempty"`
}

type ntpstats struct {
	Host      string    `json:"host"`
	Time      time.Time `json:"time"`
	Latency   string    `json:"latency"`
	Drift     string    `json:"drift"`
	Reachable bool      `json:"reachable"`
}

func (r Runner) ValidateParameters() (err error) {
	if r.Parameters.Drift != "" {
		_, err = time.ParseDuration(r.Parameters.Drift)
	}
	return err
}

func (r Runner) Run(Args []byte) string {
	var (
		stats statistics
		ct    checkedtime
		drift time.Duration
	)
	ct.LocalTime = time.Now().Format(time.RFC3339Nano)
	t1 := time.Now()
	err := json.Unmarshal(Args, &r.Parameters)
	if err != nil {
		r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
		return r.buildResults()
	}
	err = r.ValidateParameters()
	if err != nil {
		r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
		return r.buildResults()
	}
	// if drift is not set, skip the ntp test
	if r.Parameters.Drift == "" {
		r.Results.FoundAnything = true
		goto done
	}
	drift, err = time.ParseDuration(r.Parameters.Drift)
	if err != nil {
		r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
		return r.buildResults()
	}
	// assume host has synched time and set to false if not true
	ct.IsWithinDrift = true
	// attempt to get network time from each of the NTP servers, and exit
	// as soon as we get a valid result from one of them
	for i := 0; i < len(NtpPool); i++ {

		// pick a server between 0 and len of ntppool, somewhat randomly
		ntpsrv := NtpPool[time.Now().Nanosecond()%len(NtpPool)]
		t, lat, err := GetNetworkTime(ntpsrv)
		if err != nil {
			// failed to get network time, log a failure and try another one
			stats.NtpStats = append(stats.NtpStats, ntpstats{
				Host:      ntpsrv,
				Reachable: false,
			})
			continue
		}

		// compare network time to local time
		localtime := time.Now()
		if err != nil {
			r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
			continue
		}
		if localtime.Before(t.Add(-drift)) {
			ct.IsWithinDrift = false
			ct.Drifts = append(ct.Drifts, fmt.Sprintf("Local time is behind ntp host %s by %s", ntpsrv, t.Sub(localtime).String()))
		} else if localtime.After(t.Add(drift)) {
			ct.IsWithinDrift = false
			ct.Drifts = append(ct.Drifts, fmt.Sprintf("Local time is ahead of ntp host %s by %s", ntpsrv, localtime.Sub(t).String()))
		}
		stats.NtpStats = append(stats.NtpStats, ntpstats{
			Host:      ntpsrv,
			Time:      t,
			Latency:   lat,
			Drift:     localtime.Sub(t).String(),
			Reachable: true,
		})
		ct.HasCheckedDrift = true

		// comparison succeeded, exit the loop
		break
	}
	if !ct.IsWithinDrift {
		r.Results.FoundAnything = true
	}
done:
	r.Results.Elements = ct
	stats.ExecTime = time.Now().Sub(t1).String()
	r.Results.Statistics = stats
	return r.buildResults()
}

var NtpPool = [...]string{
	`time.nist.gov`,
	`0.pool.ntp.org`,
	`1.pool.ntp.org`,
	`2.pool.ntp.org`,
	`3.pool.ntp.org`}

// GetNetworkTime queries a given NTP server to obtain the network time
func GetNetworkTime(host string) (t time.Time, latency string, err error) {
	raddr, err := net.ResolveUDPAddr("udp", host+":123")
	if err != nil {
		return
	}
	// NTP request is 48 bytes long, we only set the first byte
	data := make([]byte, 48)
	// Flags: 0x1b (27)
	// 00...... leap indicator (0)
	// ..011... version number (3)
	// .....011 mode: client (3)
	data[0] = 3<<3 | 3

	t1 := time.Now()
	con, err := net.DialUDP("udp", nil, raddr)
	if err != nil {
		return
	}
	defer con.Close()
	// send the request
	_, err = con.Write(data)
	if err != nil {
		return
	}
	// wait up to 5 seconds for the response
	con.SetDeadline(time.Now().Add(5 * time.Second))
	// read up to 48 bytes from the response
	_, err = con.Read(data)
	if err != nil {
		return
	}
	latency = time.Now().Sub(t1).String()
	// Response format (from the RFC)
	//  0                   1                   2                   3
	//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |LI | VN  |Mode |    Stratum     |     Poll      |  Precision   |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                         Root Delay                            |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                         Root Dispersion                       |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                          Reference ID                         |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                     Reference Timestamp (64)                  +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                      Origin Timestamp (64)                    +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                      Receive Timestamp (64)                   +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                      Transmit Timestamp (64)                  +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	var sec, frac uint64
	sec = uint64(data[43]) | uint64(data[42])<<8 | uint64(data[41])<<16 | uint64(data[40])<<24
	frac = uint64(data[47]) | uint64(data[46])<<8 | uint64(data[45])<<16 | uint64(data[44])<<24
	if sec == 0 || frac == 0 {
		err = fmt.Errorf("null response received from NTP host")
		return
	}
	nsec := sec * 1e9
	nsec += (frac * 1e9) >> 32

	t = time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nsec)).Local()

	return
}

// buildResults marshals the results
func (r Runner) buildResults() string {
	if len(r.Results.Errors) == 0 {
		r.Results.Success = true
	}
	// if was supposed to check drift but hasn't, set success to false
	if r.Parameters.Drift != "" && !r.Results.Elements.HasCheckedDrift {
		r.Results.Success = false
	}
	jsonOutput, err := json.Marshal(r.Results)
	if err != nil {
		panic(err)
	}
	return string(jsonOutput[:])
}

func (r Runner) PrintResults(rawResults []byte, foundOnly bool) (prints []string, err error) {
	var results results
	err = json.Unmarshal(rawResults, &results)
	if err != nil {
		return
	}
	prints = append(prints, "local time is "+results.Elements.LocalTime)
	if results.Elements.HasCheckedDrift {
		if results.Elements.IsWithinDrift {
			prints = append(prints, "local time is within acceptable drift from NTP servers")
		} else {
			prints = append(prints, "local time is out of sync from NTP servers")
			for _, drift := range results.Elements.Drifts {
				prints = append(prints, drift)
			}
		}
	}
	// stop here if foundOnly is set, we don't want to see errors and stats
	if foundOnly {
		return
	}
	for _, e := range results.Errors {
		prints = append(prints, "error:", e)
	}
	fmt.Println("stat: execution time", results.Statistics.ExecTime)
	for _, ntpstat := range results.Statistics.NtpStats {
		if ntpstat.Reachable {
			prints = append(prints, "stat: "+ntpstat.Host+" responded in "+ntpstat.Latency+" with time "+ntpstat.Time.UTC().String()+". local time drifts by "+ntpstat.Drift)
		} else {
			prints = append(prints, "stat: "+ntpstat.Host+" was unreachable")
		}
	}
	if results.Success {
		prints = append(prints, fmt.Sprintf("timedrift module has succeeded"))
	} else {
		prints = append(prints, fmt.Sprintf("timedrift module has failed"))
	}
	return
}

func printHelp(isCmd bool) {
	dash := ""
	if isCmd {
		dash = "-"
	}
	fmt.Printf(`timedrift returns the local time of a system and, when %sdrift is set,
verifies that local time is within acceptable range of network time by querying NTP servers

%sdrift <duration>	allowed time drift window. a value of "5s" compares local
			time with ntp hosts and returns a drift failure if local
			time is too far out of sync.

If no drift is set, the module only returns local time.
`, dash, dash)
}

func (r Runner) ParamsCreator() (interface{}, error) {
	fmt.Println("initializing timedrift parameters creation")
	var err error
	var p params
	printHelp(false)
	scanner := bufio.NewScanner(os.Stdin)
	for {
		fmt.Printf("drift> ")
		scanner.Scan()
		if err := scanner.Err(); err != nil {
			fmt.Println("Invalid input. Try again")
			continue
		}
		input := scanner.Text()
		if input == "help" {
			printHelp(false)
			continue
		}
		if input != "" {
			_, err = time.ParseDuration(input)
			if err != nil {
				fmt.Println("invalid drift duration. try again. ex: drift> 5s")
				continue
			}
		}
		p.Drift = input
		break
	}
	r.Parameters = p
	return r.Parameters, r.ValidateParameters()
}

func (r Runner) ParamsParser(args []string) (interface{}, error) {
	var (
		err   error
		drift string
		fs    flag.FlagSet
	)
	if len(args) >= 1 && args[0] == "help" {
		printHelp(true)
		return nil, fmt.Errorf("help printed")
	}
	if len(args) == 0 {
		return r.Parameters, nil
	}
	fs.Init("time", flag.ContinueOnError)
	fs.StringVar(&drift, "drift", "", "see help")
	err = fs.Parse(args)
	if err != nil {
		return nil, err
	}
	_, err = time.ParseDuration(drift)
	if err != nil {
		return nil, fmt.Errorf("invalid drift duration. try help.")
	}
	r.Parameters.Drift = drift
	return r.Parameters, r.ValidateParameters()
}
<commit_msg>[minor] update timedrift module to new interfaces<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
package timedrift

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"mig/modules"
	"net"
	"os"
	"time"
)

// init is called by the Go runtime at startup. We use this function to
// register the module in a global array of available modules, so the
// agent knows we exist
func init() {
	modules.Register("timedrift", func() interface{} {
		return new(Runner)
	})
}

// Runner gives access to the exported functions and structs of the module
type Runner struct {
	Parameters params
	Results    modules.Result
}

// a simple parameters structure, the format is arbitrary
type params struct {
	Drift string `json:"drift"`
}

type elements struct {
	HasCheckedDrift bool     `json:"hascheckeddrift"`
	IsWithinDrift   bool     `json:"iswithindrift,omitempty"`
	Drifts          []string `json:"drifts,omitempty"`
	LocalTime       string   `json:"localtime"`
}

type statistics struct {
	ExecTime string     `json:"exectime"`
	NtpStats []ntpstats `json:"ntpstats,omitempty"`
}

type ntpstats struct {
	Host      string    `json:"host"`
	Time      time.Time `json:"time"`
	Latency   string    `json:"latency"`
	Drift     string    `json:"drift"`
	Reachable bool      `json:"reachable"`
}

func (r Runner) ValidateParameters() (err error) {
	if r.Parameters.Drift != "" {
		_, err = time.ParseDuration(r.Parameters.Drift)
	}
	return err
}

func (r Runner) Run() (out string) {
	var (
		stats statistics
		el    elements
		drift time.Duration
	)
	defer func() {
		if e := recover(); e != nil {
			r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", e))
			r.Results.Success = false
			buf, _ := json.Marshal(r.Results)
			out = string(buf[:])
		}
	}()
	el.LocalTime = time.Now().Format(time.RFC3339Nano)
	t1 := time.Now()
	err := modules.ReadInputParameters(&r.Parameters)
	if err != nil {
		panic(err)
	}
	err = r.ValidateParameters()
	if err != nil {
		panic(err)
	}
	// if drift is not set, skip the ntp test
	if r.Parameters.Drift == "" {
		r.Results.FoundAnything = true
		goto done
	}
	drift, err = time.ParseDuration(r.Parameters.Drift)
	if err != nil {
		panic(err)
	}
	// assume host has synched time and set to false if not true
	el.IsWithinDrift = true
	// attempt to get network time from each of the NTP servers, and exit
	// as soon as we get a valid result from one of them
	for i := 0; i < len(NtpPool); i++ {

		// pick a server between 0 and len of ntppool, somewhat randomly
		ntpsrv := NtpPool[time.Now().Nanosecond()%len(NtpPool)]
		t, lat, err := GetNetworkTime(ntpsrv)
		if err != nil {
			// failed to get network time, log a failure and try another one
			stats.NtpStats = append(stats.NtpStats, ntpstats{
				Host:      ntpsrv,
				Reachable: false,
			})
			continue
		}

		// compare network time to local time
		localtime := time.Now()
		if err != nil {
			r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
			continue
		}
		if localtime.Before(t.Add(-drift)) {
			el.IsWithinDrift = false
			el.Drifts = append(el.Drifts, fmt.Sprintf("Local time is behind ntp host %s by %s", ntpsrv, t.Sub(localtime).String()))
		} else if localtime.After(t.Add(drift)) {
			el.IsWithinDrift = false
			el.Drifts = append(el.Drifts, fmt.Sprintf("Local time is ahead of ntp host %s by %s", ntpsrv, localtime.Sub(t).String()))
		}
		stats.NtpStats = append(stats.NtpStats, ntpstats{
			Host:      ntpsrv,
			Time:      t,
			Latency:   lat,
			Drift:     localtime.Sub(t).String(),
			Reachable: true,
		})
		el.HasCheckedDrift = true

		// comparison succeeded, exit the loop
		break
	}
	if !el.IsWithinDrift {
		r.Results.FoundAnything = true
	}
done:
	stats.ExecTime = time.Now().Sub(t1).String()
	out = r.buildResults(el, stats)
	return
}

var NtpPool = [...]string{
	`time.nist.gov`,
	`0.pool.ntp.org`,
	`1.pool.ntp.org`,
	`2.pool.ntp.org`,
	`3.pool.ntp.org`}

// GetNetworkTime queries a given NTP server to obtain the network time
func GetNetworkTime(host string) (t time.Time, latency string, err error) {
	raddr, err := net.ResolveUDPAddr("udp", host+":123")
	if err != nil {
		return
	}
	// NTP request is 48 bytes long, we only set the first byte
	data := make([]byte, 48)
	// Flags: 0x1b (27)
	// 00...... leap indicator (0)
	// ..011... version number (3)
	// .....011 mode: client (3)
	data[0] = 3<<3 | 3

	t1 := time.Now()
	con, err := net.DialUDP("udp", nil, raddr)
	if err != nil {
		return
	}
	defer con.Close()
	// send the request
	_, err = con.Write(data)
	if err != nil {
		return
	}
	// wait up to 5 seconds for the response
	con.SetDeadline(time.Now().Add(5 * time.Second))
	// read up to 48 bytes from the response
	_, err = con.Read(data)
	if err != nil {
		return
	}
	latency = time.Now().Sub(t1).String()
	// Response format (from the RFC)
	//  0                   1                   2                   3
	//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |LI | VN  |Mode |    Stratum     |     Poll      |  Precision   |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                         Root Delay                            |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                         Root Dispersion                       |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                          Reference ID                         |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                     Reference Timestamp (64)                  +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                      Origin Timestamp (64)                    +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                      Receive Timestamp (64)                   +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                      Transmit Timestamp (64)                  +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	var sec, frac uint64
	sec = uint64(data[43]) | uint64(data[42])<<8 | uint64(data[41])<<16 | uint64(data[40])<<24
	frac = uint64(data[47]) | uint64(data[46])<<8 | uint64(data[45])<<16 | uint64(data[44])<<24
	if sec == 0 || frac == 0 {
		err = fmt.Errorf("null response received from NTP host")
		return
	}
	nsec := sec * 1e9
	nsec += (frac * 1e9) >> 32

	t = time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nsec)).Local()

	return
}

// buildResults marshals the results
func (r Runner) buildResults(el elements, stats statistics) string {
	r.Results.Elements = el
	r.Results.Statistics = stats
	if len(r.Results.Errors) == 0 {
		r.Results.Success = true
	}
	// if was supposed to check drift but hasn't, set success to false
	if r.Parameters.Drift != "" && !el.HasCheckedDrift {
		r.Results.Success = false
	}
	jsonOutput, err := json.Marshal(r.Results)
	if err != nil {
		panic(err)
	}
	return string(jsonOutput[:])
}

func (r Runner) PrintResults(result modules.Result, foundOnly bool) (prints []string, err error) {
	var (
		el    elements
		stats statistics
	)
	err = result.GetElements(&el)
	if err != nil {
		return
	}
	prints = append(prints, "local time is "+el.LocalTime)
	if el.HasCheckedDrift {
		if el.IsWithinDrift {
			prints = append(prints, "local time is within acceptable drift from NTP servers")
		} else {
			prints = append(prints, "local time is out of sync from NTP servers")
			for _, drift := range el.Drifts {
				prints = append(prints, drift)
			}
		}
	}
	// stop here if foundOnly is set, we don't want to see errors and stats
	if foundOnly {
		return
	}
	for _, e := range result.Errors {
		prints = append(prints, "error:", e)
	}
	err = result.GetStatistics(&stats)
	if err != nil {
		panic(err)
	}
	prints = append(prints, "stat: execution time was "+stats.ExecTime)
	for _, ntpstat := range stats.NtpStats {
		if ntpstat.Reachable {
			prints = append(prints, "stat: "+ntpstat.Host+" responded in "+ntpstat.Latency+" with time "+ntpstat.Time.UTC().String()+". local time drifts by "+ntpstat.Drift)
		} else {
			prints = append(prints, "stat: "+ntpstat.Host+" was unreachable")
		}
	}
	if result.Success {
		prints = append(prints, fmt.Sprintf("timedrift module has succeeded"))
	} else {
		prints = append(prints, fmt.Sprintf("timedrift module has failed"))
	}
	return
}

func printHelp(isCmd bool) {
	dash := ""
	if isCmd {
		dash = "-"
	}
	fmt.Printf(`timedrift returns the local time of a system and, when %sdrift is set,
verifies that local time is within acceptable range of network time by querying NTP servers

%sdrift <duration>	allowed time drift window. a value of "5s" compares local
			time with ntp hosts and returns a drift failure if local
			time is too far out of sync.

If no drift is set, the module only returns local time.
`, dash, dash)
}

func (r Runner) ParamsCreator() (interface{}, error) {
	fmt.Println("initializing timedrift parameters creation")
	var err error
	var p params
	printHelp(false)
	scanner := bufio.NewScanner(os.Stdin)
	for {
		fmt.Printf("drift> ")
		scanner.Scan()
		if err := scanner.Err(); err != nil {
			fmt.Println("Invalid input. Try again")
			continue
		}
		input := scanner.Text()
		if input == "help" {
			printHelp(false)
			continue
		}
		if input != "" {
			_, err = time.ParseDuration(input)
			if err != nil {
				fmt.Println("invalid drift duration. try again. ex: drift> 5s")
				continue
			}
		}
		p.Drift = input
		break
	}
	r.Parameters = p
	return r.Parameters, r.ValidateParameters()
}

func (r Runner) ParamsParser(args []string) (interface{}, error) {
	var (
		err   error
		drift string
		fs    flag.FlagSet
	)
	if len(args) >= 1 && args[0] == "help" {
		printHelp(true)
		return nil, fmt.Errorf("help printed")
	}
	if len(args) == 0 {
		return r.Parameters, nil
	}
	fs.Init("time", flag.ContinueOnError)
	fs.StringVar(&drift, "drift", "", "see help")
	err = fs.Parse(args)
	if err != nil {
		return nil, err
	}
	_, err = time.ParseDuration(drift)
	if err != nil {
		return nil, fmt.Errorf("invalid drift duration. try help.")
	}
	r.Parameters.Drift = drift
	return r.Parameters, r.ValidateParameters()
}
<|endoftext|>


<commit_before>package admin

import (
	"errors"
	"os"
	"strconv"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/ceph/go-ceph/internal/commands"
	"github.com/ceph/go-ceph/rados"
)

var (
	cachedFSAdmin *FSAdmin

	// set debugTrace to true to use tracing in tests
	debugTrace = false

	// some tests are sensitive to the server version
	serverVersion string
)

const (
	cephNautilus = "nautilus"
	cephOctopus  = "octopus"
	cephPacfic   = "pacific"
)

func init() {
	dt := os.Getenv("GO_CEPH_TEST_DEBUG_TRACE")
	if ok, err := strconv.ParseBool(dt); ok && err == nil {
		debugTrace = true
	}
	switch vname := os.Getenv("CEPH_VERSION"); vname {
	case cephNautilus, cephOctopus, cephPacfic:
		serverVersion = vname
	}
}

func TestServerSentinel(t *testing.T) {
	// there probably *is* a better way to do this but I'm doing what's easy
	// and expedient at the moment. That's tying the tests to the environment
	// var to tell us what version of the *server* we are testing against. The
	// build tags control what version of the *client libs* we use.  These
	// happen to be the same for our CI tests today, but its a lousy way to
	// organize things IMO.
	// This check is intended to fail the test suite if you don't tell it a
	// server version it expects and force us to update the tests if a new
	// version of ceph is added.
	if serverVersion == "" {
		t.Fatalf("server must be nautilus, octopus, or pacific (do the tests need updating?)")
	}
}

func getFSAdmin(t *testing.T) *FSAdmin {
	if cachedFSAdmin != nil {
		return cachedFSAdmin
	}
	cachedFSAdmin = newFSAdmin(t, "")
	return cachedFSAdmin
}

func newFSAdmin(t *testing.T, configFile string) *FSAdmin {
	conn, err := rados.NewConn()
	require.NoError(t, err)
	if configFile == "" {
		err = conn.ReadDefaultConfigFile()
		require.NoError(t, err)
	} else {
		err = conn.ReadConfigFile(configFile)
		require.NoError(t, err)
	}
	err = conn.Connect()
	require.NoError(t, err)

	var cmdr RadosCommander = conn
	if debugTrace {
		// We wrap the "real" connection, which meets the RadosCommander interface,
		// with a trace commander when debugTrace is set.
		cmdr = commands.NewTraceCommander(conn)
	}

	// We sleep briefly before returning in order to ensure we have a mgr map
	// before we start executing the tests.
	time.Sleep(50 * time.Millisecond)
	return NewFromConn(cmdr)
}

func TestInvalidFSAdmin(t *testing.T) {
	fsa := &FSAdmin{}
	res := fsa.rawMgrCommand([]byte("FOOBAR!"))
	assert.Error(t, res.Unwrap())
}

type badMarshalType bool

func (badMarshalType) MarshalJSON() ([]byte, error) {
	return nil, errors.New("Zowie! wow")
}

func TestBadMarshal(t *testing.T) {
	fsa := getFSAdmin(t)

	var bad badMarshalType
	res := fsa.marshalMgrCommand(bad)
	assert.Error(t, res.Unwrap())
}

func TestParseListNames(t *testing.T) {
	R := newResponse
	t.Run("error", func(t *testing.T) {
		_, err := parseListNames(R(nil, "", errors.New("bonk")))
		assert.Error(t, err)
		assert.Equal(t, "bonk", err.Error())
	})
	t.Run("statusSet", func(t *testing.T) {
		_, err := parseListNames(R(nil, "unexpected!", nil))
		assert.Error(t, err)
	})
	t.Run("badJSON", func(t *testing.T) {
		_, err := parseListNames(R([]byte("Foo[[["), "", nil))
		assert.Error(t, err)
	})
	t.Run("ok", func(t *testing.T) {
		l, err := parseListNames(R([]byte(`[{"name":"bob"}]`), "", nil))
		assert.NoError(t, err)
		if assert.Len(t, l, 1) {
			assert.Equal(t, "bob", l[0])
		}
	})
}

func TestCheckEmptyResponseExpected(t *testing.T) {
	R := newResponse
	t.Run("error", func(t *testing.T) {
		err := R(nil, "", errors.New("bonk")).NoData().End()
		assert.Error(t, err)
		assert.Equal(t, "bonk", err.Error())
	})
	t.Run("statusSet", func(t *testing.T) {
		err := R(nil, "unexpected!", nil).NoData().End()
		assert.Error(t, err)
	})
	t.Run("someJSON", func(t *testing.T) {
		err := R([]byte(`{"trouble": true}`), "", nil).NoData().End()
		assert.Error(t, err)
	})
	t.Run("ok", func(t *testing.T) {
		err := R([]byte{}, "", nil).NoData().End()
		assert.NoError(t, err)
	})
}
<commit_msg>cephfs admin: use the new admintest module to set up tests<commit_after>package admin

import (
	"errors"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/ceph/go-ceph/internal/admintest"
)

var (
	radosConnector = admintest.NewConnector()

	// some tests are sensitive to the server version
	serverVersion string
)

const (
	cephNautilus = "nautilus"
	cephOctopus  = "octopus"
	cephPacfic   = "pacific"
)

func init() {
	switch vname := os.Getenv("CEPH_VERSION"); vname {
	case cephNautilus, cephOctopus, cephPacfic:
		serverVersion = vname
	}
}

func TestServerSentinel(t *testing.T) {
	// there probably *is* a better way to do this but I'm doing what's easy
	// and expedient at the moment. That's tying the tests to the environment
	// var to tell us what version of the *server* we are testing against. The
	// build tags control what version of the *client libs* we use.  These
	// happen to be the same for our CI tests today, but its a lousy way to
	// organize things IMO.
	// This check is intended to fail the test suite if you don't tell it a
	// server version it expects and force us to update the tests if a new
	// version of ceph is added.
	if serverVersion == "" {
		t.Fatalf("server must be nautilus, octopus, or pacific (do the tests need updating?)")
	}
}

func getFSAdmin(t *testing.T) *FSAdmin {
	return NewFromConn(radosConnector.Get(t))
}

func newFSAdmin(t *testing.T, configFile string) *FSAdmin {
	return NewFromConn(
		admintest.WrapConn(admintest.NewConnFromConfig(t, configFile)))
}

func TestInvalidFSAdmin(t *testing.T) {
	fsa := &FSAdmin{}
	res := fsa.rawMgrCommand([]byte("FOOBAR!"))
	assert.Error(t, res.Unwrap())
}

type badMarshalType bool

func (badMarshalType) MarshalJSON() ([]byte, error) {
	return nil, errors.New("Zowie! wow")
}

func TestBadMarshal(t *testing.T) {
	fsa := getFSAdmin(t)

	var bad badMarshalType
	res := fsa.marshalMgrCommand(bad)
	assert.Error(t, res.Unwrap())
}

func TestParseListNames(t *testing.T) {
	R := newResponse
	t.Run("error", func(t *testing.T) {
		_, err := parseListNames(R(nil, "", errors.New("bonk")))
		assert.Error(t, err)
		assert.Equal(t, "bonk", err.Error())
	})
	t.Run("statusSet", func(t *testing.T) {
		_, err := parseListNames(R(nil, "unexpected!", nil))
		assert.Error(t, err)
	})
	t.Run("badJSON", func(t *testing.T) {
		_, err := parseListNames(R([]byte("Foo[[["), "", nil))
		assert.Error(t, err)
	})
	t.Run("ok", func(t *testing.T) {
		l, err := parseListNames(R([]byte(`[{"name":"bob"}]`), "", nil))
		assert.NoError(t, err)
		if assert.Len(t, l, 1) {
			assert.Equal(t, "bob", l[0])
		}
	})
}

func TestCheckEmptyResponseExpected(t *testing.T) {
	R := newResponse
	t.Run("error", func(t *testing.T) {
		err := R(nil, "", errors.New("bonk")).NoData().End()
		assert.Error(t, err)
		assert.Equal(t, "bonk", err.Error())
	})
	t.Run("statusSet", func(t *testing.T) {
		err := R(nil, "unexpected!", nil).NoData().End()
		assert.Error(t, err)
	})
	t.Run("someJSON", func(t *testing.T) {
		err := R([]byte(`{"trouble": true}`), "", nil).NoData().End()
		assert.Error(t, err)
	})
	t.Run("ok", func(t *testing.T) {
		err := R([]byte{}, "", nil).NoData().End()
		assert.NoError(t, err)
	})
}
<|endoftext|>


<commit_before>// +build !luminous,!mimic

package admin

import (
	"errors"
	"fmt"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var (
	cachedFSAdmin *FSAdmin

	// set debugTrace to true to use tracing in tests
	debugTrace = false
)

func init() {
	dt := os.Getenv("GO_CEPH_TEST_DEBUG_TRACE")
	if dt == "yes" || dt == "true" {
		debugTrace = true
	}
}

// tracingCommander serves two purposes: first, it allows one to trace the
// input and output json when running the tests. It can help with actually
// debugging the tests. Second, it demonstrates the rationale for using an
// interface in FSAdmin. You can layer any sort of debugging, error injection,
// or whatnot between the FSAdmin layer and the RADOS layer.
type tracingCommander struct {
	conn RadosCommander
}

func tracer(c RadosCommander) RadosCommander {
	return &tracingCommander{c}
}

func (t *tracingCommander) MgrCommand(buf [][]byte) ([]byte, string, error) {
	for i := range buf {
		fmt.Println("IN:", string(buf[i]))
	}
	r, s, err := t.conn.MgrCommand(buf)
	fmt.Println("OUT(result):", string(r))
	if s != "" {
		fmt.Println("OUT(status):", s)
	}
	if err != nil {
		fmt.Println("OUT(error):", err.Error())
	}
	return r, s, err
}

func getFSAdmin(t *testing.T) *FSAdmin {
	if cachedFSAdmin != nil {
		return cachedFSAdmin
	}
	fsa, err := New()
	require.NoError(t, err)
	require.NotNil(t, fsa)
	// We steal the connection set up by the New() method and wrap it in an
	// optional tracer.
	c := fsa.conn
	if debugTrace {
		c = tracer(c)
	}
	cachedFSAdmin = NewFromConn(c)
	// We sleep briefly before returning in order to ensure we have a mgr map
	// before we start executing the tests.
	time.Sleep(50 * time.Millisecond)
	return cachedFSAdmin
}

func TestInvalidFSAdmin(t *testing.T) {
	fsa := &FSAdmin{}
	_, _, err := fsa.rawMgrCommand([]byte("FOOBAR!"))
	assert.Error(t, err)
}

type badMarshalType bool

func (badMarshalType) MarshalJSON() ([]byte, error) {
	return nil, errors.New("Zowie! wow")
}

func TestBadMarshal(t *testing.T) {
	fsa := getFSAdmin(t)

	var bad badMarshalType
	_, _, err := fsa.marshalMgrCommand(bad)
	assert.Error(t, err)
}

func TestParseListNames(t *testing.T) {
	t.Run("error", func(t *testing.T) {
		_, err := parseListNames(nil, "", errors.New("bonk"))
		assert.Error(t, err)
		assert.Equal(t, "bonk", err.Error())
	})
	t.Run("statusSet", func(t *testing.T) {
		_, err := parseListNames(nil, "unexpected!", nil)
		assert.Error(t, err)
	})
	t.Run("badJSON", func(t *testing.T) {
		_, err := parseListNames([]byte("Foo[[["), "", nil)
		assert.Error(t, err)
	})
	t.Run("ok", func(t *testing.T) {
		l, err := parseListNames([]byte(`[{"name":"bob"}]`), "", nil)
		assert.NoError(t, err)
		if assert.Len(t, l, 1) {
			assert.Equal(t, "bob", l[0])
		}
	})
}

func TestCheckEmptyResponseExpected(t *testing.T) {
	t.Run("error", func(t *testing.T) {
		err := checkEmptyResponseExpected(nil, "", errors.New("bonk"))
		assert.Error(t, err)
		assert.Equal(t, "bonk", err.Error())
	})
	t.Run("statusSet", func(t *testing.T) {
		err := checkEmptyResponseExpected(nil, "unexpected!", nil)
		assert.Error(t, err)
	})
	t.Run("someJSON", func(t *testing.T) {
		err := checkEmptyResponseExpected([]byte(`{"trouble": true}`), "", nil)
		assert.Error(t, err)
	})
	t.Run("ok", func(t *testing.T) {
		err := checkEmptyResponseExpected([]byte{}, "", nil)
		assert.NoError(t, err)
	})
}
<commit_msg>cephfs admin: use strconv to parse env var content<commit_after>// +build !luminous,!mimic

package admin

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var (
	cachedFSAdmin *FSAdmin

	// set debugTrace to true to use tracing in tests
	debugTrace = false
)

func init() {
	dt := os.Getenv("GO_CEPH_TEST_DEBUG_TRACE")
	if ok, err := strconv.ParseBool(dt); ok && err == nil {
		debugTrace = true
	}
}

// tracingCommander serves two purposes: first, it allows one to trace the
// input and output json when running the tests. It can help with actually
// debugging the tests. Second, it demonstrates the rationale for using an
// interface in FSAdmin. You can layer any sort of debugging, error injection,
// or whatnot between the FSAdmin layer and the RADOS layer.
type tracingCommander struct {
	conn RadosCommander
}

func tracer(c RadosCommander) RadosCommander {
	return &tracingCommander{c}
}

func (t *tracingCommander) MgrCommand(buf [][]byte) ([]byte, string, error) {
	for i := range buf {
		fmt.Println("IN:", string(buf[i]))
	}
	r, s, err := t.conn.MgrCommand(buf)
	fmt.Println("OUT(result):", string(r))
	if s != "" {
		fmt.Println("OUT(status):", s)
	}
	if err != nil {
		fmt.Println("OUT(error):", err.Error())
	}
	return r, s, err
}

func getFSAdmin(t *testing.T) *FSAdmin {
	if cachedFSAdmin != nil {
		return cachedFSAdmin
	}
	fsa, err := New()
	require.NoError(t, err)
	require.NotNil(t, fsa)
	// We steal the connection set up by the New() method and wrap it in an
	// optional tracer.
	c := fsa.conn
	if debugTrace {
		c = tracer(c)
	}
	cachedFSAdmin = NewFromConn(c)
	// We sleep briefly before returning in order to ensure we have a mgr map
	// before we start executing the tests.
	time.Sleep(50 * time.Millisecond)
	return cachedFSAdmin
}

func TestInvalidFSAdmin(t *testing.T) {
	fsa := &FSAdmin{}
	_, _, err := fsa.rawMgrCommand([]byte("FOOBAR!"))
	assert.Error(t, err)
}

type badMarshalType bool

func (badMarshalType) MarshalJSON() ([]byte, error) {
	return nil, errors.New("Zowie! wow")
}

func TestBadMarshal(t *testing.T) {
	fsa := getFSAdmin(t)

	var bad badMarshalType
	_, _, err := fsa.marshalMgrCommand(bad)
	assert.Error(t, err)
}

func TestParseListNames(t *testing.T) {
	t.Run("error", func(t *testing.T) {
		_, err := parseListNames(nil, "", errors.New("bonk"))
		assert.Error(t, err)
		assert.Equal(t, "bonk", err.Error())
	})
	t.Run("statusSet", func(t *testing.T) {
		_, err := parseListNames(nil, "unexpected!", nil)
		assert.Error(t, err)
	})
	t.Run("badJSON", func(t *testing.T) {
		_, err := parseListNames([]byte("Foo[[["), "", nil)
		assert.Error(t, err)
	})
	t.Run("ok", func(t *testing.T) {
		l, err := parseListNames([]byte(`[{"name":"bob"}]`), "", nil)
		assert.NoError(t, err)
		if assert.Len(t, l, 1) {
			assert.Equal(t, "bob", l[0])
		}
	})
}

func TestCheckEmptyResponseExpected(t *testing.T) {
	t.Run("error", func(t *testing.T) {
		err := checkEmptyResponseExpected(nil, "", errors.New("bonk"))
		assert.Error(t, err)
		assert.Equal(t, "bonk", err.Error())
	})
	t.Run("statusSet", func(t *testing.T) {
		err := checkEmptyResponseExpected(nil, "unexpected!", nil)
		assert.Error(t, err)
	})
	t.Run("someJSON", func(t *testing.T) {
		err := checkEmptyResponseExpected([]byte(`{"trouble": true}`), "", nil)
		assert.Error(t, err)
	})
	t.Run("ok", func(t *testing.T) {
		err := checkEmptyResponseExpected([]byte{}, "", nil)
		assert.NoError(t, err)
	})
}
<|endoftext|>


<commit_before>/* -------------------------------------------------------------------------- */
/* Copyright 2002-2020, OpenNebula Project, OpenNebula Systems                */
/*                                                                            */
/* Licensed under the Apache License, Version 2.0 (the "License"); you may    */
/* not use this file except in compliance with the License. You may obtain    */
/* a copy of the License at                                                   */
/*                                                                            */
/* http://www.apache.org/licenses/LICENSE-2.0                                 */
/*                                                                            */
/* Unless required by applicable law or agreed to in writing, software        */
/* distributed under the License is distributed on an "AS IS" BASIS,          */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   */
/* See the License for the specific language governing permissions and        */
/* limitations under the License.                                             */
/*--------------------------------------------------------------------------- */

package acl

import "encoding/xml"

// Pool represents an OpenNebula ACL pool
type Pool struct {
	XMLName xml.Name `xml:"ACL_POOL"`
	ACLs    []ACL    `xml:"ACL"`
}

// ACL represents an OpenNebula ACL
type ACL struct {
	XMLName  xml.Name `xml:"ACL"`
	ID       int      `xml:"ID,omitempty"`
	User     string   `xml:"USER,omitempty"`
	Resource string   `xml:"RESOURCE,omitempty"`
	Rights   string   `xml:"RIGHTS,omitempty"`
	Zone     string   `xml:"ZONE,omitempty"`
	String   string   `xml:"STRING,omitempty"`
}

type Users uint64

const (
	UID        Users = 0x100000000
	GID        Users = 0x200000000
	All        Users = 0x400000000
	ClusterUsr Users = 0x800000000
)

type Resources uint64

const (
	VM             Resources = 0x1000000000
	Host           Resources = 0x2000000000
	Net            Resources = 0x4000000000
	Image          Resources = 0x8000000000
	User           Resources = 0x10000000000
	Template       Resources = 0x20000000000
	Group          Resources = 0x40000000000
	Datastore      Resources = 0x100000000000
	Cluster        Resources = 0x200000000000
	Document       Resources = 0x400000000000
	Zone           Resources = 0x800000000000
	SecGroup       Resources = 0x1000000000000
	Vdc            Resources = 0x2000000000000
	VRouter        Resources = 0x4000000000000
	MarketPlace    Resources = 0x8000000000000
	MarketPlaceApp Resources = 0x10000000000000
	VMGroup        Resources = 0x20000000000000
	VNTemplate     Resources = 0x40000000000000
)

type Rights uint64

const (
	Use    Rights = 0x1 // Auth. to use an object
	Manage Rights = 0x2 // Auth. to perform management actions
	Admin  Rights = 0x4 // Auth. to perform administrative actions
	Create Rights = 0x8 // Auth. to create an object
)
<commit_msg>M #- : goca add ACL Utilities (#4980)<commit_after>/* -------------------------------------------------------------------------- */
/* Copyright 2002-2020, OpenNebula Project, OpenNebula Systems                */
/*                                                                            */
/* Licensed under the Apache License, Version 2.0 (the "License"); you may    */
/* not use this file except in compliance with the License. You may obtain    */
/* a copy of the License at                                                   */
/*                                                                            */
/* http://www.apache.org/licenses/LICENSE-2.0                                 */
/*                                                                            */
/* Unless required by applicable law or agreed to in writing, software        */
/* distributed under the License is distributed on an "AS IS" BASIS,          */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   */
/* See the License for the specific language governing permissions and        */
/* limitations under the License.                                             */
/*--------------------------------------------------------------------------- */

package acl

import (
	"encoding/xml"
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// Pool represents an OpenNebula ACL pool
type Pool struct {
	XMLName xml.Name `xml:"ACL_POOL"`
	ACLs    []ACL    `xml:"ACL"`
}

// ACL represents an OpenNebula ACL
type ACL struct {
	XMLName  xml.Name `xml:"ACL"`
	ID       int      `xml:"ID,omitempty"`
	User     string   `xml:"USER,omitempty"`
	Resource string   `xml:"RESOURCE,omitempty"`
	Rights   string   `xml:"RIGHTS,omitempty"`
	Zone     string   `xml:"ZONE,omitempty"`
	String   string   `xml:"STRING,omitempty"`
}

type Users uint64

const (
	UID        Users = 0x100000000
	GID        Users = 0x200000000
	All        Users = 0x400000000
	ClusterUsr Users = 0x800000000
)

type Resources uint64

const (
	VM             Resources = 0x1000000000
	Host           Resources = 0x2000000000
	Net            Resources = 0x4000000000
	Image          Resources = 0x8000000000
	User           Resources = 0x10000000000
	Template       Resources = 0x20000000000
	Group          Resources = 0x40000000000
	Datastore      Resources = 0x100000000000
	Cluster        Resources = 0x200000000000
	Document       Resources = 0x400000000000
	Zone           Resources = 0x800000000000
	SecGroup       Resources = 0x1000000000000
	Vdc            Resources = 0x2000000000000
	VRouter        Resources = 0x4000000000000
	MarketPlace    Resources = 0x8000000000000
	MarketPlaceApp Resources = 0x10000000000000
	VMGroup        Resources = 0x20000000000000
	VNTemplate     Resources = 0x40000000000000
)

type Rights uint64

const (
	Use    Rights = 0x1 // Auth. to use an object
	Manage Rights = 0x2 // Auth. to perform management actions
	Admin  Rights = 0x4 // Auth. to perform administrative actions
	Create Rights = 0x8 // Auth. to create an object
)

var resourceMap = map[string]Resources{
	"VM":             VM,
	"HOST":           Host,
	"NET":            Net,
	"IMAGE":          Image,
	"USER":           User,
	"TEMPLATE":       Template,
	"GROUP":          Group,
	"DATASTORE":      Datastore,
	"CLUSTER":        Cluster,
	"DOCUMENT":       Document,
	"ZONE":           Zone,
	"SECGROUP":       SecGroup,
	"VDC":            Vdc,
	"VROUTER":        VRouter,
	"MARKETPLACE":    MarketPlace,
	"MARKETPLACEAPP": MarketPlaceApp,
	"VMGROUP":        VMGroup,
	"VNTEMPLATE":     VNTemplate,
}

var rightMap = map[string]Rights{
	"USE":    Use,
	"MANAGE": Manage,
	"ADMIN":  Admin,
	"CREATE": Create,
}

// CalculateIDs Calculate hex from User selector (#0, @0, *, %0)
func CalculateIDs(idString string) (int64, error) {
	match, err := regexp.Match("^([\\#@\\%]\\d+|\\*)$", []byte(idString))
	if err != nil {
		return 0, err
	}
	if !match {
		return 0, fmt.Errorf("ID String %+v malformed", idString)
	}

	var value int64

	// Match by UID
	if strings.HasPrefix(idString, "#") {
		id, err := strconv.Atoi(strings.TrimLeft(idString, "#"))
		if err != nil {
			return 0, err
		}
		value = int64(UID) + int64(id)
	}

	// Match by GID
	if strings.HasPrefix(idString, "@") {
		id, err := strconv.Atoi(strings.TrimLeft(idString, "@"))
		if err != nil {
			return 0, err
		}
		value = int64(GID) + int64(id)
	}

	// Match all
	if strings.HasPrefix(idString, "*") {
		value = int64(All)
	}

	// Match by cluster
	if strings.HasPrefix(idString, "%") {
		id, err := strconv.Atoi(strings.TrimLeft(idString, "%"))
		if err != nil {
			return 0, err
		}
		value = int64(ClusterUsr) + int64(id)
	}

	return value, nil
}

// ParseUsers Converts a string in the form [#<id>, @<id>, *] to a hex. number
func ParseUsers(users string) (string, error) {
	value, err := CalculateIDs(users)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%X", value), err
}

// ParseResources Converts a resources string to a hex. number (e.g. NET+VROUTER/@190)
func ParseResources(resources string) (string, error) {
	var ret int64
	resourceParts := strings.Split(resources, "/")
	if len(resourceParts) != 2 {
		return "", fmt.Errorf("resource '%+v' malformed", resources)
	}

	res := strings.Split(resourceParts[0], "+")
	for _, resource := range res {
		val, ok := resourceMap[strings.ToUpper(resource)]
		if !ok {
			return "", fmt.Errorf("resource '%+v' does not exist", resource)
		}
		ret += int64(val)
	}
	ids, err := CalculateIDs(resourceParts[1])
	if err != nil {
		return "", err
	}
	ret += ids

	return fmt.Sprintf("%x", ret), nil
}

// ParseRights Converts a rights string to a hex. number (MANAGE+ADMIN)
func ParseRights(rights string) (string, error) {
	var ret int64

	rightsParts := strings.Split(rights, "+")
	for _, right := range rightsParts {
		val, ok := rightMap[strings.ToUpper(right)]
		if !ok {
			return "", fmt.Errorf("right '%+v' does not exist", right)
		}
		ret += int64(val)
	}

	return fmt.Sprintf("%x", ret), nil
}

// ParseZone Convert a zone part of the ACL String (#0)
func ParseZone(zone string) (string, error) {
	ids, err := CalculateIDs(zone)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%x", ids), nil
}
<|endoftext|>


<commit_before>package secret

import (
	"fmt"

	"github.com/docker/docker/cli"
	"github.com/docker/docker/cli/command"
	"github.com/spf13/cobra"
	"golang.org/x/net/context"
)

type removeOptions struct {
	names []string
}

func newSecretRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
	return &cobra.Command{
		Use:   "rm SECRET [SECRET...]",
		Short: "Remove one or more secrets",
		Args:  cli.RequiresMinArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			opts := removeOptions{
				names: args,
			}
			return runSecretRemove(dockerCli, opts)
		},
	}
}

func runSecretRemove(dockerCli *command.DockerCli, opts removeOptions) error {
	client := dockerCli.Client()
	ctx := context.Background()

	ids, err := getCliRequestedSecretIDs(ctx, client, opts.names)
	if err != nil {
		return err
	}

	for _, id := range ids {
		if err := client.SecretRemove(ctx, id); err != nil {
			fmt.Fprintf(dockerCli.Out(), "WARN: %s\n", err)
		}

		fmt.Fprintln(dockerCli.Out(), id)
	}

	return nil
}
<commit_msg>change secret remove logic in cli<commit_after>package secret

import (
	"fmt"
	"strings"

	"github.com/docker/docker/cli"
	"github.com/docker/docker/cli/command"
	"github.com/spf13/cobra"
	"golang.org/x/net/context"
)

type removeOptions struct {
	names []string
}

func newSecretRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
	return &cobra.Command{
		Use:   "rm SECRET [SECRET...]",
		Short: "Remove one or more secrets",
		Args:  cli.RequiresMinArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			opts := removeOptions{
				names: args,
			}
			return runSecretRemove(dockerCli, opts)
		},
	}
}

func runSecretRemove(dockerCli *command.DockerCli, opts removeOptions) error {
	client := dockerCli.Client()
	ctx := context.Background()

	ids, err := getCliRequestedSecretIDs(ctx, client, opts.names)
	if err != nil {
		return err
	}

	var errs []string

	for _, id := range ids {
		if err := client.SecretRemove(ctx, id); err != nil {
			errs = append(errs, err.Error())
			continue
		}

		fmt.Fprintln(dockerCli.Out(), id)
	}

	if len(errs) > 0 {
		return fmt.Errorf("%s", strings.Join(errs, "\n"))
	}

	return nil
}
<|endoftext|>


<commit_before>package mock_client

import "github.com/plimble/kuja/client"
import "github.com/stretchr/testify/mock"

import "github.com/plimble/kuja/broker"
import "github.com/plimble/kuja/encoder"

type MockClient struct {
	mock.Mock
}

func NewMockClient() *MockClient {
	return &MockClient{}
}

func (m *MockClient) Broker(b broker.Broker) {
	m.Called(b)
}
func (m *MockClient) Publish(service string, topic string, v interface{}, meta map[string]string) error {
	ret := m.Called(service, topic, v, meta)

	r0 := ret.Error(0)

	return r0
}
func (m *MockClient) Encoder(enc encoder.Encoder) {
	m.Called(enc)
}
func (m *MockClient) AsyncRequests(as []client.AsyncRequest) []client.AsyncResponse {
	ret := m.Called(as)

	var r0 []client.AsyncResponse
	if ret.Get(0) != nil {
		r0 = ret.Get(0).([]client.AsyncResponse)
	}

	return r0
}
func (m *MockClient) DefaultHeader(hdr map[string]string) {
	m.Called(hdr)
}
func (m *MockClient) Request(service string, method string, reqv interface{}, respv interface{}, header map[string]string) (int, error) {
	ret := m.Called(service, method, reqv, respv, header)

	r0 := ret.Get(0).(int)
	r1 := ret.Error(1)

	return r0, r1
}
<commit_msg>update mock<commit_after>package mock_client

import "github.com/plimble/kuja/client"
import "github.com/stretchr/testify/mock"

import "github.com/plimble/kuja/broker"
import "github.com/plimble/kuja/encoder"

type MockClient struct {
	mock.Mock
}

func NewMockClient() *MockClient {
	return &MockClient{}
}

func (m *MockClient) Broker(b broker.Broker) {
	m.Called(b)
}
func (m *MockClient) Publish(topic string, v interface{}, meta map[string]string) error {
	ret := m.Called(topic, v, meta)

	r0 := ret.Error(0)

	return r0
}
func (m *MockClient) Encoder(enc encoder.Encoder) {
	m.Called(enc)
}
func (m *MockClient) AsyncRequests(as []client.AsyncRequest) []client.AsyncResponse {
	ret := m.Called(as)

	var r0 []client.AsyncResponse
	if ret.Get(0) != nil {
		r0 = ret.Get(0).([]client.AsyncResponse)
	}

	return r0
}
func (m *MockClient) DefaultHeader(hdr map[string]string) {
	m.Called(hdr)
}
func (m *MockClient) Request(service string, method string, reqv interface{}, respv interface{}, header map[string]string) (int, error) {
	ret := m.Called(service, method, reqv, respv, header)

	r0 := ret.Get(0).(int)
	r1 := ret.Error(1)

	return r0, r1
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go 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 tls

import (
	"bytes"
	"crypto"
	"crypto/rsa"
	"crypto/subtle"
	"crypto/x509"
	"errors"
	"io"
	"strconv"
)

func (c *Conn) clientHandshake() error {
	finishedHash := newFinishedHash(versionTLS10)

	if c.config == nil {
		c.config = defaultConfig()
	}

	hello := &clientHelloMsg{
		vers:               maxVersion,
		cipherSuites:       c.config.cipherSuites(),
		compressionMethods: []uint8{compressionNone},
		random:             make([]byte, 32),
		ocspStapling:       true,
		serverName:         c.config.ServerName,
		supportedCurves:    []uint16{curveP256, curveP384, curveP521},
		supportedPoints:    []uint8{pointFormatUncompressed},
		nextProtoNeg:       len(c.config.NextProtos) > 0,
	}

	t := uint32(c.config.time().Unix())
	hello.random[0] = byte(t >> 24)
	hello.random[1] = byte(t >> 16)
	hello.random[2] = byte(t >> 8)
	hello.random[3] = byte(t)
	_, err := io.ReadFull(c.config.rand(), hello.random[4:])
	if err != nil {
		c.sendAlert(alertInternalError)
		return errors.New("short read from Rand")
	}

	finishedHash.Write(hello.marshal())
	c.writeRecord(recordTypeHandshake, hello.marshal())

	msg, err := c.readHandshake()
	if err != nil {
		return err
	}
	serverHello, ok := msg.(*serverHelloMsg)
	if !ok {
		return c.sendAlert(alertUnexpectedMessage)
	}
	finishedHash.Write(serverHello.marshal())

	vers, ok := mutualVersion(serverHello.vers)
	if !ok {
		return c.sendAlert(alertProtocolVersion)
	}
	c.vers = vers
	c.haveVers = true

	if serverHello.compressionMethod != compressionNone {
		return c.sendAlert(alertUnexpectedMessage)
	}

	if !hello.nextProtoNeg && serverHello.nextProtoNeg {
		c.sendAlert(alertHandshakeFailure)
		return errors.New("server advertised unrequested NPN")
	}

	suite := mutualCipherSuite(c.config.cipherSuites(), serverHello.cipherSuite)
	if suite == nil {
		return c.sendAlert(alertHandshakeFailure)
	}

	msg, err = c.readHandshake()
	if err != nil {
		return err
	}
	certMsg, ok := msg.(*certificateMsg)
	if !ok || len(certMsg.certificates) == 0 {
		return c.sendAlert(alertUnexpectedMessage)
	}
	finishedHash.Write(certMsg.marshal())

	certs := make([]*x509.Certificate, len(certMsg.certificates))
	for i, asn1Data := range certMsg.certificates {
		cert, err := x509.ParseCertificate(asn1Data)
		if err != nil {
			c.sendAlert(alertBadCertificate)
			return errors.New("failed to parse certificate from server: " + err.Error())
		}
		certs[i] = cert
	}

	if !c.config.InsecureSkipVerify {
		opts := x509.VerifyOptions{
			Roots:         c.config.rootCAs(),
			CurrentTime:   c.config.time(),
			DNSName:       c.config.ServerName,
			Intermediates: x509.NewCertPool(),
		}

		for i, cert := range certs {
			if i == 0 {
				continue
			}
			opts.Intermediates.AddCert(cert)
		}
		c.verifiedChains, err = certs[0].Verify(opts)
		if err != nil {
			c.sendAlert(alertBadCertificate)
			return err
		}
	}

	if _, ok := certs[0].PublicKey.(*rsa.PublicKey); !ok {
		return c.sendAlert(alertUnsupportedCertificate)
	}

	c.peerCertificates = certs

	if serverHello.ocspStapling {
		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
		cs, ok := msg.(*certificateStatusMsg)
		if !ok {
			return c.sendAlert(alertUnexpectedMessage)
		}
		finishedHash.Write(cs.marshal())

		if cs.statusType == statusTypeOCSP {
			c.ocspResponse = cs.response
		}
	}

	msg, err = c.readHandshake()
	if err != nil {
		return err
	}

	keyAgreement := suite.ka()

	skx, ok := msg.(*serverKeyExchangeMsg)
	if ok {
		finishedHash.Write(skx.marshal())
		err = keyAgreement.processServerKeyExchange(c.config, hello, serverHello, certs[0], skx)
		if err != nil {
			c.sendAlert(alertUnexpectedMessage)
			return err
		}

		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
	}

	var certToSend *Certificate
	certReq, ok := msg.(*certificateRequestMsg)
	if ok {
		// RFC 4346 on the certificateAuthorities field:
		// A list of the distinguished names of acceptable certificate
		// authorities. These distinguished names may specify a desired
		// distinguished name for a root CA or for a subordinate CA;
		// thus, this message can be used to describe both known roots
		// and a desired authorization space. If the
		// certificate_authorities list is empty then the client MAY
		// send any certificate of the appropriate
		// ClientCertificateType, unless there is some external
		// arrangement to the contrary.

		finishedHash.Write(certReq.marshal())

		// For now, we only know how to sign challenges with RSA
		rsaAvail := false
		for _, certType := range certReq.certificateTypes {
			if certType == certTypeRSASign {
				rsaAvail = true
				break
			}
		}

		// We need to search our list of client certs for one
		// where SignatureAlgorithm is RSA and the Issuer is in
		// certReq.certificateAuthorities
	findCert:
		for i, cert := range c.config.Certificates {
			if !rsaAvail {
				continue
			}

			leaf := cert.Leaf
			if leaf == nil {
				if leaf, err = x509.ParseCertificate(cert.Certificate[0]); err != nil {
					c.sendAlert(alertInternalError)
					return errors.New("tls: failed to parse client certificate #" + strconv.Itoa(i) + ": " + err.Error())
				}
			}

			if leaf.PublicKeyAlgorithm != x509.RSA {
				continue
			}

			if len(certReq.certificateAuthorities) == 0 {
				// they gave us an empty list, so just take the
				// first RSA cert from c.config.Certificates
				certToSend = &cert
				break
			}

			for _, ca := range certReq.certificateAuthorities {
				if bytes.Equal(leaf.RawIssuer, ca) {
					certToSend = &cert
					break findCert
				}
			}
		}

		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
	}

	shd, ok := msg.(*serverHelloDoneMsg)
	if !ok {
		return c.sendAlert(alertUnexpectedMessage)
	}
	finishedHash.Write(shd.marshal())

	if certToSend != nil {
		certMsg = new(certificateMsg)
		certMsg.certificates = certToSend.Certificate
		finishedHash.Write(certMsg.marshal())
		c.writeRecord(recordTypeHandshake, certMsg.marshal())
	}

	preMasterSecret, ckx, err := keyAgreement.generateClientKeyExchange(c.config, hello, certs[0])
	if err != nil {
		c.sendAlert(alertInternalError)
		return err
	}
	if ckx != nil {
		finishedHash.Write(ckx.marshal())
		c.writeRecord(recordTypeHandshake, ckx.marshal())
	}

	if certToSend != nil {
		certVerify := new(certificateVerifyMsg)
		digest := make([]byte, 0, 36)
		digest = finishedHash.serverMD5.Sum(digest)
		digest = finishedHash.serverSHA1.Sum(digest)
		signed, err := rsa.SignPKCS1v15(c.config.rand(), c.config.Certificates[0].PrivateKey.(*rsa.PrivateKey), crypto.MD5SHA1, digest)
		if err != nil {
			return c.sendAlert(alertInternalError)
		}
		certVerify.signature = signed

		finishedHash.Write(certVerify.marshal())
		c.writeRecord(recordTypeHandshake, certVerify.marshal())
	}

	masterSecret, clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
		keysFromPreMasterSecret(c.vers, preMasterSecret, hello.random, serverHello.random, suite.macLen, suite.keyLen, suite.ivLen)

	clientCipher := suite.cipher(clientKey, clientIV, false /* not for reading */ )
	clientHash := suite.mac(c.vers, clientMAC)
	c.out.prepareCipherSpec(c.vers, clientCipher, clientHash)
	c.writeRecord(recordTypeChangeCipherSpec, []byte{1})

	if serverHello.nextProtoNeg {
		nextProto := new(nextProtoMsg)
		proto, fallback := mutualProtocol(c.config.NextProtos, serverHello.nextProtos)
		nextProto.proto = proto
		c.clientProtocol = proto
		c.clientProtocolFallback = fallback

		finishedHash.Write(nextProto.marshal())
		c.writeRecord(recordTypeHandshake, nextProto.marshal())
	}

	finished := new(finishedMsg)
	finished.verifyData = finishedHash.clientSum(masterSecret)
	finishedHash.Write(finished.marshal())
	c.writeRecord(recordTypeHandshake, finished.marshal())

	serverCipher := suite.cipher(serverKey, serverIV, true /* for reading */ )
	serverHash := suite.mac(c.vers, serverMAC)
	c.in.prepareCipherSpec(c.vers, serverCipher, serverHash)
	c.readRecord(recordTypeChangeCipherSpec)
	if c.err != nil {
		return c.err
	}

	msg, err = c.readHandshake()
	if err != nil {
		return err
	}
	serverFinished, ok := msg.(*finishedMsg)
	if !ok {
		return c.sendAlert(alertUnexpectedMessage)
	}

	verify := finishedHash.serverSum(masterSecret)
	if len(verify) != len(serverFinished.verifyData) ||
		subtle.ConstantTimeCompare(verify, serverFinished.verifyData) != 1 {
		return c.sendAlert(alertHandshakeFailure)
	}

	c.handshakeComplete = true
	c.cipherSuite = suite.id
	return nil
}

// mutualProtocol finds the mutual Next Protocol Negotiation protocol given the
// set of client and server supported protocols. The set of client supported
// protocols must not be empty. It returns the resulting protocol and flag
// indicating if the fallback case was reached.
func mutualProtocol(clientProtos, serverProtos []string) (string, bool) {
	for _, s := range serverProtos {
		for _, c := range clientProtos {
			if s == c {
				return s, false
			}
		}
	}

	return clientProtos[0], true
}
<commit_msg>crypto/tls: better error message when connecting to SSLv3 servers.<commit_after>// Copyright 2009 The Go 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 tls

import (
	"bytes"
	"crypto"
	"crypto/rsa"
	"crypto/subtle"
	"crypto/x509"
	"errors"
	"io"
	"strconv"
)

func (c *Conn) clientHandshake() error {
	finishedHash := newFinishedHash(versionTLS10)

	if c.config == nil {
		c.config = defaultConfig()
	}

	hello := &clientHelloMsg{
		vers:               maxVersion,
		cipherSuites:       c.config.cipherSuites(),
		compressionMethods: []uint8{compressionNone},
		random:             make([]byte, 32),
		ocspStapling:       true,
		serverName:         c.config.ServerName,
		supportedCurves:    []uint16{curveP256, curveP384, curveP521},
		supportedPoints:    []uint8{pointFormatUncompressed},
		nextProtoNeg:       len(c.config.NextProtos) > 0,
	}

	t := uint32(c.config.time().Unix())
	hello.random[0] = byte(t >> 24)
	hello.random[1] = byte(t >> 16)
	hello.random[2] = byte(t >> 8)
	hello.random[3] = byte(t)
	_, err := io.ReadFull(c.config.rand(), hello.random[4:])
	if err != nil {
		c.sendAlert(alertInternalError)
		return errors.New("short read from Rand")
	}

	finishedHash.Write(hello.marshal())
	c.writeRecord(recordTypeHandshake, hello.marshal())

	msg, err := c.readHandshake()
	if err != nil {
		return err
	}
	serverHello, ok := msg.(*serverHelloMsg)
	if !ok {
		return c.sendAlert(alertUnexpectedMessage)
	}
	finishedHash.Write(serverHello.marshal())

	vers, ok := mutualVersion(serverHello.vers)
	if !ok || vers < versionTLS10 {
		// TLS 1.0 is the minimum version supported as a client.
		return c.sendAlert(alertProtocolVersion)
	}
	c.vers = vers
	c.haveVers = true

	if serverHello.compressionMethod != compressionNone {
		return c.sendAlert(alertUnexpectedMessage)
	}

	if !hello.nextProtoNeg && serverHello.nextProtoNeg {
		c.sendAlert(alertHandshakeFailure)
		return errors.New("server advertised unrequested NPN")
	}

	suite := mutualCipherSuite(c.config.cipherSuites(), serverHello.cipherSuite)
	if suite == nil {
		return c.sendAlert(alertHandshakeFailure)
	}

	msg, err = c.readHandshake()
	if err != nil {
		return err
	}
	certMsg, ok := msg.(*certificateMsg)
	if !ok || len(certMsg.certificates) == 0 {
		return c.sendAlert(alertUnexpectedMessage)
	}
	finishedHash.Write(certMsg.marshal())

	certs := make([]*x509.Certificate, len(certMsg.certificates))
	for i, asn1Data := range certMsg.certificates {
		cert, err := x509.ParseCertificate(asn1Data)
		if err != nil {
			c.sendAlert(alertBadCertificate)
			return errors.New("failed to parse certificate from server: " + err.Error())
		}
		certs[i] = cert
	}

	if !c.config.InsecureSkipVerify {
		opts := x509.VerifyOptions{
			Roots:         c.config.rootCAs(),
			CurrentTime:   c.config.time(),
			DNSName:       c.config.ServerName,
			Intermediates: x509.NewCertPool(),
		}

		for i, cert := range certs {
			if i == 0 {
				continue
			}
			opts.Intermediates.AddCert(cert)
		}
		c.verifiedChains, err = certs[0].Verify(opts)
		if err != nil {
			c.sendAlert(alertBadCertificate)
			return err
		}
	}

	if _, ok := certs[0].PublicKey.(*rsa.PublicKey); !ok {
		return c.sendAlert(alertUnsupportedCertificate)
	}

	c.peerCertificates = certs

	if serverHello.ocspStapling {
		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
		cs, ok := msg.(*certificateStatusMsg)
		if !ok {
			return c.sendAlert(alertUnexpectedMessage)
		}
		finishedHash.Write(cs.marshal())

		if cs.statusType == statusTypeOCSP {
			c.ocspResponse = cs.response
		}
	}

	msg, err = c.readHandshake()
	if err != nil {
		return err
	}

	keyAgreement := suite.ka()

	skx, ok := msg.(*serverKeyExchangeMsg)
	if ok {
		finishedHash.Write(skx.marshal())
		err = keyAgreement.processServerKeyExchange(c.config, hello, serverHello, certs[0], skx)
		if err != nil {
			c.sendAlert(alertUnexpectedMessage)
			return err
		}

		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
	}

	var certToSend *Certificate
	certReq, ok := msg.(*certificateRequestMsg)
	if ok {
		// RFC 4346 on the certificateAuthorities field:
		// A list of the distinguished names of acceptable certificate
		// authorities. These distinguished names may specify a desired
		// distinguished name for a root CA or for a subordinate CA;
		// thus, this message can be used to describe both known roots
		// and a desired authorization space. If the
		// certificate_authorities list is empty then the client MAY
		// send any certificate of the appropriate
		// ClientCertificateType, unless there is some external
		// arrangement to the contrary.

		finishedHash.Write(certReq.marshal())

		// For now, we only know how to sign challenges with RSA
		rsaAvail := false
		for _, certType := range certReq.certificateTypes {
			if certType == certTypeRSASign {
				rsaAvail = true
				break
			}
		}

		// We need to search our list of client certs for one
		// where SignatureAlgorithm is RSA and the Issuer is in
		// certReq.certificateAuthorities
	findCert:
		for i, cert := range c.config.Certificates {
			if !rsaAvail {
				continue
			}

			leaf := cert.Leaf
			if leaf == nil {
				if leaf, err = x509.ParseCertificate(cert.Certificate[0]); err != nil {
					c.sendAlert(alertInternalError)
					return errors.New("tls: failed to parse client certificate #" + strconv.Itoa(i) + ": " + err.Error())
				}
			}

			if leaf.PublicKeyAlgorithm != x509.RSA {
				continue
			}

			if len(certReq.certificateAuthorities) == 0 {
				// they gave us an empty list, so just take the
				// first RSA cert from c.config.Certificates
				certToSend = &cert
				break
			}

			for _, ca := range certReq.certificateAuthorities {
				if bytes.Equal(leaf.RawIssuer, ca) {
					certToSend = &cert
					break findCert
				}
			}
		}

		msg, err = c.readHandshake()
		if err != nil {
			return err
		}
	}

	shd, ok := msg.(*serverHelloDoneMsg)
	if !ok {
		return c.sendAlert(alertUnexpectedMessage)
	}
	finishedHash.Write(shd.marshal())

	if certToSend != nil {
		certMsg = new(certificateMsg)
		certMsg.certificates = certToSend.Certificate
		finishedHash.Write(certMsg.marshal())
		c.writeRecord(recordTypeHandshake, certMsg.marshal())
	}

	preMasterSecret, ckx, err := keyAgreement.generateClientKeyExchange(c.config, hello, certs[0])
	if err != nil {
		c.sendAlert(alertInternalError)
		return err
	}
	if ckx != nil {
		finishedHash.Write(ckx.marshal())
		c.writeRecord(recordTypeHandshake, ckx.marshal())
	}

	if certToSend != nil {
		certVerify := new(certificateVerifyMsg)
		digest := make([]byte, 0, 36)
		digest = finishedHash.serverMD5.Sum(digest)
		digest = finishedHash.serverSHA1.Sum(digest)
		signed, err := rsa.SignPKCS1v15(c.config.rand(), c.config.Certificates[0].PrivateKey.(*rsa.PrivateKey), crypto.MD5SHA1, digest)
		if err != nil {
			return c.sendAlert(alertInternalError)
		}
		certVerify.signature = signed

		finishedHash.Write(certVerify.marshal())
		c.writeRecord(recordTypeHandshake, certVerify.marshal())
	}

	masterSecret, clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
		keysFromPreMasterSecret(c.vers, preMasterSecret, hello.random, serverHello.random, suite.macLen, suite.keyLen, suite.ivLen)

	clientCipher := suite.cipher(clientKey, clientIV, false /* not for reading */ )
	clientHash := suite.mac(c.vers, clientMAC)
	c.out.prepareCipherSpec(c.vers, clientCipher, clientHash)
	c.writeRecord(recordTypeChangeCipherSpec, []byte{1})

	if serverHello.nextProtoNeg {
		nextProto := new(nextProtoMsg)
		proto, fallback := mutualProtocol(c.config.NextProtos, serverHello.nextProtos)
		nextProto.proto = proto
		c.clientProtocol = proto
		c.clientProtocolFallback = fallback

		finishedHash.Write(nextProto.marshal())
		c.writeRecord(recordTypeHandshake, nextProto.marshal())
	}

	finished := new(finishedMsg)
	finished.verifyData = finishedHash.clientSum(masterSecret)
	finishedHash.Write(finished.marshal())
	c.writeRecord(recordTypeHandshake, finished.marshal())

	serverCipher := suite.cipher(serverKey, serverIV, true /* for reading */ )
	serverHash := suite.mac(c.vers, serverMAC)
	c.in.prepareCipherSpec(c.vers, serverCipher, serverHash)
	c.readRecord(recordTypeChangeCipherSpec)
	if c.err != nil {
		return c.err
	}

	msg, err = c.readHandshake()
	if err != nil {
		return err
	}
	serverFinished, ok := msg.(*finishedMsg)
	if !ok {
		return c.sendAlert(alertUnexpectedMessage)
	}

	verify := finishedHash.serverSum(masterSecret)
	if len(verify) != len(serverFinished.verifyData) ||
		subtle.ConstantTimeCompare(verify, serverFinished.verifyData) != 1 {
		return c.sendAlert(alertHandshakeFailure)
	}

	c.handshakeComplete = true
	c.cipherSuite = suite.id
	return nil
}

// mutualProtocol finds the mutual Next Protocol Negotiation protocol given the
// set of client and server supported protocols. The set of client supported
// protocols must not be empty. It returns the resulting protocol and flag
// indicating if the fallback case was reached.
func mutualProtocol(clientProtos, serverProtos []string) (string, bool) {
	for _, s := range serverProtos {
		for _, c := range clientProtos {
			if s == c {
				return s, false
			}
		}
	}

	return clientProtos[0], true
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
package timedrift

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"mig"
	"net"
	"os"
	"time"
)

// init is called by the Go runtime at startup. We use this function to
// register the module in a global array of available modules, so the
// agent knows we exist
func init() {
	mig.RegisterModule("timedrift", func() interface{} {
		return new(Runner)
	})
}

// Runner gives access to the exported functions and structs of the module
type Runner struct {
	Parameters params
	Results    results
}

type results struct {
	FoundAnything bool        `json:"foundanything"`
	Success       bool        `json:"success"`
	Elements      checkedtime `json:"elements"`
	Statistics    statistics  `json:"statistics"`
	Errors        []string    `json:"errors"`
}

// a simple parameters structure, the format is arbitrary
type params struct {
	Drift string `json:"drift"`
}

type checkedtime struct {
	HasCheckedDrift bool     `json:"hascheckeddrift"`
	IsWithinDrift   bool     `json:"iswithindrift,omitempty"`
	Drifts          []string `json:"drifts,omitempty"`
	LocalTime       string   `json:"localtime"`
}

type statistics struct {
	ExecTime string     `json:"exectime"`
	NtpStats []ntpstats `json:"ntpstats,omitempty"`
}

type ntpstats struct {
	Host      string    `json:"host"`
	Time      time.Time `json:"time"`
	Latency   string    `json:"latency"`
	Drift     string    `json:"drift"`
	Reachable bool      `json:"reachable"`
}

func (r Runner) ValidateParameters() (err error) {
	if r.Parameters.Drift != "" {
		_, err = time.ParseDuration(r.Parameters.Drift)
	}
	return err
}

func (r Runner) Run(Args []byte) string {
	var (
		stats statistics
		ct    checkedtime
		drift time.Duration
	)
	ct.LocalTime = time.Now().Format(time.RFC3339Nano)
	t1 := time.Now()
	err := json.Unmarshal(Args, &r.Parameters)
	if err != nil {
		r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
		return r.buildResults()
	}
	err = r.ValidateParameters()
	if err != nil {
		r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
		return r.buildResults()
	}
	// if drift is not set, skip the ntp test
	if r.Parameters.Drift == "" {
		r.Results.FoundAnything = true
		goto done
	}
	drift, err = time.ParseDuration(r.Parameters.Drift)
	if err != nil {
		r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
		return r.buildResults()
	}
	// assume host has synched time and set to false if not true
	ct.IsWithinDrift = true
	// attempt to get network time from each of the NTP servers, and exit
	// as soon as we get a valid result from one of them
	for i := 0; i < len(NtpPool); i++ {

		// pick a server between 0 and len of ntppool, somewhat randomly
		ntpsrv := NtpPool[time.Now().Nanosecond()%len(NtpPool)]
		t, lat, err := GetNetworkTime(ntpsrv)
		if err != nil {
			// failed to get network time, log a failure and try another one
			stats.NtpStats = append(stats.NtpStats, ntpstats{
				Host:      ntpsrv,
				Reachable: false,
			})
			continue
		}

		// compare network time to local time
		localtime := time.Now()
		if err != nil {
			r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
			continue
		}
		if localtime.Before(t.Add(-drift)) {
			ct.IsWithinDrift = false
			ct.Drifts = append(ct.Drifts, fmt.Sprintf("Local time is behind ntp host %s by %s", ntpsrv, t.Sub(localtime).String()))
		} else if localtime.After(t.Add(drift)) {
			ct.IsWithinDrift = false
			ct.Drifts = append(ct.Drifts, fmt.Sprintf("Local time is ahead of ntp host %s by %s", ntpsrv, localtime.Sub(t).String()))
		}
		stats.NtpStats = append(stats.NtpStats, ntpstats{
			Host:      ntpsrv,
			Time:      t,
			Latency:   lat,
			Drift:     localtime.Sub(t).String(),
			Reachable: true,
		})
		ct.HasCheckedDrift = true

		// comparison succeeded, exit the loop
		break
	}
	if !ct.IsWithinDrift {
		r.Results.FoundAnything = true
	}
done:
	r.Results.Elements = ct
	stats.ExecTime = time.Now().Sub(t1).String()
	r.Results.Statistics = stats
	return r.buildResults()
}

var NtpPool = [...]string{
	`time.nist.gov`,
	`0.pool.ntp.org`,
	`1.pool.ntp.org`,
	`2.pool.ntp.org`,
	`3.pool.ntp.org`}

// GetNetworkTime queries a given NTP server to obtain the network time
func GetNetworkTime(host string) (t time.Time, latency string, err error) {
	raddr, err := net.ResolveUDPAddr("udp", host+":123")
	if err != nil {
		return
	}
	// NTP request is 48 bytes long, we only set the first byte
	data := make([]byte, 48)
	// Flags: 0x1b (27)
	// 00...... leap indicator (0)
	// ..011... version number (3)
	// .....011 mode: client (3)
	data[0] = 3<<3 | 3

	t1 := time.Now()
	con, err := net.DialUDP("udp", nil, raddr)
	if err != nil {
		return
	}
	defer con.Close()
	// send the request
	_, err = con.Write(data)
	if err != nil {
		return
	}
	// wait up to 5 seconds for the response
	con.SetDeadline(time.Now().Add(5 * time.Second))
	// read up to 48 bytes from the response
	_, err = con.Read(data)
	if err != nil {
		return
	}
	latency = time.Now().Sub(t1).String()
	// Response format (from the RFC)
	//  0                   1                   2                   3
	//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |LI | VN  |Mode |    Stratum     |     Poll      |  Precision   |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                         Root Delay                            |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                         Root Dispersion                       |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                          Reference ID                         |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                     Reference Timestamp (64)                  +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                      Origin Timestamp (64)                    +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                      Receive Timestamp (64)                   +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                      Transmit Timestamp (64)                  +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	var sec, frac uint64
	sec = uint64(data[43]) | uint64(data[42])<<8 | uint64(data[41])<<16 | uint64(data[40])<<24
	frac = uint64(data[47]) | uint64(data[46])<<8 | uint64(data[45])<<16 | uint64(data[44])<<24
	if sec == 0 || frac == 0 {
		err = fmt.Errorf("null response received from NTP host")
		return
	}
	nsec := sec * 1e9
	nsec += (frac * 1e9) >> 32

	t = time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nsec)).Local()

	return
}

// buildResults marshals the results
func (r Runner) buildResults() string {
	if len(r.Results.Errors) == 0 {
		r.Results.Success = true
	}
	jsonOutput, err := json.Marshal(r.Results)
	if err != nil {
		panic(err)
	}
	return string(jsonOutput[:])
}

func (r Runner) PrintResults(rawResults []byte, foundOnly bool) (prints []string, err error) {
	var results results
	err = json.Unmarshal(rawResults, &results)
	if err != nil {
		return
	}
	prints = append(prints, "local time is "+results.Elements.LocalTime)
	if results.Elements.HasCheckedDrift {
		if results.Elements.IsWithinDrift {
			prints = append(prints, "local time is within acceptable drift from NTP servers")
		} else {
			prints = append(prints, "local time is out of sync from NTP servers")
			for _, drift := range results.Elements.Drifts {
				prints = append(prints, drift)
			}
		}
	}
	// stop here if foundOnly is set, we don't want to see errors and stats
	if foundOnly {
		return
	}
	for _, e := range results.Errors {
		prints = append(prints, "error:", e)
	}
	fmt.Println("stat: execution time", results.Statistics.ExecTime)
	for _, ntpstat := range results.Statistics.NtpStats {
		if ntpstat.Reachable {
			prints = append(prints, "stat: "+ntpstat.Host+" responded in "+ntpstat.Latency+" with time "+ntpstat.Time.UTC().String()+". local time drifts by "+ntpstat.Drift)
		} else {
			prints = append(prints, "stat: "+ntpstat.Host+" was unreachable")
		}
	}
	return
}

func printHelp(isCmd bool) {
	dash := ""
	if isCmd {
		dash = "-"
	}
	fmt.Printf(`timedrift returns the local time of a system and, when %sdrift is set,
verifies that local time is within acceptable range of network time by querying NTP servers

%sdrift <duration>	allowed time drift window. a value of "5s" compares local
			time with ntp hosts and returns a drift failure if local
			time is too far out of sync.

If no drift is set, the module only returns local time.
`, dash, dash)
}

func (r Runner) ParamsCreator() (interface{}, error) {
	fmt.Println("initializing netstat parameters creation")
	var err error
	var p params
	printHelp(false)
	scanner := bufio.NewScanner(os.Stdin)
	for {
		fmt.Printf("drift> ")
		scanner.Scan()
		if err := scanner.Err(); err != nil {
			fmt.Println("Invalid input. Try again")
			continue
		}
		input := scanner.Text()
		if input == "help" {
			printHelp(false)
			continue
		}
		if input != "" {
			_, err = time.ParseDuration(input)
			if err != nil {
				fmt.Println("invalid drift duration. try again. ex: drift> 5s")
				continue
			}
		}
		p.Drift = input
		break
	}
	return p, nil
}

func (r Runner) ParamsParser(args []string) (interface{}, error) {
	var (
		err   error
		drift string
		fs    flag.FlagSet
	)
	if len(args) >= 1 && args[0] == "help" {
		printHelp(true)
		return nil, fmt.Errorf("help printed")
	}
	if len(args) == 0 {
		return r.Parameters, nil
	}
	fs.Init("time", flag.ContinueOnError)
	fs.StringVar(&drift, "drift", "", "see help")
	err = fs.Parse(args)
	if err != nil {
		return nil, err
	}
	_, err = time.ParseDuration(drift)
	if err != nil {
		return nil, fmt.Errorf("invalid drift duration. try help.")
	}
	r.Parameters.Drift = drift
	return r.Parameters, r.ValidateParameters()
}
<commit_msg>[minor] set timedrift success to false if no ntp server was reachable<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
package timedrift

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"mig"
	"net"
	"os"
	"time"
)

// init is called by the Go runtime at startup. We use this function to
// register the module in a global array of available modules, so the
// agent knows we exist
func init() {
	mig.RegisterModule("timedrift", func() interface{} {
		return new(Runner)
	})
}

// Runner gives access to the exported functions and structs of the module
type Runner struct {
	Parameters params
	Results    results
}

type results struct {
	FoundAnything bool        `json:"foundanything"`
	Success       bool        `json:"success"`
	Elements      checkedtime `json:"elements"`
	Statistics    statistics  `json:"statistics"`
	Errors        []string    `json:"errors"`
}

// a simple parameters structure, the format is arbitrary
type params struct {
	Drift string `json:"drift"`
}

type checkedtime struct {
	HasCheckedDrift bool     `json:"hascheckeddrift"`
	IsWithinDrift   bool     `json:"iswithindrift,omitempty"`
	Drifts          []string `json:"drifts,omitempty"`
	LocalTime       string   `json:"localtime"`
}

type statistics struct {
	ExecTime string     `json:"exectime"`
	NtpStats []ntpstats `json:"ntpstats,omitempty"`
}

type ntpstats struct {
	Host      string    `json:"host"`
	Time      time.Time `json:"time"`
	Latency   string    `json:"latency"`
	Drift     string    `json:"drift"`
	Reachable bool      `json:"reachable"`
}

func (r Runner) ValidateParameters() (err error) {
	if r.Parameters.Drift != "" {
		_, err = time.ParseDuration(r.Parameters.Drift)
	}
	return err
}

func (r Runner) Run(Args []byte) string {
	var (
		stats statistics
		ct    checkedtime
		drift time.Duration
	)
	ct.LocalTime = time.Now().Format(time.RFC3339Nano)
	t1 := time.Now()
	err := json.Unmarshal(Args, &r.Parameters)
	if err != nil {
		r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
		return r.buildResults()
	}
	err = r.ValidateParameters()
	if err != nil {
		r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
		return r.buildResults()
	}
	// if drift is not set, skip the ntp test
	if r.Parameters.Drift == "" {
		r.Results.FoundAnything = true
		goto done
	}
	drift, err = time.ParseDuration(r.Parameters.Drift)
	if err != nil {
		r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
		return r.buildResults()
	}
	// assume host has synched time and set to false if not true
	ct.IsWithinDrift = true
	// attempt to get network time from each of the NTP servers, and exit
	// as soon as we get a valid result from one of them
	for i := 0; i < len(NtpPool); i++ {

		// pick a server between 0 and len of ntppool, somewhat randomly
		ntpsrv := NtpPool[time.Now().Nanosecond()%len(NtpPool)]
		t, lat, err := GetNetworkTime(ntpsrv)
		if err != nil {
			// failed to get network time, log a failure and try another one
			stats.NtpStats = append(stats.NtpStats, ntpstats{
				Host:      ntpsrv,
				Reachable: false,
			})
			continue
		}

		// compare network time to local time
		localtime := time.Now()
		if err != nil {
			r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", err))
			continue
		}
		if localtime.Before(t.Add(-drift)) {
			ct.IsWithinDrift = false
			ct.Drifts = append(ct.Drifts, fmt.Sprintf("Local time is behind ntp host %s by %s", ntpsrv, t.Sub(localtime).String()))
		} else if localtime.After(t.Add(drift)) {
			ct.IsWithinDrift = false
			ct.Drifts = append(ct.Drifts, fmt.Sprintf("Local time is ahead of ntp host %s by %s", ntpsrv, localtime.Sub(t).String()))
		}
		stats.NtpStats = append(stats.NtpStats, ntpstats{
			Host:      ntpsrv,
			Time:      t,
			Latency:   lat,
			Drift:     localtime.Sub(t).String(),
			Reachable: true,
		})
		ct.HasCheckedDrift = true

		// comparison succeeded, exit the loop
		break
	}
	if !ct.IsWithinDrift {
		r.Results.FoundAnything = true
	}
done:
	r.Results.Elements = ct
	stats.ExecTime = time.Now().Sub(t1).String()
	r.Results.Statistics = stats
	return r.buildResults()
}

var NtpPool = [...]string{
	`time.nist.gov`,
	`0.pool.ntp.org`,
	`1.pool.ntp.org`,
	`2.pool.ntp.org`,
	`3.pool.ntp.org`}

// GetNetworkTime queries a given NTP server to obtain the network time
func GetNetworkTime(host string) (t time.Time, latency string, err error) {
	raddr, err := net.ResolveUDPAddr("udp", host+":123")
	if err != nil {
		return
	}
	// NTP request is 48 bytes long, we only set the first byte
	data := make([]byte, 48)
	// Flags: 0x1b (27)
	// 00...... leap indicator (0)
	// ..011... version number (3)
	// .....011 mode: client (3)
	data[0] = 3<<3 | 3

	t1 := time.Now()
	con, err := net.DialUDP("udp", nil, raddr)
	if err != nil {
		return
	}
	defer con.Close()
	// send the request
	_, err = con.Write(data)
	if err != nil {
		return
	}
	// wait up to 5 seconds for the response
	con.SetDeadline(time.Now().Add(5 * time.Second))
	// read up to 48 bytes from the response
	_, err = con.Read(data)
	if err != nil {
		return
	}
	latency = time.Now().Sub(t1).String()
	// Response format (from the RFC)
	//  0                   1                   2                   3
	//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |LI | VN  |Mode |    Stratum     |     Poll      |  Precision   |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                         Root Delay                            |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                         Root Dispersion                       |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                          Reference ID                         |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                     Reference Timestamp (64)                  +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                      Origin Timestamp (64)                    +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                      Receive Timestamp (64)                   +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	// |                                                               |
	// +                      Transmit Timestamp (64)                  +
	// |                                                               |
	// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	var sec, frac uint64
	sec = uint64(data[43]) | uint64(data[42])<<8 | uint64(data[41])<<16 | uint64(data[40])<<24
	frac = uint64(data[47]) | uint64(data[46])<<8 | uint64(data[45])<<16 | uint64(data[44])<<24
	if sec == 0 || frac == 0 {
		err = fmt.Errorf("null response received from NTP host")
		return
	}
	nsec := sec * 1e9
	nsec += (frac * 1e9) >> 32

	t = time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nsec)).Local()

	return
}

// buildResults marshals the results
func (r Runner) buildResults() string {
	if len(r.Results.Errors) == 0 {
		r.Results.Success = true
	}
	// if was supposed to check drift but hasn't, set success to false
	if r.Parameters.Drift != "" && !r.Results.Elements.HasCheckedDrift {
		r.Results.Success = false
	}
	jsonOutput, err := json.Marshal(r.Results)
	if err != nil {
		panic(err)
	}
	return string(jsonOutput[:])
}

func (r Runner) PrintResults(rawResults []byte, foundOnly bool) (prints []string, err error) {
	var results results
	err = json.Unmarshal(rawResults, &results)
	if err != nil {
		return
	}
	prints = append(prints, "local time is "+results.Elements.LocalTime)
	if results.Elements.HasCheckedDrift {
		if results.Elements.IsWithinDrift {
			prints = append(prints, "local time is within acceptable drift from NTP servers")
		} else {
			prints = append(prints, "local time is out of sync from NTP servers")
			for _, drift := range results.Elements.Drifts {
				prints = append(prints, drift)
			}
		}
	}
	// stop here if foundOnly is set, we don't want to see errors and stats
	if foundOnly {
		return
	}
	for _, e := range results.Errors {
		prints = append(prints, "error:", e)
	}
	fmt.Println("stat: execution time", results.Statistics.ExecTime)
	for _, ntpstat := range results.Statistics.NtpStats {
		if ntpstat.Reachable {
			prints = append(prints, "stat: "+ntpstat.Host+" responded in "+ntpstat.Latency+" with time "+ntpstat.Time.UTC().String()+". local time drifts by "+ntpstat.Drift)
		} else {
			prints = append(prints, "stat: "+ntpstat.Host+" was unreachable")
		}
	}
	if results.Success {
		prints = append(prints, fmt.Sprintf("timedrift module has succeeded"))
	} else {
		prints = append(prints, fmt.Sprintf("timedrift module has failed"))
	}
	return
}

func printHelp(isCmd bool) {
	dash := ""
	if isCmd {
		dash = "-"
	}
	fmt.Printf(`timedrift returns the local time of a system and, when %sdrift is set,
verifies that local time is within acceptable range of network time by querying NTP servers

%sdrift <duration>	allowed time drift window. a value of "5s" compares local
			time with ntp hosts and returns a drift failure if local
			time is too far out of sync.

If no drift is set, the module only returns local time.
`, dash, dash)
}

func (r Runner) ParamsCreator() (interface{}, error) {
	fmt.Println("initializing netstat parameters creation")
	var err error
	var p params
	printHelp(false)
	scanner := bufio.NewScanner(os.Stdin)
	for {
		fmt.Printf("drift> ")
		scanner.Scan()
		if err := scanner.Err(); err != nil {
			fmt.Println("Invalid input. Try again")
			continue
		}
		input := scanner.Text()
		if input == "help" {
			printHelp(false)
			continue
		}
		if input != "" {
			_, err = time.ParseDuration(input)
			if err != nil {
				fmt.Println("invalid drift duration. try again. ex: drift> 5s")
				continue
			}
		}
		p.Drift = input
		break
	}
	return p, nil
}

func (r Runner) ParamsParser(args []string) (interface{}, error) {
	var (
		err   error
		drift string
		fs    flag.FlagSet
	)
	if len(args) >= 1 && args[0] == "help" {
		printHelp(true)
		return nil, fmt.Errorf("help printed")
	}
	if len(args) == 0 {
		return r.Parameters, nil
	}
	fs.Init("time", flag.ContinueOnError)
	fs.StringVar(&drift, "drift", "", "see help")
	err = fs.Parse(args)
	if err != nil {
		return nil, err
	}
	_, err = time.ParseDuration(drift)
	if err != nil {
		return nil, fmt.Errorf("invalid drift duration. try help.")
	}
	r.Parameters.Drift = drift
	return r.Parameters, r.ValidateParameters()
}
<|endoftext|>