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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  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 hostservHelp = `HostServ lets you manage your vhost (i.e., the string displayed
  12. in place of your client's hostname/IP).`
  13. var (
  14. errVHostBadCharacters = errors.New("Vhost contains prohibited characters")
  15. errVHostTooLong = errors.New("Vhost is too long")
  16. // ascii only for now
  17. defaultValidVhostRegex = regexp.MustCompile(`^[0-9A-Za-z.\-_/]+$`)
  18. )
  19. func hostservEnabled(config *Config) bool {
  20. return config.Accounts.VHosts.Enabled
  21. }
  22. func hostservRequestsEnabled(config *Config) bool {
  23. return config.Accounts.VHosts.Enabled && config.Accounts.VHosts.UserRequests.Enabled
  24. }
  25. var (
  26. hostservCommands = map[string]*serviceCommand{
  27. "on": {
  28. handler: hsOnOffHandler,
  29. help: `Syntax: $bON$b
  30. ON enables your vhost, if you have one approved.`,
  31. helpShort: `$bON$b enables your vhost, if you have one approved.`,
  32. authRequired: true,
  33. enabled: hostservEnabled,
  34. },
  35. "off": {
  36. handler: hsOnOffHandler,
  37. help: `Syntax: $bOFF$b
  38. OFF disables your vhost, if you have one approved.`,
  39. helpShort: `$bOFF$b disables your vhost, if you have one approved.`,
  40. authRequired: true,
  41. enabled: hostservEnabled,
  42. },
  43. "request": {
  44. handler: hsRequestHandler,
  45. help: `Syntax: $bREQUEST <vhost>$b
  46. REQUEST requests that a new vhost by assigned to your account. The request must
  47. then be approved by a server operator.`,
  48. helpShort: `$bREQUEST$b requests a new vhost, pending operator approval.`,
  49. authRequired: true,
  50. enabled: hostservRequestsEnabled,
  51. minParams: 1,
  52. },
  53. "status": {
  54. handler: hsStatusHandler,
  55. help: `Syntax: $bSTATUS [user]$b
  56. STATUS displays your current vhost, if any, and the status of your most recent
  57. request for a new one. A server operator can view someone else's status.`,
  58. helpShort: `$bSTATUS$b shows your vhost and request status.`,
  59. enabled: hostservEnabled,
  60. },
  61. "set": {
  62. handler: hsSetHandler,
  63. help: `Syntax: $bSET <user> <vhost>$b
  64. SET sets a user's vhost, bypassing the request system.`,
  65. helpShort: `$bSET$b sets a user's vhost.`,
  66. capabs: []string{"vhosts"},
  67. enabled: hostservEnabled,
  68. minParams: 2,
  69. },
  70. "del": {
  71. handler: hsSetHandler,
  72. help: `Syntax: $bDEL <user>$b
  73. DEL deletes a user's vhost.`,
  74. helpShort: `$bDEL$b deletes a user's vhost.`,
  75. capabs: []string{"vhosts"},
  76. enabled: hostservEnabled,
  77. minParams: 1,
  78. },
  79. "waiting": {
  80. handler: hsWaitingHandler,
  81. help: `Syntax: $bWAITING$b
  82. WAITING shows a list of pending vhost requests, which can then be approved
  83. or rejected.`,
  84. helpShort: `$bWAITING$b shows a list of pending vhost requests.`,
  85. capabs: []string{"vhosts"},
  86. enabled: hostservEnabled,
  87. },
  88. "approve": {
  89. handler: hsApproveHandler,
  90. help: `Syntax: $bAPPROVE <user>$b
  91. APPROVE approves a user's vhost request.`,
  92. helpShort: `$bAPPROVE$b approves a user's vhost request.`,
  93. capabs: []string{"vhosts"},
  94. enabled: hostservEnabled,
  95. minParams: 1,
  96. },
  97. "reject": {
  98. handler: hsRejectHandler,
  99. help: `Syntax: $bREJECT <user> [<reason>]$b
  100. REJECT rejects a user's vhost request, optionally giving them a reason
  101. for the rejection.`,
  102. helpShort: `$bREJECT$b rejects a user's vhost request.`,
  103. capabs: []string{"vhosts"},
  104. enabled: hostservEnabled,
  105. minParams: 1,
  106. maxParams: 2,
  107. unsplitFinalParam: true,
  108. },
  109. "forbid": {
  110. handler: hsForbidHandler,
  111. help: `Syntax: $bFORBID <user>$b
  112. FORBID prevents a user from using any vhost, including ones on the offer list.`,
  113. helpShort: `$bFORBID$b prevents a user from using vhosts.`,
  114. capabs: []string{"vhosts"},
  115. enabled: hostservEnabled,
  116. minParams: 1,
  117. maxParams: 1,
  118. },
  119. "permit": {
  120. handler: hsForbidHandler,
  121. help: `Syntax: $bPERMIT <user>$b
  122. PERMIT undoes FORBID, allowing the user to TAKE vhosts again.`,
  123. helpShort: `$bPERMIT$b allows a user to use vhosts again.`,
  124. capabs: []string{"vhosts"},
  125. enabled: hostservEnabled,
  126. minParams: 1,
  127. maxParams: 1,
  128. },
  129. "offerlist": {
  130. handler: hsOfferListHandler,
  131. help: `Syntax: $bOFFERLIST$b
  132. OFFERLIST lists vhosts that can be chosen without requiring operator approval;
  133. to use one of the listed vhosts, take it with /HOSTSERV TAKE.`,
  134. helpShort: `$bOFFERLIST$b lists vhosts that can be taken without operator approval.`,
  135. enabled: hostservEnabled,
  136. minParams: 0,
  137. maxParams: 0,
  138. },
  139. "take": {
  140. handler: hsTakeHandler,
  141. help: `Syntax: $bTAKE$b <vhost>
  142. TAKE sets your vhost to one of the vhosts in the server's offer list; to see
  143. the offered vhosts, use /HOSTSERV OFFERLIST.`,
  144. helpShort: `$bTAKE$b sets your vhost to one of the options from the offer list.`,
  145. enabled: hostservEnabled,
  146. authRequired: true,
  147. minParams: 1,
  148. maxParams: 1,
  149. },
  150. }
  151. )
  152. // hsNotice sends the client a notice from HostServ
  153. func hsNotice(rb *ResponseBuffer, text string) {
  154. rb.Add(nil, "HostServ!HostServ@localhost", "NOTICE", rb.target.Nick(), text)
  155. }
  156. // hsNotifyChannel notifies the designated channel of new vhost activity
  157. func hsNotifyChannel(server *Server, message string) {
  158. chname := server.AccountConfig().VHosts.UserRequests.Channel
  159. channel := server.channels.Get(chname)
  160. if channel == nil {
  161. return
  162. }
  163. chname = channel.Name()
  164. for _, client := range channel.Members() {
  165. client.Send(nil, "HostServ", "PRIVMSG", chname, message)
  166. }
  167. }
  168. func hsOnOffHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  169. enable := false
  170. if command == "on" {
  171. enable = true
  172. }
  173. _, err := server.accounts.VHostSetEnabled(client, enable)
  174. if err == errNoVhost {
  175. hsNotice(rb, client.t(err.Error()))
  176. } else if err != nil {
  177. hsNotice(rb, client.t("An error occurred"))
  178. } else if enable {
  179. hsNotice(rb, client.t("Successfully enabled your vhost"))
  180. } else {
  181. hsNotice(rb, client.t("Successfully disabled your vhost"))
  182. }
  183. }
  184. func hsRequestHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  185. vhost := params[0]
  186. if validateVhost(server, vhost, false) != nil {
  187. hsNotice(rb, client.t("Invalid vhost"))
  188. return
  189. }
  190. accountName := client.Account()
  191. _, err := server.accounts.VHostRequest(accountName, vhost, time.Duration(server.Config().Accounts.VHosts.UserRequests.Cooldown))
  192. if err != nil {
  193. if throttled, ok := err.(*vhostThrottleExceeded); ok {
  194. hsNotice(rb, fmt.Sprintf(client.t("You must wait an additional %v before making another request"), throttled.timeRemaining))
  195. } else if err == errVhostsForbidden {
  196. hsNotice(rb, client.t("An administrator has denied you the ability to use vhosts"))
  197. } else {
  198. hsNotice(rb, client.t("An error occurred"))
  199. }
  200. } else {
  201. hsNotice(rb, client.t("Your vhost request will be reviewed by an administrator"))
  202. chanMsg := fmt.Sprintf("Account %s requests vhost %s", accountName, vhost)
  203. hsNotifyChannel(server, chanMsg)
  204. server.snomasks.Send(sno.LocalVhosts, chanMsg)
  205. }
  206. }
  207. func hsStatusHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  208. var accountName string
  209. if len(params) > 0 {
  210. if !client.HasRoleCapabs("vhosts") {
  211. hsNotice(rb, client.t("Command restricted"))
  212. return
  213. }
  214. accountName = params[0]
  215. } else {
  216. accountName = client.Account()
  217. if accountName == "" {
  218. hsNotice(rb, client.t("You're not logged into an account"))
  219. return
  220. }
  221. }
  222. account, err := server.accounts.LoadAccount(accountName)
  223. if err != nil {
  224. if err != errAccountDoesNotExist {
  225. server.logger.Warning("internal", "error loading account info", accountName, err.Error())
  226. }
  227. hsNotice(rb, client.t("No such account"))
  228. return
  229. }
  230. if account.VHost.Forbidden {
  231. hsNotice(rb, client.t("An administrator has denied you the ability to use vhosts"))
  232. return
  233. }
  234. if account.VHost.ApprovedVHost != "" {
  235. hsNotice(rb, fmt.Sprintf(client.t("Account %[1]s has vhost: %[2]s"), accountName, account.VHost.ApprovedVHost))
  236. if !account.VHost.Enabled {
  237. hsNotice(rb, client.t("This vhost is currently disabled, but can be enabled with /HS ON"))
  238. }
  239. } else {
  240. hsNotice(rb, fmt.Sprintf(client.t("Account %s has no vhost"), accountName))
  241. }
  242. if account.VHost.RequestedVHost != "" {
  243. hsNotice(rb, fmt.Sprintf(client.t("A request is pending for vhost: %s"), account.VHost.RequestedVHost))
  244. }
  245. if account.VHost.RejectedVHost != "" {
  246. hsNotice(rb, fmt.Sprintf(client.t("A request was previously made for vhost: %s"), account.VHost.RejectedVHost))
  247. hsNotice(rb, fmt.Sprintf(client.t("It was rejected for reason: %s"), account.VHost.RejectionReason))
  248. }
  249. }
  250. func validateVhost(server *Server, vhost string, oper bool) error {
  251. ac := server.AccountConfig()
  252. if len(vhost) > ac.VHosts.MaxLength {
  253. return errVHostTooLong
  254. }
  255. if !ac.VHosts.ValidRegexp.MatchString(vhost) {
  256. return errVHostBadCharacters
  257. }
  258. return nil
  259. }
  260. func hsSetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  261. user := params[0]
  262. var vhost string
  263. if command == "set" {
  264. vhost = params[1]
  265. if validateVhost(server, vhost, true) != nil {
  266. hsNotice(rb, client.t("Invalid vhost"))
  267. return
  268. }
  269. }
  270. // else: command == "del", vhost == ""
  271. _, err := server.accounts.VHostSet(user, vhost)
  272. if err != nil {
  273. hsNotice(rb, client.t("An error occurred"))
  274. } else if vhost != "" {
  275. hsNotice(rb, client.t("Successfully set vhost"))
  276. } else {
  277. hsNotice(rb, client.t("Successfully cleared vhost"))
  278. }
  279. }
  280. func hsWaitingHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  281. requests, total := server.accounts.VHostListRequests(10)
  282. hsNotice(rb, fmt.Sprintf(client.t("There are %[1]d pending requests for vhosts (%[2]d displayed)"), total, len(requests)))
  283. for i, request := range requests {
  284. hsNotice(rb, fmt.Sprintf(client.t("%[1]d. User %[2]s requests vhost: %[3]s"), i+1, request.Account, request.RequestedVHost))
  285. }
  286. }
  287. func hsApproveHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  288. user := params[0]
  289. vhostInfo, err := server.accounts.VHostApprove(user)
  290. if err != nil {
  291. hsNotice(rb, client.t("An error occurred"))
  292. } else {
  293. hsNotice(rb, fmt.Sprintf(client.t("Successfully approved vhost request for %s"), user))
  294. chanMsg := fmt.Sprintf("Oper %[1]s approved vhost %[2]s for account %[3]s", client.Nick(), vhostInfo.ApprovedVHost, user)
  295. hsNotifyChannel(server, chanMsg)
  296. server.snomasks.Send(sno.LocalVhosts, chanMsg)
  297. for _, client := range server.accounts.AccountToClients(user) {
  298. client.Notice(client.t("Your vhost request was approved by an administrator"))
  299. }
  300. }
  301. }
  302. func hsRejectHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  303. var reason string
  304. user := params[0]
  305. if len(params) > 1 {
  306. reason = params[1]
  307. }
  308. vhostInfo, err := server.accounts.VHostReject(user, reason)
  309. if err != nil {
  310. hsNotice(rb, client.t("An error occurred"))
  311. } else {
  312. hsNotice(rb, fmt.Sprintf(client.t("Successfully rejected vhost request for %s"), user))
  313. chanMsg := fmt.Sprintf("Oper %s rejected vhost %s for account %s, with the reason: %v", client.Nick(), vhostInfo.RejectedVHost, user, reason)
  314. hsNotifyChannel(server, chanMsg)
  315. server.snomasks.Send(sno.LocalVhosts, chanMsg)
  316. for _, client := range server.accounts.AccountToClients(user) {
  317. if reason == "" {
  318. client.Notice("Your vhost request was rejected by an administrator")
  319. } else {
  320. client.Notice(fmt.Sprintf(client.t("Your vhost request was rejected by an administrator. The reason given was: %s"), reason))
  321. }
  322. }
  323. }
  324. }
  325. func hsForbidHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  326. user := params[0]
  327. forbidden := command == "forbid"
  328. _, err := server.accounts.VHostForbid(user, forbidden)
  329. if err == errAccountDoesNotExist {
  330. hsNotice(rb, client.t("No such account"))
  331. } else if err != nil {
  332. hsNotice(rb, client.t("An error occurred"))
  333. } else {
  334. if forbidden {
  335. hsNotice(rb, fmt.Sprintf(client.t("User %s is no longer allowed to use vhosts"), user))
  336. } else {
  337. hsNotice(rb, fmt.Sprintf(client.t("User %s is now allowed to use vhosts"), user))
  338. }
  339. }
  340. }
  341. func hsOfferListHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  342. vhostConfig := server.Config().Accounts.VHosts
  343. if len(vhostConfig.OfferList) == 0 {
  344. if vhostConfig.UserRequests.Enabled {
  345. hsNotice(rb, client.t("The server does not offer any vhosts, but you can request one with /HOSTSERV REQUEST"))
  346. } else {
  347. hsNotice(rb, client.t("The server does not offer any vhosts"))
  348. }
  349. } else {
  350. hsNotice(rb, client.t("The following vhosts are available and can be chosen with /HOSTSERV TAKE:"))
  351. for _, vhost := range vhostConfig.OfferList {
  352. hsNotice(rb, vhost)
  353. }
  354. }
  355. }
  356. func hsTakeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
  357. config := server.Config()
  358. vhost := params[0]
  359. found := false
  360. for _, offered := range config.Accounts.VHosts.OfferList {
  361. if offered == vhost {
  362. found = true
  363. }
  364. }
  365. if !found {
  366. hsNotice(rb, client.t("That vhost isn't being offered by the server"))
  367. return
  368. }
  369. account := client.Account()
  370. _, err := server.accounts.VHostTake(account, vhost, time.Duration(config.Accounts.VHosts.UserRequests.Cooldown))
  371. if err != nil {
  372. if throttled, ok := err.(*vhostThrottleExceeded); ok {
  373. hsNotice(rb, fmt.Sprintf(client.t("You must wait an additional %v before taking a vhost"), throttled.timeRemaining))
  374. } else if err == errVhostsForbidden {
  375. hsNotice(rb, client.t("An administrator has denied you the ability to use vhosts"))
  376. } else {
  377. hsNotice(rb, client.t("An error occurred"))
  378. }
  379. } else {
  380. hsNotice(rb, client.t("Successfully set vhost"))
  381. server.snomasks.Send(sno.LocalVhosts, fmt.Sprintf("Client %s (account %s) took vhost %s", client.Nick(), account, vhost))
  382. }
  383. }