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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  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. "github.com/oragono/oragono/irc/sno"
  10. )
  11. const (
  12. hostservHelp = `HostServ lets you manage your vhost (i.e., the string displayed
  13. in place of your client's hostname/IP).`
  14. hsNickMask = "HostServ!HostServ@localhost"
  15. )
  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. unsplitFinalParam: true,
  111. },
  112. "forbid": {
  113. handler: hsForbidHandler,
  114. help: `Syntax: $bFORBID <user>$b
  115. FORBID prevents a user from using any vhost, including ones on the offer list.`,
  116. helpShort: `$bFORBID$b prevents a user from using vhosts.`,
  117. capabs: []string{"vhosts"},
  118. enabled: hostservEnabled,
  119. minParams: 1,
  120. maxParams: 1,
  121. },
  122. "permit": {
  123. handler: hsForbidHandler,
  124. help: `Syntax: $bPERMIT <user>$b
  125. PERMIT undoes FORBID, allowing the user to TAKE vhosts again.`,
  126. helpShort: `$bPERMIT$b allows a user to use vhosts again.`,
  127. capabs: []string{"vhosts"},
  128. enabled: hostservEnabled,
  129. minParams: 1,
  130. maxParams: 1,
  131. },
  132. "offerlist": {
  133. handler: hsOfferListHandler,
  134. help: `Syntax: $bOFFERLIST$b
  135. OFFERLIST lists vhosts that can be chosen without requiring operator approval;
  136. to use one of the listed vhosts, take it with /HOSTSERV TAKE.`,
  137. helpShort: `$bOFFERLIST$b lists vhosts that can be taken without operator approval.`,
  138. enabled: hostservEnabled,
  139. minParams: 0,
  140. maxParams: 0,
  141. },
  142. "take": {
  143. handler: hsTakeHandler,
  144. help: `Syntax: $bTAKE$b <vhost>
  145. TAKE sets your vhost to one of the vhosts in the server's offer list; to see
  146. the offered vhosts, use /HOSTSERV OFFERLIST.`,
  147. helpShort: `$bTAKE$b sets your vhost to one of the options from the offer list.`,
  148. enabled: hostservEnabled,
  149. authRequired: true,
  150. minParams: 1,
  151. maxParams: 1,
  152. },
  153. }
  154. )
  155. // hsNotice sends the client a notice from HostServ
  156. func hsNotice(rb *ResponseBuffer, text string) {
  157. rb.Add(nil, hsNickMask, "NOTICE", rb.target.Nick(), text)
  158. }
  159. // hsNotifyChannel notifies the designated channel of new vhost activity
  160. func hsNotifyChannel(server *Server, message string) {
  161. chname := server.Config().Accounts.VHosts.UserRequests.Channel
  162. channel := server.channels.Get(chname)
  163. if channel == nil {
  164. return
  165. }
  166. chname = channel.Name()
  167. for _, client := range channel.Members() {
  168. client.Send(nil, hsNickMask, "PRIVMSG", chname, message)
  169. }
  170. }
  171. func hsOnOffHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  172. enable := false
  173. if command == "on" {
  174. enable = true
  175. }
  176. _, err := server.accounts.VHostSetEnabled(client, enable)
  177. if err == errNoVhost {
  178. hsNotice(rb, client.t(err.Error()))
  179. } else if err != nil {
  180. hsNotice(rb, client.t("An error occurred"))
  181. } else if enable {
  182. hsNotice(rb, client.t("Successfully enabled your vhost"))
  183. } else {
  184. hsNotice(rb, client.t("Successfully disabled your vhost"))
  185. }
  186. }
  187. func hsRequestHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  188. vhost := params[0]
  189. if validateVhost(server, vhost, false) != nil {
  190. hsNotice(rb, client.t("Invalid vhost"))
  191. return
  192. }
  193. accountName := client.Account()
  194. _, err := server.accounts.VHostRequest(accountName, vhost, time.Duration(server.Config().Accounts.VHosts.UserRequests.Cooldown))
  195. if err != nil {
  196. if throttled, ok := err.(*vhostThrottleExceeded); ok {
  197. hsNotice(rb, fmt.Sprintf(client.t("You must wait an additional %v before making another request"), throttled.timeRemaining))
  198. } else if err == errVhostsForbidden {
  199. hsNotice(rb, client.t("An administrator has denied you the ability to use vhosts"))
  200. } else {
  201. hsNotice(rb, client.t("An error occurred"))
  202. }
  203. } else {
  204. hsNotice(rb, client.t("Your vhost request will be reviewed by an administrator"))
  205. chanMsg := fmt.Sprintf("Account %s requests vhost %s", accountName, vhost)
  206. hsNotifyChannel(server, chanMsg)
  207. server.snomasks.Send(sno.LocalVhosts, chanMsg)
  208. }
  209. }
  210. func hsStatusHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  211. var accountName string
  212. if len(params) > 0 {
  213. if !client.HasRoleCapabs("vhosts") {
  214. hsNotice(rb, client.t("Command restricted"))
  215. return
  216. }
  217. accountName = params[0]
  218. } else {
  219. accountName = client.Account()
  220. if accountName == "" {
  221. hsNotice(rb, client.t("You're not logged into an account"))
  222. return
  223. }
  224. }
  225. account, err := server.accounts.LoadAccount(accountName)
  226. if err != nil {
  227. if err != errAccountDoesNotExist {
  228. server.logger.Warning("internal", "error loading account info", accountName, err.Error())
  229. }
  230. hsNotice(rb, client.t("No such account"))
  231. return
  232. }
  233. if account.VHost.Forbidden {
  234. hsNotice(rb, client.t("An administrator has denied you the ability to use vhosts"))
  235. return
  236. }
  237. if account.VHost.ApprovedVHost != "" {
  238. hsNotice(rb, fmt.Sprintf(client.t("Account %[1]s has vhost: %[2]s"), accountName, account.VHost.ApprovedVHost))
  239. if !account.VHost.Enabled {
  240. hsNotice(rb, client.t("This vhost is currently disabled, but can be enabled with /HS ON"))
  241. }
  242. } else {
  243. hsNotice(rb, fmt.Sprintf(client.t("Account %s has no vhost"), accountName))
  244. }
  245. if account.VHost.RequestedVHost != "" {
  246. hsNotice(rb, fmt.Sprintf(client.t("A request is pending for vhost: %s"), account.VHost.RequestedVHost))
  247. }
  248. if account.VHost.RejectedVHost != "" {
  249. hsNotice(rb, fmt.Sprintf(client.t("A request was previously made for vhost: %s"), account.VHost.RejectedVHost))
  250. hsNotice(rb, fmt.Sprintf(client.t("It was rejected for reason: %s"), account.VHost.RejectionReason))
  251. }
  252. }
  253. func validateVhost(server *Server, vhost string, oper bool) error {
  254. config := server.Config()
  255. if len(vhost) > config.Accounts.VHosts.MaxLength {
  256. return errVHostTooLong
  257. }
  258. if !config.Accounts.VHosts.ValidRegexp.MatchString(vhost) {
  259. return errVHostBadCharacters
  260. }
  261. return nil
  262. }
  263. func hsSetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  264. user := params[0]
  265. var vhost string
  266. if command == "set" {
  267. vhost = params[1]
  268. if validateVhost(server, vhost, true) != nil {
  269. hsNotice(rb, client.t("Invalid vhost"))
  270. return
  271. }
  272. }
  273. // else: command == "del", vhost == ""
  274. _, err := server.accounts.VHostSet(user, vhost)
  275. if err != nil {
  276. hsNotice(rb, client.t("An error occurred"))
  277. } else if vhost != "" {
  278. hsNotice(rb, client.t("Successfully set vhost"))
  279. } else {
  280. hsNotice(rb, client.t("Successfully cleared vhost"))
  281. }
  282. }
  283. func hsWaitingHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  284. requests, total := server.accounts.VHostListRequests(10)
  285. hsNotice(rb, fmt.Sprintf(client.t("There are %[1]d pending requests for vhosts (%[2]d displayed)"), total, len(requests)))
  286. for i, request := range requests {
  287. hsNotice(rb, fmt.Sprintf(client.t("%[1]d. User %[2]s requests vhost: %[3]s"), i+1, request.Account, request.RequestedVHost))
  288. }
  289. }
  290. func hsApproveHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  291. user := params[0]
  292. vhostInfo, err := server.accounts.VHostApprove(user)
  293. if err != nil {
  294. hsNotice(rb, client.t("An error occurred"))
  295. } else {
  296. hsNotice(rb, fmt.Sprintf(client.t("Successfully approved vhost request for %s"), user))
  297. chanMsg := fmt.Sprintf("Oper %[1]s approved vhost %[2]s for account %[3]s", client.Nick(), vhostInfo.ApprovedVHost, user)
  298. hsNotifyChannel(server, chanMsg)
  299. server.snomasks.Send(sno.LocalVhosts, chanMsg)
  300. for _, client := range server.accounts.AccountToClients(user) {
  301. client.Send(nil, hsNickMask, "NOTICE", client.Nick(), client.t("Your vhost request was approved by an administrator"))
  302. }
  303. }
  304. }
  305. func hsRejectHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  306. var reason string
  307. user := params[0]
  308. if len(params) > 1 {
  309. reason = params[1]
  310. }
  311. vhostInfo, err := server.accounts.VHostReject(user, reason)
  312. if err != nil {
  313. hsNotice(rb, client.t("An error occurred"))
  314. } else {
  315. hsNotice(rb, fmt.Sprintf(client.t("Successfully rejected vhost request for %s"), user))
  316. chanMsg := fmt.Sprintf("Oper %s rejected vhost %s for account %s, with the reason: %v", client.Nick(), vhostInfo.RejectedVHost, user, reason)
  317. hsNotifyChannel(server, chanMsg)
  318. server.snomasks.Send(sno.LocalVhosts, chanMsg)
  319. for _, client := range server.accounts.AccountToClients(user) {
  320. if reason == "" {
  321. client.Send(nil, hsNickMask, "NOTICE", client.Nick(), client.t("Your vhost request was rejected by an administrator"))
  322. } else {
  323. client.Send(nil, hsNickMask, "NOTICE", client.Nick(), fmt.Sprintf(client.t("Your vhost request was rejected by an administrator. The reason given was: %s"), reason))
  324. }
  325. }
  326. }
  327. }
  328. func hsForbidHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  329. user := params[0]
  330. forbidden := command == "forbid"
  331. _, err := server.accounts.VHostForbid(user, forbidden)
  332. if err == errAccountDoesNotExist {
  333. hsNotice(rb, client.t("No such account"))
  334. } else if err != nil {
  335. hsNotice(rb, client.t("An error occurred"))
  336. } else {
  337. if forbidden {
  338. hsNotice(rb, fmt.Sprintf(client.t("User %s is no longer allowed to use vhosts"), user))
  339. } else {
  340. hsNotice(rb, fmt.Sprintf(client.t("User %s is now allowed to use vhosts"), user))
  341. }
  342. }
  343. }
  344. func hsOfferListHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  345. vhostConfig := server.Config().Accounts.VHosts
  346. if len(vhostConfig.OfferList) == 0 {
  347. if vhostConfig.UserRequests.Enabled {
  348. hsNotice(rb, client.t("The server does not offer any vhosts, but you can request one with /HOSTSERV REQUEST"))
  349. } else {
  350. hsNotice(rb, client.t("The server does not offer any vhosts"))
  351. }
  352. } else {
  353. hsNotice(rb, client.t("The following vhosts are available and can be chosen with /HOSTSERV TAKE:"))
  354. for _, vhost := range vhostConfig.OfferList {
  355. hsNotice(rb, vhost)
  356. }
  357. }
  358. }
  359. func hsTakeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  360. config := server.Config()
  361. vhost := params[0]
  362. found := false
  363. for _, offered := range config.Accounts.VHosts.OfferList {
  364. if offered == vhost {
  365. found = true
  366. }
  367. }
  368. if !found {
  369. hsNotice(rb, client.t("That vhost isn't being offered by the server"))
  370. return
  371. }
  372. account := client.Account()
  373. _, err := server.accounts.VHostTake(account, vhost, time.Duration(config.Accounts.VHosts.UserRequests.Cooldown))
  374. if err != nil {
  375. if throttled, ok := err.(*vhostThrottleExceeded); ok {
  376. hsNotice(rb, fmt.Sprintf(client.t("You must wait an additional %v before taking a vhost"), throttled.timeRemaining))
  377. } else if err == errVhostsForbidden {
  378. hsNotice(rb, client.t("An administrator has denied you the ability to use vhosts"))
  379. } else {
  380. hsNotice(rb, client.t("An error occurred"))
  381. }
  382. } else {
  383. hsNotice(rb, client.t("Successfully set vhost"))
  384. server.snomasks.Send(sno.LocalVhosts, fmt.Sprintf("Client %s (account %s) took vhost %s", client.Nick(), account, vhost))
  385. }
  386. }