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

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