<commit_before>package slack

import (
	"encoding/json"
)

// Block Objects are also known as Composition Objects
//
// For more information: https://api.slack.com/reference/messaging/composition-objects

// BlockObject defines an interface that all block object types should
// implement.
// @TODO: Is this interface needed?

// blockObject object types
const (
	MarkdownType  = "mrkdwn"
	PlainTextType = "plain_text"
	// The following objects don't actually have types and their corresponding
	// const values are just for internal use
	motConfirmation = "confirm"
	motOption       = "option"
	motOptionGroup  = "option_group"
)

type MessageObjectType string

type blockObject interface {
	validateType() MessageObjectType
}

type BlockObjects struct {
	TextObjects         []*TextBlockObject
	ConfirmationObjects []*ConfirmationBlockObject
	OptionObjects       []*OptionBlockObject
	OptionGroupObjects  []*OptionGroupBlockObject
}

// UnmarshalJSON implements the Unmarshaller interface for BlockObjects, so that any JSON
// unmarshalling is delegated and proper type determination can be made before unmarshal
func (b *BlockObjects) UnmarshalJSON(data []byte) error {
	var raw []json.RawMessage
	err := json.Unmarshal(data, &raw)
	if err != nil {
		return err
	}

	for _, r := range raw {
		var obj map[string]interface{}
		err := json.Unmarshal(r, &obj)
		if err != nil {
			return err
		}

		blockObjectType := getBlockObjectType(obj)

		switch blockObjectType {
		case PlainTextType, MarkdownType:
			object, err := unmarshalBlockObject(r, &TextBlockObject{})
			if err != nil {
				return err
			}
			b.TextObjects = append(b.TextObjects, object.(*TextBlockObject))
		case motConfirmation:
			object, err := unmarshalBlockObject(r, &ConfirmationBlockObject{})
			if err != nil {
				return err
			}
			b.ConfirmationObjects = append(b.ConfirmationObjects, object.(*ConfirmationBlockObject))
		case motOption:
			object, err := unmarshalBlockObject(r, &OptionBlockObject{})
			if err != nil {
				return err
			}
			b.OptionObjects = append(b.OptionObjects, object.(*OptionBlockObject))
		case motOptionGroup:
			object, err := unmarshalBlockObject(r, &OptionGroupBlockObject{})
			if err != nil {
				return err
			}
			b.OptionGroupObjects = append(b.OptionGroupObjects, object.(*OptionGroupBlockObject))

		}
	}

	return nil
}

// Ideally would have a better way to identify the block objects for
// type casting at time of unmarshalling, should be adapted if possible
// to accomplish in a more reliable manner.
func getBlockObjectType(obj map[string]interface{}) string {
	if t, ok := obj["type"].(string); ok {
		return t
	}
	if _, ok := obj["confirm"].(string); ok {
		return "confirm"
	}
	if _, ok := obj["options"].(string); ok {
		return "option_group"
	}
	if _, ok := obj["text"].(string); ok {
		if _, ok := obj["value"].(string); ok {
			return "option"
		}
	}
	return ""
}

func unmarshalBlockObject(r json.RawMessage, object blockObject) (blockObject, error) {
	err := json.Unmarshal(r, object)
	if err != nil {
		return nil, err
	}
	return object, nil
}

// TextBlockObject defines a text element object to be used with blocks
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#text
type TextBlockObject struct {
	Type     string `json:"type"`
	Text     string `json:"text"`
	Emoji    bool   `json:"emoji,omitempty"`
	Verbatim bool   `json:"verbatim,omitempty"`
}

// validateType enforces block objects for element and block parameters
func (s TextBlockObject) validateType() MessageObjectType {
	return MessageObjectType(s.Type)
}

// validateType enforces block objects for element and block parameters
func (s TextBlockObject) MixedElementType() MixedElementType {
	return MixedElementText
}

// NewTextBlockObject returns an instance of a new Text Block Object
func NewTextBlockObject(elementType, text string, emoji, verbatim bool) *TextBlockObject {
	return &TextBlockObject{
		Type:     elementType,
		Text:     text,
		Emoji:    emoji,
		Verbatim: verbatim,
	}
}

// ConfirmationBlockObject defines a dialog that provides a confirmation step to
// any interactive element. This dialog will ask the user to confirm their action by
// offering a confirm and deny buttons.
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#confirm
type ConfirmationBlockObject struct {
	Title   *TextBlockObject `json:"title"`
	Text    *TextBlockObject `json:"text"`
	Confirm *TextBlockObject `json:"confirm"`
	Deny    *TextBlockObject `json:"deny"`
}

// validateType enforces block objects for element and block parameters
func (s ConfirmationBlockObject) validateType() MessageObjectType {
	return motConfirmation
}

// NewConfirmationBlockObject returns an instance of a new Confirmation Block Object
func NewConfirmationBlockObject(title, text, confirm, deny *TextBlockObject) *ConfirmationBlockObject {
	return &ConfirmationBlockObject{
		Title:   title,
		Text:    text,
		Confirm: confirm,
		Deny:    deny,
	}
}

// OptionBlockObject represents a single selectable item in a select menu
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#option
type OptionBlockObject struct {
	Text  *TextBlockObject `json:"text"`
	Value string           `json:"value"`
}

// NewOptionBlockObject returns an instance of a new Option Block Element
func NewOptionBlockObject(value string, text *TextBlockObject) *OptionBlockObject {
	return &OptionBlockObject{
		Text:  text,
		Value: value,
	}
}

// validateType enforces block objects for element and block parameters
func (s OptionBlockObject) validateType() MessageObjectType {
	return motOption
}

// OptionGroupBlockObject Provides a way to group options in a select menu.
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#option-group
type OptionGroupBlockObject struct {
	Label   *TextBlockObject     `json:"label"`
	Options []*OptionBlockObject `json:"options"`
}

// validateType enforces block objects for element and block parameters
func (s OptionGroupBlockObject) validateType() MessageObjectType {
	return motOptionGroup
}

// NewOptionGroupBlockElement returns an instance of a new option group block element
func NewOptionGroupBlockElement(label *TextBlockObject, options ...*OptionBlockObject) *OptionGroupBlockObject {
	return &OptionGroupBlockObject{
		Label:   label,
		Options: options,
	}
}
<commit_msg>Make label tag in OptionGroupBlockObject optional<commit_after>package slack

import (
	"encoding/json"
)

// Block Objects are also known as Composition Objects
//
// For more information: https://api.slack.com/reference/messaging/composition-objects

// BlockObject defines an interface that all block object types should
// implement.
// @TODO: Is this interface needed?

// blockObject object types
const (
	MarkdownType  = "mrkdwn"
	PlainTextType = "plain_text"
	// The following objects don't actually have types and their corresponding
	// const values are just for internal use
	motConfirmation = "confirm"
	motOption       = "option"
	motOptionGroup  = "option_group"
)

type MessageObjectType string

type blockObject interface {
	validateType() MessageObjectType
}

type BlockObjects struct {
	TextObjects         []*TextBlockObject
	ConfirmationObjects []*ConfirmationBlockObject
	OptionObjects       []*OptionBlockObject
	OptionGroupObjects  []*OptionGroupBlockObject
}

// UnmarshalJSON implements the Unmarshaller interface for BlockObjects, so that any JSON
// unmarshalling is delegated and proper type determination can be made before unmarshal
func (b *BlockObjects) UnmarshalJSON(data []byte) error {
	var raw []json.RawMessage
	err := json.Unmarshal(data, &raw)
	if err != nil {
		return err
	}

	for _, r := range raw {
		var obj map[string]interface{}
		err := json.Unmarshal(r, &obj)
		if err != nil {
			return err
		}

		blockObjectType := getBlockObjectType(obj)

		switch blockObjectType {
		case PlainTextType, MarkdownType:
			object, err := unmarshalBlockObject(r, &TextBlockObject{})
			if err != nil {
				return err
			}
			b.TextObjects = append(b.TextObjects, object.(*TextBlockObject))
		case motConfirmation:
			object, err := unmarshalBlockObject(r, &ConfirmationBlockObject{})
			if err != nil {
				return err
			}
			b.ConfirmationObjects = append(b.ConfirmationObjects, object.(*ConfirmationBlockObject))
		case motOption:
			object, err := unmarshalBlockObject(r, &OptionBlockObject{})
			if err != nil {
				return err
			}
			b.OptionObjects = append(b.OptionObjects, object.(*OptionBlockObject))
		case motOptionGroup:
			object, err := unmarshalBlockObject(r, &OptionGroupBlockObject{})
			if err != nil {
				return err
			}
			b.OptionGroupObjects = append(b.OptionGroupObjects, object.(*OptionGroupBlockObject))

		}
	}

	return nil
}

// Ideally would have a better way to identify the block objects for
// type casting at time of unmarshalling, should be adapted if possible
// to accomplish in a more reliable manner.
func getBlockObjectType(obj map[string]interface{}) string {
	if t, ok := obj["type"].(string); ok {
		return t
	}
	if _, ok := obj["confirm"].(string); ok {
		return "confirm"
	}
	if _, ok := obj["options"].(string); ok {
		return "option_group"
	}
	if _, ok := obj["text"].(string); ok {
		if _, ok := obj["value"].(string); ok {
			return "option"
		}
	}
	return ""
}

func unmarshalBlockObject(r json.RawMessage, object blockObject) (blockObject, error) {
	err := json.Unmarshal(r, object)
	if err != nil {
		return nil, err
	}
	return object, nil
}

// TextBlockObject defines a text element object to be used with blocks
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#text
type TextBlockObject struct {
	Type     string `json:"type"`
	Text     string `json:"text"`
	Emoji    bool   `json:"emoji,omitempty"`
	Verbatim bool   `json:"verbatim,omitempty"`
}

// validateType enforces block objects for element and block parameters
func (s TextBlockObject) validateType() MessageObjectType {
	return MessageObjectType(s.Type)
}

// validateType enforces block objects for element and block parameters
func (s TextBlockObject) MixedElementType() MixedElementType {
	return MixedElementText
}

// NewTextBlockObject returns an instance of a new Text Block Object
func NewTextBlockObject(elementType, text string, emoji, verbatim bool) *TextBlockObject {
	return &TextBlockObject{
		Type:     elementType,
		Text:     text,
		Emoji:    emoji,
		Verbatim: verbatim,
	}
}

// ConfirmationBlockObject defines a dialog that provides a confirmation step to
// any interactive element. This dialog will ask the user to confirm their action by
// offering a confirm and deny buttons.
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#confirm
type ConfirmationBlockObject struct {
	Title   *TextBlockObject `json:"title"`
	Text    *TextBlockObject `json:"text"`
	Confirm *TextBlockObject `json:"confirm"`
	Deny    *TextBlockObject `json:"deny"`
}

// validateType enforces block objects for element and block parameters
func (s ConfirmationBlockObject) validateType() MessageObjectType {
	return motConfirmation
}

// NewConfirmationBlockObject returns an instance of a new Confirmation Block Object
func NewConfirmationBlockObject(title, text, confirm, deny *TextBlockObject) *ConfirmationBlockObject {
	return &ConfirmationBlockObject{
		Title:   title,
		Text:    text,
		Confirm: confirm,
		Deny:    deny,
	}
}

// OptionBlockObject represents a single selectable item in a select menu
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#option
type OptionBlockObject struct {
	Text  *TextBlockObject `json:"text"`
	Value string           `json:"value"`
}

// NewOptionBlockObject returns an instance of a new Option Block Element
func NewOptionBlockObject(value string, text *TextBlockObject) *OptionBlockObject {
	return &OptionBlockObject{
		Text:  text,
		Value: value,
	}
}

// validateType enforces block objects for element and block parameters
func (s OptionBlockObject) validateType() MessageObjectType {
	return motOption
}

// OptionGroupBlockObject Provides a way to group options in a select menu.
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#option-group
type OptionGroupBlockObject struct {
	Label   *TextBlockObject     `json:"label,omitempty"`
	Options []*OptionBlockObject `json:"options"`
}

// validateType enforces block objects for element and block parameters
func (s OptionGroupBlockObject) validateType() MessageObjectType {
	return motOptionGroup
}

// NewOptionGroupBlockElement returns an instance of a new option group block element
func NewOptionGroupBlockElement(label *TextBlockObject, options ...*OptionBlockObject) *OptionGroupBlockObject {
	return &OptionGroupBlockObject{
		Label:   label,
		Options: options,
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 Igor Dolzhikov. All rights reserved.
// Use of this source code is governed by
// license that can be found in the LICENSE file.

package daemon

import (
	"errors"
	"os"
	"os/exec"
	"regexp"
	"text/template"
)

// systemVRecord - standard record (struct) for linux systemV version of daemon package
type systemVRecord struct {
	name        string
	description string
}

// Standard service path for systemV daemons
func (linux *systemVRecord) servicePath() string {
	return "/etc/init.d/" + linux.name
}

// Check service is installed
func (linux *systemVRecord) checkInstalled() bool {

	if _, err := os.Stat(linux.servicePath()); err == nil {
		return true
	}

	return false
}

// Check service is running
func (linux *systemVRecord) checkRunning() (string, bool) {
	output, err := exec.Command("service", linux.name, "status").Output()
	if err == nil {
		if matched, err := regexp.MatchString(linux.name, string(output)); err == nil && matched {
			reg := regexp.MustCompile("pid  ([0-9]+)")
			data := reg.FindStringSubmatch(string(output))
			if len(data) > 1 {
				return "Service (pid  " + data[1] + ") is running...", true
			}
			return "Service is running...", true
		}
	}

	return "Service is stoped", false
}

// Install the service
func (linux *systemVRecord) Install() (string, error) {
	installAction := "Install " + linux.description + ":"

	if checkPrivileges() == false {
		return installAction + failed, errors.New(rootPrivileges)
	}

	srvPath := linux.servicePath()

	if linux.checkInstalled() == true {
		return installAction + failed, errors.New(linux.description + " already installed")
	}

	file, err := os.Create(srvPath)
	if err != nil {
		return installAction + failed, err
	}
	defer file.Close()

	execPatch, err := executablePath(linux.name)
	if err != nil {
		return installAction + failed, err
	}

	templ, err := template.New("systemVConfig").Parse(systemVConfig)
	if err != nil {
		return installAction + failed, err
	}

	if err := templ.Execute(
		file,
		&struct {
			Name, Description, Path string
		}{linux.name, linux.description, execPatch},
	); err != nil {
		return installAction + failed, err
	}

	if err := os.Chmod(srvPath, 0755); err != nil {
		return installAction + failed, err
	}

	for _, i := range [...]string{"2", "3", "4", "5"} {
		if err := os.Symlink(srvPath, "/etc/rc"+i+".d/S87"+linux.name); err != nil {
			continue
		}
	}
	for _, i := range [...]string{"0", "1", "6"} {
		if err := os.Symlink(srvPath, "/etc/rc"+i+".d/K17"+linux.name); err != nil {
			continue
		}
	}

	return installAction + success, nil
}

// Remove the service
func (linux *systemVRecord) Remove() (string, error) {
	removeAction := "Removing " + linux.description + ":"

	if checkPrivileges() == false {
		return removeAction + failed, errors.New(rootPrivileges)
	}

	if linux.checkInstalled() == false {
		return removeAction + failed, errors.New(linux.description + " is not installed")
	}

	if err := os.Remove(linux.servicePath()); err != nil {
		return removeAction + failed, err
	}

	for _, i := range [...]string{"2", "3", "4", "5"} {
		if err := os.Remove("/etc/rc" + i + ".d/S87" + linux.name); err != nil {
			continue
		}
	}
	for _, i := range [...]string{"0", "1", "6"} {
		if err := os.Remove("/etc/rc" + i + ".d/K17" + linux.name); err != nil {
			continue
		}
	}

	return removeAction + success, nil
}

// Start the service
func (linux *systemVRecord) Start() (string, error) {
	startAction := "Starting " + linux.description + ":"

	if checkPrivileges() == false {
		return startAction + failed, errors.New(rootPrivileges)
	}

	if linux.checkInstalled() == false {
		return startAction + failed, errors.New(linux.description + " is not installed")
	}

	if _, status := linux.checkRunning(); status == true {
		return startAction + failed, errors.New("service already running")
	}

	if err := exec.Command("service", linux.name, "start").Run(); err != nil {
		return startAction + failed, err
	}

	return startAction + success, nil
}

// Stop the service
func (linux *systemVRecord) Stop() (string, error) {
	stopAction := "Stopping " + linux.description + ":"

	if checkPrivileges() == false {
		return stopAction + failed, errors.New(rootPrivileges)
	}

	if linux.checkInstalled() == false {
		return stopAction + failed, errors.New(linux.description + " is not installed")
	}

	if _, status := linux.checkRunning(); status == false {
		return stopAction + failed, errors.New("service already stopped")
	}

	if err := exec.Command("service", linux.name, "stop").Run(); err != nil {
		return stopAction + failed, err
	}

	return stopAction + success, nil
}

// Status - Get service status
func (linux *systemVRecord) Status() (string, error) {

	if checkPrivileges() == false {
		return "", errors.New(rootPrivileges)
	}

	if linux.checkInstalled() == false {
		return "Status could not defined", errors.New(linux.description + " is not installed")
	}

	statusAction, _ := linux.checkRunning()

	return statusAction, nil
}

var systemVConfig = `#! /bin/sh
#
#       /etc/rc.d/init.d/{{.Name}}
#
#       Starts {{.Name}} as a daemon
#
# chkconfig: 2345 87 17
# description: Starts and stops a single {{.Name}} instance on this system

### BEGIN INIT INFO
# Provides: {{.Name}}
# Required-Start: $network $named
# Required-Stop: $network $named
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: This service manages the {{.Description}}.
# Description: {{.Description}}
### END INIT INFO

#
# Source function library.
#
if [ -f /etc/rc.d/init.d/functions ]; then
    . /etc/rc.d/init.d/functions
fi

exec="{{.Path}}"
servname="{{.Description}}"

proc=$(basename $0)
pidfile="/var/run/$proc.pid"
lockfile="/var/lock/subsys/$proc"
stdoutlog="/var/log/$proc.log"
stderrlog="/var/log/$proc.err"

[ -e /etc/sysconfig/$proc ] && . /etc/sysconfig/$proc

start() {
    [ -x $exec ] || exit 5

    if ! [ -f $pidfile ]; then
        printf "Starting $servname:\t"
        echo "$(date)" >> $stdoutlog
        $exec >> $stderrlog 2>> $stdoutlog &
        echo $! > $pidfile
        touch $lockfile
        success
        echo
    else
        failure
        echo
        printf "$pidfile still exists...\n"
        exit 7
    fi
}

stop() {
    echo -n $"Stopping $servname: "
    killproc -p $pidfile $proc
    retval=$?
    echo
    [ $retval -eq 0 ] && rm -f $lockfile
    return $retval
}

restart() {
    stop
    start
}

rh_status() {
    status -p $pidfile $proc
}

rh_status_q() {
    rh_status >/dev/null 2>&1
}

case "$1" in
    start)
        rh_status_q && exit 0
        $1
        ;;
    stop)
        rh_status_q || exit 0
        $1
        ;;
    restart)
        $1
        ;;
    status)
        rh_status
        ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart}"
        exit 2
esac

exit $?
`
<commit_msg>Fixed folder does not exist in centos 6<commit_after>// Copyright 2014 Igor Dolzhikov. All rights reserved.
// Use of this source code is governed by
// license that can be found in the LICENSE file.

package daemon

import (
	"errors"
	"os"
	"os/exec"
	"regexp"
	"text/template"
)

// systemVRecord - standard record (struct) for linux systemV version of daemon package
type systemVRecord struct {
	name        string
	description string
}

// Standard service path for systemV daemons
func (linux *systemVRecord) servicePath() string {
	return "/etc/init.d/" + linux.name
}

// Check service is installed
func (linux *systemVRecord) checkInstalled() bool {

	if _, err := os.Stat(linux.servicePath()); err == nil {
		return true
	}

	return false
}

// Check service is running
func (linux *systemVRecord) checkRunning() (string, bool) {
	output, err := exec.Command("service", linux.name, "status").Output()
	if err == nil {
		if matched, err := regexp.MatchString(linux.name, string(output)); err == nil && matched {
			reg := regexp.MustCompile("pid  ([0-9]+)")
			data := reg.FindStringSubmatch(string(output))
			if len(data) > 1 {
				return "Service (pid  " + data[1] + ") is running...", true
			}
			return "Service is running...", true
		}
	}

	return "Service is stoped", false
}

// Install the service
func (linux *systemVRecord) Install() (string, error) {
	installAction := "Install " + linux.description + ":"

	if checkPrivileges() == false {
		return installAction + failed, errors.New(rootPrivileges)
	}

	srvPath := linux.servicePath()

	if linux.checkInstalled() == true {
		return installAction + failed, errors.New(linux.description + " already installed")
	}

	file, err := os.Create(srvPath)
	if err != nil {
		return installAction + failed, err
	}
	defer file.Close()

	execPatch, err := executablePath(linux.name)
	if err != nil {
		return installAction + failed, err
	}

	templ, err := template.New("systemVConfig").Parse(systemVConfig)
	if err != nil {
		return installAction + failed, err
	}

	if err := templ.Execute(
		file,
		&struct {
			Name, Description, Path string
		}{linux.name, linux.description, execPatch},
	); err != nil {
		return installAction + failed, err
	}

	if err := os.Chmod(srvPath, 0755); err != nil {
		return installAction + failed, err
	}

	for _, i := range [...]string{"2", "3", "4", "5"} {
		if err := os.Symlink(srvPath, "/etc/rc"+i+".d/S87"+linux.name); err != nil {
			continue
		}
	}
	for _, i := range [...]string{"0", "1", "6"} {
		if err := os.Symlink(srvPath, "/etc/rc"+i+".d/K17"+linux.name); err != nil {
			continue
		}
	}

	return installAction + success, nil
}

// Remove the service
func (linux *systemVRecord) Remove() (string, error) {
	removeAction := "Removing " + linux.description + ":"

	if checkPrivileges() == false {
		return removeAction + failed, errors.New(rootPrivileges)
	}

	if linux.checkInstalled() == false {
		return removeAction + failed, errors.New(linux.description + " is not installed")
	}

	if err := os.Remove(linux.servicePath()); err != nil {
		return removeAction + failed, err
	}

	for _, i := range [...]string{"2", "3", "4", "5"} {
		if err := os.Remove("/etc/rc" + i + ".d/S87" + linux.name); err != nil {
			continue
		}
	}
	for _, i := range [...]string{"0", "1", "6"} {
		if err := os.Remove("/etc/rc" + i + ".d/K17" + linux.name); err != nil {
			continue
		}
	}

	return removeAction + success, nil
}

// Start the service
func (linux *systemVRecord) Start() (string, error) {
	startAction := "Starting " + linux.description + ":"

	if checkPrivileges() == false {
		return startAction + failed, errors.New(rootPrivileges)
	}

	if linux.checkInstalled() == false {
		return startAction + failed, errors.New(linux.description + " is not installed")
	}

	if _, status := linux.checkRunning(); status == true {
		return startAction + failed, errors.New("service already running")
	}

	if err := exec.Command("service", linux.name, "start").Run(); err != nil {
		return startAction + failed, err
	}

	return startAction + success, nil
}

// Stop the service
func (linux *systemVRecord) Stop() (string, error) {
	stopAction := "Stopping " + linux.description + ":"

	if checkPrivileges() == false {
		return stopAction + failed, errors.New(rootPrivileges)
	}

	if linux.checkInstalled() == false {
		return stopAction + failed, errors.New(linux.description + " is not installed")
	}

	if _, status := linux.checkRunning(); status == false {
		return stopAction + failed, errors.New("service already stopped")
	}

	if err := exec.Command("service", linux.name, "stop").Run(); err != nil {
		return stopAction + failed, err
	}

	return stopAction + success, nil
}

// Status - Get service status
func (linux *systemVRecord) Status() (string, error) {

	if checkPrivileges() == false {
		return "", errors.New(rootPrivileges)
	}

	if linux.checkInstalled() == false {
		return "Status could not defined", errors.New(linux.description + " is not installed")
	}

	statusAction, _ := linux.checkRunning()

	return statusAction, nil
}

var systemVConfig = `#! /bin/sh
#
#       /etc/rc.d/init.d/{{.Name}}
#
#       Starts {{.Name}} as a daemon
#
# chkconfig: 2345 87 17
# description: Starts and stops a single {{.Name}} instance on this system

### BEGIN INIT INFO
# Provides: {{.Name}}
# Required-Start: $network $named
# Required-Stop: $network $named
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: This service manages the {{.Description}}.
# Description: {{.Description}}
### END INIT INFO

#
# Source function library.
#
if [ -f /etc/rc.d/init.d/functions ]; then
    . /etc/rc.d/init.d/functions
fi

exec="{{.Path}}"
servname="{{.Description}}"
mkdir -p /var/lock/subsys
proc=$(basename $0)
pidfile="/var/run/$proc.pid"
lockfile="/var/lock/subsys/$proc"
stdoutlog="/var/log/$proc.log"
stderrlog="/var/log/$proc.err"

[ -e /etc/sysconfig/$proc ] && . /etc/sysconfig/$proc

start() {
    [ -x $exec ] || exit 5

    if ! [ -f $pidfile ]; then
        printf "Starting $servname:\t"
        echo "$(date)" >> $stdoutlog
        $exec >> $stderrlog 2>> $stdoutlog &
        echo $! > $pidfile
        touch $lockfile
        success
        echo
    else
        failure
        echo
        printf "$pidfile still exists...\n"
        exit 7
    fi
}

stop() {
    echo -n $"Stopping $servname: "
    killproc -p $pidfile $proc
    retval=$?
    echo
    [ $retval -eq 0 ] && rm -f $lockfile
    return $retval
}

restart() {
    stop
    start
}

rh_status() {
    status -p $pidfile $proc
}

rh_status_q() {
    rh_status >/dev/null 2>&1
}

case "$1" in
    start)
        rh_status_q && exit 0
        $1
        ;;
    stop)
        rh_status_q || exit 0
        $1
        ;;
    restart)
        $1
        ;;
    status)
        rh_status
        ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart}"
        exit 2
esac

exit $?
`
<|endoftext|>


<commit_before>// cubied manages processes as per the cubie configuration manifest.
package main

import (
	"encoding/json"
	"flag"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"

	"golang.org/x/sys/unix"
)

// PR_SET_CHILD_SUBREAPER is defined in <sys/prctl.h> for linux >= 3.4
const PR_SET_CHILD_SUBREAPER = 36

var (
	confDir = flag.String("conf", "/etc/cubie.d", "directory holding cubie configuration files")

	ctl = NewControl()
)

func main() {
	flag.Parse()

	log.Printf("::: starting cubie-daemon [%v]...\n", Version)

	if os.Getpid() != 1 {
		// try to register as a subreaper
		err := unix.Prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0)
		log.Printf("subreaper: %v\n", err == nil)
	}

	confs, err := filepath.Glob(filepath.Join(*confDir, "*.conf"))
	if err != nil {
		log.Fatalf("could not collect configuration files: %v\n", err)
	}

	for _, fname := range confs {
		f, err := os.Open(fname)
		if err != nil {
			log.Fatalf("could not open configuration file [%s]: %v\n", fname, err)
		}
		defer f.Close()

		var proc Process
		err = json.NewDecoder(f).Decode(&proc)
		if err != nil {
			log.Fatalf(
				"could not decode configuration file [%s]: %v\n",
				fname,
				err,
			)
		}

		ctl.procs = append(ctl.procs, &proc)
	}

	if flag.NArg() > 0 {
		ctl.cmd = newProcess(flag.Args()...)
	}

	go func() {
		sigch := make(chan os.Signal)
		signal.Notify(sigch, os.Interrupt, os.Kill)
		for {
			select {
			case <-sigch:
				err = ctl.killProcs()
				if err != nil {
					log.Fatalf("error killing managed processes: %v\n", err)
				}
			}
		}
	}()

	ctl.run()

	os.Exit(0)
}

type procKeyType int

var procKey procKeyType = 0

// Process represents a process to be launched and how it needs to be launched.
type Process struct {
	Cmd    string            // Cmd is the name of the command to be launched
	Args   []string          // Args holds command-line arguments, excluding the command name
	Env    map[string]string // Env specifies the environment of the command
	Dir    string            // Dir specifies the working directory of the command
	Daemon bool

	proc *exec.Cmd
	err  error
	quit chan struct{}
}

// newProcess creates a new Process from a command and its (optional) arguments.
func newProcess(cmd ...string) *Process {
	proc := &Process{
		Cmd: cmd[0],
	}

	if len(cmd) > 1 {
		proc.Args = append(proc.Args, cmd[1:]...)
	}

	return proc
}

func (p *Process) title() string {
	return strings.Join(p.proc.Args, " ")
}

func (p *Process) wait() error {
	var err1 error
	var status syscall.WaitStatus
	_, err1 = syscall.Wait4(p.proc.Process.Pid, &status, syscall.WALL|0, nil)
	return err1
}

func (p *Process) run(done chan *Process) {
	p.proc.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
	}

	defer func() { done <- p }()
	log.Printf("starting [%s]...\n", p.title())
	p.err = p.proc.Start()
	if p.err != nil {
		return
	}

	p.err = p.wait()
	log.Printf("running [%s]... [done]\n", p.title())
	return
}

// Control runs and manages processes.
type Control struct {
	pid   int
	cmd   *Process
	procs []*Process
	quit  chan struct{}
}

// NewControl creates a Control handle.
func NewControl() *Control {
	ctl := &Control{
		pid:   os.Getpid(),
		procs: make([]*Process, 0, 2),
		quit:  make(chan struct{}),
	}
	return ctl
}

// run runs all processes according to cubie configuration.
func (ctl *Control) run() {
	n := 0
	for _, p := range ctl.procs {
		if !p.Daemon {
			n++
		}
	}

	var wg sync.WaitGroup
	wg.Add(n)
	nprocs := ""
	if n > 1 {
		nprocs = "es"
	}
	log.Printf("starting %d go-process%s...\n", n, nprocs)

	go ctl.runProcs(&wg)
	wg.Wait()

	log.Printf("now running main go-process...\n")
	ctl.runCmd()
}

func (ctl *Control) setupProc(proc *Process) {
	proc.proc = exec.Command(proc.Cmd, proc.Args...)
	if len(proc.Env) > 0 {
		env := make([]string, 0, len(proc.Env))
		for k, v := range proc.Env {
			env = append(env, k+"="+v)
		}
		proc.proc.Env = env
	}

	if proc.Dir != "" {
		proc.proc.Dir = proc.Dir
	}

	proc.proc.Stdout = os.Stdout
	proc.proc.Stderr = os.Stderr
	proc.proc.Stdin = os.Stdin
}

func (ctl *Control) runProcs(wg *sync.WaitGroup) {

	done := make(chan *Process)
	for _, p := range ctl.procs {
		ctl.setupProc(p)
		go p.run(done)
	}

	sigch := make(chan os.Signal)
	signal.Notify(sigch, syscall.SIGCHLD)

reaploop:
	for {
		select {
		case <-ctl.quit:
			ctl.killProcs()
			return

		case <-sigch:
			const nretries = 1000
			var ws syscall.WaitStatus
			for i := 0; i < nretries; i++ {
				pid, err := syscall.Wait4(ctl.pid, &ws, syscall.WNOHANG, nil)
				// pid > 0 => pid is the ID of the child that died, but
				//  there could be other children that are signalling us
				//  and not the one we in particular are waiting for.
				// pid -1 && errno == ECHILD => no new status children
				// pid -1 && errno != ECHILD => syscall interupped by signal
				// pid == 0 => no more children to wait for.
				switch {
				case err != nil:
					continue reaploop
				case pid == ctl.pid:
					return
				case pid == 0:
					// this is what we get when SIGSTOP is sent on OSX. ws == 0 in this case.
					// Note that on OSX we never get a SIGCONT signal.
					// Under WNOHANG, pid == 0 means there is nobody left to wait for,
					// so just go back to waiting for another SIGCHLD.
					continue reaploop
				default:
					time.Sleep(time.Millisecond)
				}
			}
			log.Fatalf("failed to reap children after [%d] retries\n", nretries)

		case p := <-done:
			i := -1
			for j, pp := range ctl.procs {
				if pp == p {
					i = j
					break
				}
			}
			if i < 0 {
				panic("impossible")
			}

			nprocs := len(ctl.procs)
			ctl.procs[nprocs-1], ctl.procs = nil, append(ctl.procs[:i], ctl.procs[i+1:]...)
			ctl.procs = ctl.procs[:len(ctl.procs):len(ctl.procs)]
			wg.Done()
			if p.err != nil {
				ctl.killProcs()
				log.Fatalf("error running process [%v]: %v\n", p.title(), p.err)
			}
			if len(ctl.procs) <= 0 {
				return
			}
		}
	}
}

func (ctl *Control) runCmd() {
	if ctl.cmd == nil {
		return
	}
	done := make(chan *Process)
	ctl.setupProc(ctl.cmd)
	go ctl.cmd.run(done)

	for {
		select {
		case <-ctl.quit:
			ctl.killProcs()
			return

		case p := <-done:
			if p.err != nil {
				ctl.killProcs()
				log.Fatalf("error running process [%v]: %v\n", p.title(), p.err)
			}
			return
		}
	}
}

func (ctl *Control) killProcs() error {
	var err error
	for _, p := range ctl.procs {
		if p.proc == nil {
			continue
		}
		errp := killProc(p.proc.Process)
		if errp != nil && err != nil {
			err = errp
		}
	}
	if ctl.cmd != nil && ctl.cmd.proc != nil {
		errp := killProc(ctl.cmd.proc.Process)
		if err != nil && err != nil {
			err = errp
		}
	}
	return err
}

func killProc(p *os.Process) error {
	if p == nil {
		return nil
	}
	pgid, err := syscall.Getpgid(p.Pid)
	if err != nil {
		return err
	}
	err = syscall.Kill(-pgid, syscall.SIGKILL) // note the minus sign
	return err
}
<commit_msg>cubied: fix non-daemon scheduling<commit_after>// cubied manages processes as per the cubie configuration manifest.
package main

import (
	"encoding/json"
	"flag"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"

	"golang.org/x/sys/unix"
)

// PR_SET_CHILD_SUBREAPER is defined in <sys/prctl.h> for linux >= 3.4
const PR_SET_CHILD_SUBREAPER = 36

var (
	confDir = flag.String("conf", "/etc/cubie.d", "directory holding cubie configuration files")

	ctl = NewControl()
)

func main() {
	flag.Parse()

	log.Printf("::: starting cubie-daemon [%v]...\n", Version)

	if os.Getpid() != 1 {
		// try to register as a subreaper
		err := unix.Prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0)
		log.Printf("subreaper: %v\n", err == nil)
	}

	confs, err := filepath.Glob(filepath.Join(*confDir, "*.conf"))
	if err != nil {
		log.Fatalf("could not collect configuration files: %v\n", err)
	}

	for _, fname := range confs {
		f, err := os.Open(fname)
		if err != nil {
			log.Fatalf("could not open configuration file [%s]: %v\n", fname, err)
		}
		defer f.Close()

		var proc Process
		err = json.NewDecoder(f).Decode(&proc)
		if err != nil {
			log.Fatalf(
				"could not decode configuration file [%s]: %v\n",
				fname,
				err,
			)
		}

		ctl.procs = append(ctl.procs, &proc)
	}

	if flag.NArg() > 0 {
		ctl.cmd = newProcess(flag.Args()...)
	}

	go func() {
		sigch := make(chan os.Signal)
		signal.Notify(sigch, os.Interrupt, os.Kill)
		for {
			select {
			case <-sigch:
				err = ctl.killProcs()
				if err != nil {
					log.Fatalf("error killing managed processes: %v\n", err)
				}
			}
		}
	}()

	ctl.run()

	os.Exit(0)
}

type procKeyType int

var procKey procKeyType = 0

// Process represents a process to be launched and how it needs to be launched.
type Process struct {
	Cmd    string            // Cmd is the name of the command to be launched
	Args   []string          // Args holds command-line arguments, excluding the command name
	Env    map[string]string // Env specifies the environment of the command
	Dir    string            // Dir specifies the working directory of the command
	Daemon bool

	proc *exec.Cmd
	err  error
	quit chan struct{}
}

// newProcess creates a new Process from a command and its (optional) arguments.
func newProcess(cmd ...string) *Process {
	proc := &Process{
		Cmd: cmd[0],
	}

	if len(cmd) > 1 {
		proc.Args = append(proc.Args, cmd[1:]...)
	}

	return proc
}

func (p *Process) title() string {
	return strings.Join(p.proc.Args, " ")
}

func (p *Process) run(done chan *Process) {
	p.proc.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
	}

	defer func() { done <- p }()

	if !p.Daemon {
		log.Printf("running [%s]...\n", p.title())
		p.err = p.proc.Run()
		log.Printf("running [%s]... [done]\n", p.title())
	} else {
		log.Printf("starting [%s]...\n", p.title())
		p.err = p.proc.Start()
		if p.err != nil {
			return
		}

		p.err = p.proc.Wait()
		log.Printf("running [%s]... [done]\n", p.title())
	}
	return
}

// Control runs and manages processes.
type Control struct {
	pid   int
	cmd   *Process
	procs []*Process
	quit  chan struct{}
}

// NewControl creates a Control handle.
func NewControl() *Control {
	ctl := &Control{
		pid:   os.Getpid(),
		procs: make([]*Process, 0, 2),
		quit:  make(chan struct{}),
	}
	return ctl
}

// run runs all processes according to cubie configuration.
func (ctl *Control) run() {
	n := 0
	for _, p := range ctl.procs {
		if !p.Daemon {
			n++
		}
	}

	var wg sync.WaitGroup
	wg.Add(n)
	nprocs := ""
	if n > 1 {
		nprocs = "es"
	}
	log.Printf("starting %d go-process%s...\n", n, nprocs)

	go ctl.runProcs(&wg)
	wg.Wait()

	log.Printf("now running main go-process...\n")
	ctl.runCmd()
}

func (ctl *Control) setupProc(proc *Process) {
	proc.proc = exec.Command(proc.Cmd, proc.Args...)
	if len(proc.Env) > 0 {
		env := make([]string, 0, len(proc.Env))
		for k, v := range proc.Env {
			env = append(env, k+"="+v)
		}
		proc.proc.Env = env
	}

	if proc.Dir != "" {
		proc.proc.Dir = proc.Dir
	}

	proc.proc.Stdout = os.Stdout
	proc.proc.Stderr = os.Stderr
	proc.proc.Stdin = os.Stdin
}

func (ctl *Control) runProcs(wg *sync.WaitGroup) {

	done := make(chan *Process)
	for _, p := range ctl.procs {
		ctl.setupProc(p)
		go p.run(done)
	}

	sigch := make(chan os.Signal)
	signal.Notify(sigch, syscall.SIGCHLD)

reaploop:
	for {
		select {
		case <-ctl.quit:
			ctl.killProcs()
			return

		case <-sigch:
			const nretries = 1000
			var ws syscall.WaitStatus
			for i := 0; i < nretries; i++ {
				pid, err := syscall.Wait4(ctl.pid, &ws, syscall.WNOHANG, nil)
				// pid > 0 => pid is the ID of the child that died, but
				//  there could be other children that are signalling us
				//  and not the one we in particular are waiting for.
				// pid -1 && errno == ECHILD => no new status children
				// pid -1 && errno != ECHILD => syscall interupped by signal
				// pid == 0 => no more children to wait for.
				switch {
				case err != nil:
					continue reaploop
				case pid == ctl.pid:
					return
				case pid == 0:
					// this is what we get when SIGSTOP is sent on OSX. ws == 0 in this case.
					// Note that on OSX we never get a SIGCONT signal.
					// Under WNOHANG, pid == 0 means there is nobody left to wait for,
					// so just go back to waiting for another SIGCHLD.
					continue reaploop
				default:
					time.Sleep(time.Millisecond)
				}
			}
			log.Fatalf("failed to reap children after [%d] retries\n", nretries)

		case p := <-done:
			i := -1
			for j, pp := range ctl.procs {
				if pp == p {
					i = j
					break
				}
			}
			if i < 0 {
				panic("impossible")
			}

			nprocs := len(ctl.procs)
			ctl.procs[nprocs-1], ctl.procs = nil, append(ctl.procs[:i], ctl.procs[i+1:]...)
			ctl.procs = ctl.procs[:len(ctl.procs):len(ctl.procs)]
			wg.Done()
			if p.err != nil {
				ctl.killProcs()
				log.Fatalf("error running process [%v]: %v\n", p.title(), p.err)
			}
			if len(ctl.procs) <= 0 {
				return
			}
		}
	}
}

func (ctl *Control) runCmd() {
	if ctl.cmd == nil {
		return
	}
	done := make(chan *Process)
	ctl.setupProc(ctl.cmd)
	go ctl.cmd.run(done)

	for {
		select {
		case <-ctl.quit:
			ctl.killProcs()
			return

		case p := <-done:
			if p.err != nil {
				ctl.killProcs()
				log.Fatalf("error running process [%v]: %v\n", p.title(), p.err)
			}
			return
		}
	}
}

func (ctl *Control) killProcs() error {
	var err error
	for _, p := range ctl.procs {
		if p.proc == nil {
			continue
		}
		errp := killProc(p.proc.Process)
		if errp != nil && err != nil {
			err = errp
		}
	}
	if ctl.cmd != nil && ctl.cmd.proc != nil {
		errp := killProc(ctl.cmd.proc.Process)
		if err != nil && err != nil {
			err = errp
		}
	}
	return err
}

func killProc(p *os.Process) error {
	if p == nil {
		return nil
	}
	pgid, err := syscall.Getpgid(p.Pid)
	if err != nil {
		return err
	}
	err = syscall.Kill(-pgid, syscall.SIGKILL) // note the minus sign
	return err
}
<|endoftext|>


<commit_before>// Copyright (c) 2014-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package bloom

import (
	"encoding/binary"
	"math"
	"sync"

	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcutil"
)

// ln2Squared is simply the square of the natural log of 2.
const ln2Squared = math.Ln2 * math.Ln2

// minUint32 is a convenience function to return the minimum value of the two
// passed uint32 values.
func minUint32(a, b uint32) uint32 {
	if a < b {
		return a
	}
	return b
}

// Filter defines a bitcoin bloom filter that provides easy manipulation of raw
// filter data.
type Filter struct {
	mtx           sync.Mutex
	msgFilterLoad *wire.MsgFilterLoad
}

// NewFilter creates a new bloom filter instance, mainly to be used by SPV
// clients.  The tweak parameter is a random value added to the seed value.
// The false positive rate is the probability of a false positive where 1.0 is
// "match everything" and zero is unachievable.  Thus, providing any false
// positive rates less than 0 or greater than 1 will be adjusted to the valid
// range.
//
// For more information on what values to use for both elements and fprate,
// see https://en.wikipedia.org/wiki/Bloom_filter.
func NewFilter(elements, tweak uint32, fprate float64, flags wire.BloomUpdateType) *Filter {
	// Massage the false positive rate to sane values.
	if fprate > 1.0 {
		fprate = 1.0
	}
	if fprate < 1e-9 {
		fprate = 1e-9
	}

	// Calculate the size of the filter in bytes for the given number of
	// elements and false positive rate.
	//
	// Equivalent to m = -(n*ln(p) / ln(2)^2), where m is in bits.
	// Then clamp it to the maximum filter size and convert to bytes.
	dataLen := uint32(-1 * float64(elements) * math.Log(fprate) / ln2Squared)
	dataLen = minUint32(dataLen, wire.MaxFilterLoadFilterSize*8) / 8

	// Calculate the number of hash functions based on the size of the
	// filter calculated above and the number of elements.
	//
	// Equivalent to k = (m/n) * ln(2)
	// Then clamp it to the maximum allowed hash funcs.
	hashFuncs := uint32(float64(dataLen*8) / float64(elements) * math.Ln2)
	hashFuncs = minUint32(hashFuncs, wire.MaxFilterLoadHashFuncs)

	data := make([]byte, dataLen)
	msg := wire.NewMsgFilterLoad(data, hashFuncs, tweak, flags)

	return &Filter{
		msgFilterLoad: msg,
	}
}

// LoadFilter creates a new Filter instance with the given underlying
// wire.MsgFilterLoad.
func LoadFilter(filter *wire.MsgFilterLoad) *Filter {
	return &Filter{
		msgFilterLoad: filter,
	}
}

// IsLoaded returns true if a filter is loaded, otherwise false.
//
// This function is safe for concurrent access.
func (bf *Filter) IsLoaded() bool {
	bf.mtx.Lock()
	loaded := bf.msgFilterLoad != nil
	bf.mtx.Unlock()
	return loaded
}

// Reload loads a new filter replacing any existing filter.
//
// This function is safe for concurrent access.
func (bf *Filter) Reload(filter *wire.MsgFilterLoad) {
	bf.mtx.Lock()
	bf.msgFilterLoad = filter
	bf.mtx.Unlock()
}

// Unload unloads the bloom filter.
//
// This function is safe for concurrent access.
func (bf *Filter) Unload() {
	bf.mtx.Lock()
	bf.msgFilterLoad = nil
	bf.mtx.Unlock()
}

// hash returns the bit offset in the bloom filter which corresponds to the
// passed data for the given indepedent hash function number.
func (bf *Filter) hash(hashNum uint32, data []byte) uint32 {
	// bitcoind: 0xfba4c795 chosen as it guarantees a reasonable bit
	// difference between hashNum values.
	//
	// Note that << 3 is equivalent to multiplying by 8, but is faster.
	// Thus the returned hash is brought into range of the number of bits
	// the filter has and returned.
	mm := MurmurHash3(hashNum*0xfba4c795+bf.msgFilterLoad.Tweak, data)
	return mm % (uint32(len(bf.msgFilterLoad.Filter)) << 3)
}

// matches returns true if the bloom filter might contain the passed data and
// false if it definitely does not.
//
// This function MUST be called with the filter lock held.
func (bf *Filter) matches(data []byte) bool {
	if bf.msgFilterLoad == nil {
		return false
	}

	// The bloom filter does not contain the data if any of the bit offsets
	// which result from hashing the data using each independent hash
	// function are not set.  The shifts and masks below are a faster
	// equivalent of:
	//   arrayIndex := idx / 8     (idx >> 3)
	//   bitOffset := idx % 8      (idx & 7)
	///  if filter[arrayIndex] & 1<<bitOffset == 0 { ... }
	for i := uint32(0); i < bf.msgFilterLoad.HashFuncs; i++ {
		idx := bf.hash(i, data)
		if bf.msgFilterLoad.Filter[idx>>3]&(1<<(idx&7)) == 0 {
			return false
		}
	}
	return true
}

// Matches returns true if the bloom filter might contain the passed data and
// false if it definitely does not.
//
// This function is safe for concurrent access.
func (bf *Filter) Matches(data []byte) bool {
	bf.mtx.Lock()
	match := bf.matches(data)
	bf.mtx.Unlock()
	return match
}

// matchesOutPoint returns true if the bloom filter might contain the passed
// outpoint and false if it definitely does not.
//
// This function MUST be called with the filter lock held.
func (bf *Filter) matchesOutPoint(outpoint *wire.OutPoint) bool {
	// Serialize
	var buf [chainhash.HashSize + 4]byte
	copy(buf[:], outpoint.Hash[:])
	binary.LittleEndian.PutUint32(buf[chainhash.HashSize:], outpoint.Index)

	return bf.matches(buf[:])
}

// MatchesOutPoint returns true if the bloom filter might contain the passed
// outpoint and false if it definitely does not.
//
// This function is safe for concurrent access.
func (bf *Filter) MatchesOutPoint(outpoint *wire.OutPoint) bool {
	bf.mtx.Lock()
	match := bf.matchesOutPoint(outpoint)
	bf.mtx.Unlock()
	return match
}

// add adds the passed byte slice to the bloom filter.
//
// This function MUST be called with the filter lock held.
func (bf *Filter) add(data []byte) {
	if bf.msgFilterLoad == nil {
		return
	}

	// Adding data to a bloom filter consists of setting all of the bit
	// offsets which result from hashing the data using each independent
	// hash function.  The shifts and masks below are a faster equivalent
	// of:
	//   arrayIndex := idx / 8    (idx >> 3)
	//   bitOffset := idx % 8     (idx & 7)
	///  filter[arrayIndex] |= 1<<bitOffset
	for i := uint32(0); i < bf.msgFilterLoad.HashFuncs; i++ {
		idx := bf.hash(i, data)
		bf.msgFilterLoad.Filter[idx>>3] |= (1 << (7 & idx))
	}
}

// Add adds the passed byte slice to the bloom filter.
//
// This function is safe for concurrent access.
func (bf *Filter) Add(data []byte) {
	bf.mtx.Lock()
	bf.add(data)
	bf.mtx.Unlock()
}

// AddHash adds the passed chainhash.Hash to the Filter.
//
// This function is safe for concurrent access.
func (bf *Filter) AddHash(hash *chainhash.Hash) {
	bf.mtx.Lock()
	bf.add(hash[:])
	bf.mtx.Unlock()
}

// addOutPoint adds the passed transaction outpoint to the bloom filter.
//
// This function MUST be called with the filter lock held.
func (bf *Filter) addOutPoint(outpoint *wire.OutPoint) {
	// Serialize
	var buf [chainhash.HashSize + 4]byte
	copy(buf[:], outpoint.Hash[:])
	binary.LittleEndian.PutUint32(buf[chainhash.HashSize:], outpoint.Index)

	bf.add(buf[:])
}

// AddOutPoint adds the passed transaction outpoint to the bloom filter.
//
// This function is safe for concurrent access.
func (bf *Filter) AddOutPoint(outpoint *wire.OutPoint) {
	bf.mtx.Lock()
	bf.addOutPoint(outpoint)
	bf.mtx.Unlock()
}

// maybeAddOutpoint potentially adds the passed outpoint to the bloom filter
// depending on the bloom update flags and the type of the passed public key
// script.
//
// This function MUST be called with the filter lock held.
func (bf *Filter) maybeAddOutpoint(pkScript []byte, outHash *chainhash.Hash, outIdx uint32) {
	switch bf.msgFilterLoad.Flags {
	case wire.BloomUpdateAll:
		outpoint := wire.NewOutPoint(outHash, outIdx)
		bf.addOutPoint(outpoint)
	case wire.BloomUpdateP2PubkeyOnly:
		class := txscript.GetScriptClass(pkScript)
		if class == txscript.PubKeyTy || class == txscript.MultiSigTy {
			outpoint := wire.NewOutPoint(outHash, outIdx)
			bf.addOutPoint(outpoint)
		}
	}
}

// matchTxAndUpdate returns true if the bloom filter matches data within the
// passed transaction, otherwise false is returned.  If the filter does match
// the passed transaction, it will also update the filter depending on the bloom
// update flags set via the loaded filter if needed.
//
// This function MUST be called with the filter lock held.
func (bf *Filter) matchTxAndUpdate(tx *btcutil.Tx) bool {
	// Check if the filter matches the hash of the transaction.
	// This is useful for finding transactions when they appear in a block.
	matched := bf.matches(tx.Hash()[:])

	// Check if the filter matches any data elements in the public key
	// scripts of any of the outputs.  When it does, add the outpoint that
	// matched so transactions which spend from the matched transaction are
	// also included in the filter.  This removes the burden of updating the
	// filter for this scenario from the client.  It is also more efficient
	// on the network since it avoids the need for another filteradd message
	// from the client and avoids some potential races that could otherwise
	// occur.
	for i, txOut := range tx.MsgTx().TxOut {
		pushedData, err := txscript.PushedData(txOut.PkScript)
		if err != nil {
			continue
		}

		for _, data := range pushedData {
			if !bf.matches(data) {
				continue
			}

			matched = true
			bf.maybeAddOutpoint(txOut.PkScript, tx.Hash(), uint32(i))
			break
		}
	}

	// Nothing more to do if a match has already been made.
	if matched {
		return true
	}

	// At this point, the transaction and none of the data elements in the
	// public key scripts of its outputs matched.

	// Check if the filter matches any outpoints this transaction spends or
	// any any data elements in the signature scripts of any of the inputs.
	for _, txin := range tx.MsgTx().TxIn {
		if bf.matchesOutPoint(&txin.PreviousOutPoint) {
			return true
		}

		pushedData, err := txscript.PushedData(txin.SignatureScript)
		if err != nil {
			continue
		}
		for _, data := range pushedData {
			if bf.matches(data) {
				return true
			}
		}
	}

	return false
}

// MatchTxAndUpdate returns true if the bloom filter matches data within the
// passed transaction, otherwise false is returned.  If the filter does match
// the passed transaction, it will also update the filter depending on the bloom
// update flags set via the loaded filter if needed.
//
// This function is safe for concurrent access.
func (bf *Filter) MatchTxAndUpdate(tx *btcutil.Tx) bool {
	bf.mtx.Lock()
	match := bf.matchTxAndUpdate(tx)
	bf.mtx.Unlock()
	return match
}

// MsgFilterLoad returns the underlying wire.MsgFilterLoad for the bloom
// filter.
//
// This function is safe for concurrent access.
func (bf *Filter) MsgFilterLoad() *wire.MsgFilterLoad {
	bf.mtx.Lock()
	msg := bf.msgFilterLoad
	bf.mtx.Unlock()
	return msg
}
<commit_msg>Fix typo<commit_after>// Copyright (c) 2014-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package bloom

import (
	"encoding/binary"
	"math"
	"sync"

	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcutil"
)

// ln2Squared is simply the square of the natural log of 2.
const ln2Squared = math.Ln2 * math.Ln2

// minUint32 is a convenience function to return the minimum value of the two
// passed uint32 values.
func minUint32(a, b uint32) uint32 {
	if a < b {
		return a
	}
	return b
}

// Filter defines a bitcoin bloom filter that provides easy manipulation of raw
// filter data.
type Filter struct {
	mtx           sync.Mutex
	msgFilterLoad *wire.MsgFilterLoad
}

// NewFilter creates a new bloom filter instance, mainly to be used by SPV
// clients.  The tweak parameter is a random value added to the seed value.
// The false positive rate is the probability of a false positive where 1.0 is
// "match everything" and zero is unachievable.  Thus, providing any false
// positive rates less than 0 or greater than 1 will be adjusted to the valid
// range.
//
// For more information on what values to use for both elements and fprate,
// see https://en.wikipedia.org/wiki/Bloom_filter.
func NewFilter(elements, tweak uint32, fprate float64, flags wire.BloomUpdateType) *Filter {
	// Massage the false positive rate to sane values.
	if fprate > 1.0 {
		fprate = 1.0
	}
	if fprate < 1e-9 {
		fprate = 1e-9
	}

	// Calculate the size of the filter in bytes for the given number of
	// elements and false positive rate.
	//
	// Equivalent to m = -(n*ln(p) / ln(2)^2), where m is in bits.
	// Then clamp it to the maximum filter size and convert to bytes.
	dataLen := uint32(-1 * float64(elements) * math.Log(fprate) / ln2Squared)
	dataLen = minUint32(dataLen, wire.MaxFilterLoadFilterSize*8) / 8

	// Calculate the number of hash functions based on the size of the
	// filter calculated above and the number of elements.
	//
	// Equivalent to k = (m/n) * ln(2)
	// Then clamp it to the maximum allowed hash funcs.
	hashFuncs := uint32(float64(dataLen*8) / float64(elements) * math.Ln2)
	hashFuncs = minUint32(hashFuncs, wire.MaxFilterLoadHashFuncs)

	data := make([]byte, dataLen)
	msg := wire.NewMsgFilterLoad(data, hashFuncs, tweak, flags)

	return &Filter{
		msgFilterLoad: msg,
	}
}

// LoadFilter creates a new Filter instance with the given underlying
// wire.MsgFilterLoad.
func LoadFilter(filter *wire.MsgFilterLoad) *Filter {
	return &Filter{
		msgFilterLoad: filter,
	}
}

// IsLoaded returns true if a filter is loaded, otherwise false.
//
// This function is safe for concurrent access.
func (bf *Filter) IsLoaded() bool {
	bf.mtx.Lock()
	loaded := bf.msgFilterLoad != nil
	bf.mtx.Unlock()
	return loaded
}

// Reload loads a new filter replacing any existing filter.
//
// This function is safe for concurrent access.
func (bf *Filter) Reload(filter *wire.MsgFilterLoad) {
	bf.mtx.Lock()
	bf.msgFilterLoad = filter
	bf.mtx.Unlock()
}

// Unload unloads the bloom filter.
//
// This function is safe for concurrent access.
func (bf *Filter) Unload() {
	bf.mtx.Lock()
	bf.msgFilterLoad = nil
	bf.mtx.Unlock()
}

// hash returns the bit offset in the bloom filter which corresponds to the
// passed data for the given independent hash function number.
func (bf *Filter) hash(hashNum uint32, data []byte) uint32 {
	// bitcoind: 0xfba4c795 chosen as it guarantees a reasonable bit
	// difference between hashNum values.
	//
	// Note that << 3 is equivalent to multiplying by 8, but is faster.
	// Thus the returned hash is brought into range of the number of bits
	// the filter has and returned.
	mm := MurmurHash3(hashNum*0xfba4c795+bf.msgFilterLoad.Tweak, data)
	return mm % (uint32(len(bf.msgFilterLoad.Filter)) << 3)
}

// matches returns true if the bloom filter might contain the passed data and
// false if it definitely does not.
//
// This function MUST be called with the filter lock held.
func (bf *Filter) matches(data []byte) bool {
	if bf.msgFilterLoad == nil {
		return false
	}

	// The bloom filter does not contain the data if any of the bit offsets
	// which result from hashing the data using each independent hash
	// function are not set.  The shifts and masks below are a faster
	// equivalent of:
	//   arrayIndex := idx / 8     (idx >> 3)
	//   bitOffset := idx % 8      (idx & 7)
	///  if filter[arrayIndex] & 1<<bitOffset == 0 { ... }
	for i := uint32(0); i < bf.msgFilterLoad.HashFuncs; i++ {
		idx := bf.hash(i, data)
		if bf.msgFilterLoad.Filter[idx>>3]&(1<<(idx&7)) == 0 {
			return false
		}
	}
	return true
}

// Matches returns true if the bloom filter might contain the passed data and
// false if it definitely does not.
//
// This function is safe for concurrent access.
func (bf *Filter) Matches(data []byte) bool {
	bf.mtx.Lock()
	match := bf.matches(data)
	bf.mtx.Unlock()
	return match
}

// matchesOutPoint returns true if the bloom filter might contain the passed
// outpoint and false if it definitely does not.
//
// This function MUST be called with the filter lock held.
func (bf *Filter) matchesOutPoint(outpoint *wire.OutPoint) bool {
	// Serialize
	var buf [chainhash.HashSize + 4]byte
	copy(buf[:], outpoint.Hash[:])
	binary.LittleEndian.PutUint32(buf[chainhash.HashSize:], outpoint.Index)

	return bf.matches(buf[:])
}

// MatchesOutPoint returns true if the bloom filter might contain the passed
// outpoint and false if it definitely does not.
//
// This function is safe for concurrent access.
func (bf *Filter) MatchesOutPoint(outpoint *wire.OutPoint) bool {
	bf.mtx.Lock()
	match := bf.matchesOutPoint(outpoint)
	bf.mtx.Unlock()
	return match
}

// add adds the passed byte slice to the bloom filter.
//
// This function MUST be called with the filter lock held.
func (bf *Filter) add(data []byte) {
	if bf.msgFilterLoad == nil {
		return
	}

	// Adding data to a bloom filter consists of setting all of the bit
	// offsets which result from hashing the data using each independent
	// hash function.  The shifts and masks below are a faster equivalent
	// of:
	//   arrayIndex := idx / 8    (idx >> 3)
	//   bitOffset := idx % 8     (idx & 7)
	///  filter[arrayIndex] |= 1<<bitOffset
	for i := uint32(0); i < bf.msgFilterLoad.HashFuncs; i++ {
		idx := bf.hash(i, data)
		bf.msgFilterLoad.Filter[idx>>3] |= (1 << (7 & idx))
	}
}

// Add adds the passed byte slice to the bloom filter.
//
// This function is safe for concurrent access.
func (bf *Filter) Add(data []byte) {
	bf.mtx.Lock()
	bf.add(data)
	bf.mtx.Unlock()
}

// AddHash adds the passed chainhash.Hash to the Filter.
//
// This function is safe for concurrent access.
func (bf *Filter) AddHash(hash *chainhash.Hash) {
	bf.mtx.Lock()
	bf.add(hash[:])
	bf.mtx.Unlock()
}

// addOutPoint adds the passed transaction outpoint to the bloom filter.
//
// This function MUST be called with the filter lock held.
func (bf *Filter) addOutPoint(outpoint *wire.OutPoint) {
	// Serialize
	var buf [chainhash.HashSize + 4]byte
	copy(buf[:], outpoint.Hash[:])
	binary.LittleEndian.PutUint32(buf[chainhash.HashSize:], outpoint.Index)

	bf.add(buf[:])
}

// AddOutPoint adds the passed transaction outpoint to the bloom filter.
//
// This function is safe for concurrent access.
func (bf *Filter) AddOutPoint(outpoint *wire.OutPoint) {
	bf.mtx.Lock()
	bf.addOutPoint(outpoint)
	bf.mtx.Unlock()
}

// maybeAddOutpoint potentially adds the passed outpoint to the bloom filter
// depending on the bloom update flags and the type of the passed public key
// script.
//
// This function MUST be called with the filter lock held.
func (bf *Filter) maybeAddOutpoint(pkScript []byte, outHash *chainhash.Hash, outIdx uint32) {
	switch bf.msgFilterLoad.Flags {
	case wire.BloomUpdateAll:
		outpoint := wire.NewOutPoint(outHash, outIdx)
		bf.addOutPoint(outpoint)
	case wire.BloomUpdateP2PubkeyOnly:
		class := txscript.GetScriptClass(pkScript)
		if class == txscript.PubKeyTy || class == txscript.MultiSigTy {
			outpoint := wire.NewOutPoint(outHash, outIdx)
			bf.addOutPoint(outpoint)
		}
	}
}

// matchTxAndUpdate returns true if the bloom filter matches data within the
// passed transaction, otherwise false is returned.  If the filter does match
// the passed transaction, it will also update the filter depending on the bloom
// update flags set via the loaded filter if needed.
//
// This function MUST be called with the filter lock held.
func (bf *Filter) matchTxAndUpdate(tx *btcutil.Tx) bool {
	// Check if the filter matches the hash of the transaction.
	// This is useful for finding transactions when they appear in a block.
	matched := bf.matches(tx.Hash()[:])

	// Check if the filter matches any data elements in the public key
	// scripts of any of the outputs.  When it does, add the outpoint that
	// matched so transactions which spend from the matched transaction are
	// also included in the filter.  This removes the burden of updating the
	// filter for this scenario from the client.  It is also more efficient
	// on the network since it avoids the need for another filteradd message
	// from the client and avoids some potential races that could otherwise
	// occur.
	for i, txOut := range tx.MsgTx().TxOut {
		pushedData, err := txscript.PushedData(txOut.PkScript)
		if err != nil {
			continue
		}

		for _, data := range pushedData {
			if !bf.matches(data) {
				continue
			}

			matched = true
			bf.maybeAddOutpoint(txOut.PkScript, tx.Hash(), uint32(i))
			break
		}
	}

	// Nothing more to do if a match has already been made.
	if matched {
		return true
	}

	// At this point, the transaction and none of the data elements in the
	// public key scripts of its outputs matched.

	// Check if the filter matches any outpoints this transaction spends or
	// any data elements in the signature scripts of any of the inputs.
	for _, txin := range tx.MsgTx().TxIn {
		if bf.matchesOutPoint(&txin.PreviousOutPoint) {
			return true
		}

		pushedData, err := txscript.PushedData(txin.SignatureScript)
		if err != nil {
			continue
		}
		for _, data := range pushedData {
			if bf.matches(data) {
				return true
			}
		}
	}

	return false
}

// MatchTxAndUpdate returns true if the bloom filter matches data within the
// passed transaction, otherwise false is returned.  If the filter does match
// the passed transaction, it will also update the filter depending on the bloom
// update flags set via the loaded filter if needed.
//
// This function is safe for concurrent access.
func (bf *Filter) MatchTxAndUpdate(tx *btcutil.Tx) bool {
	bf.mtx.Lock()
	match := bf.matchTxAndUpdate(tx)
	bf.mtx.Unlock()
	return match
}

// MsgFilterLoad returns the underlying wire.MsgFilterLoad for the bloom
// filter.
//
// This function is safe for concurrent access.
func (bf *Filter) MsgFilterLoad() *wire.MsgFilterLoad {
	bf.mtx.Lock()
	msg := bf.msgFilterLoad
	bf.mtx.Unlock()
	return msg
}
<|endoftext|>


<commit_before>/**
 * (c) 2014, Tonnerre Lombard <tonnerre@ancient-solutions.com>,
 *	     Ancient Solutions. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the  name  of  Ancient Solutions  nor  the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
	"code.google.com/p/goprotobuf/proto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"hash"
	"io"
	"io/ioutil"
	"os"
	"strconv"
)

// Special "writer" which just counts the number of bytes passed through.
type CountingWriter struct {
	n uint64
}

// Add the length of p to the counter.
func (self *CountingWriter) Write(p []byte) (n int, err error) {
	n = len(p)
	self.n += uint64(n)
	return
}

// Return the full number of bytes passed through Write().
func (self *CountingWriter) BytesWritten() uint64 {
	return self.n
}

type blubberStore struct {
	blob_path string
	insecure  bool
	priv      *rsa.PrivateKey
}

// Construct the file name prefix for the blob with the given blob ID.
// Returns the full name prefix and the parent directory.
func (self *blubberStore) blobId2FileName(blobId []byte) (
	string, string) {
	var parent_dir, first_dir, second_dir string
	var path_name string = hex.EncodeToString(blobId)

	// Extract the first and second directory part piece.
	if len(blobId) > 0 {
		first_dir = strconv.FormatUint(uint64(blobId[0]), 16)
	} else {
		path_name = "zz"
		first_dir = "00"
	}
	if len(blobId) > 1 {
		second_dir = first_dir + strconv.FormatUint(uint64(blobId[1]), 16)
	} else {
		second_dir = first_dir + "00"
	}

	parent_dir = self.blob_path + "/" + first_dir + "/" + second_dir
	return parent_dir + "/" + path_name, parent_dir
}

// Create a new blob with the given blobId, or overwrite an existing one.
// The contents of the blob will be read from input.
func (self *blubberStore) StoreBlob(blobId []byte, input io.Reader) error {
	var outstream cipher.StreamWriter
	var bh BlubberBlockHeader
	var aescipher cipher.Block
	var cksum hash.Hash = sha256.New()
	var parent_dir, file_prefix string
	var counter CountingWriter
	var outfile *os.File
	var buf []byte
	var n int
	var err error

	// Create a block key and IV for the blob data.
	bh.BlockKey = make([]byte, aes.BlockSize)
	n, err = rand.Read(bh.BlockKey)
	if err != nil {
		return err
	}
	if n != aes.BlockSize {
		return errors.New("Unexpected length of random data")
	}

	bh.Iv = make([]byte, aes.BlockSize)
	n, err = rand.Read(bh.Iv)
	if err != nil {
		return err
	}
	if n != aes.BlockSize {
		return errors.New("Unexpected length of random data")
	}

	file_prefix, parent_dir = self.blobId2FileName(blobId)

	// Ensure we have the full directory path in place.
	err = os.MkdirAll(parent_dir, 0700)
	if err != nil {
		return err
	}

	// Now, write the actual data.
	outfile, err = os.Create(file_prefix + ".data")
	if err != nil {
		return err
	}
	aescipher, err = aes.NewCipher(bh.BlockKey)
	if err != nil {
		return err
	}
	outstream = cipher.StreamWriter{
		S: cipher.NewCTR(aescipher, bh.Iv),
		W: io.MultiWriter(outfile, cksum, &counter),
	}
	_, err = io.Copy(outstream, input)
	if err != nil {
		return err
	}
	err = outfile.Close()
	if err != nil {
		return err
	}

	// Fill in the last bits of the blob header.
	bh.Checksum = cksum.Sum(bh.Checksum)
	bh.Size = new(uint64)
	*bh.Size = counter.BytesWritten()
	buf, err = proto.Marshal(&bh)
	if err != nil {
		return err
	}
	if !self.insecure {
		// Encrypt the AES key and IV with the RSA key.
		buf, err = rsa.DecryptPKCS1v15(rand.Reader, self.priv, buf)
		if err != nil {
			return err
		}
	}

	// Write out the crypto head with the IV and the blob key.
	outfile, err = os.Create(file_prefix + ".crypthead")
	if err != nil {
		return err
	}
	n, err = outfile.Write(buf)
	if err != nil {
		return err
	}
	if n < len(buf) {
		return errors.New("Short write to file")
	}
	return outstream.Close()
}

// Extract the blubber block head for the given blob ID and return it.
func (self *blubberStore) extractBlockHead(blobId []byte) (
	bh *BlubberBlockHeader, err error) {
	var file_prefix string
	var data []byte

	file_prefix, _ = self.blobId2FileName(blobId)
	data, err = ioutil.ReadFile(file_prefix + ".crypthead")

	if !self.insecure {
		// Decrypt the AES key and IV with the RSA key.
		data, err = rsa.DecryptPKCS1v15(rand.Reader, self.priv, data)
		if err != nil {
			return
		}
	}

	bh = new(BlubberBlockHeader)
	err = proto.Unmarshal(data, bh)
	return
}

// Read the blob with the specified blob ID and return it to the caller.
// The contents will be sent as a regular HTTP response.
func (self *blubberStore) RetrieveBlob(blobId []byte, rw io.Writer) error {
	var file_prefix string
	var infile *os.File
	var bh *BlubberBlockHeader
	var instream cipher.StreamReader
	var aescipher cipher.Block
	var err error

	// Get the metadata.
	bh, err = self.extractBlockHead(blobId)
	if err != nil {
		return err
	}

	file_prefix, _ = self.blobId2FileName(blobId)
	infile, err = os.Open(file_prefix + ".data")
	if err != nil {
		return err
	}

	aescipher, err = aes.NewCipher(bh.BlockKey)
	if err != nil {
		infile.Close()
		return err
	}
	instream = cipher.StreamReader{
		S: cipher.NewCTR(aescipher, bh.Iv),
		R: infile,
	}

	_, err = io.Copy(rw, instream)
	if err != nil {
		infile.Close()
		return err
	}

	return infile.Close()
}

// Delete the blob with the given blob ID.
func (self *blubberStore) DeleteBlob(blobId []byte) error {
	return errors.New("Not yet implemented")
}

// Get some details about the specified blob.
func (self *blubberStore) StatBlob(blobId []byte) (
	ret BlubberStat, err error) {
	var bh *BlubberBlockHeader

	bh, err = self.extractBlockHead(blobId)
	if err != nil {
		return
	}

	ret.BlockId = make([]byte, len(blobId))
	copy(ret.BlockId, blobId)

	ret.Checksum = make([]byte, len(bh.Checksum))
	copy(ret.Checksum, bh.Checksum)

	ret.Size = bh.Size
	return
}
<commit_msg>Finally, implement the delete method.<commit_after>/**
 * (c) 2014, Tonnerre Lombard <tonnerre@ancient-solutions.com>,
 *	     Ancient Solutions. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the  name  of  Ancient Solutions  nor  the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
	"code.google.com/p/goprotobuf/proto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"hash"
	"io"
	"io/ioutil"
	"os"
	"strconv"
)

// Special "writer" which just counts the number of bytes passed through.
type CountingWriter struct {
	n uint64
}

// Add the length of p to the counter.
func (self *CountingWriter) Write(p []byte) (n int, err error) {
	n = len(p)
	self.n += uint64(n)
	return
}

// Return the full number of bytes passed through Write().
func (self *CountingWriter) BytesWritten() uint64 {
	return self.n
}

type blubberStore struct {
	blob_path string
	insecure  bool
	priv      *rsa.PrivateKey
}

// Construct the file name prefix for the blob with the given blob ID.
// Returns the full name prefix and the parent directory.
func (self *blubberStore) blobId2FileName(blobId []byte) (
	string, string) {
	var parent_dir, first_dir, second_dir string
	var path_name string = hex.EncodeToString(blobId)

	// Extract the first and second directory part piece.
	if len(blobId) > 0 {
		first_dir = strconv.FormatUint(uint64(blobId[0]), 16)
	} else {
		path_name = "zz"
		first_dir = "00"
	}
	if len(blobId) > 1 {
		second_dir = first_dir + strconv.FormatUint(uint64(blobId[1]), 16)
	} else {
		second_dir = first_dir + "00"
	}

	parent_dir = self.blob_path + "/" + first_dir + "/" + second_dir
	return parent_dir + "/" + path_name, parent_dir
}

// Create a new blob with the given blobId, or overwrite an existing one.
// The contents of the blob will be read from input.
func (self *blubberStore) StoreBlob(blobId []byte, input io.Reader) error {
	var outstream cipher.StreamWriter
	var bh BlubberBlockHeader
	var aescipher cipher.Block
	var cksum hash.Hash = sha256.New()
	var parent_dir, file_prefix string
	var counter CountingWriter
	var outfile *os.File
	var buf []byte
	var n int
	var err error

	// Create a block key and IV for the blob data.
	bh.BlockKey = make([]byte, aes.BlockSize)
	n, err = rand.Read(bh.BlockKey)
	if err != nil {
		return err
	}
	if n != aes.BlockSize {
		return errors.New("Unexpected length of random data")
	}

	bh.Iv = make([]byte, aes.BlockSize)
	n, err = rand.Read(bh.Iv)
	if err != nil {
		return err
	}
	if n != aes.BlockSize {
		return errors.New("Unexpected length of random data")
	}

	file_prefix, parent_dir = self.blobId2FileName(blobId)

	// Ensure we have the full directory path in place.
	err = os.MkdirAll(parent_dir, 0700)
	if err != nil {
		return err
	}

	// Now, write the actual data.
	outfile, err = os.Create(file_prefix + ".data")
	if err != nil {
		return err
	}
	aescipher, err = aes.NewCipher(bh.BlockKey)
	if err != nil {
		return err
	}
	outstream = cipher.StreamWriter{
		S: cipher.NewCTR(aescipher, bh.Iv),
		W: io.MultiWriter(outfile, cksum, &counter),
	}
	_, err = io.Copy(outstream, input)
	if err != nil {
		return err
	}
	err = outfile.Close()
	if err != nil {
		return err
	}

	// Fill in the last bits of the blob header.
	bh.Checksum = cksum.Sum(bh.Checksum)
	bh.Size = new(uint64)
	*bh.Size = counter.BytesWritten()
	buf, err = proto.Marshal(&bh)
	if err != nil {
		return err
	}
	if !self.insecure {
		// Encrypt the AES key and IV with the RSA key.
		buf, err = rsa.DecryptPKCS1v15(rand.Reader, self.priv, buf)
		if err != nil {
			return err
		}
	}

	// Write out the crypto head with the IV and the blob key.
	outfile, err = os.Create(file_prefix + ".crypthead")
	if err != nil {
		return err
	}
	n, err = outfile.Write(buf)
	if err != nil {
		return err
	}
	if n < len(buf) {
		return errors.New("Short write to file")
	}
	return outstream.Close()
}

// Extract the blubber block head for the given blob ID and return it.
func (self *blubberStore) extractBlockHead(blobId []byte) (
	bh *BlubberBlockHeader, err error) {
	var file_prefix string
	var data []byte

	file_prefix, _ = self.blobId2FileName(blobId)
	data, err = ioutil.ReadFile(file_prefix + ".crypthead")

	if !self.insecure {
		// Decrypt the AES key and IV with the RSA key.
		data, err = rsa.DecryptPKCS1v15(rand.Reader, self.priv, data)
		if err != nil {
			return
		}
	}

	bh = new(BlubberBlockHeader)
	err = proto.Unmarshal(data, bh)
	return
}

// Read the blob with the specified blob ID and return it to the caller.
// The contents will be sent as a regular HTTP response.
func (self *blubberStore) RetrieveBlob(blobId []byte, rw io.Writer) error {
	var file_prefix string
	var infile *os.File
	var bh *BlubberBlockHeader
	var instream cipher.StreamReader
	var aescipher cipher.Block
	var err error

	// Get the metadata.
	bh, err = self.extractBlockHead(blobId)
	if err != nil {
		return err
	}

	file_prefix, _ = self.blobId2FileName(blobId)
	infile, err = os.Open(file_prefix + ".data")
	if err != nil {
		return err
	}

	aescipher, err = aes.NewCipher(bh.BlockKey)
	if err != nil {
		infile.Close()
		return err
	}
	instream = cipher.StreamReader{
		S: cipher.NewCTR(aescipher, bh.Iv),
		R: infile,
	}

	_, err = io.Copy(rw, instream)
	if err != nil {
		infile.Close()
		return err
	}

	return infile.Close()
}

// Delete the blob with the given blob ID.
func (self *blubberStore) DeleteBlob(blobId []byte) error {
	var prefix string
	var err error

	prefix, _ = self.blobId2FileName(blobId)
	err = os.Remove(prefix + ".data")
	if err != nil {
		return err
	}
	return os.Remove(prefix + ".crypthead")
}

// Get some details about the specified blob.
func (self *blubberStore) StatBlob(blobId []byte) (
	ret BlubberStat, err error) {
	var bh *BlubberBlockHeader

	bh, err = self.extractBlockHead(blobId)
	if err != nil {
		return
	}

	ret.BlockId = make([]byte, len(blobId))
	copy(ret.BlockId, blobId)

	ret.Checksum = make([]byte, len(bh.Checksum))
	copy(ret.Checksum, bh.Checksum)

	ret.Size = bh.Size
	return
}
<|endoftext|>


<commit_before>// Package oidc implements logging in through OpenID Connect providers.
package oidc

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/coreos/go-oidc"
	"golang.org/x/oauth2"

	"github.com/dexidp/dex/connector"
	"github.com/dexidp/dex/pkg/log"
)

// Config holds configuration options for OpenID Connect logins.
type Config struct {
	Issuer       string `json:"issuer"`
	ClientID     string `json:"clientID"`
	ClientSecret string `json:"clientSecret"`
	RedirectURI  string `json:"redirectURI"`

	// Causes client_secret to be passed as POST parameters instead of basic
	// auth. This is specifically "NOT RECOMMENDED" by the OAuth2 RFC, but some
	// providers require it.
	//
	// https://tools.ietf.org/html/rfc6749#section-2.3.1
	BasicAuthUnsupported *bool `json:"basicAuthUnsupported"`

	Scopes []string `json:"scopes"` // defaults to "profile" and "email"

	// Optional list of whitelisted domains when using Google
	// If this field is nonempty, only users from a listed domain will be allowed to log in
	HostedDomains []string `json:"hostedDomains"`

	// Override the value of email_verifed to true in the returned claims
	InsecureSkipEmailVerified bool `json:"insecureSkipEmailVerified"`

	// GetUserInfo uses the userinfo endpoint to get additional claims for
	// the token. This is especially useful where upstreams return "thin"
	// id tokens
	GetUserInfo bool `json:"getUserInfo"`

	// Configurable key which contains the user id claim
	UserIDKey string `json:"userIDKey"`

	// Configurable key which contains the user name claim
	UserNameKey string `json:"userNameKey"`
}

// Domains that don't support basic auth. golang.org/x/oauth2 has an internal
// list, but it only matches specific URLs, not top level domains.
var brokenAuthHeaderDomains = []string{
	// See: https://github.com/dexidp/dex/issues/859
	"okta.com",
	"oktapreview.com",
}

// Detect auth header provider issues for known providers. This lets users
// avoid having to explicitly set "basicAuthUnsupported" in their config.
//
// Setting the config field always overrides values returned by this function.
func knownBrokenAuthHeaderProvider(issuerURL string) bool {
	if u, err := url.Parse(issuerURL); err == nil {
		for _, host := range brokenAuthHeaderDomains {
			if u.Host == host || strings.HasSuffix(u.Host, "."+host) {
				return true
			}
		}
	}
	return false
}

// golang.org/x/oauth2 doesn't do internal locking. Need to do it in this
// package ourselves and hope that other packages aren't calling it at the
// same time.
var registerMu = new(sync.Mutex)

func registerBrokenAuthHeaderProvider(url string) {
	registerMu.Lock()
	defer registerMu.Unlock()

	oauth2.RegisterBrokenAuthHeaderProvider(url)
}

// Open returns a connector which can be used to login users through an upstream
// OpenID Connect provider.
func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, err error) {
	ctx, cancel := context.WithCancel(context.Background())

	provider, err := oidc.NewProvider(ctx, c.Issuer)
	if err != nil {
		cancel()
		return nil, fmt.Errorf("failed to get provider: %v", err)
	}

	if c.BasicAuthUnsupported != nil {
		// Setting "basicAuthUnsupported" always overrides our detection.
		if *c.BasicAuthUnsupported {
			registerBrokenAuthHeaderProvider(provider.Endpoint().TokenURL)
		}
	} else if knownBrokenAuthHeaderProvider(c.Issuer) {
		registerBrokenAuthHeaderProvider(provider.Endpoint().TokenURL)
	}

	scopes := []string{oidc.ScopeOpenID}
	if len(c.Scopes) > 0 {
		scopes = append(scopes, c.Scopes...)
	} else {
		scopes = append(scopes, "profile", "email")
	}

	clientID := c.ClientID
	return &oidcConnector{
		provider:    provider,
		redirectURI: c.RedirectURI,
		oauth2Config: &oauth2.Config{
			ClientID:     clientID,
			ClientSecret: c.ClientSecret,
			Endpoint:     provider.Endpoint(),
			Scopes:       scopes,
			RedirectURL:  c.RedirectURI,
		},
		verifier: provider.Verifier(
			&oidc.Config{ClientID: clientID},
		),
		logger:                    logger,
		cancel:                    cancel,
		hostedDomains:             c.HostedDomains,
		insecureSkipEmailVerified: c.InsecureSkipEmailVerified,
		getUserInfo:               c.GetUserInfo,
		userIDKey:                 c.UserIDKey,
		userNameKey:               c.UserNameKey,
	}, nil
}

var (
	_ connector.CallbackConnector = (*oidcConnector)(nil)
	_ connector.RefreshConnector  = (*oidcConnector)(nil)
)

type oidcConnector struct {
	provider                  *oidc.Provider
	redirectURI               string
	oauth2Config              *oauth2.Config
	verifier                  *oidc.IDTokenVerifier
	cancel                    context.CancelFunc
	logger                    log.Logger
	hostedDomains             []string
	insecureSkipEmailVerified bool
	getUserInfo               bool
	userIDKey                 string
	userNameKey               string
}

func (c *oidcConnector) Close() error {
	c.cancel()
	return nil
}

func (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string) (string, error) {
	if c.redirectURI != callbackURL {
		return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
	}

	var opts []oauth2.AuthCodeOption
	if len(c.hostedDomains) > 0 {
		preferredDomain := c.hostedDomains[0]
		if len(c.hostedDomains) > 1 {
			preferredDomain = "*"
		}
		opts = append(opts, oauth2.SetAuthURLParam("hd", preferredDomain))
	}

	if s.OfflineAccess {
		opts = append(opts, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent"))
	}
	return c.oauth2Config.AuthCodeURL(state, opts...), nil
}

type oauth2Error struct {
	error            string
	errorDescription string
}

func (e *oauth2Error) Error() string {
	if e.errorDescription == "" {
		return e.error
	}
	return e.error + ": " + e.errorDescription
}

func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) {
	q := r.URL.Query()
	if errType := q.Get("error"); errType != "" {
		return identity, &oauth2Error{errType, q.Get("error_description")}
	}
	token, err := c.oauth2Config.Exchange(r.Context(), q.Get("code"))
	if err != nil {
		return identity, fmt.Errorf("oidc: failed to get token: %v", err)
	}

	rawIDToken, ok := token.Extra("id_token").(string)
	if !ok {
		return identity, errors.New("oidc: no id_token in token response")
	}
	idToken, err := c.verifier.Verify(r.Context(), rawIDToken)
	if err != nil {
		return identity, fmt.Errorf("oidc: failed to verify ID Token: %v", err)
	}

	var claims map[string]interface{}
	if err := idToken.Claims(&claims); err != nil {
		return identity, fmt.Errorf("oidc: failed to decode claims: %v", err)
	}

	// We immediately want to run getUserInfo if configured before we validate the claims
	if c.getUserInfo {
		userInfo, err := c.provider.UserInfo(r.Context(), oauth2.StaticTokenSource(token))
		if err != nil {
			return identity, fmt.Errorf("oidc: error loading userinfo: %v", err)
		}
		if err := userInfo.Claims(&claims); err != nil {
			return identity, fmt.Errorf("oidc: failed to decode userinfo claims: %v", err)
		}
	}

	userNameKey := "name"
	if c.userNameKey != "" {
		userNameKey = c.userNameKey
	}
	name, found := claims[userNameKey].(string)
	if !found {
		return identity, fmt.Errorf("missing \"%s\" claim", userNameKey)
	}
	email, found := claims["email"].(string)
	if !found {
		return identity, errors.New("missing \"email\" claim")
	}
	emailVerified, found := claims["email_verified"].(bool)
	if !found {
		if c.insecureSkipEmailVerified {
			emailVerified = true
		} else {
			return identity, errors.New("missing \"email_verified\" claim")
		}
	}
	hostedDomain, _ := claims["hd"].(string)

	if len(c.hostedDomains) > 0 {
		found := false
		for _, domain := range c.hostedDomains {
			if hostedDomain == domain {
				found = true
				break
			}
		}

		if !found {
			return identity, fmt.Errorf("oidc: unexpected hd claim %v", hostedDomain)
		}
	}

	identity = connector.Identity{
		UserID:        idToken.Subject,
		Username:      name,
		Email:         email,
		EmailVerified: emailVerified,
		ConnectorData: []byte(token.RefreshToken),
	}

	if c.userIDKey != "" {
		userID, found := claims[c.userIDKey].(string)
		if !found {
			return identity, fmt.Errorf("oidc: not found %v claim", c.userIDKey)
		}
		identity.UserID = userID
	}

	return identity, nil
}

// Refresh is implemented for backwards compatibility, even though it's a no-op.
func (c *oidcConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) {
	t := &oauth2.Token{
		RefreshToken: string(identity.ConnectorData),
		Expiry:       time.Now().Add(-time.Hour),
	}
	token, err := c.oauth2Config.TokenSource(ctx, t).Token()
	if err != nil {
		return identity, fmt.Errorf("oidc: failed to get token: %v", err)
	}

	rawIDToken, ok := token.Extra("id_token").(string)
	if !ok {
		return identity, errors.New("oidc: no id_token in token response")
	}
	idToken, err := c.verifier.Verify(ctx, rawIDToken)
	if err != nil {
		return identity, fmt.Errorf("oidc: failed to verify ID Token: %v", err)
	}

	var claims struct {
		Username      string `json:"name"`
		Email         string `json:"email"`
		EmailVerified bool   `json:"email_verified"`
		HostedDomain  string `json:"hd"`
	}
	if err := idToken.Claims(&claims); err != nil {
		return identity, fmt.Errorf("oidc: failed to decode claims: %v", err)
	}

	if len(c.hostedDomains) > 0 {
		found := false
		for _, domain := range c.hostedDomains {
			if claims.HostedDomain == domain {
				found = true
				break
			}
		}

		if !found {
			return identity, fmt.Errorf("oidc: unexpected hd claim %v", claims.HostedDomain)
		}
	}

	identity = connector.Identity{
		UserID:        idToken.Subject,
		Username:      claims.Username,
		Email:         claims.Email,
		EmailVerified: claims.EmailVerified,
		ConnectorData: []byte(token.RefreshToken),
	}
	return identity, nil
}
<commit_msg>Remove duplicate code<commit_after>// Package oidc implements logging in through OpenID Connect providers.
package oidc

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/coreos/go-oidc"
	"golang.org/x/oauth2"

	"github.com/dexidp/dex/connector"
	"github.com/dexidp/dex/pkg/log"
)

// Config holds configuration options for OpenID Connect logins.
type Config struct {
	Issuer       string `json:"issuer"`
	ClientID     string `json:"clientID"`
	ClientSecret string `json:"clientSecret"`
	RedirectURI  string `json:"redirectURI"`

	// Causes client_secret to be passed as POST parameters instead of basic
	// auth. This is specifically "NOT RECOMMENDED" by the OAuth2 RFC, but some
	// providers require it.
	//
	// https://tools.ietf.org/html/rfc6749#section-2.3.1
	BasicAuthUnsupported *bool `json:"basicAuthUnsupported"`

	Scopes []string `json:"scopes"` // defaults to "profile" and "email"

	// Optional list of whitelisted domains when using Google
	// If this field is nonempty, only users from a listed domain will be allowed to log in
	HostedDomains []string `json:"hostedDomains"`

	// Override the value of email_verifed to true in the returned claims
	InsecureSkipEmailVerified bool `json:"insecureSkipEmailVerified"`

	// GetUserInfo uses the userinfo endpoint to get additional claims for
	// the token. This is especially useful where upstreams return "thin"
	// id tokens
	GetUserInfo bool `json:"getUserInfo"`

	// Configurable key which contains the user id claim
	UserIDKey string `json:"userIDKey"`

	// Configurable key which contains the user name claim
	UserNameKey string `json:"userNameKey"`
}

// Domains that don't support basic auth. golang.org/x/oauth2 has an internal
// list, but it only matches specific URLs, not top level domains.
var brokenAuthHeaderDomains = []string{
	// See: https://github.com/dexidp/dex/issues/859
	"okta.com",
	"oktapreview.com",
}

// Detect auth header provider issues for known providers. This lets users
// avoid having to explicitly set "basicAuthUnsupported" in their config.
//
// Setting the config field always overrides values returned by this function.
func knownBrokenAuthHeaderProvider(issuerURL string) bool {
	if u, err := url.Parse(issuerURL); err == nil {
		for _, host := range brokenAuthHeaderDomains {
			if u.Host == host || strings.HasSuffix(u.Host, "."+host) {
				return true
			}
		}
	}
	return false
}

// golang.org/x/oauth2 doesn't do internal locking. Need to do it in this
// package ourselves and hope that other packages aren't calling it at the
// same time.
var registerMu = new(sync.Mutex)

func registerBrokenAuthHeaderProvider(url string) {
	registerMu.Lock()
	defer registerMu.Unlock()

	oauth2.RegisterBrokenAuthHeaderProvider(url)
}

// Open returns a connector which can be used to login users through an upstream
// OpenID Connect provider.
func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, err error) {
	ctx, cancel := context.WithCancel(context.Background())

	provider, err := oidc.NewProvider(ctx, c.Issuer)
	if err != nil {
		cancel()
		return nil, fmt.Errorf("failed to get provider: %v", err)
	}

	if c.BasicAuthUnsupported != nil {
		// Setting "basicAuthUnsupported" always overrides our detection.
		if *c.BasicAuthUnsupported {
			registerBrokenAuthHeaderProvider(provider.Endpoint().TokenURL)
		}
	} else if knownBrokenAuthHeaderProvider(c.Issuer) {
		registerBrokenAuthHeaderProvider(provider.Endpoint().TokenURL)
	}

	scopes := []string{oidc.ScopeOpenID}
	if len(c.Scopes) > 0 {
		scopes = append(scopes, c.Scopes...)
	} else {
		scopes = append(scopes, "profile", "email")
	}

	clientID := c.ClientID
	return &oidcConnector{
		provider:    provider,
		redirectURI: c.RedirectURI,
		oauth2Config: &oauth2.Config{
			ClientID:     clientID,
			ClientSecret: c.ClientSecret,
			Endpoint:     provider.Endpoint(),
			Scopes:       scopes,
			RedirectURL:  c.RedirectURI,
		},
		verifier: provider.Verifier(
			&oidc.Config{ClientID: clientID},
		),
		logger:                    logger,
		cancel:                    cancel,
		hostedDomains:             c.HostedDomains,
		insecureSkipEmailVerified: c.InsecureSkipEmailVerified,
		getUserInfo:               c.GetUserInfo,
		userIDKey:                 c.UserIDKey,
		userNameKey:               c.UserNameKey,
	}, nil
}

var (
	_ connector.CallbackConnector = (*oidcConnector)(nil)
	_ connector.RefreshConnector  = (*oidcConnector)(nil)
)

type oidcConnector struct {
	provider                  *oidc.Provider
	redirectURI               string
	oauth2Config              *oauth2.Config
	verifier                  *oidc.IDTokenVerifier
	cancel                    context.CancelFunc
	logger                    log.Logger
	hostedDomains             []string
	insecureSkipEmailVerified bool
	getUserInfo               bool
	userIDKey                 string
	userNameKey               string
}

func (c *oidcConnector) Close() error {
	c.cancel()
	return nil
}

func (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string) (string, error) {
	if c.redirectURI != callbackURL {
		return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
	}

	var opts []oauth2.AuthCodeOption
	if len(c.hostedDomains) > 0 {
		preferredDomain := c.hostedDomains[0]
		if len(c.hostedDomains) > 1 {
			preferredDomain = "*"
		}
		opts = append(opts, oauth2.SetAuthURLParam("hd", preferredDomain))
	}

	if s.OfflineAccess {
		opts = append(opts, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent"))
	}
	return c.oauth2Config.AuthCodeURL(state, opts...), nil
}

type oauth2Error struct {
	error            string
	errorDescription string
}

func (e *oauth2Error) Error() string {
	if e.errorDescription == "" {
		return e.error
	}
	return e.error + ": " + e.errorDescription
}

func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) {
	q := r.URL.Query()
	if errType := q.Get("error"); errType != "" {
		return identity, &oauth2Error{errType, q.Get("error_description")}
	}
	token, err := c.oauth2Config.Exchange(r.Context(), q.Get("code"))
	if err != nil {
		return identity, fmt.Errorf("oidc: failed to get token: %v", err)
	}

	return c.createIdentity(r.Context(), identity, token)
}

// Refresh is implemented for backwards compatibility, even though it's a no-op.
func (c *oidcConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) {
	t := &oauth2.Token{
		RefreshToken: string(identity.ConnectorData),
		Expiry:       time.Now().Add(-time.Hour),
	}
	token, err := c.oauth2Config.TokenSource(ctx, t).Token()
	if err != nil {
		return identity, fmt.Errorf("oidc: failed to get token: %v", err)
	}

	return c.createIdentity(ctx, identity, token)
}

func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.Identity, token *oauth2.Token) (connector.Identity, error) {
	rawIDToken, ok := token.Extra("id_token").(string)
	if !ok {
		return identity, errors.New("oidc: no id_token in token response")
	}
	idToken, err := c.verifier.Verify(ctx, rawIDToken)
	if err != nil {
		return identity, fmt.Errorf("oidc: failed to verify ID Token: %v", err)
	}

	var claims map[string]interface{}
	if err := idToken.Claims(&claims); err != nil {
		return identity, fmt.Errorf("oidc: failed to decode claims: %v", err)
	}

	// We immediately want to run getUserInfo if configured before we validate the claims
	if c.getUserInfo {
		userInfo, err := c.provider.UserInfo(ctx, oauth2.StaticTokenSource(token))
		if err != nil {
			return identity, fmt.Errorf("oidc: error loading userinfo: %v", err)
		}
		if err := userInfo.Claims(&claims); err != nil {
			return identity, fmt.Errorf("oidc: failed to decode userinfo claims: %v", err)
		}
	}

	userNameKey := "name"
	if c.userNameKey != "" {
		userNameKey = c.userNameKey
	}
	name, found := claims[userNameKey].(string)
	if !found {
		return identity, fmt.Errorf("missing \"%s\" claim", userNameKey)
	}
	email, found := claims["email"].(string)
	if !found {
		return identity, errors.New("missing \"email\" claim")
	}
	emailVerified, found := claims["email_verified"].(bool)
	if !found {
		if c.insecureSkipEmailVerified {
			emailVerified = true
		} else {
			return identity, errors.New("missing \"email_verified\" claim")
		}
	}
	hostedDomain, _ := claims["hd"].(string)

	if len(c.hostedDomains) > 0 {
		found := false
		for _, domain := range c.hostedDomains {
			if hostedDomain == domain {
				found = true
				break
			}
		}

		if !found {
			return identity, fmt.Errorf("oidc: unexpected hd claim %v", hostedDomain)
		}
	}

	identity = connector.Identity{
		UserID:        idToken.Subject,
		Username:      name,
		Email:         email,
		EmailVerified: emailVerified,
		ConnectorData: []byte(token.RefreshToken),
	}

	if c.userIDKey != "" {
		userID, found := claims[c.userIDKey].(string)
		if !found {
			return identity, fmt.Errorf("oidc: not found %v claim", c.userIDKey)
		}
		identity.UserID = userID
	}

	return identity, nil
}
<|endoftext|>