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"
2020-02-20 06:09:39 +01:00
"time"
2020-02-02 08:03:08 +01:00
2020-05-08 07:16:49 +02:00
"github.com/goshuirc/irc-go/ircfmt"
2020-02-02 08:03:08 +01:00
"github.com/oragono/oragono/irc/sno"
2020-05-08 07:16:49 +02:00
"github.com/oragono/oragono/irc/utils"
2018-04-19 08:48:19 +02:00
)
2020-03-18 11:01:19 +01:00
const (
hostservHelp = ` HostServ lets you manage your vhost ( i . e . , the string displayed
2019-07-02 18:36:32 +02:00
in place of your client ' s hostname / IP ) . `
2020-03-18 11:01:19 +01:00
hsNickMask = "HostServ!HostServ@localhost"
)
2018-04-19 08:48:19 +02:00
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 . ` ,
2019-05-29 10:25:20 +02:00
helpShort : ` $bREJECT$b rejects a user's vhost request. ` ,
capabs : [ ] string { "vhosts" } ,
enabled : hostservEnabled ,
minParams : 1 ,
maxParams : 2 ,
unsplitFinalParam : true ,
2018-04-19 08:48:19 +02:00
} ,
2020-05-08 07:16:49 +02:00
"setcloaksecret" : {
handler : hsSetCloakSecretHandler ,
help : ` Syntax : $ bSETCLOAKSECRET $ b < secret > [ code ]
SETCLOAKSECRET can be used to set or rotate the cloak secret . You should use
a cryptographically strong secret . To prevent accidental modification , a
verification code is required ; invoking the command without a code will
display the necessary code . ` ,
helpShort : ` $bSETCLOAKSECRET$b modifies the IP cloaking secret. ` ,
capabs : [ ] string { "vhosts" , "rehash" } ,
minParams : 1 ,
maxParams : 2 ,
} ,
2018-04-19 08:48:19 +02:00
}
)
// hsNotice sends the client a notice from HostServ
func hsNotice ( rb * ResponseBuffer , text string ) {
2020-03-18 11:01:19 +01:00
rb . Add ( nil , hsNickMask , "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 ) {
2020-03-16 12:54:50 +01:00
chname := server . Config ( ) . Accounts . VHosts . UserRequests . Channel
2018-04-23 08:38:35 +02:00
channel := server . channels . Get ( chname )
if channel == nil {
return
}
chname = channel . Name ( )
for _ , client := range channel . Members ( ) {
2020-03-18 11:01:19 +01:00
client . Send ( nil , hsNickMask , "PRIVMSG" , chname , message )
2018-04-23 08:38:35 +02:00
}
}
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 )
2019-05-22 05:55:04 +02:00
if err == errNoVhost {
hsNotice ( rb , client . t ( err . Error ( ) ) )
} else if err != nil {
2018-04-19 08:48:19 +02:00
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 ( )
2020-02-20 06:09:39 +01:00
_ , err := server . accounts . VHostRequest ( accountName , vhost , time . Duration ( server . Config ( ) . Accounts . VHosts . UserRequests . Cooldown ) )
2018-04-19 08:48:19 +02:00
if err != nil {
2020-01-29 04:27:56 +01:00
if throttled , ok := err . ( * vhostThrottleExceeded ) ; ok {
hsNotice ( rb , fmt . Sprintf ( client . t ( "You must wait an additional %v before making another request" ) , throttled . timeRemaining ) )
} else {
hsNotice ( rb , client . t ( "An error occurred" ) )
}
2018-04-19 08:48:19 +02:00
} else {
2019-12-17 01:50:15 +01:00
hsNotice ( rb , 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 )
2020-02-02 08:03:08 +01:00
server . snomasks . Send ( sno . LocalVhosts , chanMsg )
2018-04-19 08:48:19 +02:00
}
}
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 {
2019-12-17 01:50:15 +01:00
hsNotice ( rb , client . t ( "This vhost is currently disabled, but can be enabled with /HS ON" ) )
2018-04-19 08:48:19 +02:00
}
} 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 {
2020-03-16 12:54:50 +01:00
config := server . Config ( )
if len ( vhost ) > config . Accounts . VHosts . MaxLength {
2018-04-19 08:48:19 +02:00
return errVHostTooLong
}
2020-03-16 12:54:50 +01:00
if ! config . Accounts . 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
2020-02-02 04:19:33 +01: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 )
2020-02-02 08:03:08 +01:00
server . snomasks . Send ( sno . LocalVhosts , chanMsg )
2018-04-19 08:48:19 +02:00
for _ , client := range server . accounts . AccountToClients ( user ) {
2020-03-18 11:01:19 +01:00
client . Send ( nil , hsNickMask , "NOTICE" , client . Nick ( ) , client . t ( "Your vhost request was approved by an administrator" ) )
2018-04-19 08:48:19 +02:00
}
}
}
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 )
2020-02-02 08:03:08 +01:00
server . snomasks . Send ( sno . LocalVhosts , chanMsg )
2018-04-19 08:48:19 +02:00
for _ , client := range server . accounts . AccountToClients ( user ) {
if reason == "" {
2020-03-18 11:01:19 +01:00
client . Send ( nil , hsNickMask , "NOTICE" , client . Nick ( ) , client . t ( "Your vhost request was rejected by an administrator" ) )
2018-04-19 08:48:19 +02:00
} else {
2020-03-18 11:01:19 +01:00
client . Send ( nil , hsNickMask , "NOTICE" , client . Nick ( ) , fmt . Sprintf ( client . t ( "Your vhost request was rejected by an administrator. The reason given was: %s" ) , reason ) )
2018-04-19 08:48:19 +02:00
}
}
}
}
2020-01-12 04:43:40 +01:00
2020-05-08 07:16:49 +02:00
func hsSetCloakSecretHandler ( server * Server , client * Client , command string , params [ ] string , rb * ResponseBuffer ) {
secret := params [ 0 ]
expectedCode := utils . ConfirmationCode ( secret , server . ctime )
if len ( params ) == 1 || params [ 1 ] != expectedCode {
hsNotice ( rb , ircfmt . Unescape ( client . t ( "$bWarning: changing the cloak secret will invalidate stored ban/invite/exception lists.$b" ) ) )
2020-05-08 08:47:08 +02:00
hsNotice ( rb , fmt . Sprintf ( client . t ( "To confirm, run this command: %s" ) , fmt . Sprintf ( "/HS SETCLOAKSECRET %s %s" , secret , expectedCode ) ) )
2020-05-08 07:16:49 +02:00
return
}
StoreCloakSecret ( server . store , secret )
hsNotice ( rb , client . t ( "Rotated the cloak secret; you must rehash or restart the server for it to take effect" ) )
}