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

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