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.9KB

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