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

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