選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

hostserv.go 10.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. // Copyright (c) 2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "errors"
  6. "fmt"
  7. "regexp"
  8. "time"
  9. )
  10. const hostservHelp = `HostServ lets you manage your vhost (i.e., the string displayed
  11. in place of your client's hostname/IP).`
  12. var (
  13. errVHostBadCharacters = errors.New("Vhost contains prohibited characters")
  14. errVHostTooLong = errors.New("Vhost is too long")
  15. // ascii only for now
  16. defaultValidVhostRegex = regexp.MustCompile(`^[0-9A-Za-z.\-_/]+$`)
  17. )
  18. func hostservEnabled(config *Config) bool {
  19. return config.Accounts.VHosts.Enabled
  20. }
  21. func hostservRequestsEnabled(config *Config) bool {
  22. return config.Accounts.VHosts.Enabled && config.Accounts.VHosts.UserRequests.Enabled
  23. }
  24. var (
  25. hostservCommands = map[string]*serviceCommand{
  26. "on": {
  27. handler: hsOnOffHandler,
  28. help: `Syntax: $bON$b
  29. ON enables your vhost, if you have one approved.`,
  30. helpShort: `$bON$b enables your vhost, if you have one approved.`,
  31. authRequired: true,
  32. enabled: hostservEnabled,
  33. },
  34. "off": {
  35. handler: hsOnOffHandler,
  36. help: `Syntax: $bOFF$b
  37. OFF disables your vhost, if you have one approved.`,
  38. helpShort: `$bOFF$b disables your vhost, if you have one approved.`,
  39. authRequired: true,
  40. enabled: hostservEnabled,
  41. },
  42. "request": {
  43. handler: hsRequestHandler,
  44. help: `Syntax: $bREQUEST <vhost>$b
  45. REQUEST requests that a new vhost by assigned to your account. The request must
  46. then be approved by a server operator.`,
  47. helpShort: `$bREQUEST$b requests a new vhost, pending operator approval.`,
  48. authRequired: true,
  49. enabled: hostservRequestsEnabled,
  50. minParams: 1,
  51. },
  52. "status": {
  53. handler: hsStatusHandler,
  54. help: `Syntax: $bSTATUS [user]$b
  55. STATUS displays your current vhost, if any, and the status of your most recent
  56. request for a new one. A server operator can view someone else's status.`,
  57. helpShort: `$bSTATUS$b shows your vhost and request status.`,
  58. enabled: hostservEnabled,
  59. },
  60. "set": {
  61. handler: hsSetHandler,
  62. help: `Syntax: $bSET <user> <vhost>$b
  63. SET sets a user's vhost, bypassing the request system.`,
  64. helpShort: `$bSET$b sets a user's vhost.`,
  65. capabs: []string{"vhosts"},
  66. enabled: hostservEnabled,
  67. minParams: 2,
  68. },
  69. "del": {
  70. handler: hsSetHandler,
  71. help: `Syntax: $bDEL <user>$b
  72. DEL deletes a user's vhost.`,
  73. helpShort: `$bDEL$b deletes a user's vhost.`,
  74. capabs: []string{"vhosts"},
  75. enabled: hostservEnabled,
  76. minParams: 1,
  77. },
  78. "waiting": {
  79. handler: hsWaitingHandler,
  80. help: `Syntax: $bWAITING$b
  81. WAITING shows a list of pending vhost requests, which can then be approved
  82. or rejected.`,
  83. helpShort: `$bWAITING$b shows a list of pending vhost requests.`,
  84. capabs: []string{"vhosts"},
  85. enabled: hostservEnabled,
  86. },
  87. "approve": {
  88. handler: hsApproveHandler,
  89. help: `Syntax: $bAPPROVE <user>$b
  90. APPROVE approves a user's vhost request.`,
  91. helpShort: `$bAPPROVE$b approves a user's vhost request.`,
  92. capabs: []string{"vhosts"},
  93. enabled: hostservEnabled,
  94. minParams: 1,
  95. },
  96. "reject": {
  97. handler: hsRejectHandler,
  98. help: `Syntax: $bREJECT <user> [<reason>]$b
  99. REJECT rejects a user's vhost request, optionally giving them a reason
  100. for the rejection.`,
  101. helpShort: `$bREJECT$b rejects a user's vhost request.`,
  102. capabs: []string{"vhosts"},
  103. enabled: hostservEnabled,
  104. minParams: 1,
  105. maxParams: 2,
  106. unsplitFinalParam: true,
  107. },
  108. }
  109. )
  110. // hsNotice sends the client a notice from HostServ
  111. func hsNotice(rb *ResponseBuffer, text string) {
  112. rb.Add(nil, "HostServ!HostServ@localhost", "NOTICE", rb.target.Nick(), text)
  113. }
  114. // hsNotifyChannel notifies the designated channel of new vhost activity
  115. func hsNotifyChannel(server *Server, message string) {
  116. chname := server.AccountConfig().VHosts.UserRequests.Channel
  117. channel := server.channels.Get(chname)
  118. if channel == nil {
  119. return
  120. }
  121. chname = channel.Name()
  122. for _, client := range channel.Members() {
  123. client.Send(nil, "HostServ", "PRIVMSG", chname, message)
  124. }
  125. }
  126. func hsOnOffHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  127. enable := false
  128. if command == "on" {
  129. enable = true
  130. }
  131. _, err := server.accounts.VHostSetEnabled(client, enable)
  132. if err == errNoVhost {
  133. hsNotice(rb, client.t(err.Error()))
  134. } else if err != nil {
  135. hsNotice(rb, client.t("An error occurred"))
  136. } else if enable {
  137. hsNotice(rb, client.t("Successfully enabled your vhost"))
  138. } else {
  139. hsNotice(rb, client.t("Successfully disabled your vhost"))
  140. }
  141. }
  142. func hsRequestHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  143. vhost := params[0]
  144. if validateVhost(server, vhost, false) != nil {
  145. hsNotice(rb, client.t("Invalid vhost"))
  146. return
  147. }
  148. accountName := client.Account()
  149. account, err := server.accounts.LoadAccount(client.Account())
  150. if err != nil {
  151. hsNotice(rb, client.t("An error occurred"))
  152. return
  153. }
  154. elapsed := time.Since(account.VHost.LastRequestTime)
  155. remainingTime := server.AccountConfig().VHosts.UserRequests.Cooldown - elapsed
  156. // you can update your existing request, but if you were rejected,
  157. // you can't spam a replacement request
  158. if account.VHost.RequestedVHost == "" && remainingTime > 0 {
  159. hsNotice(rb, fmt.Sprintf(client.t("You must wait an additional %v before making another request"), remainingTime))
  160. return
  161. }
  162. _, err = server.accounts.VHostRequest(accountName, vhost)
  163. if err != nil {
  164. hsNotice(rb, client.t("An error occurred"))
  165. } else {
  166. hsNotice(rb, client.t("Your vhost request will be reviewed by an administrator"))
  167. chanMsg := fmt.Sprintf("Account %s requests vhost %s", accountName, vhost)
  168. hsNotifyChannel(server, chanMsg)
  169. // TODO send admins a snomask of some kind
  170. }
  171. }
  172. func hsStatusHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  173. var accountName string
  174. if len(params) > 0 {
  175. if !client.HasRoleCapabs("vhosts") {
  176. hsNotice(rb, client.t("Command restricted"))
  177. return
  178. }
  179. accountName = params[0]
  180. } else {
  181. accountName = client.Account()
  182. if accountName == "" {
  183. hsNotice(rb, client.t("You're not logged into an account"))
  184. return
  185. }
  186. }
  187. account, err := server.accounts.LoadAccount(accountName)
  188. if err != nil {
  189. if err != errAccountDoesNotExist {
  190. server.logger.Warning("internal", "error loading account info", accountName, err.Error())
  191. }
  192. hsNotice(rb, client.t("No such account"))
  193. return
  194. }
  195. if account.VHost.ApprovedVHost != "" {
  196. hsNotice(rb, fmt.Sprintf(client.t("Account %[1]s has vhost: %[2]s"), accountName, account.VHost.ApprovedVHost))
  197. if !account.VHost.Enabled {
  198. hsNotice(rb, client.t("This vhost is currently disabled, but can be enabled with /HS ON"))
  199. }
  200. } else {
  201. hsNotice(rb, fmt.Sprintf(client.t("Account %s has no vhost"), accountName))
  202. }
  203. if account.VHost.RequestedVHost != "" {
  204. hsNotice(rb, fmt.Sprintf(client.t("A request is pending for vhost: %s"), account.VHost.RequestedVHost))
  205. }
  206. if account.VHost.RejectedVHost != "" {
  207. hsNotice(rb, fmt.Sprintf(client.t("A request was previously made for vhost: %s"), account.VHost.RejectedVHost))
  208. hsNotice(rb, fmt.Sprintf(client.t("It was rejected for reason: %s"), account.VHost.RejectionReason))
  209. }
  210. }
  211. func validateVhost(server *Server, vhost string, oper bool) error {
  212. ac := server.AccountConfig()
  213. if len(vhost) > ac.VHosts.MaxLength {
  214. return errVHostTooLong
  215. }
  216. if !ac.VHosts.ValidRegexp.MatchString(vhost) {
  217. return errVHostBadCharacters
  218. }
  219. return nil
  220. }
  221. func hsSetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  222. user := params[0]
  223. var vhost string
  224. if command == "set" {
  225. vhost = params[1]
  226. if validateVhost(server, vhost, true) != nil {
  227. hsNotice(rb, client.t("Invalid vhost"))
  228. return
  229. }
  230. }
  231. // else: command == "del", vhost == ""
  232. _, err := server.accounts.VHostSet(user, vhost)
  233. if err != nil {
  234. hsNotice(rb, client.t("An error occurred"))
  235. } else if vhost != "" {
  236. hsNotice(rb, client.t("Successfully set vhost"))
  237. } else {
  238. hsNotice(rb, client.t("Successfully cleared vhost"))
  239. }
  240. }
  241. func hsWaitingHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  242. requests, total := server.accounts.VHostListRequests(10)
  243. hsNotice(rb, fmt.Sprintf(client.t("There are %[1]d pending requests for vhosts (%[2]d displayed)"), total, len(requests)))
  244. for i, request := range requests {
  245. hsNotice(rb, fmt.Sprintf(client.t("%[1]d. User %[2]s requests vhost: %[3]s"), i+1, request.Account, request.RequestedVHost))
  246. }
  247. }
  248. func hsApproveHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  249. user := params[0]
  250. vhostInfo, err := server.accounts.VHostApprove(user)
  251. if err != nil {
  252. hsNotice(rb, client.t("An error occurred"))
  253. } else {
  254. hsNotice(rb, fmt.Sprintf(client.t("Successfully approved vhost request for %s"), user))
  255. chanMsg := fmt.Sprintf("Oper %[1]s approved vhost %[2]s for account %[3]s", client.Nick(), vhostInfo.ApprovedVHost, user)
  256. hsNotifyChannel(server, chanMsg)
  257. for _, client := range server.accounts.AccountToClients(user) {
  258. client.Notice(client.t("Your vhost request was approved by an administrator"))
  259. }
  260. }
  261. }
  262. func hsRejectHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  263. var reason string
  264. user := params[0]
  265. if len(params) > 1 {
  266. reason = params[1]
  267. }
  268. vhostInfo, err := server.accounts.VHostReject(user, reason)
  269. if err != nil {
  270. hsNotice(rb, client.t("An error occurred"))
  271. } else {
  272. hsNotice(rb, fmt.Sprintf(client.t("Successfully rejected vhost request for %s"), user))
  273. chanMsg := fmt.Sprintf("Oper %s rejected vhost %s for account %s, with the reason: %v", client.Nick(), vhostInfo.RejectedVHost, user, reason)
  274. hsNotifyChannel(server, chanMsg)
  275. for _, client := range server.accounts.AccountToClients(user) {
  276. if reason == "" {
  277. client.Notice("Your vhost request was rejected by an administrator")
  278. } else {
  279. client.Notice(fmt.Sprintf(client.t("Your vhost request was rejected by an administrator. The reason given was: %s"), reason))
  280. }
  281. }
  282. }
  283. }