2018-04-23 08:38:35 +02:00
|
|
|
// Copyright (c) 2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
|
2018-04-19 08:48:19 +02:00
|
|
|
// released under the MIT license
|
|
|
|
|
|
|
|
package irc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const hostservHelp = `HostServ lets you manage your vhost (i.e., the string displayed
|
|
|
|
in place of your client's hostname/IP).
|
|
|
|
|
|
|
|
To see in-depth help for a specific HostServ command, try:
|
|
|
|
$b/HS HELP <command>$b
|
|
|
|
|
|
|
|
Here are the commands you can use:
|
|
|
|
%s`
|
|
|
|
|
|
|
|
var (
|
|
|
|
errVHostBadCharacters = errors.New("Vhost contains prohibited characters")
|
|
|
|
errVHostTooLong = errors.New("Vhost is too long")
|
|
|
|
// ascii only for now
|
2018-04-23 08:38:35 +02:00
|
|
|
defaultValidVhostRegex = regexp.MustCompile(`^[0-9A-Za-z.\-_/]+$`)
|
2018-04-19 08:48:19 +02:00
|
|
|
)
|
|
|
|
|
2019-01-04 04:32:07 +01:00
|
|
|
func hostservEnabled(config *Config) bool {
|
|
|
|
return config.Accounts.VHosts.Enabled
|
2018-04-19 08:48:19 +02:00
|
|
|
}
|
|
|
|
|
2019-01-04 04:32:07 +01:00
|
|
|
func hostservRequestsEnabled(config *Config) bool {
|
|
|
|
return config.Accounts.VHosts.Enabled && config.Accounts.VHosts.UserRequests.Enabled
|
2018-04-19 08:48:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
hostservCommands = map[string]*serviceCommand{
|
|
|
|
"on": {
|
|
|
|
handler: hsOnOffHandler,
|
|
|
|
help: `Syntax: $bON$b
|
|
|
|
|
|
|
|
ON enables your vhost, if you have one approved.`,
|
|
|
|
helpShort: `$bON$b enables your vhost, if you have one approved.`,
|
|
|
|
authRequired: true,
|
|
|
|
enabled: hostservEnabled,
|
|
|
|
},
|
|
|
|
"off": {
|
|
|
|
handler: hsOnOffHandler,
|
|
|
|
help: `Syntax: $bOFF$b
|
|
|
|
|
|
|
|
OFF disables your vhost, if you have one approved.`,
|
|
|
|
helpShort: `$bOFF$b disables your vhost, if you have one approved.`,
|
|
|
|
authRequired: true,
|
|
|
|
enabled: hostservEnabled,
|
|
|
|
},
|
|
|
|
"request": {
|
|
|
|
handler: hsRequestHandler,
|
|
|
|
help: `Syntax: $bREQUEST <vhost>$b
|
|
|
|
|
|
|
|
REQUEST requests that a new vhost by assigned to your account. The request must
|
|
|
|
then be approved by a server operator.`,
|
|
|
|
helpShort: `$bREQUEST$b requests a new vhost, pending operator approval.`,
|
|
|
|
authRequired: true,
|
|
|
|
enabled: hostservRequestsEnabled,
|
2019-01-04 04:32:07 +01:00
|
|
|
minParams: 1,
|
2018-04-19 08:48:19 +02:00
|
|
|
},
|
|
|
|
"status": {
|
|
|
|
handler: hsStatusHandler,
|
2019-01-04 04:32:07 +01:00
|
|
|
help: `Syntax: $bSTATUS [user]$b
|
2018-04-19 08:48:19 +02:00
|
|
|
|
|
|
|
STATUS displays your current vhost, if any, and the status of your most recent
|
2019-01-04 04:32:07 +01:00
|
|
|
request for a new one. A server operator can view someone else's status.`,
|
|
|
|
helpShort: `$bSTATUS$b shows your vhost and request status.`,
|
|
|
|
enabled: hostservEnabled,
|
2018-04-19 08:48:19 +02:00
|
|
|
},
|
|
|
|
"set": {
|
|
|
|
handler: hsSetHandler,
|
|
|
|
help: `Syntax: $bSET <user> <vhost>$b
|
|
|
|
|
|
|
|
SET sets a user's vhost, bypassing the request system.`,
|
|
|
|
helpShort: `$bSET$b sets a user's vhost.`,
|
2018-04-23 08:38:35 +02:00
|
|
|
capabs: []string{"vhosts"},
|
2018-04-19 08:48:19 +02:00
|
|
|
enabled: hostservEnabled,
|
2019-01-04 04:32:07 +01:00
|
|
|
minParams: 2,
|
2018-04-19 08:48:19 +02:00
|
|
|
},
|
|
|
|
"del": {
|
|
|
|
handler: hsSetHandler,
|
|
|
|
help: `Syntax: $bDEL <user>$b
|
|
|
|
|
2018-12-28 17:07:08 +01:00
|
|
|
DEL deletes a user's vhost.`,
|
2018-04-19 08:48:19 +02:00
|
|
|
helpShort: `$bDEL$b deletes a user's vhost.`,
|
2018-04-23 08:38:35 +02:00
|
|
|
capabs: []string{"vhosts"},
|
2018-04-19 08:48:19 +02:00
|
|
|
enabled: hostservEnabled,
|
2019-01-04 04:32:07 +01:00
|
|
|
minParams: 1,
|
2018-04-19 08:48:19 +02:00
|
|
|
},
|
|
|
|
"waiting": {
|
|
|
|
handler: hsWaitingHandler,
|
|
|
|
help: `Syntax: $bWAITING$b
|
|
|
|
|
|
|
|
WAITING shows a list of pending vhost requests, which can then be approved
|
|
|
|
or rejected.`,
|
|
|
|
helpShort: `$bWAITING$b shows a list of pending vhost requests.`,
|
2018-04-23 08:38:35 +02:00
|
|
|
capabs: []string{"vhosts"},
|
2018-04-19 08:48:19 +02:00
|
|
|
enabled: hostservEnabled,
|
|
|
|
},
|
|
|
|
"approve": {
|
|
|
|
handler: hsApproveHandler,
|
|
|
|
help: `Syntax: $bAPPROVE <user>$b
|
|
|
|
|
|
|
|
APPROVE approves a user's vhost request.`,
|
|
|
|
helpShort: `$bAPPROVE$b approves a user's vhost request.`,
|
2018-04-23 08:38:35 +02:00
|
|
|
capabs: []string{"vhosts"},
|
2018-04-19 08:48:19 +02:00
|
|
|
enabled: hostservEnabled,
|
2019-01-04 04:32:07 +01:00
|
|
|
minParams: 1,
|
2018-04-19 08:48:19 +02:00
|
|
|
},
|
|
|
|
"reject": {
|
|
|
|
handler: hsRejectHandler,
|
|
|
|
help: `Syntax: $bREJECT <user> [<reason>]$b
|
|
|
|
|
|
|
|
REJECT rejects a user's vhost request, optionally giving them a reason
|
|
|
|
for the rejection.`,
|
|
|
|
helpShort: `$bREJECT$b rejects a user's vhost request.`,
|
2018-04-23 08:38:35 +02:00
|
|
|
capabs: []string{"vhosts"},
|
2018-04-19 08:48:19 +02:00
|
|
|
enabled: hostservEnabled,
|
2019-01-04 04:32:07 +01:00
|
|
|
minParams: 1,
|
2019-01-04 16:03:12 +01:00
|
|
|
maxParams: 2,
|
2018-04-19 08:48:19 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// hsNotice sends the client a notice from HostServ
|
|
|
|
func hsNotice(rb *ResponseBuffer, text string) {
|
2019-05-19 10:27:44 +02:00
|
|
|
rb.Add(nil, "HostServ!HostServ@localhost", "NOTICE", rb.target.Nick(), text)
|
2018-04-19 08:48:19 +02:00
|
|
|
}
|
|
|
|
|
2018-04-23 08:38:35 +02:00
|
|
|
// hsNotifyChannel notifies the designated channel of new vhost activity
|
|
|
|
func hsNotifyChannel(server *Server, message string) {
|
|
|
|
chname := server.AccountConfig().VHosts.UserRequests.Channel
|
|
|
|
channel := server.channels.Get(chname)
|
|
|
|
if channel == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
chname = channel.Name()
|
|
|
|
for _, client := range channel.Members() {
|
|
|
|
client.Send(nil, "HostServ", "PRIVMSG", chname, message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 04:32:07 +01:00
|
|
|
func hsOnOffHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
2018-04-19 08:48:19 +02:00
|
|
|
enable := false
|
|
|
|
if command == "on" {
|
|
|
|
enable = true
|
|
|
|
}
|
|
|
|
|
2018-04-23 08:38:35 +02:00
|
|
|
_, err := server.accounts.VHostSetEnabled(client, enable)
|
2018-04-19 08:48:19 +02:00
|
|
|
if err != nil {
|
|
|
|
hsNotice(rb, client.t("An error occurred"))
|
|
|
|
} else if enable {
|
|
|
|
hsNotice(rb, client.t("Successfully enabled your vhost"))
|
|
|
|
} else {
|
|
|
|
hsNotice(rb, client.t("Successfully disabled your vhost"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 04:32:07 +01:00
|
|
|
func hsRequestHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
|
|
vhost := params[0]
|
2018-04-19 08:48:19 +02:00
|
|
|
if validateVhost(server, vhost, false) != nil {
|
|
|
|
hsNotice(rb, client.t("Invalid vhost"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
accountName := client.Account()
|
|
|
|
account, err := server.accounts.LoadAccount(client.Account())
|
|
|
|
if err != nil {
|
|
|
|
hsNotice(rb, client.t("An error occurred"))
|
|
|
|
return
|
|
|
|
}
|
2019-05-12 09:12:50 +02:00
|
|
|
elapsed := time.Since(account.VHost.LastRequestTime)
|
2018-04-23 08:38:35 +02:00
|
|
|
remainingTime := server.AccountConfig().VHosts.UserRequests.Cooldown - elapsed
|
2018-04-19 08:48:19 +02:00
|
|
|
// you can update your existing request, but if you were rejected,
|
|
|
|
// you can't spam a replacement request
|
|
|
|
if account.VHost.RequestedVHost == "" && remainingTime > 0 {
|
|
|
|
hsNotice(rb, fmt.Sprintf(client.t("You must wait an additional %v before making another request"), remainingTime))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-04-23 08:38:35 +02:00
|
|
|
_, err = server.accounts.VHostRequest(accountName, vhost)
|
2018-04-19 08:48:19 +02:00
|
|
|
if err != nil {
|
|
|
|
hsNotice(rb, client.t("An error occurred"))
|
|
|
|
} else {
|
|
|
|
hsNotice(rb, fmt.Sprintf(client.t("Your vhost request will be reviewed by an administrator")))
|
2018-04-23 08:38:35 +02:00
|
|
|
chanMsg := fmt.Sprintf("Account %s requests vhost %s", accountName, vhost)
|
|
|
|
hsNotifyChannel(server, chanMsg)
|
2018-04-19 08:48:19 +02:00
|
|
|
// TODO send admins a snomask of some kind
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 04:32:07 +01:00
|
|
|
func hsStatusHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
|
|
var accountName string
|
|
|
|
if len(params) > 0 {
|
|
|
|
if !client.HasRoleCapabs("vhosts") {
|
|
|
|
hsNotice(rb, client.t("Command restricted"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
accountName = params[0]
|
|
|
|
} else {
|
|
|
|
accountName = client.Account()
|
2019-02-14 19:10:35 +01:00
|
|
|
if accountName == "" {
|
|
|
|
hsNotice(rb, client.t("You're not logged into an account"))
|
|
|
|
return
|
|
|
|
}
|
2019-01-04 04:32:07 +01:00
|
|
|
}
|
|
|
|
|
2018-04-19 08:48:19 +02:00
|
|
|
account, err := server.accounts.LoadAccount(accountName)
|
|
|
|
if err != nil {
|
2019-01-04 04:32:07 +01:00
|
|
|
if err != errAccountDoesNotExist {
|
|
|
|
server.logger.Warning("internal", "error loading account info", accountName, err.Error())
|
|
|
|
}
|
|
|
|
hsNotice(rb, client.t("No such account"))
|
2018-04-19 08:48:19 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if account.VHost.ApprovedVHost != "" {
|
2019-02-22 03:37:11 +01:00
|
|
|
hsNotice(rb, fmt.Sprintf(client.t("Account %[1]s has vhost: %[2]s"), accountName, account.VHost.ApprovedVHost))
|
2018-04-19 08:48:19 +02:00
|
|
|
if !account.VHost.Enabled {
|
|
|
|
hsNotice(rb, fmt.Sprintf(client.t("This vhost is currently disabled, but can be enabled with /HS ON")))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
hsNotice(rb, fmt.Sprintf(client.t("Account %s has no vhost"), accountName))
|
|
|
|
}
|
|
|
|
if account.VHost.RequestedVHost != "" {
|
|
|
|
hsNotice(rb, fmt.Sprintf(client.t("A request is pending for vhost: %s"), account.VHost.RequestedVHost))
|
|
|
|
}
|
|
|
|
if account.VHost.RejectedVHost != "" {
|
|
|
|
hsNotice(rb, fmt.Sprintf(client.t("A request was previously made for vhost: %s"), account.VHost.RejectedVHost))
|
|
|
|
hsNotice(rb, fmt.Sprintf(client.t("It was rejected for reason: %s"), account.VHost.RejectionReason))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateVhost(server *Server, vhost string, oper bool) error {
|
|
|
|
ac := server.AccountConfig()
|
2018-04-23 08:38:35 +02:00
|
|
|
if len(vhost) > ac.VHosts.MaxLength {
|
2018-04-19 08:48:19 +02:00
|
|
|
return errVHostTooLong
|
|
|
|
}
|
2018-04-23 08:38:35 +02:00
|
|
|
if !ac.VHosts.ValidRegexp.MatchString(vhost) {
|
2018-04-19 08:48:19 +02:00
|
|
|
return errVHostBadCharacters
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-01-04 04:32:07 +01:00
|
|
|
func hsSetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
|
|
user := params[0]
|
|
|
|
var vhost string
|
|
|
|
|
2018-04-19 08:48:19 +02:00
|
|
|
if command == "set" {
|
2019-01-04 04:32:07 +01:00
|
|
|
vhost = params[1]
|
2018-04-19 08:48:19 +02:00
|
|
|
if validateVhost(server, vhost, true) != nil {
|
|
|
|
hsNotice(rb, client.t("Invalid vhost"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2019-01-04 04:32:07 +01:00
|
|
|
// else: command == "del", vhost == ""
|
2018-04-19 08:48:19 +02:00
|
|
|
|
2018-04-23 08:38:35 +02:00
|
|
|
_, err := server.accounts.VHostSet(user, vhost)
|
2018-04-19 08:48:19 +02:00
|
|
|
if err != nil {
|
|
|
|
hsNotice(rb, client.t("An error occurred"))
|
|
|
|
} else if vhost != "" {
|
|
|
|
hsNotice(rb, client.t("Successfully set vhost"))
|
|
|
|
} else {
|
|
|
|
hsNotice(rb, client.t("Successfully cleared vhost"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 04:32:07 +01:00
|
|
|
func hsWaitingHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
2018-04-19 08:48:19 +02:00
|
|
|
requests, total := server.accounts.VHostListRequests(10)
|
2019-02-22 03:37:11 +01:00
|
|
|
hsNotice(rb, fmt.Sprintf(client.t("There are %[1]d pending requests for vhosts (%[2]d displayed)"), total, len(requests)))
|
2018-04-19 08:48:19 +02:00
|
|
|
for i, request := range requests {
|
2019-02-22 03:37:11 +01:00
|
|
|
hsNotice(rb, fmt.Sprintf(client.t("%[1]d. User %[2]s requests vhost: %[3]s"), i+1, request.Account, request.RequestedVHost))
|
2018-04-19 08:48:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 04:32:07 +01:00
|
|
|
func hsApproveHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
|
|
user := params[0]
|
2018-04-19 08:48:19 +02:00
|
|
|
|
2018-04-23 08:38:35 +02:00
|
|
|
vhostInfo, err := server.accounts.VHostApprove(user)
|
2018-04-19 08:48:19 +02:00
|
|
|
if err != nil {
|
|
|
|
hsNotice(rb, client.t("An error occurred"))
|
|
|
|
} else {
|
|
|
|
hsNotice(rb, fmt.Sprintf(client.t("Successfully approved vhost request for %s"), user))
|
2019-02-22 03:37:11 +01:00
|
|
|
chanMsg := fmt.Sprintf("Oper %[1]s approved vhost %[2]s for account %[3]s", client.Nick(), vhostInfo.ApprovedVHost, user)
|
2018-04-23 08:38:35 +02:00
|
|
|
hsNotifyChannel(server, chanMsg)
|
2018-04-19 08:48:19 +02:00
|
|
|
for _, client := range server.accounts.AccountToClients(user) {
|
|
|
|
client.Notice(client.t("Your vhost request was approved by an administrator"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 04:32:07 +01:00
|
|
|
func hsRejectHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
|
|
var reason string
|
|
|
|
user := params[0]
|
|
|
|
if len(params) > 1 {
|
2019-01-04 16:03:12 +01:00
|
|
|
reason = params[1]
|
2018-04-19 08:48:19 +02:00
|
|
|
}
|
|
|
|
|
2018-04-23 08:38:35 +02:00
|
|
|
vhostInfo, err := server.accounts.VHostReject(user, reason)
|
2018-04-19 08:48:19 +02:00
|
|
|
if err != nil {
|
|
|
|
hsNotice(rb, client.t("An error occurred"))
|
|
|
|
} else {
|
|
|
|
hsNotice(rb, fmt.Sprintf(client.t("Successfully rejected vhost request for %s"), user))
|
2018-04-23 08:38:35 +02:00
|
|
|
chanMsg := fmt.Sprintf("Oper %s rejected vhost %s for account %s, with the reason: %v", client.Nick(), vhostInfo.RejectedVHost, user, reason)
|
|
|
|
hsNotifyChannel(server, chanMsg)
|
2018-04-19 08:48:19 +02:00
|
|
|
for _, client := range server.accounts.AccountToClients(user) {
|
|
|
|
if reason == "" {
|
|
|
|
client.Notice("Your vhost request was rejected by an administrator")
|
|
|
|
} else {
|
|
|
|
client.Notice(fmt.Sprintf(client.t("Your vhost request was rejected by an administrator. The reason given was: %s"), reason))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|