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 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. // Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "bytes"
  6. "fmt"
  7. "hash/crc32"
  8. "sort"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "github.com/goshuirc/irc-go/ircfmt"
  13. "github.com/oragono/oragono/irc/modes"
  14. "github.com/oragono/oragono/irc/sno"
  15. )
  16. const chanservHelp = `ChanServ lets you register and manage channels.
  17. To see in-depth help for a specific ChanServ command, try:
  18. $b/CS HELP <command>$b
  19. Here are the commands you can use:
  20. %s`
  21. func chanregEnabled(config *Config) bool {
  22. return config.Channels.Registration.Enabled
  23. }
  24. var (
  25. chanservCommands = map[string]*serviceCommand{
  26. "op": {
  27. handler: csOpHandler,
  28. help: `Syntax: $bOP #channel [nickname]$b
  29. OP makes the given nickname, or yourself, a channel admin. You can only use
  30. this command if you're the founder of the channel.`,
  31. helpShort: `$bOP$b makes the given user (or yourself) a channel admin.`,
  32. authRequired: true,
  33. enabled: chanregEnabled,
  34. minParams: 1,
  35. },
  36. "register": {
  37. handler: csRegisterHandler,
  38. help: `Syntax: $bREGISTER #channel$b
  39. REGISTER lets you own the given channel. If you rejoin this channel, you'll be
  40. given admin privs on it. Modes set on the channel and the topic will also be
  41. remembered.`,
  42. helpShort: `$bREGISTER$b lets you own a given channel.`,
  43. authRequired: true,
  44. enabled: chanregEnabled,
  45. minParams: 1,
  46. },
  47. "unregister": {
  48. handler: csUnregisterHandler,
  49. help: `Syntax: $bUNREGISTER #channel [code]$b
  50. UNREGISTER deletes a channel registration, allowing someone else to claim it.
  51. To prevent accidental unregistrations, a verification code is required;
  52. invoking the command without a code will display the necessary code.`,
  53. helpShort: `$bUNREGISTER$b deletes a channel registration.`,
  54. enabled: chanregEnabled,
  55. minParams: 1,
  56. },
  57. "drop": {
  58. aliasOf: "unregister",
  59. },
  60. "amode": {
  61. handler: csAmodeHandler,
  62. help: `Syntax: $bAMODE #channel [mode change] [account]$b
  63. AMODE lists or modifies persistent mode settings that affect channel members.
  64. For example, $bAMODE #channel +o dan$b grants the the holder of the "dan"
  65. account the +o operator mode every time they join #channel. To list current
  66. accounts and modes, use $bAMODE #channel$b. Note that users are always
  67. referenced by their registered account names, not their nicknames.`,
  68. helpShort: `$bAMODE$b modifies persistent mode settings for channel members.`,
  69. enabled: chanregEnabled,
  70. minParams: 1,
  71. },
  72. }
  73. )
  74. // csNotice sends the client a notice from ChanServ
  75. func csNotice(rb *ResponseBuffer, text string) {
  76. rb.Add(nil, "ChanServ", "NOTICE", rb.target.Nick(), text)
  77. }
  78. func csAmodeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  79. channelName := params[0]
  80. channel := server.channels.Get(channelName)
  81. if channel == nil {
  82. csNotice(rb, client.t("Channel does not exist"))
  83. return
  84. } else if channel.Founder() == "" {
  85. csNotice(rb, client.t("Channel is not registered"))
  86. return
  87. }
  88. modeChanges, unknown := modes.ParseChannelModeChanges(params[1:]...)
  89. var change modes.ModeChange
  90. if len(modeChanges) > 1 || len(unknown) > 0 {
  91. csNotice(rb, client.t("Invalid mode change"))
  92. return
  93. } else if len(modeChanges) == 1 {
  94. change = modeChanges[0]
  95. } else {
  96. change = modes.ModeChange{Op: modes.List}
  97. }
  98. // normalize and validate the account argument
  99. accountIsValid := false
  100. change.Arg, _ = CasefoldName(change.Arg)
  101. switch change.Op {
  102. case modes.List:
  103. accountIsValid = true
  104. case modes.Add:
  105. // if we're adding a mode, the account must exist
  106. if change.Arg != "" {
  107. _, err := server.accounts.LoadAccount(change.Arg)
  108. accountIsValid = (err == nil)
  109. }
  110. case modes.Remove:
  111. // allow removal of accounts that may have been deleted
  112. accountIsValid = (change.Arg != "")
  113. }
  114. if !accountIsValid {
  115. csNotice(rb, client.t("Account does not exist"))
  116. return
  117. }
  118. affectedModes, err := channel.ProcessAccountToUmodeChange(client, change)
  119. if err == errInsufficientPrivs {
  120. csNotice(rb, client.t("Insufficient privileges"))
  121. return
  122. } else if err != nil {
  123. csNotice(rb, client.t("Internal error"))
  124. return
  125. }
  126. switch change.Op {
  127. case modes.List:
  128. // sort the persistent modes in descending order of priority
  129. sort.Slice(affectedModes, func(i, j int) bool {
  130. return umodeGreaterThan(affectedModes[i].Mode, affectedModes[j].Mode)
  131. })
  132. csNotice(rb, fmt.Sprintf(client.t("Channel %[1]s has %[2]d persistent modes set"), channelName, len(affectedModes)))
  133. for _, modeChange := range affectedModes {
  134. csNotice(rb, fmt.Sprintf(client.t("Account %[1]s receives mode +%[2]s"), modeChange.Arg, string(modeChange.Mode)))
  135. }
  136. case modes.Add, modes.Remove:
  137. if len(affectedModes) > 0 {
  138. csNotice(rb, fmt.Sprintf(client.t("Successfully set mode %s"), change.String()))
  139. } else {
  140. csNotice(rb, client.t("No changes were made"))
  141. }
  142. }
  143. }
  144. func csOpHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  145. channelInfo := server.channels.Get(params[0])
  146. if channelInfo == nil {
  147. csNotice(rb, client.t("Channel does not exist"))
  148. return
  149. }
  150. channelName := channelInfo.Name()
  151. clientAccount := client.Account()
  152. if clientAccount == "" || clientAccount != channelInfo.Founder() {
  153. csNotice(rb, client.t("You must be the channel founder to op"))
  154. return
  155. }
  156. var target *Client
  157. if len(params) > 1 {
  158. target = server.clients.Get(params[1])
  159. if target == nil {
  160. csNotice(rb, client.t("Could not find given client"))
  161. return
  162. }
  163. } else {
  164. target = client
  165. }
  166. // give them privs
  167. givenMode := modes.ChannelOperator
  168. if clientAccount == target.Account() {
  169. givenMode = modes.ChannelFounder
  170. }
  171. change := channelInfo.applyModeToMember(client, givenMode, modes.Add, target.NickCasefolded(), rb)
  172. if change != nil {
  173. //TODO(dan): we should change the name of String and make it return a slice here
  174. //TODO(dan): unify this code with code in modes.go
  175. args := append([]string{channelName}, strings.Split(change.String(), " ")...)
  176. for _, member := range channelInfo.Members() {
  177. member.Send(nil, fmt.Sprintf("ChanServ!services@%s", client.server.name), "MODE", args...)
  178. }
  179. }
  180. csNotice(rb, fmt.Sprintf(client.t("Successfully op'd in channel %s"), channelName))
  181. tnick := target.Nick()
  182. server.logger.Info("services", fmt.Sprintf("Client %s op'd [%s] in channel %s", client.Nick(), tnick, channelName))
  183. 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))
  184. }
  185. func csRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  186. channelName := params[0]
  187. channelKey, err := CasefoldChannel(channelName)
  188. if err != nil {
  189. csNotice(rb, client.t("Channel name is not valid"))
  190. return
  191. }
  192. channelInfo := server.channels.Get(channelKey)
  193. if channelInfo == nil || !channelInfo.ClientIsAtLeast(client, modes.ChannelOperator) {
  194. csNotice(rb, client.t("You must be an oper on the channel to register it"))
  195. return
  196. }
  197. account := client.Account()
  198. channelsAlreadyRegistered := server.accounts.ChannelsForAccount(account)
  199. if server.Config().Channels.Registration.MaxChannelsPerAccount <= len(channelsAlreadyRegistered) {
  200. csNotice(rb, client.t("You have already registered the maximum number of channels; try dropping some with /CS UNREGISTER"))
  201. return
  202. }
  203. // this provides the synchronization that allows exactly one registration of the channel:
  204. err = server.channels.SetRegistered(channelKey, account)
  205. if err != nil {
  206. csNotice(rb, err.Error())
  207. return
  208. }
  209. csNotice(rb, fmt.Sprintf(client.t("Channel %s successfully registered"), channelName))
  210. server.logger.Info("services", fmt.Sprintf("Client %s registered channel %s", client.nick, channelName))
  211. 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))
  212. // give them founder privs
  213. change := channelInfo.applyModeToMember(client, modes.ChannelFounder, modes.Add, client.NickCasefolded(), rb)
  214. if change != nil {
  215. //TODO(dan): we should change the name of String and make it return a slice here
  216. //TODO(dan): unify this code with code in modes.go
  217. args := append([]string{channelName}, strings.Split(change.String(), " ")...)
  218. for _, member := range channelInfo.Members() {
  219. member.Send(nil, fmt.Sprintf("ChanServ!services@%s", client.server.name), "MODE", args...)
  220. }
  221. }
  222. }
  223. func csUnregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  224. channelName := params[0]
  225. var verificationCode string
  226. if len(params) > 1 {
  227. verificationCode = params[1]
  228. }
  229. channelKey, err := CasefoldChannel(channelName)
  230. if channelKey == "" || err != nil {
  231. csNotice(rb, client.t("Channel name is not valid"))
  232. return
  233. }
  234. channel := server.channels.Get(channelKey)
  235. if channel == nil {
  236. csNotice(rb, client.t("No such channel"))
  237. return
  238. }
  239. founder := channel.Founder()
  240. if founder == "" {
  241. csNotice(rb, client.t("That channel is not registered"))
  242. return
  243. }
  244. hasPrivs := client.HasRoleCapabs("chanreg") || founder == client.Account()
  245. if !hasPrivs {
  246. csNotice(rb, client.t("Insufficient privileges"))
  247. return
  248. }
  249. info := channel.ExportRegistration(0)
  250. expectedCode := unregisterConfirmationCode(info.Name, info.RegisteredAt)
  251. if expectedCode != verificationCode {
  252. csNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this channel will remove all stored channel attributes.$b")))
  253. csNotice(rb, fmt.Sprintf(client.t("To confirm channel unregistration, type: /CS UNREGISTER %[1]s %[2]s"), channelKey, expectedCode))
  254. return
  255. }
  256. server.channels.SetUnregistered(channelKey, founder)
  257. csNotice(rb, fmt.Sprintf(client.t("Channel %s is now unregistered"), channelKey))
  258. }
  259. // deterministically generates a confirmation code for unregistering a channel / account
  260. func unregisterConfirmationCode(name string, registeredAt time.Time) (code string) {
  261. var codeInput bytes.Buffer
  262. codeInput.WriteString(name)
  263. codeInput.WriteString(strconv.FormatInt(registeredAt.Unix(), 16))
  264. return strconv.Itoa(int(crc32.ChecksumIEEE(codeInput.Bytes())))
  265. }