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