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

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