1// Copyright (c) 2020 Shivaram Lingamneni
2// released under the MIT license
3
4package irc
5
6import (
7	"crypto/x509"
8	"encoding/json"
9	"encoding/pem"
10	"fmt"
11	"net"
12
13	"github.com/ergochat/ergo/irc/utils"
14)
15
16// JSON-serializable input and output types for the script
17type AuthScriptInput struct {
18	AccountName string   `json:"accountName,omitempty"`
19	Passphrase  string   `json:"passphrase,omitempty"`
20	Certfp      string   `json:"certfp,omitempty"`
21	PeerCerts   []string `json:"peerCerts,omitempty"`
22	peerCerts   []*x509.Certificate
23	IP          string `json:"ip,omitempty"`
24}
25
26type AuthScriptOutput struct {
27	AccountName string `json:"accountName"`
28	Success     bool   `json:"success"`
29	Error       string `json:"error"`
30}
31
32func CheckAuthScript(sem utils.Semaphore, config ScriptConfig, input AuthScriptInput) (output AuthScriptOutput, err error) {
33	if sem != nil {
34		sem.Acquire()
35		defer sem.Release()
36	}
37
38	// PEM-encode the peer certificates before applying JSON
39	if len(input.peerCerts) != 0 {
40		input.PeerCerts = make([]string, len(input.peerCerts))
41		for i, cert := range input.peerCerts {
42			input.PeerCerts[i] = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}))
43		}
44	}
45
46	inputBytes, err := json.Marshal(input)
47	if err != nil {
48		return
49	}
50	outBytes, err := RunScript(config.Command, config.Args, inputBytes, config.Timeout, config.KillTimeout)
51	if err != nil {
52		return
53	}
54	err = json.Unmarshal(outBytes, &output)
55	if err != nil {
56		return
57	}
58
59	if output.Error != "" {
60		err = fmt.Errorf("Authentication process reported error: %s", output.Error)
61	}
62	return
63}
64
65type IPScriptResult uint
66
67const (
68	IPNotChecked  IPScriptResult = 0
69	IPAccepted    IPScriptResult = 1
70	IPBanned      IPScriptResult = 2
71	IPRequireSASL IPScriptResult = 3
72)
73
74type IPScriptInput struct {
75	IP string `json:"ip"`
76}
77
78type IPScriptOutput struct {
79	Result     IPScriptResult `json:"result"`
80	BanMessage string         `json:"banMessage"`
81	// for caching: the network to which this result is applicable, and a TTL in seconds:
82	CacheNet     string `json:"cacheNet"`
83	CacheSeconds int    `json:"cacheSeconds"`
84	Error        string `json:"error"`
85}
86
87func CheckIPBan(sem utils.Semaphore, config ScriptConfig, addr net.IP) (output IPScriptOutput, err error) {
88	if sem != nil {
89		sem.Acquire()
90		defer sem.Release()
91	}
92
93	inputBytes, err := json.Marshal(IPScriptInput{IP: addr.String()})
94	if err != nil {
95		return
96	}
97	outBytes, err := RunScript(config.Command, config.Args, inputBytes, config.Timeout, config.KillTimeout)
98	if err != nil {
99		return
100	}
101	err = json.Unmarshal(outBytes, &output)
102	if err != nil {
103		return
104	}
105
106	if output.Error != "" {
107		err = fmt.Errorf("IP ban process reported error: %s", output.Error)
108	} else if !(IPAccepted <= output.Result && output.Result <= IPRequireSASL) {
109		err = fmt.Errorf("Invalid result from IP checking script: %d", output.Result)
110	}
111
112	return
113}
114