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.

hostserv.go 9.8KB

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