1// Copyright (c) 2018 Shivaram Lingamneni <slingamn@cs.stanford.edu> 2// released under the MIT license 3 4package irc 5 6import ( 7 "errors" 8 "fmt" 9 "regexp" 10 11 "github.com/ergochat/irc-go/ircfmt" 12 13 "github.com/ergochat/ergo/irc/utils" 14) 15 16const ( 17 hostservHelp = `HostServ lets you manage your vhost (i.e., the string displayed 18in place of your client's hostname/IP).` 19) 20 21var ( 22 errVHostBadCharacters = errors.New("Vhost contains prohibited characters") 23 errVHostTooLong = errors.New("Vhost is too long") 24 // ascii only for now 25 defaultValidVhostRegex = regexp.MustCompile(`^[0-9A-Za-z.\-_/]+$`) 26) 27 28func hostservEnabled(config *Config) bool { 29 return config.Accounts.VHosts.Enabled 30} 31 32var ( 33 hostservCommands = map[string]*serviceCommand{ 34 "on": { 35 handler: hsOnOffHandler, 36 help: `Syntax: $bON$b 37 38ON enables your vhost, if you have one approved.`, 39 helpShort: `$bON$b enables your vhost, if you have one approved.`, 40 authRequired: true, 41 enabled: hostservEnabled, 42 }, 43 "off": { 44 handler: hsOnOffHandler, 45 help: `Syntax: $bOFF$b 46 47OFF disables your vhost, if you have one approved.`, 48 helpShort: `$bOFF$b disables your vhost, if you have one approved.`, 49 authRequired: true, 50 enabled: hostservEnabled, 51 }, 52 "status": { 53 handler: hsStatusHandler, 54 help: `Syntax: $bSTATUS [user]$b 55 56STATUS displays your current vhost, if any, and whether it is enabled or 57disabled. A server operator can view someone else's status.`, 58 helpShort: `$bSTATUS$b shows your vhost status.`, 59 enabled: hostservEnabled, 60 }, 61 "set": { 62 handler: hsSetHandler, 63 help: `Syntax: $bSET <user> <vhost>$b 64 65SET sets a user's vhost, bypassing the request system.`, 66 helpShort: `$bSET$b sets a user's vhost.`, 67 capabs: []string{"vhosts"}, 68 enabled: hostservEnabled, 69 minParams: 2, 70 }, 71 "del": { 72 handler: hsSetHandler, 73 help: `Syntax: $bDEL <user>$b 74 75DEL deletes a user's vhost.`, 76 helpShort: `$bDEL$b deletes a user's vhost.`, 77 capabs: []string{"vhosts"}, 78 enabled: hostservEnabled, 79 minParams: 1, 80 }, 81 "setcloaksecret": { 82 handler: hsSetCloakSecretHandler, 83 help: `Syntax: $bSETCLOAKSECRET$b <secret> [code] 84 85SETCLOAKSECRET can be used to set or rotate the cloak secret. You should use 86a cryptographically strong secret. To prevent accidental modification, a 87verification code is required; invoking the command without a code will 88display the necessary code.`, 89 helpShort: `$bSETCLOAKSECRET$b modifies the IP cloaking secret.`, 90 capabs: []string{"vhosts", "rehash"}, 91 minParams: 1, 92 maxParams: 2, 93 }, 94 } 95) 96 97func hsOnOffHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 98 enable := false 99 if command == "on" { 100 enable = true 101 } 102 103 _, err := server.accounts.VHostSetEnabled(client, enable) 104 if err == errNoVhost { 105 service.Notice(rb, client.t(err.Error())) 106 } else if err != nil { 107 service.Notice(rb, client.t("An error occurred")) 108 } else if enable { 109 service.Notice(rb, client.t("Successfully enabled your vhost")) 110 } else { 111 service.Notice(rb, client.t("Successfully disabled your vhost")) 112 } 113} 114 115func hsStatusHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 116 var accountName string 117 if len(params) > 0 { 118 if !client.HasRoleCapabs("vhosts") { 119 service.Notice(rb, client.t("Command restricted")) 120 return 121 } 122 accountName = params[0] 123 } else { 124 accountName = client.Account() 125 if accountName == "" { 126 service.Notice(rb, client.t("You're not logged into an account")) 127 return 128 } 129 } 130 131 account, err := server.accounts.LoadAccount(accountName) 132 if err != nil { 133 if err != errAccountDoesNotExist { 134 server.logger.Warning("internal", "error loading account info", accountName, err.Error()) 135 } 136 service.Notice(rb, client.t("No such account")) 137 return 138 } 139 140 if account.VHost.ApprovedVHost != "" { 141 service.Notice(rb, fmt.Sprintf(client.t("Account %[1]s has vhost: %[2]s"), accountName, account.VHost.ApprovedVHost)) 142 if !account.VHost.Enabled { 143 service.Notice(rb, client.t("This vhost is currently disabled, but can be enabled with /HS ON")) 144 } 145 } else { 146 service.Notice(rb, fmt.Sprintf(client.t("Account %s has no vhost"), accountName)) 147 } 148} 149 150func validateVhost(server *Server, vhost string, oper bool) error { 151 config := server.Config() 152 if len(vhost) > config.Accounts.VHosts.MaxLength { 153 return errVHostTooLong 154 } 155 if !config.Accounts.VHosts.validRegexp.MatchString(vhost) { 156 return errVHostBadCharacters 157 } 158 return nil 159} 160 161func hsSetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 162 user := params[0] 163 var vhost string 164 165 if command == "set" { 166 vhost = params[1] 167 if validateVhost(server, vhost, true) != nil { 168 service.Notice(rb, client.t("Invalid vhost")) 169 return 170 } 171 } 172 // else: command == "del", vhost == "" 173 174 _, err := server.accounts.VHostSet(user, vhost) 175 if err != nil { 176 service.Notice(rb, client.t("An error occurred")) 177 } else if vhost != "" { 178 service.Notice(rb, client.t("Successfully set vhost")) 179 } else { 180 service.Notice(rb, client.t("Successfully cleared vhost")) 181 } 182} 183 184func hsSetCloakSecretHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 185 secret := params[0] 186 expectedCode := utils.ConfirmationCode(secret, server.ctime) 187 if len(params) == 1 || params[1] != expectedCode { 188 service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: changing the cloak secret will invalidate stored ban/invite/exception lists.$b"))) 189 service.Notice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/HS SETCLOAKSECRET %s %s", secret, expectedCode))) 190 return 191 } 192 StoreCloakSecret(server.store, secret) 193 service.Notice(rb, client.t("Rotated the cloak secret; you must rehash or restart the server for it to take effect")) 194} 195