1// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net> 2// released under the MIT license 3 4package irc 5 6import ( 7 "fmt" 8 "regexp" 9 "sort" 10 "strings" 11 "time" 12 13 "github.com/ergochat/ergo/irc/modes" 14 "github.com/ergochat/ergo/irc/sno" 15 "github.com/ergochat/ergo/irc/utils" 16 "github.com/ergochat/irc-go/ircfmt" 17) 18 19const chanservHelp = `ChanServ lets you register and manage channels.` 20 21func chanregEnabled(config *Config) bool { 22 return config.Channels.Registration.Enabled 23} 24 25var ( 26 chanservCommands = map[string]*serviceCommand{ 27 "op": { 28 handler: csOpHandler, 29 help: `Syntax: $bOP #channel [nickname]$b 30 31OP makes the given nickname, or yourself, a channel admin. You can only use 32this command if you're a founder or in the AMODEs of the channel.`, 33 helpShort: `$bOP$b makes the given user (or yourself) a channel admin.`, 34 authRequired: true, 35 enabled: chanregEnabled, 36 minParams: 1, 37 }, 38 "deop": { 39 handler: csDeopHandler, 40 help: `Syntax: $bDEOP #channel [nickname]$b 41 42DEOP removes the given nickname, or yourself, the channel admin. You can only use 43this command if you're the founder of the channel.`, 44 helpShort: `$bDEOP$b removes the given user (or yourself) from a channel admin.`, 45 enabled: chanregEnabled, 46 minParams: 1, 47 }, 48 "register": { 49 handler: csRegisterHandler, 50 help: `Syntax: $bREGISTER #channel$b 51 52REGISTER lets you own the given channel. If you rejoin this channel, you'll be 53given admin privs on it. Modes set on the channel and the topic will also be 54remembered.`, 55 helpShort: `$bREGISTER$b lets you own a given channel.`, 56 authRequired: true, 57 enabled: chanregEnabled, 58 minParams: 1, 59 }, 60 "unregister": { 61 handler: csUnregisterHandler, 62 help: `Syntax: $bUNREGISTER #channel [code]$b 63 64UNREGISTER deletes a channel registration, allowing someone else to claim it. 65To prevent accidental unregistrations, a verification code is required; 66invoking the command without a code will display the necessary code.`, 67 helpShort: `$bUNREGISTER$b deletes a channel registration.`, 68 enabled: chanregEnabled, 69 minParams: 1, 70 }, 71 "drop": { 72 aliasOf: "unregister", 73 }, 74 "amode": { 75 handler: csAmodeHandler, 76 help: `Syntax: $bAMODE #channel [mode change] [account]$b 77 78AMODE lists or modifies persistent mode settings that affect channel members. 79For example, $bAMODE #channel +o dan$b grants the holder of the "dan" 80account the +o operator mode every time they join #channel. To list current 81accounts and modes, use $bAMODE #channel$b. Note that users are always 82referenced by their registered account names, not their nicknames. 83The permissions hierarchy for adding and removing modes is the same as in 84the ordinary /MODE command.`, 85 helpShort: `$bAMODE$b modifies persistent mode settings for channel members.`, 86 enabled: chanregEnabled, 87 minParams: 1, 88 }, 89 "clear": { 90 handler: csClearHandler, 91 help: `Syntax: $bCLEAR #channel target$b 92 93CLEAR removes users or settings from a channel. Specifically: 94 95$bCLEAR #channel users$b kicks all users except for you. 96$bCLEAR #channel access$b resets all stored bans, invites, ban exceptions, 97and persistent user-mode grants made with CS AMODE.`, 98 helpShort: `$bCLEAR$b removes users or settings from a channel.`, 99 enabled: chanregEnabled, 100 minParams: 2, 101 }, 102 "transfer": { 103 handler: csTransferHandler, 104 help: `Syntax: $bTRANSFER [accept] #channel user [code]$b 105 106TRANSFER transfers ownership of a channel from one user to another. 107To prevent accidental transfers, a verification code is required. For 108example, $bTRANSFER #channel alice$b displays the required confirmation 109code, then $bTRANSFER #channel alice 2930242125$b initiates the transfer. 110Unless you are an IRC operator with the correct permissions, alice must 111then accept the transfer, which she can do with $bTRANSFER accept #channel$b. 112To cancel a pending transfer, transfer the channel to yourself.`, 113 helpShort: `$bTRANSFER$b transfers ownership of a channel to another user.`, 114 enabled: chanregEnabled, 115 minParams: 2, 116 }, 117 "purge": { 118 handler: csPurgeHandler, 119 help: `Syntax: $bPURGE <ADD | DEL | LIST> #channel [code] [reason]$b 120 121PURGE ADD blacklists a channel from the server, making it impossible to join 122or otherwise interact with the channel. If the channel currently has members, 123they will be kicked from it. PURGE may also be applied preemptively to 124channels that do not currently have members. A purge can be undone with 125PURGE DEL. To list purged channels, use PURGE LIST.`, 126 helpShort: `$bPURGE$b blacklists a channel from the server.`, 127 capabs: []string{"chanreg"}, 128 minParams: 1, 129 maxParams: 3, 130 unsplitFinalParam: true, 131 }, 132 "list": { 133 handler: csListHandler, 134 help: `Syntax: $bLIST [regex]$b 135 136LIST returns the list of registered channels, which match the given regex. 137If no regex is provided, all registered channels are returned.`, 138 helpShort: `$bLIST$b searches the list of registered channels.`, 139 capabs: []string{"chanreg"}, 140 minParams: 0, 141 }, 142 "info": { 143 handler: csInfoHandler, 144 help: `Syntax: $INFO #channel$b 145 146INFO displays info about a registered channel.`, 147 helpShort: `$bINFO$b displays info about a registered channel.`, 148 enabled: chanregEnabled, 149 }, 150 "get": { 151 handler: csGetHandler, 152 help: `Syntax: $bGET #channel <setting>$b 153 154GET queries the current values of the channel settings. For more information 155on the settings and their possible values, see HELP SET.`, 156 helpShort: `$bGET$b queries the current values of a channel's settings`, 157 enabled: chanregEnabled, 158 minParams: 2, 159 }, 160 "set": { 161 handler: csSetHandler, 162 helpShort: `$bSET$b modifies a channel's settings`, 163 // these are broken out as separate strings so they can be translated separately 164 helpStrings: []string{ 165 `Syntax $bSET #channel <setting> <value>$b 166 167SET modifies a channel's settings. The following settings are available:`, 168 169 `$bHISTORY$b 170'history' lets you control how channel history is stored. Your options are: 1711. 'off' [no history] 1722. 'ephemeral' [a limited amount of temporary history, not stored on disk] 1733. 'on' [history stored in a permanent database, if available] 1744. 'default' [use the server default]`, 175 `$bQUERY-CUTOFF$b 176'query-cutoff' lets you restrict how much channel history can be retrieved 177by unprivileged users. Your options are: 1781. 'none' [no restrictions] 1792. 'registration-time' [users can view history from after their account was 180 registered, plus a grace period] 1813. 'join-time' [users can view history from after they joined the 182 channel; note that history will be effectively 183 unavailable to clients that are not always-on] 1844. 'default' [use the server default]`, 185 }, 186 enabled: chanregEnabled, 187 minParams: 3, 188 }, 189 "howtoban": { 190 handler: csHowToBanHandler, 191 helpShort: `$bHOWTOBAN$b suggests the best available way of banning a user`, 192 help: `Syntax: $bHOWTOBAN #channel <nick> 193 194The best way to ban a user from a channel will depend on how they are 195connected to the server. $bHOWTOBAN$b suggests a ban command that will 196(ideally) prevent the user from returning to the channel.`, 197 enabled: chanregEnabled, 198 minParams: 2, 199 }, 200 } 201) 202 203func csAmodeHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 204 channelName := params[0] 205 206 channel := server.channels.Get(channelName) 207 if channel == nil { 208 service.Notice(rb, client.t("Channel does not exist")) 209 return 210 } else if channel.Founder() == "" { 211 service.Notice(rb, client.t("Channel is not registered")) 212 return 213 } 214 215 modeChanges, unknown := modes.ParseChannelModeChanges(params[1:]...) 216 var change modes.ModeChange 217 if len(modeChanges) > 1 || len(unknown) > 0 { 218 service.Notice(rb, client.t("Invalid mode change")) 219 return 220 } else if len(modeChanges) == 1 { 221 change = modeChanges[0] 222 } else { 223 change = modes.ModeChange{Op: modes.List} 224 } 225 226 // normalize and validate the account argument 227 accountIsValid := false 228 change.Arg, _ = CasefoldName(change.Arg) 229 switch change.Op { 230 case modes.List: 231 accountIsValid = true 232 case modes.Add: 233 // if we're adding a mode, the account must exist 234 if change.Arg != "" { 235 _, err := server.accounts.LoadAccount(change.Arg) 236 accountIsValid = (err == nil) 237 } 238 case modes.Remove: 239 // allow removal of accounts that may have been deleted 240 accountIsValid = (change.Arg != "") 241 } 242 if !accountIsValid { 243 service.Notice(rb, client.t("Account does not exist")) 244 return 245 } 246 247 affectedModes, err := channel.ProcessAccountToUmodeChange(client, change) 248 249 if err == errInsufficientPrivs { 250 service.Notice(rb, client.t("Insufficient privileges")) 251 return 252 } else if err != nil { 253 service.Notice(rb, client.t("Internal error")) 254 return 255 } 256 257 switch change.Op { 258 case modes.List: 259 // sort the persistent modes in descending order of priority 260 sort.Slice(affectedModes, func(i, j int) bool { 261 return umodeGreaterThan(affectedModes[i].Mode, affectedModes[j].Mode) 262 }) 263 service.Notice(rb, fmt.Sprintf(client.t("Channel %[1]s has %[2]d persistent modes set"), channelName, len(affectedModes))) 264 for _, modeChange := range affectedModes { 265 service.Notice(rb, fmt.Sprintf(client.t("Account %[1]s receives mode +%[2]s"), modeChange.Arg, string(modeChange.Mode))) 266 } 267 case modes.Add, modes.Remove: 268 if len(affectedModes) > 0 { 269 service.Notice(rb, fmt.Sprintf(client.t("Successfully set persistent mode %[1]s on %[2]s"), strings.Join([]string{string(change.Op), string(change.Mode)}, ""), change.Arg)) 270 // #729: apply change to current membership 271 for _, member := range channel.Members() { 272 if member.Account() == change.Arg { 273 applied, change := channel.applyModeToMember(client, change, rb) 274 if applied { 275 announceCmodeChanges(channel, modes.ModeChanges{change}, server.name, "*", "", false, rb) 276 } 277 } 278 } 279 } else { 280 service.Notice(rb, client.t("No changes were made")) 281 } 282 } 283} 284 285func csOpHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 286 channelInfo := server.channels.Get(params[0]) 287 if channelInfo == nil { 288 service.Notice(rb, client.t("Channel does not exist")) 289 return 290 } 291 channelName := channelInfo.Name() 292 founder := channelInfo.Founder() 293 294 clientAccount := client.Account() 295 if clientAccount == "" { 296 service.Notice(rb, client.t("You're not logged into an account")) 297 return 298 } 299 300 var target *Client 301 if len(params) > 1 { 302 target = server.clients.Get(params[1]) 303 if target == nil { 304 service.Notice(rb, client.t("Could not find given client")) 305 return 306 } 307 } else { 308 target = client 309 } 310 311 var givenMode modes.Mode 312 if target == client { 313 if clientAccount == founder { 314 givenMode = modes.ChannelFounder 315 } else { 316 givenMode = channelInfo.getAmode(clientAccount) 317 if givenMode == modes.Mode(0) { 318 service.Notice(rb, client.t("You don't have any stored privileges on that channel")) 319 return 320 } 321 } 322 } else { 323 if clientAccount == founder { 324 givenMode = modes.ChannelOperator 325 } else { 326 service.Notice(rb, client.t("Only the channel founder can do this")) 327 return 328 } 329 } 330 331 applied, change := channelInfo.applyModeToMember(client, 332 modes.ModeChange{Mode: givenMode, 333 Op: modes.Add, 334 Arg: target.NickCasefolded(), 335 }, 336 rb) 337 if applied { 338 announceCmodeChanges(channelInfo, modes.ModeChanges{change}, server.name, "*", "", false, rb) 339 } 340 341 service.Notice(rb, client.t("Successfully granted operator privileges")) 342 343 tnick := target.Nick() 344 server.logger.Info("services", fmt.Sprintf("Client %s op'd [%s] in channel %s", client.Nick(), tnick, channelName)) 345 server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] CS OP'd $c[grey][$r%s$c[grey]] in channel $c[grey][$r%s$c[grey]]"), client.NickMaskString(), tnick, channelName)) 346} 347 348func csDeopHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 349 channel := server.channels.Get(params[0]) 350 if channel == nil { 351 service.Notice(rb, client.t("Channel does not exist")) 352 return 353 } 354 if !channel.hasClient(client) { 355 service.Notice(rb, client.t("You're not on that channel")) 356 return 357 } 358 359 var target *Client 360 if len(params) > 1 { 361 target = server.clients.Get(params[1]) 362 if target == nil { 363 service.Notice(rb, client.t("Could not find given client")) 364 return 365 } 366 } else { 367 target = client 368 } 369 370 present, _, cumodes := channel.ClientStatus(target) 371 if !present || len(cumodes) == 0 { 372 service.Notice(rb, client.t("Target has no privileges to remove")) 373 return 374 } 375 376 tnick := target.Nick() 377 modeChanges := make(modes.ModeChanges, len(cumodes)) 378 for i, mode := range cumodes { 379 modeChanges[i] = modes.ModeChange{ 380 Mode: mode, 381 Op: modes.Remove, 382 Arg: tnick, 383 } 384 } 385 386 // use the user's own permissions for the check, then announce 387 // the changes as coming from chanserv 388 applied := channel.ApplyChannelModeChanges(client, false, modeChanges, rb) 389 details := client.Details() 390 isBot := client.HasMode(modes.Bot) 391 announceCmodeChanges(channel, applied, details.nickMask, details.accountName, details.account, isBot, rb) 392 393 if len(applied) == 0 { 394 return 395 } 396 397 service.Notice(rb, client.t("Successfully removed operator privileges")) 398} 399 400func csRegisterHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 401 if server.Config().Channels.Registration.OperatorOnly && !client.HasRoleCapabs("chanreg") { 402 service.Notice(rb, client.t("Channel registration is restricted to server operators")) 403 return 404 } 405 channelName := params[0] 406 channelInfo := server.channels.Get(channelName) 407 if channelInfo == nil { 408 service.Notice(rb, client.t("No such channel")) 409 return 410 } 411 if !channelInfo.ClientIsAtLeast(client, modes.ChannelOperator) { 412 service.Notice(rb, client.t("You must be an oper on the channel to register it")) 413 return 414 } 415 416 account := client.Account() 417 if !checkChanLimit(service, client, rb) { 418 return 419 } 420 421 // this provides the synchronization that allows exactly one registration of the channel: 422 err := server.channels.SetRegistered(channelName, account) 423 if err != nil { 424 service.Notice(rb, err.Error()) 425 return 426 } 427 428 service.Notice(rb, fmt.Sprintf(client.t("Channel %s successfully registered"), channelName)) 429 430 server.logger.Info("services", fmt.Sprintf("Client %s registered channel %s", client.Nick(), channelName)) 431 server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString)) 432 433 // give them founder privs 434 applied, change := channelInfo.applyModeToMember(client, 435 modes.ModeChange{ 436 Mode: modes.ChannelFounder, 437 Op: modes.Add, 438 Arg: client.NickCasefolded(), 439 }, 440 rb) 441 if applied { 442 announceCmodeChanges(channelInfo, modes.ModeChanges{change}, service.prefix, "*", "", false, rb) 443 } 444} 445 446// check whether a client has already registered too many channels 447func checkChanLimit(service *ircService, client *Client, rb *ResponseBuffer) (ok bool) { 448 account := client.Account() 449 channelsAlreadyRegistered := client.server.accounts.ChannelsForAccount(account) 450 ok = len(channelsAlreadyRegistered) < client.server.Config().Channels.Registration.MaxChannelsPerAccount || client.HasRoleCapabs("chanreg") 451 if !ok { 452 service.Notice(rb, client.t("You have already registered the maximum number of channels; try dropping some with /CS UNREGISTER")) 453 } 454 return 455} 456 457func csPrivsCheck(service *ircService, channel RegisteredChannel, client *Client, rb *ResponseBuffer) (success bool) { 458 founder := channel.Founder 459 if founder == "" { 460 service.Notice(rb, client.t("That channel is not registered")) 461 return false 462 } 463 if client.HasRoleCapabs("chanreg") { 464 return true 465 } 466 if founder != client.Account() { 467 service.Notice(rb, client.t("Insufficient privileges")) 468 return false 469 } 470 return true 471} 472 473func csUnregisterHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 474 channelName := params[0] 475 var verificationCode string 476 if len(params) > 1 { 477 verificationCode = params[1] 478 } 479 480 channel := server.channels.Get(channelName) 481 if channel == nil { 482 service.Notice(rb, client.t("No such channel")) 483 return 484 } 485 486 info := channel.ExportRegistration(0) 487 channelKey := info.NameCasefolded 488 if !csPrivsCheck(service, info, client, rb) { 489 return 490 } 491 492 expectedCode := utils.ConfirmationCode(info.Name, info.RegisteredAt) 493 if expectedCode != verificationCode { 494 service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this channel will remove all stored channel attributes.$b"))) 495 service.Notice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/CS UNREGISTER %s %s", channelKey, expectedCode))) 496 return 497 } 498 499 server.channels.SetUnregistered(channelKey, info.Founder) 500 service.Notice(rb, fmt.Sprintf(client.t("Channel %s is now unregistered"), channelKey)) 501} 502 503func csClearHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 504 channel := server.channels.Get(params[0]) 505 if channel == nil { 506 service.Notice(rb, client.t("Channel does not exist")) 507 return 508 } 509 if !csPrivsCheck(service, channel.ExportRegistration(0), client, rb) { 510 return 511 } 512 513 switch strings.ToLower(params[1]) { 514 case "access": 515 channel.resetAccess() 516 service.Notice(rb, client.t("Successfully reset channel access")) 517 case "users": 518 for _, target := range channel.Members() { 519 if target != client { 520 channel.Kick(client, target, "Cleared by ChanServ", rb, true) 521 } 522 } 523 default: 524 service.Notice(rb, client.t("Invalid parameters")) 525 } 526 527} 528 529func csTransferHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 530 if strings.ToLower(params[0]) == "accept" { 531 processTransferAccept(service, client, params[1], rb) 532 return 533 } 534 chname := params[0] 535 channel := server.channels.Get(chname) 536 if channel == nil { 537 service.Notice(rb, client.t("Channel does not exist")) 538 return 539 } 540 regInfo := channel.ExportRegistration(0) 541 chname = regInfo.Name 542 account := client.Account() 543 isFounder := account != "" && account == regInfo.Founder 544 oper := client.Oper() 545 hasPrivs := oper.HasRoleCapab("chanreg") 546 if !isFounder && !hasPrivs { 547 service.Notice(rb, client.t("Insufficient privileges")) 548 return 549 } 550 target := params[1] 551 targetAccount, err := server.accounts.LoadAccount(params[1]) 552 if err != nil { 553 service.Notice(rb, client.t("Account does not exist")) 554 return 555 } 556 if targetAccount.NameCasefolded != account { 557 expectedCode := utils.ConfirmationCode(regInfo.Name, regInfo.RegisteredAt) 558 codeValidated := 2 < len(params) && params[2] == expectedCode 559 if !codeValidated { 560 service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: you are about to transfer control of your channel to another user.$b"))) 561 service.Notice(rb, fmt.Sprintf(client.t("To confirm your channel transfer, type: /CS TRANSFER %[1]s %[2]s %[3]s"), chname, target, expectedCode)) 562 return 563 } 564 } 565 if !isFounder { 566 message := fmt.Sprintf("Operator %s ran CS TRANSFER on %s to account %s", oper.Name, chname, target) 567 server.snomasks.Send(sno.LocalOpers, message) 568 server.logger.Info("opers", message) 569 } 570 status, err := channel.Transfer(client, target, hasPrivs) 571 if err == nil { 572 switch status { 573 case channelTransferComplete: 574 service.Notice(rb, fmt.Sprintf(client.t("Successfully transferred channel %[1]s to account %[2]s"), chname, target)) 575 case channelTransferPending: 576 sendTransferPendingNotice(service, server, target, chname) 577 service.Notice(rb, fmt.Sprintf(client.t("Transfer of channel %[1]s to account %[2]s succeeded, pending acceptance"), chname, target)) 578 case channelTransferCancelled: 579 service.Notice(rb, fmt.Sprintf(client.t("Cancelled pending transfer of channel %s"), chname)) 580 } 581 } else { 582 switch err { 583 case errChannelNotOwnedByAccount: 584 service.Notice(rb, client.t("You don't own that channel")) 585 default: 586 service.Notice(rb, client.t("Could not transfer channel")) 587 } 588 } 589} 590 591func sendTransferPendingNotice(service *ircService, server *Server, account, chname string) { 592 clients := server.accounts.AccountToClients(account) 593 if len(clients) == 0 { 594 return 595 } 596 var client *Client 597 for _, candidate := range clients { 598 client = candidate 599 if candidate.NickCasefolded() == candidate.Account() { 600 break // prefer the login where the nick is the account 601 } 602 } 603 client.Send(nil, service.prefix, "NOTICE", client.Nick(), fmt.Sprintf(client.t("You have been offered ownership of channel %[1]s. To accept, /CS TRANSFER ACCEPT %[1]s"), chname)) 604} 605 606func processTransferAccept(service *ircService, client *Client, chname string, rb *ResponseBuffer) { 607 channel := client.server.channels.Get(chname) 608 if channel == nil { 609 service.Notice(rb, client.t("Channel does not exist")) 610 return 611 } 612 if !checkChanLimit(service, client, rb) { 613 return 614 } 615 switch channel.AcceptTransfer(client) { 616 case nil: 617 service.Notice(rb, fmt.Sprintf(client.t("Successfully accepted ownership of channel %s"), channel.Name())) 618 case errChannelTransferNotOffered: 619 service.Notice(rb, fmt.Sprintf(client.t("You weren't offered ownership of channel %s"), channel.Name())) 620 default: 621 service.Notice(rb, fmt.Sprintf(client.t("Could not accept ownership of channel %s"), channel.Name())) 622 } 623} 624 625func csPurgeHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 626 oper := client.Oper() 627 if oper == nil { 628 return // should be impossible because you need oper capabs for this 629 } 630 631 switch strings.ToLower(params[0]) { 632 case "add": 633 csPurgeAddHandler(service, client, params[1:], oper.Name, rb) 634 case "del", "remove": 635 csPurgeDelHandler(service, client, params[1:], oper.Name, rb) 636 case "list": 637 csPurgeListHandler(service, client, rb) 638 default: 639 service.Notice(rb, client.t("Invalid parameters")) 640 } 641} 642 643func csPurgeAddHandler(service *ircService, client *Client, params []string, operName string, rb *ResponseBuffer) { 644 if len(params) == 0 { 645 service.Notice(rb, client.t("Invalid parameters")) 646 return 647 } 648 649 chname := params[0] 650 params = params[1:] 651 channel := client.server.channels.Get(chname) // possibly nil 652 var ctime time.Time 653 if channel != nil { 654 chname = channel.Name() 655 ctime = channel.Ctime() 656 } 657 code := utils.ConfirmationCode(chname, ctime) 658 659 if len(params) == 0 || params[0] != code { 660 service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: you are about to empty this channel and remove it from the server.$b"))) 661 service.Notice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/CS PURGE ADD %s %s", chname, code))) 662 return 663 } 664 params = params[1:] 665 666 var reason string 667 if 1 < len(params) { 668 reason = params[1] 669 } 670 671 purgeRecord := ChannelPurgeRecord{ 672 Oper: operName, 673 PurgedAt: time.Now().UTC(), 674 Reason: reason, 675 } 676 switch client.server.channels.Purge(chname, purgeRecord) { 677 case nil: 678 if channel != nil { // channel need not exist to be purged 679 for _, target := range channel.Members() { 680 channel.Kick(client, target, "Cleared by ChanServ", rb, true) 681 } 682 } 683 service.Notice(rb, fmt.Sprintf(client.t("Successfully purged channel %s from the server"), chname)) 684 case errInvalidChannelName: 685 service.Notice(rb, fmt.Sprintf(client.t("Can't purge invalid channel %s"), chname)) 686 default: 687 service.Notice(rb, client.t("An error occurred")) 688 } 689} 690 691func csPurgeDelHandler(service *ircService, client *Client, params []string, operName string, rb *ResponseBuffer) { 692 if len(params) == 0 { 693 service.Notice(rb, client.t("Invalid parameters")) 694 return 695 } 696 697 chname := params[0] 698 switch client.server.channels.Unpurge(chname) { 699 case nil: 700 service.Notice(rb, fmt.Sprintf(client.t("Successfully unpurged channel %s from the server"), chname)) 701 case errNoSuchChannel: 702 service.Notice(rb, fmt.Sprintf(client.t("Channel %s wasn't previously purged from the server"), chname)) 703 default: 704 service.Notice(rb, client.t("An error occurred")) 705 } 706} 707 708func csPurgeListHandler(service *ircService, client *Client, rb *ResponseBuffer) { 709 l := client.server.channels.ListPurged() 710 service.Notice(rb, fmt.Sprintf(client.t("There are %d purged channel(s)."), len(l))) 711 for i, c := range l { 712 service.Notice(rb, fmt.Sprintf("%d: %s", i+1, c)) 713 } 714} 715 716func csListHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 717 if !client.HasRoleCapabs("chanreg") { 718 service.Notice(rb, client.t("Insufficient privileges")) 719 return 720 } 721 722 var searchRegex *regexp.Regexp 723 if len(params) > 0 { 724 var err error 725 searchRegex, err = regexp.Compile(params[0]) 726 if err != nil { 727 service.Notice(rb, client.t("Invalid regex")) 728 return 729 } 730 } 731 732 service.Notice(rb, ircfmt.Unescape(client.t("*** $bChanServ LIST$b ***"))) 733 734 channels := server.channelRegistry.AllChannels() 735 for _, channel := range channels { 736 if searchRegex == nil || searchRegex.MatchString(channel) { 737 service.Notice(rb, fmt.Sprintf(" %s", channel)) 738 } 739 } 740 741 service.Notice(rb, ircfmt.Unescape(client.t("*** $bEnd of ChanServ LIST$b ***"))) 742} 743 744func csInfoHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 745 if len(params) == 0 { 746 // #765 747 listRegisteredChannels(service, client.Account(), rb) 748 return 749 } 750 751 chname, err := CasefoldChannel(params[0]) 752 if err != nil { 753 service.Notice(rb, client.t("Invalid channel name")) 754 return 755 } 756 757 // purge status 758 if client.HasRoleCapabs("chanreg") { 759 purgeRecord, err := server.channelRegistry.LoadPurgeRecord(chname) 760 if err == nil { 761 service.Notice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname)) 762 service.Notice(rb, fmt.Sprintf(client.t("Purged by operator: %s"), purgeRecord.Oper)) 763 service.Notice(rb, fmt.Sprintf(client.t("Purged at: %s"), purgeRecord.PurgedAt.Format(time.RFC1123))) 764 if purgeRecord.Reason != "" { 765 service.Notice(rb, fmt.Sprintf(client.t("Purge reason: %s"), purgeRecord.Reason)) 766 } 767 } 768 } else { 769 if server.channels.IsPurged(chname) { 770 service.Notice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname)) 771 } 772 } 773 774 var chinfo RegisteredChannel 775 channel := server.channels.Get(params[0]) 776 if channel != nil { 777 chinfo = channel.ExportRegistration(0) 778 } else { 779 chinfo, err = server.channelRegistry.LoadChannel(chname) 780 if err != nil && !(err == errNoSuchChannel || err == errFeatureDisabled) { 781 service.Notice(rb, client.t("An error occurred")) 782 return 783 } 784 } 785 786 // channel exists but is unregistered, or doesn't exist: 787 if chinfo.Founder == "" { 788 service.Notice(rb, fmt.Sprintf(client.t("Channel %s is not registered"), chname)) 789 return 790 } 791 service.Notice(rb, fmt.Sprintf(client.t("Channel %s is registered"), chinfo.Name)) 792 service.Notice(rb, fmt.Sprintf(client.t("Founder: %s"), chinfo.Founder)) 793 service.Notice(rb, fmt.Sprintf(client.t("Registered at: %s"), chinfo.RegisteredAt.Format(time.RFC1123))) 794} 795 796func displayChannelSetting(service *ircService, settingName string, settings ChannelSettings, client *Client, rb *ResponseBuffer) { 797 config := client.server.Config() 798 799 switch strings.ToLower(settingName) { 800 case "history": 801 effectiveValue := historyEnabled(config.History.Persistent.RegisteredChannels, settings.History) 802 service.Notice(rb, fmt.Sprintf(client.t("The stored channel history setting is: %s"), historyStatusToString(settings.History))) 803 service.Notice(rb, fmt.Sprintf(client.t("Given current server settings, the channel history setting is: %s"), historyStatusToString(effectiveValue))) 804 case "query-cutoff": 805 effectiveValue := settings.QueryCutoff 806 if effectiveValue == HistoryCutoffDefault { 807 effectiveValue = config.History.Restrictions.queryCutoff 808 } 809 service.Notice(rb, fmt.Sprintf(client.t("The stored channel history query cutoff setting is: %s"), historyCutoffToString(settings.QueryCutoff))) 810 service.Notice(rb, fmt.Sprintf(client.t("Given current server settings, the channel history query cutoff setting is: %s"), historyCutoffToString(effectiveValue))) 811 default: 812 service.Notice(rb, client.t("Invalid params")) 813 } 814} 815 816func csGetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 817 chname, setting := params[0], params[1] 818 channel := server.channels.Get(chname) 819 if channel == nil { 820 service.Notice(rb, client.t("No such channel")) 821 return 822 } 823 info := channel.ExportRegistration(IncludeSettings) 824 if !csPrivsCheck(service, info, client, rb) { 825 return 826 } 827 828 displayChannelSetting(service, setting, info.Settings, client, rb) 829} 830 831func csSetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 832 chname, setting, value := params[0], params[1], params[2] 833 channel := server.channels.Get(chname) 834 if channel == nil { 835 service.Notice(rb, client.t("No such channel")) 836 return 837 } 838 info := channel.ExportRegistration(IncludeSettings) 839 settings := info.Settings 840 if !csPrivsCheck(service, info, client, rb) { 841 return 842 } 843 844 var err error 845 switch strings.ToLower(setting) { 846 case "history": 847 settings.History, err = historyStatusFromString(value) 848 if err != nil { 849 err = errInvalidParams 850 break 851 } 852 channel.SetSettings(settings) 853 channel.resizeHistory(server.Config()) 854 case "query-cutoff": 855 settings.QueryCutoff, err = historyCutoffFromString(value) 856 if err != nil { 857 err = errInvalidParams 858 break 859 } 860 channel.SetSettings(settings) 861 } 862 863 switch err { 864 case nil: 865 service.Notice(rb, client.t("Successfully changed the channel settings")) 866 displayChannelSetting(service, setting, settings, client, rb) 867 case errInvalidParams: 868 service.Notice(rb, client.t("Invalid parameters")) 869 default: 870 server.logger.Error("internal", "CS SET error:", err.Error()) 871 service.Notice(rb, client.t("An error occurred")) 872 } 873} 874 875func csHowToBanHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { 876 success := false 877 defer func() { 878 if success { 879 service.Notice(rb, client.t("Note that if the user is currently in the channel, you must /KICK them after you ban them")) 880 } 881 }() 882 883 chname, nick := params[0], params[1] 884 channel := server.channels.Get(chname) 885 if channel == nil { 886 service.Notice(rb, client.t("No such channel")) 887 return 888 } 889 890 if !(channel.ClientIsAtLeast(client, modes.ChannelOperator) || client.HasRoleCapabs("samode")) { 891 service.Notice(rb, client.t("Insufficient privileges")) 892 return 893 } 894 895 var details WhoWas 896 target := server.clients.Get(nick) 897 if target == nil { 898 whowasList := server.whoWas.Find(nick, 1) 899 if len(whowasList) == 0 { 900 service.Notice(rb, client.t("No such nick")) 901 return 902 } 903 service.Notice(rb, fmt.Sprintf(client.t("Warning: %s is not currently connected to the server. Using WHOWAS data, which may be inaccurate:"), nick)) 904 details = whowasList[0] 905 } else { 906 details = target.Details().WhoWas 907 } 908 909 if details.account != "" { 910 if channel.getAmode(details.account) != modes.Mode(0) { 911 service.Notice(rb, fmt.Sprintf(client.t("Warning: account %s currently has a persistent channel privilege granted with CS AMODE. If this mode is not removed, bans will not be respected"), details.accountName)) 912 return 913 } else if details.account == channel.Founder() { 914 service.Notice(rb, fmt.Sprintf(client.t("Warning: account %s is the channel founder and cannot be banned"), details.accountName)) 915 return 916 } 917 } 918 919 config := server.Config() 920 if !config.Server.Cloaks.EnabledForAlwaysOn { 921 service.Notice(rb, client.t("Warning: server.ip-cloaking.enabled-for-always-on is disabled. This reduces the precision of channel bans.")) 922 } 923 924 if details.account != "" { 925 if config.Accounts.NickReservation.ForceNickEqualsAccount || target.AlwaysOn() { 926 service.Notice(rb, fmt.Sprintf(client.t("User %[1]s is authenticated and can be banned by nickname: /MODE %[2]s +b %[3]s!*@*"), details.nick, channel.Name(), details.nick)) 927 success = true 928 return 929 } 930 } 931 932 ban := fmt.Sprintf("*!*@%s", strings.ToLower(details.hostname)) 933 banRe, err := utils.CompileGlob(ban, false) 934 if err != nil { 935 server.logger.Error("internal", "couldn't compile ban regex", ban, err.Error()) 936 service.Notice(rb, "An error occurred") 937 return 938 } 939 var collateralDamage []string 940 for _, mcl := range channel.Members() { 941 if mcl != target && banRe.MatchString(mcl.NickMaskCasefolded()) { 942 collateralDamage = append(collateralDamage, mcl.Nick()) 943 } 944 } 945 service.Notice(rb, fmt.Sprintf(client.t("User %[1]s can be banned by hostname: /MODE %[2]s +b %[3]s"), details.nick, channel.Name(), ban)) 946 success = true 947 if len(collateralDamage) != 0 { 948 service.Notice(rb, fmt.Sprintf(client.t("Warning: this ban will affect %d other users:"), len(collateralDamage))) 949 for _, line := range utils.BuildTokenLines(400, collateralDamage, " ") { 950 service.Notice(rb, line) 951 } 952 } 953} 954