<commit_before>package aws

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"strconv"

	"github.com/hashicorp/terraform/helper/schema"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/sqs"
)

var AttributeMap = map[string]string{
	"delay_seconds":              "DelaySeconds",
	"max_message_size":           "MaximumMessageSize",
	"message_retention_seconds":  "MessageRetentionPeriod",
	"receive_wait_time_seconds":  "ReceiveMessageWaitTimeSeconds",
	"visibility_timeout_seconds": "VisibilityTimeout",
	"policy":                     "Policy",
	"redrive_policy":             "RedrivePolicy",
	"arn":                        "QueueArn",
}

// A number of these are marked as computed because if you don't
// provide a value, SQS will provide you with defaults (which are the
// default values specified below)
func resourceAwsSqsQueue() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsSqsQueueCreate,
		Read:   resourceAwsSqsQueueRead,
		Update: resourceAwsSqsQueueUpdate,
		Delete: resourceAwsSqsQueueDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"delay_seconds": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"max_message_size": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"message_retention_seconds": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"receive_wait_time_seconds": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"visibility_timeout_seconds": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"policy": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				StateFunc: func(v interface{}) string {
					s, ok := v.(string)
					if !ok || s == "" {
						return ""
					}
					jsonb := []byte(s)
					buffer := new(bytes.Buffer)
					if err := json.Compact(buffer, jsonb); err != nil {
						log.Printf("[WARN] Error compacting JSON for Policy in SNS Queue")
						return ""
					}
					return buffer.String()
				},
			},
			"redrive_policy": &schema.Schema{
				Type:      schema.TypeString,
				Optional:  true,
				StateFunc: normalizeJson,
			},
			"arn": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsSqsQueueCreate(d *schema.ResourceData, meta interface{}) error {
	sqsconn := meta.(*AWSClient).sqsconn

	name := d.Get("name").(string)

	log.Printf("[DEBUG] SQS queue create: %s", name)

	req := &sqs.CreateQueueInput{
		QueueName: aws.String(name),
	}

	attributes := make(map[string]*string)

	resource := *resourceAwsSqsQueue()

	for k, s := range resource.Schema {
		if attrKey, ok := AttributeMap[k]; ok {
			if value, ok := d.GetOk(k); ok {
				if s.Type == schema.TypeInt {
					attributes[attrKey] = aws.String(strconv.Itoa(value.(int)))
				} else {
					attributes[attrKey] = aws.String(value.(string))
				}
			}

		}
	}

	if len(attributes) > 0 {
		req.Attributes = attributes
	}

	output, err := sqsconn.CreateQueue(req)
	if err != nil {
		return fmt.Errorf("Error creating SQS queue: %s", err)
	}

	d.SetId(*output.QueueUrl)

	return resourceAwsSqsQueueUpdate(d, meta)
}

func resourceAwsSqsQueueUpdate(d *schema.ResourceData, meta interface{}) error {
	sqsconn := meta.(*AWSClient).sqsconn
	attributes := make(map[string]*string)

	resource := *resourceAwsSqsQueue()

	for k, s := range resource.Schema {
		if attrKey, ok := AttributeMap[k]; ok {
			if d.HasChange(k) {
				log.Printf("[DEBUG] Updating %s", attrKey)
				_, n := d.GetChange(k)
				if s.Type == schema.TypeInt {
					attributes[attrKey] = aws.String(strconv.Itoa(n.(int)))
				} else {
					attributes[attrKey] = aws.String(n.(string))
				}
			}
		}
	}

	if len(attributes) > 0 {
		req := &sqs.SetQueueAttributesInput{
			QueueUrl:   aws.String(d.Id()),
			Attributes: attributes,
		}
		sqsconn.SetQueueAttributes(req)
	}

	return resourceAwsSqsQueueRead(d, meta)
}

func resourceAwsSqsQueueRead(d *schema.ResourceData, meta interface{}) error {
	sqsconn := meta.(*AWSClient).sqsconn

	attributeOutput, err := sqsconn.GetQueueAttributes(&sqs.GetQueueAttributesInput{
		QueueUrl:       aws.String(d.Id()),
		AttributeNames: []*string{aws.String("All")},
	})

	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			log.Printf("ERROR Found %s", awsErr.Code())
			if "AWS.SimpleQueueService.NonExistentQueue" == awsErr.Code() {
				d.SetId("")
				log.Printf("[DEBUG] SQS Queue (%s) not found", d.Get("name").(string))
				return nil
			}
		}
		return err
	}

	if attributeOutput.Attributes != nil && len(attributeOutput.Attributes) > 0 {
		attrmap := attributeOutput.Attributes
		resource := *resourceAwsSqsQueue()
		// iKey = internal struct key, oKey = AWS Attribute Map key
		for iKey, oKey := range AttributeMap {
			if attrmap[oKey] != nil {
				if resource.Schema[iKey].Type == schema.TypeInt {
					value, err := strconv.Atoi(*attrmap[oKey])
					if err != nil {
						return err
					}
					d.Set(iKey, value)
					log.Printf("[DEBUG] Reading %s => %s -> %d", iKey, oKey, value)
				} else {
					log.Printf("[DEBUG] Reading %s => %s -> %s", iKey, oKey, *attrmap[oKey])
					d.Set(iKey, *attrmap[oKey])
				}
			}
		}
	}

	return nil
}

func resourceAwsSqsQueueDelete(d *schema.ResourceData, meta interface{}) error {
	sqsconn := meta.(*AWSClient).sqsconn

	log.Printf("[DEBUG] SQS Delete Queue: %s", d.Id())
	_, err := sqsconn.DeleteQueue(&sqs.DeleteQueueInput{
		QueueUrl: aws.String(d.Id()),
	})
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>provider/aws: SQS use raw policy string if compact fails (#6724)<commit_after>package aws

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"strconv"

	"github.com/hashicorp/terraform/helper/schema"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/sqs"
)

var AttributeMap = map[string]string{
	"delay_seconds":              "DelaySeconds",
	"max_message_size":           "MaximumMessageSize",
	"message_retention_seconds":  "MessageRetentionPeriod",
	"receive_wait_time_seconds":  "ReceiveMessageWaitTimeSeconds",
	"visibility_timeout_seconds": "VisibilityTimeout",
	"policy":                     "Policy",
	"redrive_policy":             "RedrivePolicy",
	"arn":                        "QueueArn",
}

// A number of these are marked as computed because if you don't
// provide a value, SQS will provide you with defaults (which are the
// default values specified below)
func resourceAwsSqsQueue() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsSqsQueueCreate,
		Read:   resourceAwsSqsQueueRead,
		Update: resourceAwsSqsQueueUpdate,
		Delete: resourceAwsSqsQueueDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"delay_seconds": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"max_message_size": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"message_retention_seconds": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"receive_wait_time_seconds": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"visibility_timeout_seconds": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"policy": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				StateFunc: func(v interface{}) string {
					s, ok := v.(string)
					if !ok || s == "" {
						return ""
					}
					jsonb := []byte(s)
					buffer := new(bytes.Buffer)
					if err := json.Compact(buffer, jsonb); err != nil {
						log.Printf("[WARN] Error compacting JSON for Policy in SNS Queue, using raw string: %s", err)
						return s
					}
					return buffer.String()
				},
			},
			"redrive_policy": &schema.Schema{
				Type:      schema.TypeString,
				Optional:  true,
				StateFunc: normalizeJson,
			},
			"arn": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsSqsQueueCreate(d *schema.ResourceData, meta interface{}) error {
	sqsconn := meta.(*AWSClient).sqsconn

	name := d.Get("name").(string)

	log.Printf("[DEBUG] SQS queue create: %s", name)

	req := &sqs.CreateQueueInput{
		QueueName: aws.String(name),
	}

	attributes := make(map[string]*string)

	resource := *resourceAwsSqsQueue()

	for k, s := range resource.Schema {
		if attrKey, ok := AttributeMap[k]; ok {
			if value, ok := d.GetOk(k); ok {
				if s.Type == schema.TypeInt {
					attributes[attrKey] = aws.String(strconv.Itoa(value.(int)))
				} else {
					attributes[attrKey] = aws.String(value.(string))
				}
			}

		}
	}

	if len(attributes) > 0 {
		req.Attributes = attributes
	}

	output, err := sqsconn.CreateQueue(req)
	if err != nil {
		return fmt.Errorf("Error creating SQS queue: %s", err)
	}

	d.SetId(*output.QueueUrl)

	return resourceAwsSqsQueueUpdate(d, meta)
}

func resourceAwsSqsQueueUpdate(d *schema.ResourceData, meta interface{}) error {
	sqsconn := meta.(*AWSClient).sqsconn
	attributes := make(map[string]*string)

	resource := *resourceAwsSqsQueue()

	for k, s := range resource.Schema {
		if attrKey, ok := AttributeMap[k]; ok {
			if d.HasChange(k) {
				log.Printf("[DEBUG] Updating %s", attrKey)
				_, n := d.GetChange(k)
				if s.Type == schema.TypeInt {
					attributes[attrKey] = aws.String(strconv.Itoa(n.(int)))
				} else {
					attributes[attrKey] = aws.String(n.(string))
				}
			}
		}
	}

	if len(attributes) > 0 {
		req := &sqs.SetQueueAttributesInput{
			QueueUrl:   aws.String(d.Id()),
			Attributes: attributes,
		}
		sqsconn.SetQueueAttributes(req)
	}

	return resourceAwsSqsQueueRead(d, meta)
}

func resourceAwsSqsQueueRead(d *schema.ResourceData, meta interface{}) error {
	sqsconn := meta.(*AWSClient).sqsconn

	attributeOutput, err := sqsconn.GetQueueAttributes(&sqs.GetQueueAttributesInput{
		QueueUrl:       aws.String(d.Id()),
		AttributeNames: []*string{aws.String("All")},
	})

	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			log.Printf("ERROR Found %s", awsErr.Code())
			if "AWS.SimpleQueueService.NonExistentQueue" == awsErr.Code() {
				d.SetId("")
				log.Printf("[DEBUG] SQS Queue (%s) not found", d.Get("name").(string))
				return nil
			}
		}
		return err
	}

	if attributeOutput.Attributes != nil && len(attributeOutput.Attributes) > 0 {
		attrmap := attributeOutput.Attributes
		resource := *resourceAwsSqsQueue()
		// iKey = internal struct key, oKey = AWS Attribute Map key
		for iKey, oKey := range AttributeMap {
			if attrmap[oKey] != nil {
				if resource.Schema[iKey].Type == schema.TypeInt {
					value, err := strconv.Atoi(*attrmap[oKey])
					if err != nil {
						return err
					}
					d.Set(iKey, value)
					log.Printf("[DEBUG] Reading %s => %s -> %d", iKey, oKey, value)
				} else {
					log.Printf("[DEBUG] Reading %s => %s -> %s", iKey, oKey, *attrmap[oKey])
					d.Set(iKey, *attrmap[oKey])
				}
			}
		}
	}

	return nil
}

func resourceAwsSqsQueueDelete(d *schema.ResourceData, meta interface{}) error {
	sqsconn := meta.(*AWSClient).sqsconn

	log.Printf("[DEBUG] SQS Delete Queue: %s", d.Id())
	_, err := sqsconn.DeleteQueue(&sqs.DeleteQueueInput{
		QueueUrl: aws.String(d.Id()),
	})
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package install

import (
	"log"
	"os"
	"os/user"
)

const script = `#!/usr/bin/env bash
dtags=dtags
declare -a arr=("" "add" "del" "tags" "list" "ls" "completion", "bash-script")
found=0

# check if arg is a single arg command in arr array
for i in "${arr[@]}"; do
    if [[ $i == $1 ]]; then
        found=1
        ${dtags} $@
        false 1
    fi
done

if [[ -d "$(${dtags} $1)" && ${found} -ne 1 ]]; then
    cd "$(${dtags} $1)"
elif [[ ${found} -ne 1 ]]; then
    echo "no directory found for tag [$1]"
    false 1
fi`

func check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}

func WriteFile() (string, error) {

	// get user info
	u, err := user.Current()
	check(err)

	file := u.HomeDir + "/.config/dtags/dt"

	// create file
	f, err := os.Create(file)
	check(err)
	err = os.Chmod(file, 0755)
	check(err)
	defer f.Close()

	// write script to file
	f.Write([]byte(script))

	return script, nil
}
<commit_msg>update bash helper script<commit_after>package install

import (
	"log"
	"os"
	"os/user"
)

const script = `#!/usr/bin/env bash
dtags=dtags
declare -a arr=("" "add" "del" "tags" "list" "ls" "completion", "install")
found=0

# check if arg is a single arg command in arr array
for i in "${arr[@]}"; do
    if [[ $i == $1 ]]; then
        found=1
        ${dtags} $@
        false 1
    fi
done

if [[ -d "$(${dtags} $1)" && ${found} -ne 1 ]]; then
    cd "$(${dtags} $1)"
elif [[ ${found} -ne 1 ]]; then
    echo "no directory found for tag [$1]"
    false 1
fi`

func check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}

func WriteFile() (string, error) {

	// get user info
	u, err := user.Current()
	check(err)

	file := u.HomeDir + "/.config/dtags/dt"

	// create file
	f, err := os.Create(file)
	check(err)
	err = os.Chmod(file, 0755)
	check(err)
	defer f.Close()

	// write script to file
	f.Write([]byte(script))

	return script, nil
}
<|endoftext|>


<commit_before>package smtpd

import (
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/HouzuoGuo/laitos/email"
	"github.com/HouzuoGuo/laitos/env"
	"github.com/HouzuoGuo/laitos/frontend/mailp"
	"github.com/HouzuoGuo/laitos/frontend/smtpd/smtp"
	"github.com/HouzuoGuo/laitos/lalog"
	"github.com/HouzuoGuo/laitos/ratelimit"
	"net"
	"strings"
	"time"
)

const (
	RateLimitIntervalSec  = 10  // Rate limit is calculated at 10 seconds interval
	IOTimeoutSec          = 120 // IO timeout for both read and write operations
	MaxConversationLength = 64  // Only converse up to this number of messages in an SMTP connection
)

// An SMTP daemon that receives mails addressed to its domain name, and optionally forward the received mails to other addresses.
type SMTPD struct {
	ListenAddress string   `json:"ListenAddress"` // Network address to listen to, e.g. 0.0.0.0 for all network interfaces.
	ListenPort    int      `json:"ListenPort"`    // Port number to listen on
	TLSCertPath   string   `json:"TLSCertPath"`   // (Optional) serve StartTLS via this certificate
	TLSKeyPath    string   `json:"TLSKeyPath"`    // (Optional) serve StartTLS via this certificte (key)
	PerIPLimit    int      `json:"PerIPLimit"`    // How many times in 10 seconds interval an IP may deliver an email to this server
	ForwardTo     []string `json:"ForwardTo"`     // Forward received mails to these addresses

	ForwardMailer email.Mailer `json:"-"` // Use this mailer to forward arrived mails
	SMTPConfig    smtp.Config  `json:"-"` // SMTP processor configuration
	MyPublicIP    string       `json:"-"` // My public IP address as discovered by external services

	Listener       net.Listener    `json:"-"` // Once daemon is started, this is its TCP listener.
	TLSCertificate tls.Certificate `json:"-"` // TLS certificate read from the certificate and key files

	MailProcessor *mailp.MailProcessor `json:"-"` // Process feature commands from incoming mails
	RateLimit     *ratelimit.RateLimit `json:"-"` // Rate limit counter per IP address
	Logger        lalog.Logger         `json:"-"` // Logger
}

// Check configuration and initialise internal states.
func (smtpd *SMTPD) Initialise() error {
	if !smtpd.MailProcessor.ReplyMailer.IsConfigured() {
		return errors.New("SMTPD.Initialise: mail processor's reply mailer must be configured")
	}
	if smtpd.ListenAddress == "" {
		return errors.New("SMTPD.Initialise: listen address must not be empty")
	}
	if smtpd.ListenPort < 1 {
		return errors.New("SMTPD.Initialise: listen port must be greater than 0")
	}
	if smtpd.ForwardTo == nil || len(smtpd.ForwardTo) == 0 || !smtpd.ForwardMailer.IsConfigured() {
		return errors.New("SMTPD.Initialise: the server is not useful if forward addresses/forward mailer are not configured")
	}
	if smtpd.TLSCertPath != "" || smtpd.TLSKeyPath != "" {
		if smtpd.TLSCertPath == "" || smtpd.TLSKeyPath == "" {
			return errors.New("SMTPD.Initialise: if TLS is to be enabled, both TLS certificate and key path must be present.")
		}
		var err error
		smtpd.TLSCertificate, err = tls.LoadX509KeyPair(smtpd.TLSCertPath, smtpd.TLSKeyPath)
		if err != nil {
			return fmt.Errorf("SMTPD.Initialise: failed to read TLS certificate - %v", err)
		}
	}
	// Initialise SMTP processor configuration
	smtpd.MyPublicIP = env.GetPublicIP()
	if smtpd.MyPublicIP == "" {
		// Not a fatal error
		smtpd.Logger.Printf("Initialise", "", nil, "unable to determine public IP address")
	}
	smtpd.SMTPConfig = smtp.Config{
		Limits: &smtp.Limits{
			MsgSize:   2 * 1024 * 1024,            // Accept mails up to 2 MB large
			IOTimeout: IOTimeoutSec * time.Second, // IO timeout is a reasonable minute
			BadCmds:   64,                         // Abort connection after consecutive bad commands
		},
		ServerName: smtpd.MyPublicIP,
	}
	if smtpd.TLSCertPath != "" {
		smtpd.SMTPConfig.TLSConfig = &tls.Config{Certificates: []tls.Certificate{smtpd.TLSCertificate}}
	}
	smtpd.RateLimit = &ratelimit.RateLimit{
		MaxCount: smtpd.PerIPLimit,
		UnitSecs: RateLimitIntervalSec,
		Logger:   smtpd.Logger,
	}
	smtpd.RateLimit.Initialise()
	// Do not allow forward to this daemon itself
	if (strings.HasPrefix(smtpd.ForwardMailer.MTAHost, "127.") || smtpd.ForwardMailer.MTAHost == smtpd.MyPublicIP) &&
		smtpd.ForwardMailer.MTAPort == smtpd.ListenPort {
		return errors.New("SMTPD.Initialise: forward MTA must not be myself")
	}
	// Do not allow mail processor to reply to this daemon itself
	if (strings.HasPrefix(smtpd.MailProcessor.ReplyMailer.MTAHost, "127.") || smtpd.MailProcessor.ReplyMailer.MTAHost == smtpd.MyPublicIP) &&
		smtpd.MailProcessor.ReplyMailer.MTAPort == smtpd.ListenPort {
		return errors.New("SMTPD.Initialise: mail processor's reply MTA must not be myself")
	}
	return nil
}

// Unconditionally forward the mail to forward addresses, then process feature commands if they are found.
func (smtpd *SMTPD) ProcessMail(fromAddr, mailBody string) {
	bodyBytes := []byte(mailBody)
	// Forward the mail
	if err := smtpd.ForwardMailer.SendRaw(smtpd.ForwardMailer.MailFrom, bodyBytes, smtpd.ForwardTo...); err == nil {
		smtpd.Logger.Printf("ProcessMail", fromAddr, nil, "successfully forwarded mail to %v", smtpd.ForwardTo)
	} else {
		smtpd.Logger.Printf("ProcessMail", fromAddr, err, "failed to forward email")
	}
	// Run feature command from mail body
	if err := smtpd.MailProcessor.Process(bodyBytes, smtpd.ForwardTo...); err != nil {
		smtpd.Logger.Printf("ProcessMail", fromAddr, err, "failed to process feature command")
	}
}

// Converse with SMTP client to retrieve mail, then immediately process the retrieved mail. Finally close the connection.
func (smtpd *SMTPD) ServeConn(clientConn net.Conn) {
	defer clientConn.Close()
	clientIP := clientConn.RemoteAddr().String()[:strings.LastIndexByte(clientConn.RemoteAddr().String(), ':')]
	smtpd.Logger.Printf("ServeConn", clientIP, nil, "connected")
	var numConversations int
	var finishedNormally bool
	var finishReason string
	// SMTP conversation will tell from/to addresses and mail mailBody
	var fromAddr, mailBody string
	toAddrs := make([]string, 0, 4)
	smtpConn := smtp.NewConn(clientConn, smtpd.SMTPConfig, nil)
	rateLimitOK := smtpd.RateLimit.Add(clientIP, true)
	for ; numConversations < MaxConversationLength; numConversations++ {
		ev := smtpConn.Next()
		// Politely reject the mail if rate is exceeded
		if !rateLimitOK {
			smtpConn.ReplyRateExceeded()
			return
		}
		// Converse with the client to retrieve mail
		switch ev.What {
		case smtp.DONE:
			finishReason = "finished normally"
			finishedNormally = true
			goto conversationDone
		case smtp.ABORT:
			finishReason = "aborted"
			goto conversationDone
		case smtp.TLSERROR:
			finishReason = "TLS error"
			goto conversationDone
		case smtp.COMMAND:
			switch ev.Cmd {
			case smtp.MAILFROM:
				fromAddr = ev.Arg
			case smtp.RCPTTO:
				toAddrs = append(toAddrs, ev.Arg)
			}
		case smtp.GOTDATA:
			mailBody = ev.Arg
		}
	}
conversationDone:
	if finishedNormally {
		smtpd.Logger.Printf("ServeConn", clientIP, nil, "got a mail from \"%s\" addressed to %v", fromAddr, toAddrs)
		// Forward the mail to forward-recipients, hence the original To-Addresses are not relevant.
		smtpd.ProcessMail(fromAddr, mailBody)
	}
	smtpd.Logger.Printf("ServeConn", clientIP, nil, "%s after %d conversations", finishReason, numConversations)
}

/*
You may call this function only after having called Initialise()!
Start SMTP daemon and block until daemon is told to stop.
*/
func (smtpd *SMTPD) StartAndBlock() (err error) {
	smtpd.Logger.Printf("StartAndBlock", "", nil, "going to listen for connections")
	smtpd.Listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", smtpd.ListenAddress, smtpd.ListenPort))
	if err != nil {
		return fmt.Errorf("SMTPD.StartAndBlock: failed to listen on %s:%d - %v", smtpd.ListenAddress, smtpd.ListenPort, err)
	}
	for {
		clientConn, err := smtpd.Listener.Accept()
		if err != nil {
			// Listener is told to stop
			if strings.Contains(err.Error(), "closed") {
				return nil
			} else {
				return fmt.Errorf("SMTPD.StartAndBlock: failed to accept new connection - %v", err)
			}
		}
		go smtpd.ServeConn(clientConn)
	}
	return nil
}

// If SMTP daemon has started (i.e. listener is set), close the listener so that its connection loop will terminate.
func (smtpd *SMTPD) Stop() {
	if smtpd.Listener != nil {
		if err := smtpd.Listener.Close(); err != nil {
			smtpd.Logger.Printf("Stop", "", err, "failed to close listener")
		}
	}
}
<commit_msg>do not process a mail if no addresses are given<commit_after>package smtpd

import (
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/HouzuoGuo/laitos/email"
	"github.com/HouzuoGuo/laitos/env"
	"github.com/HouzuoGuo/laitos/frontend/mailp"
	"github.com/HouzuoGuo/laitos/frontend/smtpd/smtp"
	"github.com/HouzuoGuo/laitos/lalog"
	"github.com/HouzuoGuo/laitos/ratelimit"
	"net"
	"strings"
	"time"
)

const (
	RateLimitIntervalSec  = 10  // Rate limit is calculated at 10 seconds interval
	IOTimeoutSec          = 120 // IO timeout for both read and write operations
	MaxConversationLength = 64  // Only converse up to this number of messages in an SMTP connection
)

// An SMTP daemon that receives mails addressed to its domain name, and optionally forward the received mails to other addresses.
type SMTPD struct {
	ListenAddress string   `json:"ListenAddress"` // Network address to listen to, e.g. 0.0.0.0 for all network interfaces.
	ListenPort    int      `json:"ListenPort"`    // Port number to listen on
	TLSCertPath   string   `json:"TLSCertPath"`   // (Optional) serve StartTLS via this certificate
	TLSKeyPath    string   `json:"TLSKeyPath"`    // (Optional) serve StartTLS via this certificte (key)
	PerIPLimit    int      `json:"PerIPLimit"`    // How many times in 10 seconds interval an IP may deliver an email to this server
	ForwardTo     []string `json:"ForwardTo"`     // Forward received mails to these addresses

	ForwardMailer email.Mailer `json:"-"` // Use this mailer to forward arrived mails
	SMTPConfig    smtp.Config  `json:"-"` // SMTP processor configuration
	MyPublicIP    string       `json:"-"` // My public IP address as discovered by external services

	Listener       net.Listener    `json:"-"` // Once daemon is started, this is its TCP listener.
	TLSCertificate tls.Certificate `json:"-"` // TLS certificate read from the certificate and key files

	MailProcessor *mailp.MailProcessor `json:"-"` // Process feature commands from incoming mails
	RateLimit     *ratelimit.RateLimit `json:"-"` // Rate limit counter per IP address
	Logger        lalog.Logger         `json:"-"` // Logger
}

// Check configuration and initialise internal states.
func (smtpd *SMTPD) Initialise() error {
	if !smtpd.MailProcessor.ReplyMailer.IsConfigured() {
		return errors.New("SMTPD.Initialise: mail processor's reply mailer must be configured")
	}
	if smtpd.ListenAddress == "" {
		return errors.New("SMTPD.Initialise: listen address must not be empty")
	}
	if smtpd.ListenPort < 1 {
		return errors.New("SMTPD.Initialise: listen port must be greater than 0")
	}
	if smtpd.ForwardTo == nil || len(smtpd.ForwardTo) == 0 || !smtpd.ForwardMailer.IsConfigured() {
		return errors.New("SMTPD.Initialise: the server is not useful if forward addresses/forward mailer are not configured")
	}
	if smtpd.TLSCertPath != "" || smtpd.TLSKeyPath != "" {
		if smtpd.TLSCertPath == "" || smtpd.TLSKeyPath == "" {
			return errors.New("SMTPD.Initialise: if TLS is to be enabled, both TLS certificate and key path must be present.")
		}
		var err error
		smtpd.TLSCertificate, err = tls.LoadX509KeyPair(smtpd.TLSCertPath, smtpd.TLSKeyPath)
		if err != nil {
			return fmt.Errorf("SMTPD.Initialise: failed to read TLS certificate - %v", err)
		}
	}
	// Initialise SMTP processor configuration
	smtpd.MyPublicIP = env.GetPublicIP()
	if smtpd.MyPublicIP == "" {
		// Not a fatal error
		smtpd.Logger.Printf("Initialise", "", nil, "unable to determine public IP address")
	}
	smtpd.SMTPConfig = smtp.Config{
		Limits: &smtp.Limits{
			MsgSize:   2 * 1024 * 1024,            // Accept mails up to 2 MB large
			IOTimeout: IOTimeoutSec * time.Second, // IO timeout is a reasonable minute
			BadCmds:   64,                         // Abort connection after consecutive bad commands
		},
		ServerName: smtpd.MyPublicIP,
	}
	if smtpd.TLSCertPath != "" {
		smtpd.SMTPConfig.TLSConfig = &tls.Config{Certificates: []tls.Certificate{smtpd.TLSCertificate}}
	}
	smtpd.RateLimit = &ratelimit.RateLimit{
		MaxCount: smtpd.PerIPLimit,
		UnitSecs: RateLimitIntervalSec,
		Logger:   smtpd.Logger,
	}
	smtpd.RateLimit.Initialise()
	// Do not allow forward to this daemon itself
	if (strings.HasPrefix(smtpd.ForwardMailer.MTAHost, "127.") || smtpd.ForwardMailer.MTAHost == smtpd.MyPublicIP) &&
		smtpd.ForwardMailer.MTAPort == smtpd.ListenPort {
		return errors.New("SMTPD.Initialise: forward MTA must not be myself")
	}
	// Do not allow mail processor to reply to this daemon itself
	if (strings.HasPrefix(smtpd.MailProcessor.ReplyMailer.MTAHost, "127.") || smtpd.MailProcessor.ReplyMailer.MTAHost == smtpd.MyPublicIP) &&
		smtpd.MailProcessor.ReplyMailer.MTAPort == smtpd.ListenPort {
		return errors.New("SMTPD.Initialise: mail processor's reply MTA must not be myself")
	}
	return nil
}

// Unconditionally forward the mail to forward addresses, then process feature commands if they are found.
func (smtpd *SMTPD) ProcessMail(fromAddr, mailBody string) {
	bodyBytes := []byte(mailBody)
	// Forward the mail
	if err := smtpd.ForwardMailer.SendRaw(smtpd.ForwardMailer.MailFrom, bodyBytes, smtpd.ForwardTo...); err == nil {
		smtpd.Logger.Printf("ProcessMail", fromAddr, nil, "successfully forwarded mail to %v", smtpd.ForwardTo)
	} else {
		smtpd.Logger.Printf("ProcessMail", fromAddr, err, "failed to forward email")
	}
	// Run feature command from mail body
	if err := smtpd.MailProcessor.Process(bodyBytes, smtpd.ForwardTo...); err != nil {
		smtpd.Logger.Printf("ProcessMail", fromAddr, err, "failed to process feature command")
	}
}

// Converse with SMTP client to retrieve mail, then immediately process the retrieved mail. Finally close the connection.
func (smtpd *SMTPD) ServeConn(clientConn net.Conn) {
	defer clientConn.Close()
	clientIP := clientConn.RemoteAddr().String()[:strings.LastIndexByte(clientConn.RemoteAddr().String(), ':')]
	smtpd.Logger.Printf("ServeConn", clientIP, nil, "connected")
	var numConversations int
	var finishedNormally bool
	var finishReason string
	// SMTP conversation will tell from/to addresses and mail mailBody
	var fromAddr, mailBody string
	toAddrs := make([]string, 0, 4)
	smtpConn := smtp.NewConn(clientConn, smtpd.SMTPConfig, nil)
	rateLimitOK := smtpd.RateLimit.Add(clientIP, true)
	for ; numConversations < MaxConversationLength; numConversations++ {
		ev := smtpConn.Next()
		// Politely reject the mail if rate is exceeded
		if !rateLimitOK {
			smtpConn.ReplyRateExceeded()
			return
		}
		// Converse with the client to retrieve mail
		switch ev.What {
		case smtp.DONE:
			finishReason = "finished normally"
			finishedNormally = true
			goto conversationDone
		case smtp.ABORT:
			finishReason = "aborted"
			goto conversationDone
		case smtp.TLSERROR:
			finishReason = "TLS error"
			goto conversationDone
		case smtp.COMMAND:
			switch ev.Cmd {
			case smtp.MAILFROM:
				fromAddr = ev.Arg
			case smtp.RCPTTO:
				toAddrs = append(toAddrs, ev.Arg)
			}
		case smtp.GOTDATA:
			mailBody = ev.Arg
		}
	}
conversationDone:
	if fromAddr == "" || len(toAddrs) == 0 {
		finishReason = "discarded malformed mail"
	} else if finishedNormally {
		smtpd.Logger.Printf("ServeConn", clientIP, nil, "got a mail from \"%s\" addressed to %v", fromAddr, toAddrs)
		// Forward the mail to forward-recipients, hence the original To-Addresses are not relevant.
		smtpd.ProcessMail(fromAddr, mailBody)
	}
	smtpd.Logger.Printf("ServeConn", clientIP, nil, "%s after %d conversations", finishReason, numConversations)
}

/*
You may call this function only after having called Initialise()!
Start SMTP daemon and block until daemon is told to stop.
*/
func (smtpd *SMTPD) StartAndBlock() (err error) {
	smtpd.Logger.Printf("StartAndBlock", "", nil, "going to listen for connections")
	smtpd.Listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", smtpd.ListenAddress, smtpd.ListenPort))
	if err != nil {
		return fmt.Errorf("SMTPD.StartAndBlock: failed to listen on %s:%d - %v", smtpd.ListenAddress, smtpd.ListenPort, err)
	}
	for {
		clientConn, err := smtpd.Listener.Accept()
		if err != nil {
			// Listener is told to stop
			if strings.Contains(err.Error(), "closed") {
				return nil
			} else {
				return fmt.Errorf("SMTPD.StartAndBlock: failed to accept new connection - %v", err)
			}
		}
		go smtpd.ServeConn(clientConn)
	}
	return nil
}

// If SMTP daemon has started (i.e. listener is set), close the listener so that its connection loop will terminate.
func (smtpd *SMTPD) Stop() {
	if smtpd.Listener != nil {
		if err := smtpd.Listener.Close(); err != nil {
			smtpd.Logger.Printf("Stop", "", err, "failed to close listener")
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"bytes"
	"flag"
	"fmt"
	"go/build"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
)

const testdata = "testdata"

var (
	name     = flag.String("name", "", "name of the test to run")
	warnsRe  = regexp.MustCompile(`^WARN (.*)\n?$`)
	singleRe = regexp.MustCompile(`([^ ]*) can be ([^ ]*)`)
)

func goFiles(p string) ([]string, error) {
	if strings.HasSuffix(p, ".go") {
		return []string{p}, nil
	}
	dirs, err := recurse(p)
	if err != nil {
		return nil, err
	}
	var paths []string
	for _, dir := range dirs {
		files, err := ioutil.ReadDir(dir)
		if err != nil {
			return nil, err
		}
		for _, file := range files {
			if file.IsDir() {
				continue
			}
			paths = append(paths, filepath.Join(dir, file.Name()))
		}
	}
	return paths, nil
}

func wantedWarnings(t *testing.T, p string) []Warn {
	paths, err := goFiles(p)
	if err != nil {
		t.Fatal(err)
	}
	fset := token.NewFileSet()
	var warns []Warn
	for _, path := range paths {
		src, err := os.Open(path)
		if err != nil {
			t.Fatal(err)
		}
		defer src.Close()
		f, err := parser.ParseFile(fset, path, src, parser.ParseComments)
		if err != nil {
			t.Fatal(err)
		}
		for _, group := range f.Comments {
			m := warnsRe.FindStringSubmatch(group.Text())
			if m == nil {
				continue
			}
			for _, m := range singleRe.FindAllStringSubmatch(m[1], -1) {
				warns = append(warns, Warn{
					Pos: token.Position{
						Filename: path,
					},
					Name: m[1],
					Type: m[2],
				})
			}
		}
	}
	return warns
}

func doTest(t *testing.T, p string) {
	warns := wantedWarnings(t, p)
	doTestWarns(t, p, warns, p)
}

func warnsEqual(got, want []Warn) bool {
	if len(got) != len(want) {
		return false
	}
	for i, w1 := range got {
		w2 := want[i]
		if w1.Name != w2.Name {
			return false
		}
		if w1.Type != w2.Type {
			return false
		}
	}
	return true
}

func warnsJoin(warns []Warn) string {
	var b bytes.Buffer
	for _, warn := range warns {
		fmt.Fprintln(&b, warn.String())
	}
	return b.String()
}

func doTestWarns(t *testing.T, name string, exp []Warn, args ...string) {
	got, err := CheckArgsList(args)
	if err != nil {
		t.Fatalf("Did not want error in %s:\n%v", name, err)
	}
	if !warnsEqual(exp, got) {
		t.Fatalf("Output mismatch in %s:\nExpected:\n%sGot:\n%s",
			name, warnsJoin(exp), warnsJoin(got))
	}
}

func endNewline(s string) string {
	if strings.HasSuffix(s, "\n") {
		return s
	}
	return s + "\n"
}

func doTestString(t *testing.T, name, exp string, args ...string) {
	var b bytes.Buffer
	switch len(args) {
	case 0:
		args = []string{name}
	case 1:
		if args[0] == "" {
			args = nil
		}
	}
	err := CheckArgsOutput(args, &b, true)
	if err != nil {
		t.Fatalf("Did not want error in %s:\n%v", name, err)
	}
	exp = endNewline(exp)
	got := b.String()
	if exp != got {
		t.Fatalf("Output mismatch in %s:\nExpected:\n%sGot:\n%s",
			name, exp, got)
	}
}

func inputPaths(t *testing.T, glob string) []string {
	all, err := filepath.Glob(glob)
	if err != nil {
		t.Fatal(err)
	}
	var paths []string
	for _, p := range all {
		paths = append(paths, p)
	}
	return paths
}

func chdirUndo(t *testing.T, d string) func() {
	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Chdir(d); err != nil {
		t.Fatal(err)
	}
	return func() {
		if err := os.Chdir(wd); err != nil {
			t.Fatal(err)
		}
	}
}

func runFileTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "files")()
	if len(paths) == 0 {
		paths = inputPaths(t, "*")
	}
	for _, p := range paths {
		doTest(t, p)
	}
}

func runLocalTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "local")()
	if len(paths) == 0 {
		for _, p := range inputPaths(t, "*") {
			paths = append(paths, "./"+p+"/...")
		}
		// non-recursive
		paths = append(paths, "./single")
	}
	for _, p := range paths {
		doTest(t, p)
	}
	doTestString(t, "no-args", ".", "")
}

func runNonlocalTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "src")()
	if len(paths) > 0 {
		for _, p := range paths {
			doTest(t, p)
		}
		return
	}
	paths = inputPaths(t, "*")
	for _, p := range paths {
		doTest(t, p+"/...")
	}
	// local recursive
	doTest(t, "./nested/...")
	// non-recursive
	doTest(t, "single")
	// make sure we don't miss a package's imports
	doTestString(t, "grab-import", "grab-import\ngrab-import/use.go:27:15: s can be def2.Fooer")
	defer chdirUndo(t, "nested/pkg")()
	// relative paths
	doTestString(t, "rel-path", "nested/pkg\nsimple.go:12:17: rc can be Closer", "./...")
}

func TestMain(m *testing.M) {
	flag.Parse()
	if err := os.Chdir(testdata); err != nil {
		panic(err)
	}
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	build.Default.GOPATH = wd
	os.Exit(m.Run())
}

func TestCheckWarnings(t *testing.T) {
	switch {
	case *name == "":
	case strings.HasSuffix(*name, ".go"):
		runFileTests(t, *name)
		return
	case strings.HasPrefix(*name, "./"):
		runLocalTests(t, *name)
		return
	default:
		runNonlocalTests(t, *name)
		return
	}
	runFileTests(t)
	runLocalTests(t)
	runNonlocalTests(t)
}

func doTestError(t *testing.T, name, exp string, args ...string) {
	switch len(args) {
	case 0:
		args = []string{name}
	case 1:
		if args[0] == "" {
			args = nil
		}
	}
	err := CheckArgsOutput(args, ioutil.Discard, false)
	if err == nil {
		t.Fatalf("Wanted error in %s, but none found.", name)
	}
	got := err.Error()
	if exp != got {
		t.Fatalf("Error mismatch in %s:\nExpected:\n%sGot:\n%s",
			name, exp, got)
	}
}

func TestErrors(t *testing.T) {
	// non-existent Go file
	doTestError(t, "missing.go", "open missing.go: no such file or directory")
	// local non-existent non-recursive
	doTestError(t, "./missing", "no initial packages were loaded")
	// non-local non-existent non-recursive
	doTestError(t, "missing", "no initial packages were loaded")
	// local non-existent recursive
	doTestError(t, "./missing-rec/...", "lstat ./missing-rec: no such file or directory")
	// Mixing Go files and dirs
	doTestError(t, "wrong-args", "named files must be .go files: bar", "foo.go", "bar")
}

func TestExtraArg(t *testing.T) {
	err := CheckArgsOutput([]string{"single", "--", "foo", "bar"}, ioutil.Discard, false)
	got := err.Error()
	want := "unwanted extra args: [foo bar]"
	if got != want {
		t.Fatalf("Error mismatch:\nExpected:\n%sGot:\n%s", want, got)
	}
}
<commit_msg>Simplify inputPaths<commit_after>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"bytes"
	"flag"
	"fmt"
	"go/build"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
)

const testdata = "testdata"

var (
	name     = flag.String("name", "", "name of the test to run")
	warnsRe  = regexp.MustCompile(`^WARN (.*)\n?$`)
	singleRe = regexp.MustCompile(`([^ ]*) can be ([^ ]*)`)
)

func goFiles(p string) ([]string, error) {
	if strings.HasSuffix(p, ".go") {
		return []string{p}, nil
	}
	dirs, err := recurse(p)
	if err != nil {
		return nil, err
	}
	var paths []string
	for _, dir := range dirs {
		files, err := ioutil.ReadDir(dir)
		if err != nil {
			return nil, err
		}
		for _, file := range files {
			if file.IsDir() {
				continue
			}
			paths = append(paths, filepath.Join(dir, file.Name()))
		}
	}
	return paths, nil
}

func wantedWarnings(t *testing.T, p string) []Warn {
	paths, err := goFiles(p)
	if err != nil {
		t.Fatal(err)
	}
	fset := token.NewFileSet()
	var warns []Warn
	for _, path := range paths {
		src, err := os.Open(path)
		if err != nil {
			t.Fatal(err)
		}
		defer src.Close()
		f, err := parser.ParseFile(fset, path, src, parser.ParseComments)
		if err != nil {
			t.Fatal(err)
		}
		for _, group := range f.Comments {
			m := warnsRe.FindStringSubmatch(group.Text())
			if m == nil {
				continue
			}
			for _, m := range singleRe.FindAllStringSubmatch(m[1], -1) {
				warns = append(warns, Warn{
					Pos: token.Position{
						Filename: path,
					},
					Name: m[1],
					Type: m[2],
				})
			}
		}
	}
	return warns
}

func doTest(t *testing.T, p string) {
	warns := wantedWarnings(t, p)
	doTestWarns(t, p, warns, p)
}

func warnsEqual(got, want []Warn) bool {
	if len(got) != len(want) {
		return false
	}
	for i, w1 := range got {
		w2 := want[i]
		if w1.Name != w2.Name {
			return false
		}
		if w1.Type != w2.Type {
			return false
		}
	}
	return true
}

func warnsJoin(warns []Warn) string {
	var b bytes.Buffer
	for _, warn := range warns {
		fmt.Fprintln(&b, warn.String())
	}
	return b.String()
}

func doTestWarns(t *testing.T, name string, exp []Warn, args ...string) {
	got, err := CheckArgsList(args)
	if err != nil {
		t.Fatalf("Did not want error in %s:\n%v", name, err)
	}
	if !warnsEqual(exp, got) {
		t.Fatalf("Output mismatch in %s:\nExpected:\n%sGot:\n%s",
			name, warnsJoin(exp), warnsJoin(got))
	}
}

func endNewline(s string) string {
	if strings.HasSuffix(s, "\n") {
		return s
	}
	return s + "\n"
}

func doTestString(t *testing.T, name, exp string, args ...string) {
	var b bytes.Buffer
	switch len(args) {
	case 0:
		args = []string{name}
	case 1:
		if args[0] == "" {
			args = nil
		}
	}
	err := CheckArgsOutput(args, &b, true)
	if err != nil {
		t.Fatalf("Did not want error in %s:\n%v", name, err)
	}
	exp = endNewline(exp)
	got := b.String()
	if exp != got {
		t.Fatalf("Output mismatch in %s:\nExpected:\n%sGot:\n%s",
			name, exp, got)
	}
}

func inputPaths(t *testing.T, glob string) []string {
	all, err := filepath.Glob(glob)
	if err != nil {
		t.Fatal(err)
	}
	return all
}

func chdirUndo(t *testing.T, d string) func() {
	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Chdir(d); err != nil {
		t.Fatal(err)
	}
	return func() {
		if err := os.Chdir(wd); err != nil {
			t.Fatal(err)
		}
	}
}

func runFileTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "files")()
	if len(paths) == 0 {
		paths = inputPaths(t, "*")
	}
	for _, p := range paths {
		doTest(t, p)
	}
}

func runLocalTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "local")()
	if len(paths) == 0 {
		for _, p := range inputPaths(t, "*") {
			paths = append(paths, "./"+p+"/...")
		}
		// non-recursive
		paths = append(paths, "./single")
	}
	for _, p := range paths {
		doTest(t, p)
	}
	doTestString(t, "no-args", ".", "")
}

func runNonlocalTests(t *testing.T, paths ...string) {
	defer chdirUndo(t, "src")()
	if len(paths) > 0 {
		for _, p := range paths {
			doTest(t, p)
		}
		return
	}
	paths = inputPaths(t, "*")
	for _, p := range paths {
		doTest(t, p+"/...")
	}
	// local recursive
	doTest(t, "./nested/...")
	// non-recursive
	doTest(t, "single")
	// make sure we don't miss a package's imports
	doTestString(t, "grab-import", "grab-import\ngrab-import/use.go:27:15: s can be def2.Fooer")
	defer chdirUndo(t, "nested/pkg")()
	// relative paths
	doTestString(t, "rel-path", "nested/pkg\nsimple.go:12:17: rc can be Closer", "./...")
}

func TestMain(m *testing.M) {
	flag.Parse()
	if err := os.Chdir(testdata); err != nil {
		panic(err)
	}
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	build.Default.GOPATH = wd
	os.Exit(m.Run())
}

func TestCheckWarnings(t *testing.T) {
	switch {
	case *name == "":
	case strings.HasSuffix(*name, ".go"):
		runFileTests(t, *name)
		return
	case strings.HasPrefix(*name, "./"):
		runLocalTests(t, *name)
		return
	default:
		runNonlocalTests(t, *name)
		return
	}
	runFileTests(t)
	runLocalTests(t)
	runNonlocalTests(t)
}

func doTestError(t *testing.T, name, exp string, args ...string) {
	switch len(args) {
	case 0:
		args = []string{name}
	case 1:
		if args[0] == "" {
			args = nil
		}
	}
	err := CheckArgsOutput(args, ioutil.Discard, false)
	if err == nil {
		t.Fatalf("Wanted error in %s, but none found.", name)
	}
	got := err.Error()
	if exp != got {
		t.Fatalf("Error mismatch in %s:\nExpected:\n%sGot:\n%s",
			name, exp, got)
	}
}

func TestErrors(t *testing.T) {
	// non-existent Go file
	doTestError(t, "missing.go", "open missing.go: no such file or directory")
	// local non-existent non-recursive
	doTestError(t, "./missing", "no initial packages were loaded")
	// non-local non-existent non-recursive
	doTestError(t, "missing", "no initial packages were loaded")
	// local non-existent recursive
	doTestError(t, "./missing-rec/...", "lstat ./missing-rec: no such file or directory")
	// Mixing Go files and dirs
	doTestError(t, "wrong-args", "named files must be .go files: bar", "foo.go", "bar")
}

func TestExtraArg(t *testing.T) {
	err := CheckArgsOutput([]string{"single", "--", "foo", "bar"}, ioutil.Discard, false)
	got := err.Error()
	want := "unwanted extra args: [foo bar]"
	if got != want {
		t.Fatalf("Error mismatch:\nExpected:\n%sGot:\n%s", want, got)
	}
}
<|endoftext|>


<commit_before>package custom

import (
    "fmt"

    "github.com/jtyr/gbt/gbt/core/car"
    "github.com/jtyr/gbt/gbt/core/utils"
)

// Car inherits the core.Car.
type Car struct {
    car.Car
}

// Init initializes the car.
func (c *Car) Init() {
    defaultRootBg := utils.GetEnv("GBT_CAR_BG", "yellow")
    defaultRootFg := utils.GetEnv("GBT_CAR_FG", "default")
    defaultRootFm := utils.GetEnv("GBT_CAR_FM", "none")
    defaultTextBg := defaultRootBg
    defaultTextFg := defaultRootFg
    defaultTextFm := defaultRootFm

    prefix := fmt.Sprintf("GBT_CAR_CUSTOM%s", c.Params["name"].(string))
    defaultTextText := "?"
    defaultTextCmd := utils.GetEnv(fmt.Sprintf("%s_TEXT_CMD", prefix), "")

    if defaultTextCmd != "" {
        _, defaultTextText, _ = utils.Run([]string{"sh", "-c", defaultTextCmd})
    }

    c.Model = map[string]car.ModelElement {
        "root": {
            Bg: utils.GetEnv(fmt.Sprintf("%s_BG", prefix), defaultRootBg),
            Fg: utils.GetEnv(fmt.Sprintf("%s_FG", prefix), defaultRootFg),
            Fm: utils.GetEnv(fmt.Sprintf("%s_FM", prefix), defaultRootFm),
            Text: utils.GetEnv(fmt.Sprintf("%s_FORMAT", prefix), " {{ Text }} "),
        },
        "Text": {
            Bg: utils.GetEnv(
                fmt.Sprintf("%s_TEXT_BG", prefix), utils.GetEnv(
                    fmt.Sprintf("%s_BG", prefix), defaultTextBg)),
            Fg: utils.GetEnv(
                fmt.Sprintf("%s_TEXT_FG", prefix), utils.GetEnv(
                    fmt.Sprintf("%s_FG", prefix), defaultTextFg)),
            Fm: utils.GetEnv(
                fmt.Sprintf("%s_TEXT_FM", prefix), utils.GetEnv(
                    fmt.Sprintf("%s_FM", prefix), defaultTextFm)),
            Text: utils.GetEnv(
                fmt.Sprintf("%s_TEXT_TEXT", prefix), defaultTextText),
        },
    }

    c.Display = utils.GetEnvBool(fmt.Sprintf("%s_DISPLAY", prefix), true)
    c.Wrap = utils.GetEnvBool(fmt.Sprintf("%s_WRAP", prefix), false)
    c.Sep = utils.GetEnv(fmt.Sprintf("%s_SEP", prefix), "\000")
}
<commit_msg>Adding support for DISPLAY_CMD<commit_after>package custom

import (
    "fmt"

    "github.com/jtyr/gbt/gbt/core/car"
    "github.com/jtyr/gbt/gbt/core/utils"
)

// Car inherits the core.Car.
type Car struct {
    car.Car
}

// Init initializes the car.
func (c *Car) Init() {
    defaultRootBg := utils.GetEnv("GBT_CAR_BG", "yellow")
    defaultRootFg := utils.GetEnv("GBT_CAR_FG", "default")
    defaultRootFm := utils.GetEnv("GBT_CAR_FM", "none")
    defaultTextBg := defaultRootBg
    defaultTextFg := defaultRootFg
    defaultTextFm := defaultRootFm

    prefix := fmt.Sprintf("GBT_CAR_CUSTOM%s", c.Params["name"].(string))
    defaultTextText := "?"
    defaultTextCmd := utils.GetEnv(fmt.Sprintf("%s_TEXT_CMD", prefix), "")
    defaultDisplayCmd := utils.GetEnv(fmt.Sprintf("%s_DISPLAY_CMD", prefix), "")
    defaultDisplay := true

    if defaultTextCmd != "" {
        _, defaultTextText, _ = utils.Run([]string{"sh", "-c", defaultTextCmd})
    }

    if defaultDisplayCmd != "" {
        _, defaultDisplayOutput, _ := utils.Run([]string{"sh", "-c", defaultDisplayCmd})

        if ! utils.IsTrue(defaultDisplayOutput) {
            defaultDisplay = false
        }
    }

    c.Model = map[string]car.ModelElement {
        "root": {
            Bg: utils.GetEnv(fmt.Sprintf("%s_BG", prefix), defaultRootBg),
            Fg: utils.GetEnv(fmt.Sprintf("%s_FG", prefix), defaultRootFg),
            Fm: utils.GetEnv(fmt.Sprintf("%s_FM", prefix), defaultRootFm),
            Text: utils.GetEnv(fmt.Sprintf("%s_FORMAT", prefix), " {{ Text }} "),
        },
        "Text": {
            Bg: utils.GetEnv(
                fmt.Sprintf("%s_TEXT_BG", prefix), utils.GetEnv(
                    fmt.Sprintf("%s_BG", prefix), defaultTextBg)),
            Fg: utils.GetEnv(
                fmt.Sprintf("%s_TEXT_FG", prefix), utils.GetEnv(
                    fmt.Sprintf("%s_FG", prefix), defaultTextFg)),
            Fm: utils.GetEnv(
                fmt.Sprintf("%s_TEXT_FM", prefix), utils.GetEnv(
                    fmt.Sprintf("%s_FM", prefix), defaultTextFm)),
            Text: utils.GetEnv(
                fmt.Sprintf("%s_TEXT_TEXT", prefix), defaultTextText),
        },
    }

    c.Display = utils.GetEnvBool(fmt.Sprintf("%s_DISPLAY", prefix), defaultDisplay)
    c.Wrap = utils.GetEnvBool(fmt.Sprintf("%s_WRAP", prefix), false)
    c.Sep = utils.GetEnv(fmt.Sprintf("%s_SEP", prefix), "\000")
}
<|endoftext|>


<commit_before>package rx

import (
	"sync/atomic"
	"time"

	"github.com/reactivego/subscriber"
)

//jig:template Connectable<Foo>
//jig:embeds Observable<Foo>
//jig:needs SubscribeOption

// ConnectableFoo is an ObservableFoo that has an additional method Connect()
// used to Subscribe to the parent observable and then multicasting values to
// all subscribers of ConnectableFoo.
type ConnectableFoo struct {
	ObservableFoo
	connect func(options []SubscribeOption) Subscription
}

//jig:template Connectable<Foo> Connect
//jig:needs Connectable<Foo>

// Connect instructs a connectable Observable to begin emitting items to its
// subscribers. All values will then be passed on to the observers that
// subscribed to this connectable observable
func (c ConnectableFoo) Connect(options ...SubscribeOption) Subscription {
	return c.connect(options)
}

//jig:template Observable<Foo> Multicast
//jig:needs Observable<Foo> Subscribe, Connectable<Foo>, Schedulers

// Multicast converts an ordinary Observable into a connectable Observable.
// A connectable observable will only start emitting values after its Connect
// method has been called. The factory method passed in should return a
// new SubjectFoo that implements the actual multicasting behavior.
func (o ObservableFoo) Multicast(factory func() SubjectFoo) ConnectableFoo {
	const (
		active int32 = iota
		notifying
		terminated
	)
	var subjectValue struct {
		state int32
		atomic.Value
	}
	subjectValue.Store(factory())
	observable := func(observe FooObserveFunc, subscribeOn Scheduler, subscriber Subscriber) {
		if s, ok := subjectValue.Load().(SubjectFoo); ok {
			s.ObservableFoo(observe, subscribeOn, subscriber)
		}
	}
	observer := func(next foo, err error, done bool) {
		if atomic.CompareAndSwapInt32(&subjectValue.state, active, notifying) {
			if s, ok := subjectValue.Load().(SubjectFoo); ok {
				s.FooObserveFunc(next, err, done)
			}
			if !done {
				atomic.CompareAndSwapInt32(&subjectValue.state, notifying, active)
			} else {
				atomic.CompareAndSwapInt32(&subjectValue.state, notifying, terminated)
			}
		}
	}
	const (
		unsubscribed int32 = iota
		subscribed
	)
	var subscriberValue struct {
		state int32
		atomic.Value
	}
	connect := func(options []SubscribeOption) Subscription {
		if atomic.CompareAndSwapInt32(&subjectValue.state, terminated, active) {
			subjectValue.Store(factory())
		}
		if atomic.CompareAndSwapInt32(&subscriberValue.state, unsubscribed, subscribed) {
			scheduler := TrampolineScheduler()
			subscriber := subscriber.New()
			o.Subscribe(observer, SubscribeOn(scheduler, options...), WithSubscriber(subscriber))
			subscriberValue.Store(subscriber)
			subscriber.OnUnsubscribe(func() {
				atomic.CompareAndSwapInt32(&subscriberValue.state, subscribed, unsubscribed)
			})
		}
		subscription := subscriberValue.Load().(Subscriber)
		return subscription.Add(func() { subscription.Unsubscribe() })
	}
	return ConnectableFoo{ObservableFoo: observable, connect: connect}
}

//jig:template Observable<Foo> Publish
//jig:needs Observable<Foo> Multicast, NewSubject<Foo>, Connectable<Foo>

// Publish uses Multicast to control the subscription of a Subject to a
// source observable and turns the subject it into a connnectable observable.
// A Subject emits to an observer only those items that are emitted by
// the source Observable subsequent to the time of the subscription.
//
// If the source completed and as a result the internal Subject terminated, then
// calling Connect again will replace the old Subject with a newly created one.
// So this Publish operator is re-connectable, unlike the RxJS 5 behavior that
// isn't. To simulate the RxJS 5 behavior use Publish().AutoConnect(1) this will
// connect on the first subscription but will never re-connect.
func (o ObservableFoo) Publish() ConnectableFoo {
	return o.Multicast(NewSubjectFoo)
}

//jig:template Observable<Foo> PublishReplay
//jig:needs Observable<Foo> Multicast, NewReplaySubject<Foo>, Connectable<Foo>

// Replay uses Multicast to control the subscription of a ReplaySubject to a
// source observable and turns the subject into a connectable observable.
// A ReplaySubject emits to any observer all of the items that were emitted by
// the source observable, regardless of when the observer subscribes.
//
// If the source completed and as a result the internal ReplaySubject
// terminated, then calling Connect again will replace the old ReplaySubject
// with a newly created one.
func (o ObservableFoo) PublishReplay(bufferCapacity int, windowDuration time.Duration) ConnectableFoo {
	factory := func() SubjectFoo {
		return NewReplaySubjectFoo(bufferCapacity, windowDuration)
	}
	return o.Multicast(factory)
}

//jig:template Connectable<Foo> RefCount
//jig:needs Observable<Foo>, SubscribeOption

// RefCount makes a ConnectableFoo behave like an ordinary ObservableFoo. On
// first Subscribe it will call Connect on its ConnectableFoo and when its last
// subscriber is Unsubscribed it will cancel the connection by calling
// Unsubscribe on the subscription returned by the call to Connect.
func (o ConnectableFoo) RefCount(options ...SubscribeOption) ObservableFoo {
	var (
		refcount   int32
		connection Subscription
	)
	observable := func(observe FooObserveFunc, subscribeOn Scheduler, subscriber Subscriber) {
		if atomic.AddInt32(&refcount, 1) == 1 {
			connection = o.connect(options)
		}
		subscriber.OnUnsubscribe(func() {
			if atomic.AddInt32(&refcount, -1) == 0 {
				connection.Unsubscribe()
			}
		})
		o.ObservableFoo(observe, subscribeOn, subscriber)
	}
	return observable
}

//jig:template Connectable<Foo> AutoConnect
//jig:needs Observable<Foo>, SubscribeOption

// AutoConnect makes a ConnectableFoo behave like an ordinary ObservableFoo that
// automatically connects when the specified number of clients subscribe to it.
// If count is 0, then AutoConnect will immediately call connect on the
// ConnectableFoo before returning the ObservableFoo part of the ConnectableFoo.
func (o ConnectableFoo) AutoConnect(count int, options ...SubscribeOption) ObservableFoo {
	if count == 0 {
		o.connect(options)
		return o.ObservableFoo
	}
	var refcount int32
	observable := func(observe FooObserveFunc, subscribeOn Scheduler, subscriber Subscriber) {
		if atomic.AddInt32(&refcount, 1) == int32(count) {
			o.connect(options)
		}
		o.ObservableFoo(observe, subscribeOn, subscriber)
	}
	return observable
}
<commit_msg>Multicast really currently needs a Goroutine scheduler to implement internal subscribing correctly.<commit_after>package rx

import (
	"sync/atomic"
	"time"

	"github.com/reactivego/subscriber"
)

//jig:template Connectable<Foo>
//jig:embeds Observable<Foo>
//jig:needs SubscribeOption

// ConnectableFoo is an ObservableFoo that has an additional method Connect()
// used to Subscribe to the parent observable and then multicasting values to
// all subscribers of ConnectableFoo.
type ConnectableFoo struct {
	ObservableFoo
	connect func(options []SubscribeOption) Subscription
}

//jig:template Connectable<Foo> Connect
//jig:needs Connectable<Foo>

// Connect instructs a connectable Observable to begin emitting items to its
// subscribers. All values will then be passed on to the observers that
// subscribed to this connectable observable
func (c ConnectableFoo) Connect(options ...SubscribeOption) Subscription {
	return c.connect(options)
}

//jig:template Observable<Foo> Multicast
//jig:needs Observable<Foo> Subscribe, Connectable<Foo>, Schedulers

// Multicast converts an ordinary Observable into a connectable Observable.
// A connectable observable will only start emitting values after its Connect
// method has been called. The factory method passed in should return a
// new SubjectFoo that implements the actual multicasting behavior.
func (o ObservableFoo) Multicast(factory func() SubjectFoo) ConnectableFoo {
	const (
		active int32 = iota
		notifying
		terminated
	)
	var subjectValue struct {
		state int32
		atomic.Value
	}
	subjectValue.Store(factory())
	observable := func(observe FooObserveFunc, subscribeOn Scheduler, subscriber Subscriber) {
		if s, ok := subjectValue.Load().(SubjectFoo); ok {
			s.ObservableFoo(observe, subscribeOn, subscriber)
		}
	}
	observer := func(next foo, err error, done bool) {
		if atomic.CompareAndSwapInt32(&subjectValue.state, active, notifying) {
			if s, ok := subjectValue.Load().(SubjectFoo); ok {
				s.FooObserveFunc(next, err, done)
			}
			if !done {
				atomic.CompareAndSwapInt32(&subjectValue.state, notifying, active)
			} else {
				atomic.CompareAndSwapInt32(&subjectValue.state, notifying, terminated)
			}
		}
	}
	const (
		unsubscribed int32 = iota
		subscribed
	)
	var subscriberValue struct {
		state int32
		atomic.Value
	}
	connect := func(options []SubscribeOption) Subscription {
		if atomic.CompareAndSwapInt32(&subjectValue.state, terminated, active) {
			subjectValue.Store(factory())
		}
		if atomic.CompareAndSwapInt32(&subscriberValue.state, unsubscribed, subscribed) {
			scheduler := GoroutineScheduler()
			subscriber := subscriber.New()
			o.Subscribe(observer, SubscribeOn(scheduler, options...), WithSubscriber(subscriber))
			subscriberValue.Store(subscriber)
			subscriber.OnUnsubscribe(func() {
				atomic.CompareAndSwapInt32(&subscriberValue.state, subscribed, unsubscribed)
			})
		}
		subscription := subscriberValue.Load().(Subscriber)
		return subscription.Add(func() { subscription.Unsubscribe() })
	}
	return ConnectableFoo{ObservableFoo: observable, connect: connect}
}

//jig:template Observable<Foo> Publish
//jig:needs Observable<Foo> Multicast, NewSubject<Foo>, Connectable<Foo>

// Publish uses Multicast to control the subscription of a Subject to a
// source observable and turns the subject it into a connnectable observable.
// A Subject emits to an observer only those items that are emitted by
// the source Observable subsequent to the time of the subscription.
//
// If the source completed and as a result the internal Subject terminated, then
// calling Connect again will replace the old Subject with a newly created one.
// So this Publish operator is re-connectable, unlike the RxJS 5 behavior that
// isn't. To simulate the RxJS 5 behavior use Publish().AutoConnect(1) this will
// connect on the first subscription but will never re-connect.
func (o ObservableFoo) Publish() ConnectableFoo {
	return o.Multicast(NewSubjectFoo)
}

//jig:template Observable<Foo> PublishReplay
//jig:needs Observable<Foo> Multicast, NewReplaySubject<Foo>, Connectable<Foo>

// Replay uses Multicast to control the subscription of a ReplaySubject to a
// source observable and turns the subject into a connectable observable.
// A ReplaySubject emits to any observer all of the items that were emitted by
// the source observable, regardless of when the observer subscribes.
//
// If the source completed and as a result the internal ReplaySubject
// terminated, then calling Connect again will replace the old ReplaySubject
// with a newly created one.
func (o ObservableFoo) PublishReplay(bufferCapacity int, windowDuration time.Duration) ConnectableFoo {
	factory := func() SubjectFoo {
		return NewReplaySubjectFoo(bufferCapacity, windowDuration)
	}
	return o.Multicast(factory)
}

//jig:template Connectable<Foo> RefCount
//jig:needs Observable<Foo>, SubscribeOption

// RefCount makes a ConnectableFoo behave like an ordinary ObservableFoo. On
// first Subscribe it will call Connect on its ConnectableFoo and when its last
// subscriber is Unsubscribed it will cancel the connection by calling
// Unsubscribe on the subscription returned by the call to Connect.
func (o ConnectableFoo) RefCount(options ...SubscribeOption) ObservableFoo {
	var (
		refcount   int32
		connection Subscription
	)
	observable := func(observe FooObserveFunc, subscribeOn Scheduler, subscriber Subscriber) {
		if atomic.AddInt32(&refcount, 1) == 1 {
			connection = o.connect(options)
		}
		subscriber.OnUnsubscribe(func() {
			if atomic.AddInt32(&refcount, -1) == 0 {
				connection.Unsubscribe()
			}
		})
		o.ObservableFoo(observe, subscribeOn, subscriber)
	}
	return observable
}

//jig:template Connectable<Foo> AutoConnect
//jig:needs Observable<Foo>, SubscribeOption

// AutoConnect makes a ConnectableFoo behave like an ordinary ObservableFoo that
// automatically connects when the specified number of clients subscribe to it.
// If count is 0, then AutoConnect will immediately call connect on the
// ConnectableFoo before returning the ObservableFoo part of the ConnectableFoo.
func (o ConnectableFoo) AutoConnect(count int, options ...SubscribeOption) ObservableFoo {
	if count == 0 {
		o.connect(options)
		return o.ObservableFoo
	}
	var refcount int32
	observable := func(observe FooObserveFunc, subscribeOn Scheduler, subscriber Subscriber) {
		if atomic.AddInt32(&refcount, 1) == int32(count) {
			o.connect(options)
		}
		o.ObservableFoo(observe, subscribeOn, subscriber)
	}
	return observable
}
<|endoftext|>


<commit_before>package server

import (
	"crypto/rand"
	"crypto/rsa"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"log"
	"time"

	"golang.org/x/net/context"
	"gopkg.in/square/go-jose.v2"

	"github.com/coreos/dex/storage"
)

// rotationStrategy describes a strategy for generating cryptographic keys, how
// often to rotate them, and how long they can validate signatures after rotation.
type rotationStrategy struct {
	// Time between rotations.
	period time.Duration

	// After being rotated how long can a key validate signatues?
	verifyFor time.Duration

	// Keys are always RSA keys. Though cryptopasta recommends ECDSA keys, not every
	// client may support these (e.g. github.com/coreos/go-oidc/oidc).
	key func() (*rsa.PrivateKey, error)
}

// staticRotationStrategy returns a strategy which never rotates keys.
func staticRotationStrategy(key *rsa.PrivateKey) rotationStrategy {
	return rotationStrategy{
		// Setting these values to 100 years is easier than having a flag indicating no rotation.
		period:    time.Hour * 8760 * 100,
		verifyFor: time.Hour * 8760 * 100,
		key:       func() (*rsa.PrivateKey, error) { return key, nil },
	}
}

// defaultRotationStrategy returns a strategy which rotates keys every provided period,
// holding onto the public parts for some specified amount of time.
func defaultRotationStrategy(rotationPeriod, verifyFor time.Duration) rotationStrategy {
	return rotationStrategy{
		period:    rotationPeriod,
		verifyFor: verifyFor,
		key: func() (*rsa.PrivateKey, error) {
			return rsa.GenerateKey(rand.Reader, 2048)
		},
	}
}

type keyRotater struct {
	storage.Storage

	strategy rotationStrategy
	now      func() time.Time
}

// startKeyRotation begins key rotation in a new goroutine, closing once the context is canceled.
//
// The method blocks until after the first attempt to rotate keys has completed. That way
// healthy storages will return from this call with valid keys.
func startKeyRotation(ctx context.Context, s storage.Storage, strategy rotationStrategy, now func() time.Time) {
	rotater := keyRotater{s, strategy, now}

	// Try to rotate immediately so properly configured storages will have keys.
	if err := rotater.rotate(); err != nil {
		log.Printf("failed to rotate keys: %v", err)
	}

	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case <-time.After(time.Second * 30):
				if err := rotater.rotate(); err != nil {
					log.Printf("failed to rotate keys: %v", err)
				}
			}
		}
	}()
	return
}

func (k keyRotater) rotate() error {
	keys, err := k.GetKeys()
	if err != nil && err != storage.ErrNotFound {
		return fmt.Errorf("get keys: %v", err)
	}
	if k.now().Before(keys.NextRotation) {
		return nil
	}
	log.Println("keys expired, rotating")

	// Generate the key outside of a storage transaction.
	key, err := k.strategy.key()
	if err != nil {
		return fmt.Errorf("generate key: %v", err)
	}
	b := make([]byte, 20)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		panic(err)
	}
	keyID := hex.EncodeToString(b)
	priv := &jose.JSONWebKey{
		Key:       key,
		KeyID:     keyID,
		Algorithm: "RS256",
		Use:       "sig",
	}
	pub := &jose.JSONWebKey{
		Key:       key.Public(),
		KeyID:     keyID,
		Algorithm: "RS256",
		Use:       "sig",
	}

	var nextRotation time.Time
	err = k.Storage.UpdateKeys(func(keys storage.Keys) (storage.Keys, error) {
		tNow := k.now()
		if tNow.Before(keys.NextRotation) {
			return storage.Keys{}, errors.New("keys already rotated")
		}

		// Remove expired verification keys.
		i := 0
		for _, key := range keys.VerificationKeys {
			if !key.Expiry.After(tNow) {
				keys.VerificationKeys[i] = key
				i++
			}
		}
		keys.VerificationKeys = keys.VerificationKeys[:i]

		if keys.SigningKeyPub != nil {
			// Move current signing key to a verification only key.
			verificationKey := storage.VerificationKey{
				PublicKey: keys.SigningKeyPub,
				Expiry:    tNow.Add(k.strategy.verifyFor),
			}
			keys.VerificationKeys = append(keys.VerificationKeys, verificationKey)
		}

		nextRotation = k.now().Add(k.strategy.period)
		keys.SigningKey = priv
		keys.SigningKeyPub = pub
		keys.NextRotation = nextRotation
		return keys, nil
	})
	if err != nil {
		return err
	}
	log.Printf("keys rotated, next rotation: %s", nextRotation)
	return nil
}
<commit_msg>server: rename "rotationStrategy.period" to "rotationFrequency"<commit_after>package server

import (
	"crypto/rand"
	"crypto/rsa"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"log"
	"time"

	"golang.org/x/net/context"
	"gopkg.in/square/go-jose.v2"

	"github.com/coreos/dex/storage"
)

// rotationStrategy describes a strategy for generating cryptographic keys, how
// often to rotate them, and how long they can validate signatures after rotation.
type rotationStrategy struct {
	// Time between rotations.
	rotationFrequency time.Duration

	// After being rotated how long can a key validate signatues?
	verifyFor time.Duration

	// Keys are always RSA keys. Though cryptopasta recommends ECDSA keys, not every
	// client may support these (e.g. github.com/coreos/go-oidc/oidc).
	key func() (*rsa.PrivateKey, error)
}

// staticRotationStrategy returns a strategy which never rotates keys.
func staticRotationStrategy(key *rsa.PrivateKey) rotationStrategy {
	return rotationStrategy{
		// Setting these values to 100 years is easier than having a flag indicating no rotation.
		rotationFrequency: time.Hour * 8760 * 100,
		verifyFor:         time.Hour * 8760 * 100,
		key:               func() (*rsa.PrivateKey, error) { return key, nil },
	}
}

// defaultRotationStrategy returns a strategy which rotates keys every provided period,
// holding onto the public parts for some specified amount of time.
func defaultRotationStrategy(rotationFrequency, verifyFor time.Duration) rotationStrategy {
	return rotationStrategy{
		rotationFrequency: rotationFrequency,
		verifyFor:         verifyFor,
		key: func() (*rsa.PrivateKey, error) {
			return rsa.GenerateKey(rand.Reader, 2048)
		},
	}
}

type keyRotater struct {
	storage.Storage

	strategy rotationStrategy
	now      func() time.Time
}

// startKeyRotation begins key rotation in a new goroutine, closing once the context is canceled.
//
// The method blocks until after the first attempt to rotate keys has completed. That way
// healthy storages will return from this call with valid keys.
func startKeyRotation(ctx context.Context, s storage.Storage, strategy rotationStrategy, now func() time.Time) {
	rotater := keyRotater{s, strategy, now}

	// Try to rotate immediately so properly configured storages will have keys.
	if err := rotater.rotate(); err != nil {
		log.Printf("failed to rotate keys: %v", err)
	}

	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case <-time.After(time.Second * 30):
				if err := rotater.rotate(); err != nil {
					log.Printf("failed to rotate keys: %v", err)
				}
			}
		}
	}()
	return
}

func (k keyRotater) rotate() error {
	keys, err := k.GetKeys()
	if err != nil && err != storage.ErrNotFound {
		return fmt.Errorf("get keys: %v", err)
	}
	if k.now().Before(keys.NextRotation) {
		return nil
	}
	log.Println("keys expired, rotating")

	// Generate the key outside of a storage transaction.
	key, err := k.strategy.key()
	if err != nil {
		return fmt.Errorf("generate key: %v", err)
	}
	b := make([]byte, 20)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		panic(err)
	}
	keyID := hex.EncodeToString(b)
	priv := &jose.JSONWebKey{
		Key:       key,
		KeyID:     keyID,
		Algorithm: "RS256",
		Use:       "sig",
	}
	pub := &jose.JSONWebKey{
		Key:       key.Public(),
		KeyID:     keyID,
		Algorithm: "RS256",
		Use:       "sig",
	}

	var nextRotation time.Time
	err = k.Storage.UpdateKeys(func(keys storage.Keys) (storage.Keys, error) {
		tNow := k.now()
		if tNow.Before(keys.NextRotation) {
			return storage.Keys{}, errors.New("keys already rotated")
		}

		// Remove expired verification keys.
		i := 0
		for _, key := range keys.VerificationKeys {
			if !key.Expiry.After(tNow) {
				keys.VerificationKeys[i] = key
				i++
			}
		}
		keys.VerificationKeys = keys.VerificationKeys[:i]

		if keys.SigningKeyPub != nil {
			// Move current signing key to a verification only key.
			verificationKey := storage.VerificationKey{
				PublicKey: keys.SigningKeyPub,
				Expiry:    tNow.Add(k.strategy.verifyFor),
			}
			keys.VerificationKeys = append(keys.VerificationKeys, verificationKey)
		}

		nextRotation = k.now().Add(k.strategy.rotationFrequency)
		keys.SigningKey = priv
		keys.SigningKeyPub = pub
		keys.NextRotation = nextRotation
		return keys, nil
	})
	if err != nil {
		return err
	}
	log.Printf("keys rotated, next rotation: %s", nextRotation)
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Openprovider Authors. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

package service

import (
	"fmt"
	"log"
	"net"
	"os"
	"os/signal"
	"syscall"

	"github.com/openprovider/whoisd/client"
	"github.com/openprovider/whoisd/config"
	"github.com/openprovider/whoisd/storage"
	"github.com/takama/daemon"
)

// Version of the Whois Daemon
// Date of current version release
const (
	Version = "0.4.1"
	Date    = "2016-02-07T21:15:00Z"
)

// simplest logger, which initialized during starts of the application
var (
	stdlog = log.New(os.Stdout, "[SERVICE]: ", log.Ldate|log.Ltime)
	errlog = log.New(os.Stderr, "[SERVICE:ERROR]: ", log.Ldate|log.Ltime|log.Lshortfile)
)

// Record - standard record (struct) for service package
type Record struct {
	Name   string
	Config *config.Record
	daemon.Daemon
}

// New - Create a new service record
func New(name, description string) (*Record, error) {
	daemonInstance, err := daemon.New(name, description)
	if err != nil {
		return nil, err
	}

	return &Record{name, config.New(), daemonInstance}, nil
}

// Run or manage the service
func (service *Record) Run() (string, error) {

	// if received any kind of command, do it
	if len(os.Args) > 1 {
		command := os.Args[1]
		switch command {
		case "install":
			return service.Install()
		case "remove":
			return service.Remove()
		case "start":
			return service.Start()
		case "stop":
			return service.Stop()
		case "status":
			return service.Status()
		}
	}

	// Load configuration and get mapping
	bundle, err := service.Config.Load()
	if err != nil {
		return "Loading mapping file was unsuccessful", err
	}

	// Logs for what is host&port used
	serviceHostPort := fmt.Sprintf("%s:%d", service.Config.Host, service.Config.Port)
	stdlog.Printf("%s started on %s\n", service.Name, serviceHostPort)
	stdlog.Printf("Used storage %s on %s:%d\n",
		service.Config.Storage.StorageType,
		service.Config.Storage.Host,
		service.Config.Storage.Port,
	)

	// Set up listener for defined host and port
	listener, err := net.Listen("tcp", serviceHostPort)
	if err != nil {
		return "Possibly was a problem with the port binding", err
	}

	// set up channel to collect client queries
	channel := make(chan client.Record, service.Config.Connections)

	// set up current storage
	repository := storage.New(service.Config, bundle)

	// init workers
	for i := 0; i < service.Config.Workers; i++ {
		go client.ProcessClient(channel, repository)
	}

	// This block is for testing purpose only
	if service.Config.TestMode == true {
		// make pipe connections for testing
		// connIn will ready to write into by function ProcessClient
		connIn, connOut := net.Pipe()
		defer connIn.Close()
		defer connOut.Close()
		newClient := client.Record{Conn: connIn}

		// prepare query for ProcessClient
		newClient.Query = []byte(service.Config.TestQuery)

		// send it into channel
		channel <- newClient
		// just read answer from channel pipe
		buffer := make([]byte, 4096)
		numBytes, err := connOut.Read(buffer)
		stdlog.Println("Read bytes:", numBytes)
		return string(buffer), err
	}

	// Set up channel on which to send signal notifications.
	// We must use a buffered channel or risk missing the signal
	// if we're not ready to receive when the signal is sent.
	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)

	// set up channel on which to send accepted connections
	listen := make(chan net.Conn, service.Config.Connections)
	go acceptConnection(listener, listen)

	// loop work cycle with accept connections or interrupt
	// by system signal
	for {
		select {
		case conn := <-listen:
			newClient := client.Record{Conn: conn}
			go newClient.HandleClient(channel)
		case killSignal := <-interrupt:
			stdlog.Println("Got signal:", killSignal)
			stdlog.Println("Stoping listening on ", listener.Addr())
			listener.Close()
			if killSignal == os.Interrupt {
				return "Daemon was interruped by system signal", nil
			}
			return "Daemon was killed", nil
		}
	}

	// never happen, but need to complete code
	return "If you see that, you are lucky bastard", nil
}

// Accept a client connection and collect it in a channel
func acceptConnection(listener net.Listener, listen chan<- net.Conn) {
	defer func() {
		if recovery := recover(); recovery != nil {
			errlog.Println("Recovered in ListenConnection:", recovery)
		}
	}()
	for {
		conn, err := listener.Accept()
		if err != nil {
			continue
		}
		listen <- conn
	}
}
<commit_msg>Bumped version number to 0.4.3<commit_after>// Copyright 2015 Openprovider Authors. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

package service

import (
	"fmt"
	"log"
	"net"
	"os"
	"os/signal"
	"syscall"

	"github.com/openprovider/whoisd/client"
	"github.com/openprovider/whoisd/config"
	"github.com/openprovider/whoisd/storage"
	"github.com/takama/daemon"
)

// Version of the Whois Daemon
// Date of current version release
const (
	Version = "0.4.3"
	Date    = "2016-02-07T22:17:17Z"
)

// simplest logger, which initialized during starts of the application
var (
	stdlog = log.New(os.Stdout, "[SERVICE]: ", log.Ldate|log.Ltime)
	errlog = log.New(os.Stderr, "[SERVICE:ERROR]: ", log.Ldate|log.Ltime|log.Lshortfile)
)

// Record - standard record (struct) for service package
type Record struct {
	Name   string
	Config *config.Record
	daemon.Daemon
}

// New - Create a new service record
func New(name, description string) (*Record, error) {
	daemonInstance, err := daemon.New(name, description)
	if err != nil {
		return nil, err
	}

	return &Record{name, config.New(), daemonInstance}, nil
}

// Run or manage the service
func (service *Record) Run() (string, error) {

	// if received any kind of command, do it
	if len(os.Args) > 1 {
		command := os.Args[1]
		switch command {
		case "install":
			return service.Install()
		case "remove":
			return service.Remove()
		case "start":
			return service.Start()
		case "stop":
			return service.Stop()
		case "status":
			return service.Status()
		}
	}

	// Load configuration and get mapping
	bundle, err := service.Config.Load()
	if err != nil {
		return "Loading mapping file was unsuccessful", err
	}

	// Logs for what is host&port used
	serviceHostPort := fmt.Sprintf("%s:%d", service.Config.Host, service.Config.Port)
	stdlog.Printf("%s started on %s\n", service.Name, serviceHostPort)
	stdlog.Printf("Used storage %s on %s:%d\n",
		service.Config.Storage.StorageType,
		service.Config.Storage.Host,
		service.Config.Storage.Port,
	)

	// Set up listener for defined host and port
	listener, err := net.Listen("tcp", serviceHostPort)
	if err != nil {
		return "Possibly was a problem with the port binding", err
	}

	// set up channel to collect client queries
	channel := make(chan client.Record, service.Config.Connections)

	// set up current storage
	repository := storage.New(service.Config, bundle)

	// init workers
	for i := 0; i < service.Config.Workers; i++ {
		go client.ProcessClient(channel, repository)
	}

	// This block is for testing purpose only
	if service.Config.TestMode == true {
		// make pipe connections for testing
		// connIn will ready to write into by function ProcessClient
		connIn, connOut := net.Pipe()
		defer connIn.Close()
		defer connOut.Close()
		newClient := client.Record{Conn: connIn}

		// prepare query for ProcessClient
		newClient.Query = []byte(service.Config.TestQuery)

		// send it into channel
		channel <- newClient
		// just read answer from channel pipe
		buffer := make([]byte, 4096)
		numBytes, err := connOut.Read(buffer)
		stdlog.Println("Read bytes:", numBytes)
		return string(buffer), err
	}

	// Set up channel on which to send signal notifications.
	// We must use a buffered channel or risk missing the signal
	// if we're not ready to receive when the signal is sent.
	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)

	// set up channel on which to send accepted connections
	listen := make(chan net.Conn, service.Config.Connections)
	go acceptConnection(listener, listen)

	// loop work cycle with accept connections or interrupt
	// by system signal
	for {
		select {
		case conn := <-listen:
			newClient := client.Record{Conn: conn}
			go newClient.HandleClient(channel)
		case killSignal := <-interrupt:
			stdlog.Println("Got signal:", killSignal)
			stdlog.Println("Stoping listening on ", listener.Addr())
			listener.Close()
			if killSignal == os.Interrupt {
				return "Daemon was interruped by system signal", nil
			}
			return "Daemon was killed", nil
		}
	}

	// never happen, but need to complete code
	return "If you see that, you are lucky bastard", nil
}

// Accept a client connection and collect it in a channel
func acceptConnection(listener net.Listener, listen chan<- net.Conn) {
	defer func() {
		if recovery := recover(); recovery != nil {
			errlog.Println("Recovered in ListenConnection:", recovery)
		}
	}()
	for {
		conn, err := listener.Accept()
		if err != nil {
			continue
		}
		listen <- conn
	}
}
<|endoftext|>


<commit_before>package service

import (
	"fmt"
	"log"
	"net"
	"os"
	"os/signal"
	"syscall"

	"github.com/takama/daemon"
	"github.com/takama/whoisd/client"
	"github.com/takama/whoisd/config"
	"github.com/takama/whoisd/storage"
)

// Version of the Whois Daemon
// Date of current version release
const (
	Version = "0.2.0"
	Date    = "2015-10-02T16:16:16Z"
)

// Record - standard record (struct) for service package
type Record struct {
	Name   string
	Config *config.Record
	daemon.Daemon
}

// New - Create a new service record
func New(name, description string) (*Record, error) {
	daemonInstance, err := daemon.New(name, description)
	if err != nil {
		return nil, err
	}

	return &Record{name, config.New(), daemonInstance}, nil
}

// Run or manage the service
func (service *Record) Run() (string, error) {

	// if received any kind of command, do it
	if len(os.Args) > 1 {
		command := os.Args[1]
		switch command {
		case "install":
			return service.Install()
		case "remove":
			return service.Remove()
		case "start":
			return service.Start()
		case "stop":
			return service.Stop()
		case "status":
			return service.Status()
		}
	}

	// Load configuration and get mapping
	bundle, err := service.Config.Load()
	if err != nil {
		return "Loading mapping file was unsuccessful", err
	}

	// Logs for what is host&port used
	serviceHostPort := fmt.Sprintf("%s:%d", service.Config.Host, service.Config.Port)
	log.Printf("%s started on %s\n", service.Name, serviceHostPort)
	log.Printf("Used storage %s on %s:%d\n",
		service.Config.Storage.StorageType,
		service.Config.Storage.Host,
		service.Config.Storage.Port,
	)

	// Set up listener for defined host and port
	listener, err := net.Listen("tcp", serviceHostPort)
	if err != nil {
		return "Possibly was a problem with the port binding", err
	}

	// set up channel to collect client queries
	channel := make(chan client.Record, service.Config.Connections)

	// set up current storage
	repository := storage.New(service.Config, bundle)

	// init workers
	for i := 0; i < service.Config.Workers; i++ {
		go client.ProcessClient(channel, repository)
	}

	// This block is for testing purpose only
	if service.Config.TestMode == true {
		// make pipe connections for testing
		// connIn will ready to write into by function ProcessClient
		connIn, connOut := net.Pipe()
		defer connIn.Close()
		defer connOut.Close()
		newClient := client.Record{Conn: connIn}

		// prepare query for ProcessClient
		newClient.Query = []byte(service.Config.TestQuery)

		// send it into channel
		channel <- newClient
		// just read answer from channel pipe
		buffer := make([]byte, 4096)
		numBytes, err := connOut.Read(buffer)
		log.Println("Read bytes:", numBytes)
		return string(buffer), err
	}

	// Set up channel on which to send signal notifications.
	// We must use a buffered channel or risk missing the signal
	// if we're not ready to receive when the signal is sent.
	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)

	// set up channel on which to send accepted connections
	listen := make(chan net.Conn, service.Config.Connections)
	go acceptConnection(listener, listen)

	// loop work cycle with accept connections or interrupt
	// by system signal
	for {
		select {
		case conn := <-listen:
			newClient := client.Record{Conn: conn}
			go newClient.HandleClient(channel)
		case killSignal := <-interrupt:
			log.Println("Got signal:", killSignal)
			log.Println("Stoping listening on ", listener.Addr())
			listener.Close()
			if killSignal == os.Interrupt {
				return "Daemon was interruped by system signal", nil
			}
			return "Daemon was killed", nil
		}
	}

	// never happen, but need to complete code
	return "If you see that, you are lucky bastard", nil
}

// Accept a client connection and collect it in a channel
func acceptConnection(listener net.Listener, listen chan<- net.Conn) {
	defer func() {
		if recovery := recover(); recovery != nil {
			log.Println("Recovered in ListenConnection:", recovery)
		}
	}()
	for {
		conn, err := listener.Accept()
		if err != nil {
			continue
		}
		listen <- conn
	}
}
<commit_msg>Bumped version number to 0.2.1<commit_after>package service

import (
	"fmt"
	"log"
	"net"
	"os"
	"os/signal"
	"syscall"

	"github.com/takama/daemon"
	"github.com/takama/whoisd/client"
	"github.com/takama/whoisd/config"
	"github.com/takama/whoisd/storage"
)

// Version of the Whois Daemon
// Date of current version release
const (
	Version = "0.2.1"
	Date    = "2015-10-04T09:16:16Z"
)

// Record - standard record (struct) for service package
type Record struct {
	Name   string
	Config *config.Record
	daemon.Daemon
}

// New - Create a new service record
func New(name, description string) (*Record, error) {
	daemonInstance, err := daemon.New(name, description)
	if err != nil {
		return nil, err
	}

	return &Record{name, config.New(), daemonInstance}, nil
}

// Run or manage the service
func (service *Record) Run() (string, error) {

	// if received any kind of command, do it
	if len(os.Args) > 1 {
		command := os.Args[1]
		switch command {
		case "install":
			return service.Install()
		case "remove":
			return service.Remove()
		case "start":
			return service.Start()
		case "stop":
			return service.Stop()
		case "status":
			return service.Status()
		}
	}

	// Load configuration and get mapping
	bundle, err := service.Config.Load()
	if err != nil {
		return "Loading mapping file was unsuccessful", err
	}

	// Logs for what is host&port used
	serviceHostPort := fmt.Sprintf("%s:%d", service.Config.Host, service.Config.Port)
	log.Printf("%s started on %s\n", service.Name, serviceHostPort)
	log.Printf("Used storage %s on %s:%d\n",
		service.Config.Storage.StorageType,
		service.Config.Storage.Host,
		service.Config.Storage.Port,
	)

	// Set up listener for defined host and port
	listener, err := net.Listen("tcp", serviceHostPort)
	if err != nil {
		return "Possibly was a problem with the port binding", err
	}

	// set up channel to collect client queries
	channel := make(chan client.Record, service.Config.Connections)

	// set up current storage
	repository := storage.New(service.Config, bundle)

	// init workers
	for i := 0; i < service.Config.Workers; i++ {
		go client.ProcessClient(channel, repository)
	}

	// This block is for testing purpose only
	if service.Config.TestMode == true {
		// make pipe connections for testing
		// connIn will ready to write into by function ProcessClient
		connIn, connOut := net.Pipe()
		defer connIn.Close()
		defer connOut.Close()
		newClient := client.Record{Conn: connIn}

		// prepare query for ProcessClient
		newClient.Query = []byte(service.Config.TestQuery)

		// send it into channel
		channel <- newClient
		// just read answer from channel pipe
		buffer := make([]byte, 4096)
		numBytes, err := connOut.Read(buffer)
		log.Println("Read bytes:", numBytes)
		return string(buffer), err
	}

	// Set up channel on which to send signal notifications.
	// We must use a buffered channel or risk missing the signal
	// if we're not ready to receive when the signal is sent.
	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)

	// set up channel on which to send accepted connections
	listen := make(chan net.Conn, service.Config.Connections)
	go acceptConnection(listener, listen)

	// loop work cycle with accept connections or interrupt
	// by system signal
	for {
		select {
		case conn := <-listen:
			newClient := client.Record{Conn: conn}
			go newClient.HandleClient(channel)
		case killSignal := <-interrupt:
			log.Println("Got signal:", killSignal)
			log.Println("Stoping listening on ", listener.Addr())
			listener.Close()
			if killSignal == os.Interrupt {
				return "Daemon was interruped by system signal", nil
			}
			return "Daemon was killed", nil
		}
	}

	// never happen, but need to complete code
	return "If you see that, you are lucky bastard", nil
}

// Accept a client connection and collect it in a channel
func acceptConnection(listener net.Listener, listen chan<- net.Conn) {
	defer func() {
		if recovery := recover(); recovery != nil {
			log.Println("Recovered in ListenConnection:", recovery)
		}
	}()
	for {
		conn, err := listener.Accept()
		if err != nil {
			continue
		}
		listen <- conn
	}
}
<|endoftext|>


<commit_before>package services

import (
    "github.com/earaujoassis/space/datastore"
    "github.com/earaujoassis/space/models"
)

const (
    DefaultClient = "Jupiter"
)

// CreateNewClient creates a new client application entry
func CreateNewClient(name, description, secret, scopes, canonicalURI, redirectURI string) models.Client {
    var client models.Client = models.Client{
        Name: name,
        Description: description,
        Secret: secret,
        Scopes: scopes,
        CanonicalURI: canonicalURI,
        RedirectURI: redirectURI,
        Type: models.ConfidentialClient,
    }

    dataStoreSession := datastore.GetDataStoreConnection()
    dataStoreSession.Create(&client)
    return client
}

// FindOrCreateClient attempts to find a client application by its name; otherwise, it creates a new one
func FindOrCreateClient(name string) models.Client {
    var client models.Client

    dataStoreSession := datastore.GetDataStoreConnection()
    dataStoreSession.Where("name = ?", name).First(&client)
    if dataStoreSession.NewRecord(client) {
        client = models.Client{
            Name: name,
            Secret: models.GenerateRandomString(64),
            CanonicalURI: "localhost",
            RedirectURI: "/",
            Scopes: models.PublicScope,
            Type: models.PublicClient,
        }
        dataStoreSession.Create(&client)
    }
    return client
}

// FindClientByKey gets a client application by its key
func FindClientByKey(key string) models.Client {
    var client models.Client

    dataStoreSession := datastore.GetDataStoreConnection()
    dataStoreSession.Where("key = ?", key).First(&client)
    return client
}

// FindClientByUUID gets a client application by its UUID
func FindClientByUUID(uuid string) models.Client {
    var client models.Client

    dataStoreSession := datastore.GetDataStoreConnection()
    dataStoreSession.Where("uuid = ?", uuid).First(&client)
    return client
}

// ClientAuthentication gets a client application by its key-secret pair
func ClientAuthentication(key, secret string) models.Client {
    var client models.Client

    client = FindClientByKey(key)
    if client.ID != 0 && client.Authentic(secret) {
        return client
    }
    return models.Client{}
}

// ActiveClients lists all client applications
func ActiveClients() []models.Client {
    var clients []models.Client

    dataStoreSession := datastore.GetDataStoreConnection()
    dataStoreSession.
        Raw("SELECT clients.uuid, clients.name, clients.description, clients.canonical_uri, clients.redirect_uri FROM clients " +
        "WHERE clients.name != 'Jupiter' ORDER BY clients.created_at ASC").
        Scan(&clients)
    return clients
}
<commit_msg>hotfix: fix a linting issue in golang<commit_after>package services

import (
    "github.com/earaujoassis/space/datastore"
    "github.com/earaujoassis/space/models"
)

const (
    // DefaultClient is the default (and internal) client application
    DefaultClient = "Jupiter"
)

// CreateNewClient creates a new client application entry
func CreateNewClient(name, description, secret, scopes, canonicalURI, redirectURI string) models.Client {
    var client models.Client = models.Client{
        Name: name,
        Description: description,
        Secret: secret,
        Scopes: scopes,
        CanonicalURI: canonicalURI,
        RedirectURI: redirectURI,
        Type: models.ConfidentialClient,
    }

    dataStoreSession := datastore.GetDataStoreConnection()
    dataStoreSession.Create(&client)
    return client
}

// FindOrCreateClient attempts to find a client application by its name; otherwise, it creates a new one
func FindOrCreateClient(name string) models.Client {
    var client models.Client

    dataStoreSession := datastore.GetDataStoreConnection()
    dataStoreSession.Where("name = ?", name).First(&client)
    if dataStoreSession.NewRecord(client) {
        client = models.Client{
            Name: name,
            Secret: models.GenerateRandomString(64),
            CanonicalURI: "localhost",
            RedirectURI: "/",
            Scopes: models.PublicScope,
            Type: models.PublicClient,
        }
        dataStoreSession.Create(&client)
    }
    return client
}

// FindClientByKey gets a client application by its key
func FindClientByKey(key string) models.Client {
    var client models.Client

    dataStoreSession := datastore.GetDataStoreConnection()
    dataStoreSession.Where("key = ?", key).First(&client)
    return client
}

// FindClientByUUID gets a client application by its UUID
func FindClientByUUID(uuid string) models.Client {
    var client models.Client

    dataStoreSession := datastore.GetDataStoreConnection()
    dataStoreSession.Where("uuid = ?", uuid).First(&client)
    return client
}

// ClientAuthentication gets a client application by its key-secret pair
func ClientAuthentication(key, secret string) models.Client {
    var client models.Client

    client = FindClientByKey(key)
    if client.ID != 0 && client.Authentic(secret) {
        return client
    }
    return models.Client{}
}

// ActiveClients lists all client applications
func ActiveClients() []models.Client {
    var clients []models.Client

    dataStoreSession := datastore.GetDataStoreConnection()
    dataStoreSession.
        Raw("SELECT clients.uuid, clients.name, clients.description, clients.canonical_uri, clients.redirect_uri FROM clients " +
        "WHERE clients.name != 'Jupiter' ORDER BY clients.created_at ASC").
        Scan(&clients)
    return clients
}
<|endoftext|>