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.

uban.go 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. // Copyright (c) 2021 Shivaram Lingamneni
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "fmt"
  6. "regexp"
  7. "strings"
  8. "time"
  9. "github.com/ergochat/irc-go/ircmsg"
  10. "github.com/ergochat/ergo/irc/custime"
  11. "github.com/ergochat/ergo/irc/flatip"
  12. "github.com/ergochat/ergo/irc/sno"
  13. "github.com/ergochat/ergo/irc/utils"
  14. )
  15. func consumeDuration(params []string, rb *ResponseBuffer) (duration time.Duration, requireSASL bool, remainingParams []string, err error) {
  16. remainingParams = params
  17. for {
  18. if duration == 0 && 2 <= len(remainingParams) && strings.ToLower(remainingParams[0]) == "duration" {
  19. duration, err = custime.ParseDuration(remainingParams[1])
  20. if err != nil {
  21. rb.Notice(rb.session.client.t("Invalid time duration for NS SUSPEND"))
  22. return
  23. }
  24. remainingParams = remainingParams[2:]
  25. continue
  26. }
  27. if !requireSASL && 1 <= len(remainingParams) && strings.ToLower(remainingParams[0]) == "require-sasl" {
  28. requireSASL = true
  29. remainingParams = remainingParams[1:]
  30. continue
  31. }
  32. break
  33. }
  34. return
  35. }
  36. // a UBAN target is one of these syntactically unambiguous entities:
  37. // an IP, a CIDR, a NUH mask, or an account name
  38. type ubanType uint
  39. const (
  40. ubanCIDR ubanType = iota
  41. ubanNickmask
  42. ubanNick
  43. )
  44. // tagged union, i guess
  45. type ubanTarget struct {
  46. banType ubanType
  47. cidr flatip.IPNet
  48. matcher *regexp.Regexp
  49. nickOrMask string
  50. }
  51. func parseUbanTarget(param string) (target ubanTarget, err error) {
  52. if utils.SafeErrorParam(param) == "*" {
  53. err = errInvalidParams
  54. return
  55. }
  56. ipnet, ipErr := flatip.ParseToNormalizedNet(param)
  57. if ipErr == nil {
  58. target.banType = ubanCIDR
  59. target.cidr = ipnet
  60. return
  61. }
  62. if strings.IndexByte(param, '!') != -1 || strings.IndexByte(param, '@') != -1 {
  63. canonicalized, cErr := CanonicalizeMaskWildcard(param)
  64. if cErr != nil {
  65. err = errInvalidParams
  66. return
  67. }
  68. re, reErr := utils.CompileGlob(canonicalized, false)
  69. if reErr != nil {
  70. err = errInvalidParams
  71. return
  72. }
  73. target.banType = ubanNickmask
  74. target.nickOrMask = canonicalized
  75. target.matcher = re
  76. return
  77. }
  78. if _, cErr := CasefoldName(param); cErr == nil {
  79. target.banType = ubanNick
  80. target.nickOrMask = param
  81. return
  82. }
  83. err = errInvalidParams
  84. return
  85. }
  86. // UBAN <subcommand> [target] [DURATION <duration>] [reason...]
  87. func ubanHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
  88. subcommand := strings.ToLower(msg.Params[0])
  89. params := msg.Params[1:]
  90. var target ubanTarget
  91. if subcommand != "list" {
  92. if len(msg.Params) == 1 {
  93. rb.Add(nil, client.server.name, "FAIL", "UBAN", "INVALID_PARAMS", client.t("Not enough parameters"))
  94. return false
  95. }
  96. var parseErr error
  97. target, parseErr = parseUbanTarget(params[0])
  98. if parseErr != nil {
  99. rb.Add(nil, client.server.name, "FAIL", "UBAN", "INVALID_PARAMS", client.t("Couldn't parse ban target"))
  100. return false
  101. }
  102. params = params[1:]
  103. }
  104. switch subcommand {
  105. case "add":
  106. return ubanAddHandler(client, target, params, rb)
  107. case "del", "remove", "rm":
  108. return ubanDelHandler(client, target, params, rb)
  109. case "list":
  110. return ubanListHandler(client, params, rb)
  111. case "info":
  112. return ubanInfoHandler(client, target, params, rb)
  113. default:
  114. rb.Add(nil, server.name, "FAIL", "UBAN", "UNKNOWN_COMMAND", client.t("Unknown command"))
  115. return false
  116. }
  117. }
  118. func sessionsForCIDR(server *Server, cidr flatip.IPNet, exclude *Session, requireSASL bool) (sessions []*Session, nicks []string) {
  119. for _, client := range server.clients.AllClients() {
  120. if requireSASL && client.Account() != "" {
  121. continue
  122. }
  123. for _, session := range client.Sessions() {
  124. seen := false
  125. if session != exclude && cidr.Contains(flatip.FromNetIP(session.IP())) {
  126. sessions = append(sessions, session)
  127. if !seen {
  128. seen = true
  129. nicks = append(nicks, session.client.Nick())
  130. }
  131. }
  132. }
  133. }
  134. return
  135. }
  136. func ubanAddHandler(client *Client, target ubanTarget, params []string, rb *ResponseBuffer) bool {
  137. duration, requireSASL, params, err := consumeDuration(params, rb)
  138. if err != nil {
  139. return false
  140. }
  141. operReason := strings.Join(params, " ")
  142. switch target.banType {
  143. case ubanCIDR:
  144. err = ubanAddCIDR(client, target, duration, requireSASL, operReason, rb)
  145. case ubanNickmask:
  146. err = ubanAddNickmask(client, target, duration, operReason, rb)
  147. case ubanNick:
  148. err = ubanAddAccount(client, target, duration, operReason, rb)
  149. }
  150. if err == nil {
  151. announceUban(client, true, target, duration, requireSASL, operReason)
  152. }
  153. return false
  154. }
  155. func announceUban(client *Client, add bool, target ubanTarget, duration time.Duration, requireSASL bool, operReason string) {
  156. oper := client.Oper()
  157. if oper == nil {
  158. return
  159. }
  160. operName := oper.Name
  161. var buf strings.Builder
  162. fmt.Fprintf(&buf, "Operator %s", operName)
  163. if add {
  164. buf.WriteString(" added")
  165. } else {
  166. buf.WriteString(" removed")
  167. }
  168. switch target.banType {
  169. case ubanCIDR:
  170. buf.WriteString(" an IP-based")
  171. case ubanNickmask:
  172. buf.WriteString(" a NUH-mask")
  173. case ubanNick:
  174. buf.WriteString(" an account suspension")
  175. }
  176. buf.WriteString(" UBAN against ")
  177. switch target.banType {
  178. case ubanCIDR:
  179. buf.WriteString(target.cidr.String())
  180. case ubanNickmask, ubanNick:
  181. buf.WriteString(target.nickOrMask)
  182. }
  183. if duration != 0 {
  184. fmt.Fprintf(&buf, " [duration: %v]", duration)
  185. }
  186. if requireSASL {
  187. buf.WriteString(" [require-SASL]")
  188. }
  189. if operReason != "" {
  190. fmt.Fprintf(&buf, " [reason: %s]", operReason)
  191. }
  192. line := buf.String()
  193. client.server.snomasks.Send(sno.LocalXline, line)
  194. client.server.logger.Info("opers", line)
  195. }
  196. func ubanAddCIDR(client *Client, target ubanTarget, duration time.Duration, requireSASL bool, operReason string, rb *ResponseBuffer) (err error) {
  197. err = client.server.dlines.AddNetwork(target.cidr, duration, requireSASL, "", operReason, client.Oper().Name)
  198. if err == nil {
  199. rb.Notice(fmt.Sprintf(client.t("Successfully added UBAN for %s"), target.cidr.HumanReadableString()))
  200. } else {
  201. client.server.logger.Error("internal", "ubanAddCIDR failed", err.Error())
  202. rb.Notice(client.t("An error occurred"))
  203. return
  204. }
  205. sessions, nicks := sessionsForCIDR(client.server, target.cidr, rb.session, requireSASL)
  206. for _, session := range sessions {
  207. session.client.Quit("You have been banned from this server", session)
  208. session.client.destroy(session)
  209. }
  210. if len(sessions) != 0 {
  211. rb.Notice(fmt.Sprintf(client.t("Killed %[1]d active client(s) from %[2]s, associated with %[3]d nickname(s):"), len(sessions), target.cidr.String(), len(nicks)))
  212. for _, line := range utils.BuildTokenLines(400, nicks, " ") {
  213. rb.Notice(line)
  214. }
  215. }
  216. return
  217. }
  218. func ubanAddNickmask(client *Client, target ubanTarget, duration time.Duration, operReason string, rb *ResponseBuffer) (err error) {
  219. err = client.server.klines.AddMask(target.nickOrMask, duration, "", operReason, client.Oper().Name)
  220. if err == nil {
  221. rb.Notice(fmt.Sprintf(client.t("Successfully added UBAN for %s"), target.nickOrMask))
  222. } else {
  223. client.server.logger.Error("internal", "ubanAddNickmask failed", err.Error())
  224. rb.Notice(client.t("An error occurred"))
  225. return
  226. }
  227. var killed []string
  228. var alwaysOn []string
  229. for _, mcl := range client.server.clients.AllClients() {
  230. if mcl != client && target.matcher.MatchString(mcl.NickMaskCasefolded()) {
  231. if !mcl.AlwaysOn() {
  232. killed = append(killed, mcl.Nick())
  233. mcl.Quit("You have been banned from this server", nil)
  234. mcl.destroy(nil)
  235. } else {
  236. alwaysOn = append(alwaysOn, mcl.Nick())
  237. }
  238. }
  239. }
  240. if len(killed) != 0 {
  241. rb.Notice(fmt.Sprintf(client.t("Killed %d clients:"), len(killed)))
  242. for _, line := range utils.BuildTokenLines(400, killed, " ") {
  243. rb.Notice(line)
  244. }
  245. }
  246. if len(alwaysOn) != 0 {
  247. rb.Notice(fmt.Sprintf(client.t("Warning: %d clients matched this rule, but were not killed due to being always-on:"), len(alwaysOn)))
  248. for _, line := range utils.BuildTokenLines(400, alwaysOn, " ") {
  249. rb.Notice(line)
  250. }
  251. rb.Notice(client.t("You can suspend their accounts instead; try /UBAN ADD <nickname>"))
  252. }
  253. return
  254. }
  255. func ubanAddAccount(client *Client, target ubanTarget, duration time.Duration, operReason string, rb *ResponseBuffer) (err error) {
  256. account := target.nickOrMask
  257. // TODO this doesn't enumerate all sessions if ForceNickEqualsAccount is disabled
  258. var sessionData []SessionData
  259. if mcl := client.server.clients.Get(account); mcl != nil {
  260. sessionData, _ = mcl.AllSessionData(nil, true)
  261. }
  262. err = client.server.accounts.Suspend(account, duration, client.Oper().Name, operReason)
  263. switch err {
  264. case nil:
  265. rb.Notice(fmt.Sprintf(client.t("Successfully suspended account %s"), account))
  266. if len(sessionData) != 0 {
  267. rb.Notice(fmt.Sprintf(client.t("Disconnected %d client(s) associated with the account, using the following IPs:"), len(sessionData)))
  268. for i, d := range sessionData {
  269. rb.Notice(fmt.Sprintf("%d. %s", i+1, d.ip.String()))
  270. }
  271. }
  272. case errAccountDoesNotExist:
  273. rb.Notice(client.t("No such account"))
  274. default:
  275. rb.Notice(client.t("An error occurred"))
  276. }
  277. return
  278. }
  279. func ubanDelHandler(client *Client, target ubanTarget, params []string, rb *ResponseBuffer) bool {
  280. var err error
  281. var targetString string
  282. switch target.banType {
  283. case ubanCIDR:
  284. if target.cidr.PrefixLen == 128 {
  285. client.server.connectionLimiter.ResetThrottle(target.cidr.IP)
  286. rb.Notice(fmt.Sprintf(client.t("Reset throttle for IP: %s"), target.cidr.IP.String()))
  287. }
  288. targetString = target.cidr.HumanReadableString()
  289. err = client.server.dlines.RemoveNetwork(target.cidr)
  290. case ubanNickmask:
  291. targetString = target.nickOrMask
  292. err = client.server.klines.RemoveMask(target.nickOrMask)
  293. case ubanNick:
  294. targetString = target.nickOrMask
  295. err = client.server.accounts.Unsuspend(target.nickOrMask)
  296. }
  297. if err == nil {
  298. rb.Notice(fmt.Sprintf(client.t("Successfully removed ban on %s"), targetString))
  299. announceUban(client, false, target, 0, false, "")
  300. } else {
  301. rb.Notice(fmt.Sprintf(client.t("Could not remove ban: %v"), err))
  302. }
  303. return false
  304. }
  305. func ubanListHandler(client *Client, params []string, rb *ResponseBuffer) bool {
  306. allDlines := client.server.dlines.AllBans()
  307. rb.Notice(fmt.Sprintf(client.t("There are %d active IP/network ban(s) (DLINEs)"), len(allDlines)))
  308. for key, info := range allDlines {
  309. rb.Notice(formatBanForListing(client, key, info))
  310. }
  311. rb.Notice(client.t("Some IPs may also be prevented from connecting by the connection limiter and/or throttler"))
  312. allKlines := client.server.klines.AllBans()
  313. rb.Notice(fmt.Sprintf(client.t("There are %d active ban(s) on nick-user-host masks (KLINEs)"), len(allKlines)))
  314. for key, info := range allKlines {
  315. rb.Notice(formatBanForListing(client, key, info))
  316. }
  317. listAccountSuspensions(client, rb, client.server.name)
  318. return false
  319. }
  320. func ubanInfoHandler(client *Client, target ubanTarget, params []string, rb *ResponseBuffer) bool {
  321. switch target.banType {
  322. case ubanCIDR:
  323. ubanInfoCIDR(client, target, rb)
  324. case ubanNickmask:
  325. ubanInfoNickmask(client, target, rb)
  326. case ubanNick:
  327. ubanInfoNick(client, target, rb)
  328. }
  329. return false
  330. }
  331. func ubanInfoCIDR(client *Client, target ubanTarget, rb *ResponseBuffer) {
  332. config := client.server.Config()
  333. // show connection limiter/throttler state if this CIDR is entirely
  334. // contained in a single limiter/throttler bucket:
  335. ones, bits := target.cidr.Size()
  336. showLimiter := (bits == 32 && ones >= config.Server.IPLimits.CidrLenIPv4) ||
  337. (bits == 128 && ones >= config.Server.IPLimits.CidrLenIPv6)
  338. sendMaskWarning := (bits == 128 && ones > config.Server.IPLimits.CidrLenIPv6)
  339. if showLimiter {
  340. netName, status := client.server.connectionLimiter.Status(target.cidr.IP)
  341. if status.Exempt {
  342. rb.Notice(fmt.Sprintf(client.t("IP %s is exempt from connection limits"), target.cidr.IP.String()))
  343. } else {
  344. rb.Notice(fmt.Sprintf(client.t("Network %[1]s has %[2]d active connections out of a maximum of %[3]d"), netName, status.Count, status.MaxCount))
  345. rb.Notice(fmt.Sprintf(client.t("Network %[1]s has had %[2]d connection attempts in the past %[3]v, out of a maximum of %[4]d"), netName, status.Throttle, status.ThrottleDuration, status.MaxPerWindow))
  346. }
  347. }
  348. str := target.cidr.HumanReadableString()
  349. isBanned, banInfo := client.server.dlines.CheckIP(target.cidr.IP)
  350. if isBanned {
  351. rb.Notice(formatBanForListing(client, str, banInfo))
  352. } else {
  353. rb.Notice(fmt.Sprintf(client.t("There is no active IP ban against %s"), str))
  354. }
  355. sessions, nicks := sessionsForCIDR(client.server, target.cidr, nil, false)
  356. if len(sessions) != 0 {
  357. rb.Notice(fmt.Sprintf(client.t("There are %[1]d active client(s) from %[2]s, associated with %[3]d nickname(s):"), len(sessions), target.cidr.String(), len(nicks)))
  358. for _, line := range utils.BuildTokenLines(400, nicks, " ") {
  359. rb.Notice(line)
  360. }
  361. }
  362. if sendMaskWarning {
  363. rb.Notice(fmt.Sprintf(client.t("Note: try evaluating a wider IPv6 CIDR like %s/%d"),
  364. target.cidr.IP.String(), config.Server.IPLimits.CidrLenIPv6))
  365. }
  366. }
  367. func ubanInfoNickmask(client *Client, target ubanTarget, rb *ResponseBuffer) {
  368. isBanned, info := client.server.klines.ContainsMask(target.nickOrMask)
  369. if isBanned {
  370. rb.Notice(formatBanForListing(client, target.nickOrMask, info))
  371. } else {
  372. rb.Notice(fmt.Sprintf(client.t("No ban exists for %[1]s"), target.nickOrMask))
  373. }
  374. affectedCount := 0
  375. alwaysOnCount := 0
  376. for _, mcl := range client.server.clients.AllClients() {
  377. matches := false
  378. for _, mask := range mcl.AllNickmasks() {
  379. if target.matcher.MatchString(mask) {
  380. matches = true
  381. break
  382. }
  383. }
  384. if matches {
  385. if mcl.AlwaysOn() {
  386. alwaysOnCount++
  387. } else {
  388. affectedCount++
  389. }
  390. }
  391. }
  392. rb.Notice(fmt.Sprintf(client.t("Adding this mask would affect %[1]d clients (an additional %[2]d clients are exempt due to always-on)"), affectedCount, alwaysOnCount))
  393. }
  394. func ubanInfoNick(client *Client, target ubanTarget, rb *ResponseBuffer) {
  395. mcl := client.server.clients.Get(target.nickOrMask)
  396. if mcl != nil {
  397. details := mcl.Details()
  398. sessions := mcl.Sessions()
  399. ip := mcl.IP()
  400. sendIPBanWarning := false
  401. if details.account == "" {
  402. rb.Notice(fmt.Sprintf(client.t("Client %[1]s is unauthenticated and connected from %[2]s"), details.nick, ip.String()))
  403. sendIPBanWarning = true
  404. } else {
  405. rb.Notice(fmt.Sprintf(client.t("Client %[1]s is logged into account %[2]s and has %[3]d active clients (see /NICKSERV CLIENTS LIST %[4]s for more info)"), details.nick, details.accountName, len(mcl.Sessions()), details.nick))
  406. if !ip.IsLoopback() && len(sessions) == 1 {
  407. rb.Notice(fmt.Sprintf(client.t("Client %[1]s is associated with IP %[2]s"), details.nick, ip.String()))
  408. sendIPBanWarning = true
  409. }
  410. }
  411. if sendIPBanWarning {
  412. rb.Notice(client.t("Warning: banning this IP or a network that contains it may affect other users. Use /UBAN INFO on the candidate IP or network for more information."))
  413. }
  414. } else {
  415. rb.Notice(fmt.Sprintf(client.t("No client is currently using that nickname")))
  416. }
  417. account, err := client.server.accounts.LoadAccount(target.nickOrMask)
  418. if err != nil {
  419. if err == errAccountDoesNotExist {
  420. rb.Notice(fmt.Sprintf(client.t("There is no account registered for %s"), target.nickOrMask))
  421. } else {
  422. rb.Notice(fmt.Sprintf(client.t("Couldn't load account: %v"), err.Error()))
  423. }
  424. return
  425. }
  426. if account.Verified {
  427. if account.Suspended == nil {
  428. rb.Notice(fmt.Sprintf(client.t("Account %[1]s is in good standing; see /NICKSERV INFO %[2]s for more details"), target.nickOrMask, target.nickOrMask))
  429. } else {
  430. rb.Notice(fmt.Sprintf(client.t("Account %[1]s has been suspended: %[2]s"), target.nickOrMask, suspensionToString(client, *account.Suspended)))
  431. }
  432. } else {
  433. rb.Notice(fmt.Sprintf(client.t("Account %[1]s was created, but has not been verified"), target.nickOrMask))
  434. }
  435. }