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.

client_lookup_set.go 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. // Copyright (c) 2012-2014 Jeremy Latt
  2. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  3. // released under the MIT license
  4. package irc
  5. import (
  6. "regexp"
  7. "strings"
  8. "time"
  9. "github.com/goshuirc/irc-go/ircmatch"
  10. "github.com/oragono/oragono/irc/caps"
  11. "github.com/oragono/oragono/irc/modes"
  12. "sync"
  13. )
  14. // ClientManager keeps track of clients by nick, enforcing uniqueness of casefolded nicks
  15. type ClientManager struct {
  16. sync.RWMutex // tier 2
  17. byNick map[string]*Client
  18. bySkeleton map[string]*Client
  19. }
  20. // Initialize initializes a ClientManager.
  21. func (clients *ClientManager) Initialize() {
  22. clients.byNick = make(map[string]*Client)
  23. clients.bySkeleton = make(map[string]*Client)
  24. }
  25. // Count returns how many clients are in the manager.
  26. func (clients *ClientManager) Count() int {
  27. clients.RLock()
  28. defer clients.RUnlock()
  29. count := len(clients.byNick)
  30. return count
  31. }
  32. // Get retrieves a client from the manager, if they exist.
  33. func (clients *ClientManager) Get(nick string) *Client {
  34. casefoldedName, err := CasefoldName(nick)
  35. if err == nil {
  36. clients.RLock()
  37. defer clients.RUnlock()
  38. cli := clients.byNick[casefoldedName]
  39. return cli
  40. }
  41. return nil
  42. }
  43. func (clients *ClientManager) removeInternal(client *Client) (err error) {
  44. // requires holding the writable Lock()
  45. oldcfnick, oldskeleton := client.uniqueIdentifiers()
  46. if oldcfnick == "*" || oldcfnick == "" {
  47. return errNickMissing
  48. }
  49. currentEntry, present := clients.byNick[oldcfnick]
  50. if present {
  51. if currentEntry == client {
  52. delete(clients.byNick, oldcfnick)
  53. } else {
  54. // this shouldn't happen, but we can ignore it
  55. client.server.logger.Warning("internal", "clients for nick out of sync", oldcfnick)
  56. err = errNickMissing
  57. }
  58. } else {
  59. err = errNickMissing
  60. }
  61. currentEntry, present = clients.bySkeleton[oldskeleton]
  62. if present {
  63. if currentEntry == client {
  64. delete(clients.bySkeleton, oldskeleton)
  65. } else {
  66. client.server.logger.Warning("internal", "clients for skeleton out of sync", oldskeleton)
  67. err = errNickMissing
  68. }
  69. } else {
  70. err = errNickMissing
  71. }
  72. return
  73. }
  74. // Remove removes a client from the lookup set.
  75. func (clients *ClientManager) Remove(client *Client) error {
  76. clients.Lock()
  77. defer clients.Unlock()
  78. return clients.removeInternal(client)
  79. }
  80. // Handles a RESUME by attaching a session to a designated client. It is the
  81. // caller's responsibility to verify that the resume is allowed (checking tokens,
  82. // TLS status, etc.) before calling this.
  83. func (clients *ClientManager) Resume(oldClient *Client, session *Session) (err error) {
  84. clients.Lock()
  85. defer clients.Unlock()
  86. cfnick := oldClient.NickCasefolded()
  87. if _, ok := clients.byNick[cfnick]; !ok {
  88. return errNickMissing
  89. }
  90. if !oldClient.AddSession(session) {
  91. return errNickMissing
  92. }
  93. return nil
  94. }
  95. // SetNick sets a client's nickname, validating it against nicknames in use
  96. func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string) error {
  97. if len(newNick) > client.server.Config().Limits.NickLen {
  98. return errNicknameInvalid
  99. }
  100. newcfnick, err := CasefoldName(newNick)
  101. if err != nil {
  102. return errNicknameInvalid
  103. }
  104. newSkeleton, err := Skeleton(newNick)
  105. if err != nil {
  106. return errNicknameInvalid
  107. }
  108. if restrictedCasefoldedNicks[newcfnick] || restrictedSkeletons[newSkeleton] {
  109. return errNicknameInvalid
  110. }
  111. reservedAccount, method := client.server.accounts.EnforcementStatus(newcfnick, newSkeleton)
  112. account := client.Account()
  113. config := client.server.Config()
  114. var bouncerAllowed bool
  115. if config.Accounts.Bouncer.Enabled {
  116. if session != nil && session.capabilities.Has(caps.Bouncer) {
  117. bouncerAllowed = true
  118. } else {
  119. settings := client.AccountSettings()
  120. if config.Accounts.Bouncer.AllowedByDefault && settings.AllowBouncer != BouncerDisallowedByUser {
  121. bouncerAllowed = true
  122. } else if settings.AllowBouncer == BouncerAllowedByUser {
  123. bouncerAllowed = true
  124. }
  125. }
  126. }
  127. clients.Lock()
  128. defer clients.Unlock()
  129. currentClient := clients.byNick[newcfnick]
  130. // the client may just be changing case
  131. if currentClient != nil && currentClient != client && session != nil {
  132. // these conditions forbid reattaching to an existing session:
  133. if client.Registered() || !bouncerAllowed || account == "" || account != currentClient.Account() || client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
  134. return errNicknameInUse
  135. }
  136. if !currentClient.AddSession(session) {
  137. return errNicknameInUse
  138. }
  139. // successful reattach!
  140. return nil
  141. }
  142. // analogous checks for skeletons
  143. skeletonHolder := clients.bySkeleton[newSkeleton]
  144. if skeletonHolder != nil && skeletonHolder != client {
  145. return errNicknameInUse
  146. }
  147. if method == NickEnforcementStrict && reservedAccount != "" && reservedAccount != account {
  148. return errNicknameReserved
  149. }
  150. clients.removeInternal(client)
  151. clients.byNick[newcfnick] = client
  152. clients.bySkeleton[newSkeleton] = client
  153. client.updateNick(newNick, newcfnick, newSkeleton)
  154. return nil
  155. }
  156. func (clients *ClientManager) AllClients() (result []*Client) {
  157. clients.RLock()
  158. defer clients.RUnlock()
  159. result = make([]*Client, len(clients.byNick))
  160. i := 0
  161. for _, client := range clients.byNick {
  162. result[i] = client
  163. i++
  164. }
  165. return
  166. }
  167. // AllWithCaps returns all clients with the given capabilities.
  168. func (clients *ClientManager) AllWithCaps(capabs ...caps.Capability) (sessions []*Session) {
  169. clients.RLock()
  170. defer clients.RUnlock()
  171. for _, client := range clients.byNick {
  172. for _, session := range client.Sessions() {
  173. if session.capabilities.HasAll(capabs...) {
  174. sessions = append(sessions, session)
  175. }
  176. }
  177. }
  178. return
  179. }
  180. // AllWithCapsNotify returns all clients with the given capabilities, and that support cap-notify.
  181. func (clients *ClientManager) AllWithCapsNotify(capabs ...caps.Capability) (sessions []*Session) {
  182. capabs = append(capabs, caps.CapNotify)
  183. clients.RLock()
  184. defer clients.RUnlock()
  185. for _, client := range clients.byNick {
  186. for _, session := range client.Sessions() {
  187. // cap-notify is implicit in cap version 302 and above
  188. if session.capabilities.HasAll(capabs...) || 302 <= session.capVersion {
  189. sessions = append(sessions, session)
  190. }
  191. }
  192. }
  193. return
  194. }
  195. // FindAll returns all clients that match the given userhost mask.
  196. func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
  197. set = make(ClientSet)
  198. userhost, err := CanonicalizeMaskWildcard(userhost)
  199. if err != nil {
  200. return set
  201. }
  202. matcher := ircmatch.MakeMatch(userhost)
  203. clients.RLock()
  204. defer clients.RUnlock()
  205. for _, client := range clients.byNick {
  206. if matcher.Match(client.NickMaskCasefolded()) {
  207. set.Add(client)
  208. }
  209. }
  210. return set
  211. }
  212. //
  213. // usermask to regexp
  214. //
  215. //TODO(dan): move this over to generally using glob syntax instead?
  216. // kinda more expected in normal ban/etc masks, though regex is useful (probably as an extban?)
  217. type MaskInfo struct {
  218. TimeCreated time.Time
  219. CreatorNickmask string
  220. CreatorAccount string
  221. }
  222. // UserMaskSet holds a set of client masks and lets you match hostnames to them.
  223. type UserMaskSet struct {
  224. sync.RWMutex
  225. masks map[string]MaskInfo
  226. regexp *regexp.Regexp
  227. }
  228. func NewUserMaskSet() *UserMaskSet {
  229. return new(UserMaskSet)
  230. }
  231. // Add adds the given mask to this set.
  232. func (set *UserMaskSet) Add(mask, creatorNickmask, creatorAccount string) (maskAdded string, err error) {
  233. casefoldedMask, err := CanonicalizeMaskWildcard(mask)
  234. if err != nil {
  235. return
  236. }
  237. set.Lock()
  238. if set.masks == nil {
  239. set.masks = make(map[string]MaskInfo)
  240. }
  241. _, present := set.masks[casefoldedMask]
  242. if !present {
  243. maskAdded = casefoldedMask
  244. set.masks[casefoldedMask] = MaskInfo{
  245. TimeCreated: time.Now().UTC(),
  246. CreatorNickmask: creatorNickmask,
  247. CreatorAccount: creatorAccount,
  248. }
  249. }
  250. set.Unlock()
  251. if !present {
  252. set.setRegexp()
  253. }
  254. return
  255. }
  256. // Remove removes the given mask from this set.
  257. func (set *UserMaskSet) Remove(mask string) (maskRemoved string, err error) {
  258. mask, err = CanonicalizeMaskWildcard(mask)
  259. if err != nil {
  260. return
  261. }
  262. set.Lock()
  263. _, removed := set.masks[mask]
  264. if removed {
  265. maskRemoved = mask
  266. delete(set.masks, mask)
  267. }
  268. set.Unlock()
  269. if removed {
  270. set.setRegexp()
  271. }
  272. return
  273. }
  274. func (set *UserMaskSet) SetMasks(masks map[string]MaskInfo) {
  275. set.Lock()
  276. set.masks = masks
  277. set.Unlock()
  278. set.setRegexp()
  279. }
  280. func (set *UserMaskSet) Masks() (result map[string]MaskInfo) {
  281. set.RLock()
  282. defer set.RUnlock()
  283. result = make(map[string]MaskInfo, len(set.masks))
  284. for mask, info := range set.masks {
  285. result[mask] = info
  286. }
  287. return
  288. }
  289. // Match matches the given n!u@h.
  290. func (set *UserMaskSet) Match(userhost string) bool {
  291. set.RLock()
  292. regexp := set.regexp
  293. set.RUnlock()
  294. if regexp == nil {
  295. return false
  296. }
  297. return regexp.MatchString(userhost)
  298. }
  299. func (set *UserMaskSet) Length() int {
  300. set.RLock()
  301. defer set.RUnlock()
  302. return len(set.masks)
  303. }
  304. // setRegexp generates a regular expression from the set of user mask
  305. // strings. Masks are split at the two types of wildcards, `*` and
  306. // `?`. All the pieces are meta-escaped. `*` is replaced with `.*`,
  307. // the regexp equivalent. Likewise, `?` is replaced with `.`. The
  308. // parts are re-joined and finally all masks are joined into a big
  309. // or-expression.
  310. func (set *UserMaskSet) setRegexp() {
  311. var re *regexp.Regexp
  312. set.RLock()
  313. maskExprs := make([]string, len(set.masks))
  314. index := 0
  315. for mask := range set.masks {
  316. manyParts := strings.Split(mask, "*")
  317. manyExprs := make([]string, len(manyParts))
  318. for mindex, manyPart := range manyParts {
  319. oneParts := strings.Split(manyPart, "?")
  320. oneExprs := make([]string, len(oneParts))
  321. for oindex, onePart := range oneParts {
  322. oneExprs[oindex] = regexp.QuoteMeta(onePart)
  323. }
  324. manyExprs[mindex] = strings.Join(oneExprs, ".")
  325. }
  326. maskExprs[index] = strings.Join(manyExprs, ".*")
  327. index++
  328. }
  329. set.RUnlock()
  330. if index > 0 {
  331. expr := "^" + strings.Join(maskExprs, "|") + "$"
  332. re, _ = regexp.Compile(expr)
  333. }
  334. set.Lock()
  335. set.regexp = re
  336. set.Unlock()
  337. }