You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

chanserv.go 33KB

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