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.6KB

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