Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

client_lookup_set.go 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  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. "errors"
  7. "fmt"
  8. "log"
  9. "regexp"
  10. "strings"
  11. "github.com/goshuirc/irc-go/ircmatch"
  12. "github.com/oragono/oragono/irc/caps"
  13. "sync"
  14. )
  15. var (
  16. ErrNickMissing = errors.New("nick missing")
  17. ErrNicknameInUse = errors.New("nickname in use")
  18. )
  19. // ExpandUserHost takes a userhost, and returns an expanded version.
  20. func ExpandUserHost(userhost string) (expanded string) {
  21. expanded = userhost
  22. // fill in missing wildcards for nicks
  23. //TODO(dan): this would fail with dan@lol, fix that.
  24. if !strings.Contains(expanded, "!") {
  25. expanded += "!*"
  26. }
  27. if !strings.Contains(expanded, "@") {
  28. expanded += "@*"
  29. }
  30. return
  31. }
  32. // ClientManager keeps track of clients by nick, enforcing uniqueness of casefolded nicks
  33. type ClientManager struct {
  34. sync.RWMutex // tier 2
  35. byNick map[string]*Client
  36. }
  37. // NewClientManager returns a new ClientManager.
  38. func NewClientManager() *ClientManager {
  39. return &ClientManager{
  40. byNick: make(map[string]*Client),
  41. }
  42. }
  43. // Count returns how many clients are in the manager.
  44. func (clients *ClientManager) Count() int {
  45. clients.RLock()
  46. defer clients.RUnlock()
  47. count := len(clients.byNick)
  48. return count
  49. }
  50. // Get retrieves a client from the manager, if they exist.
  51. func (clients *ClientManager) Get(nick string) *Client {
  52. casefoldedName, err := CasefoldName(nick)
  53. if err == nil {
  54. clients.RLock()
  55. defer clients.RUnlock()
  56. cli := clients.byNick[casefoldedName]
  57. return cli
  58. }
  59. return nil
  60. }
  61. func (clients *ClientManager) removeInternal(client *Client) (removed bool) {
  62. // requires holding the writable Lock()
  63. oldcfnick := client.NickCasefolded()
  64. currentEntry, present := clients.byNick[oldcfnick]
  65. if present {
  66. if currentEntry == client {
  67. delete(clients.byNick, oldcfnick)
  68. removed = true
  69. } else {
  70. // this shouldn't happen, but we can ignore it
  71. client.server.logger.Warning("internal", fmt.Sprintf("clients for nick %s out of sync", oldcfnick))
  72. }
  73. }
  74. return
  75. }
  76. // Remove removes a client from the lookup set.
  77. func (clients *ClientManager) Remove(client *Client) error {
  78. clients.Lock()
  79. defer clients.Unlock()
  80. if !client.HasNick() {
  81. return ErrNickMissing
  82. }
  83. clients.removeInternal(client)
  84. return nil
  85. }
  86. // SetNick sets a client's nickname, validating it against nicknames in use
  87. func (clients *ClientManager) SetNick(client *Client, newNick string) error {
  88. newcfnick, err := CasefoldName(newNick)
  89. if err != nil {
  90. return err
  91. }
  92. clients.Lock()
  93. defer clients.Unlock()
  94. clients.removeInternal(client)
  95. currentNewEntry := clients.byNick[newcfnick]
  96. // the client may just be changing case
  97. if currentNewEntry != nil && currentNewEntry != client {
  98. return ErrNicknameInUse
  99. }
  100. clients.byNick[newcfnick] = client
  101. client.updateNickMask(newNick)
  102. return nil
  103. }
  104. func (clients *ClientManager) AllClients() (result []*Client) {
  105. clients.RLock()
  106. defer clients.RUnlock()
  107. result = make([]*Client, len(clients.byNick))
  108. i := 0
  109. for _, client := range clients.byNick {
  110. result[i] = client
  111. i++
  112. }
  113. return
  114. }
  115. // AllWithCaps returns all clients with the given capabilities.
  116. func (clients *ClientManager) AllWithCaps(capabs ...caps.Capability) (set ClientSet) {
  117. set = make(ClientSet)
  118. clients.RLock()
  119. defer clients.RUnlock()
  120. var client *Client
  121. for _, client = range clients.byNick {
  122. // make sure they have all the required caps
  123. for _, capab := range capabs {
  124. if !client.capabilities.Has(capab) {
  125. continue
  126. }
  127. }
  128. set.Add(client)
  129. }
  130. return set
  131. }
  132. // FindAll returns all clients that match the given userhost mask.
  133. func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
  134. set = make(ClientSet)
  135. userhost, err := Casefold(ExpandUserHost(userhost))
  136. if err != nil {
  137. return set
  138. }
  139. matcher := ircmatch.MakeMatch(userhost)
  140. clients.RLock()
  141. defer clients.RUnlock()
  142. for _, client := range clients.byNick {
  143. if matcher.Match(client.nickMaskCasefolded) {
  144. set.Add(client)
  145. }
  146. }
  147. return set
  148. }
  149. // Find returns the first client that matches the given userhost mask.
  150. func (clients *ClientManager) Find(userhost string) *Client {
  151. userhost, err := Casefold(ExpandUserHost(userhost))
  152. if err != nil {
  153. return nil
  154. }
  155. matcher := ircmatch.MakeMatch(userhost)
  156. var matchedClient *Client
  157. clients.RLock()
  158. defer clients.RUnlock()
  159. for _, client := range clients.byNick {
  160. if matcher.Match(client.nickMaskCasefolded) {
  161. matchedClient = client
  162. break
  163. }
  164. }
  165. return matchedClient
  166. }
  167. //
  168. // usermask to regexp
  169. //
  170. //TODO(dan): move this over to generally using glob syntax instead?
  171. // kinda more expected in normal ban/etc masks, though regex is useful (probably as an extban?)
  172. // UserMaskSet holds a set of client masks and lets you match hostnames to them.
  173. type UserMaskSet struct {
  174. sync.RWMutex
  175. masks map[string]bool
  176. regexp *regexp.Regexp
  177. }
  178. // NewUserMaskSet returns a new UserMaskSet.
  179. func NewUserMaskSet() *UserMaskSet {
  180. return &UserMaskSet{
  181. masks: make(map[string]bool),
  182. }
  183. }
  184. // Add adds the given mask to this set.
  185. func (set *UserMaskSet) Add(mask string) (added bool) {
  186. casefoldedMask, err := Casefold(mask)
  187. if err != nil {
  188. log.Println(fmt.Sprintf("ERROR: Could not add mask to usermaskset: [%s]", mask))
  189. return false
  190. }
  191. set.Lock()
  192. added = !set.masks[casefoldedMask]
  193. if added {
  194. set.masks[casefoldedMask] = true
  195. }
  196. set.Unlock()
  197. if added {
  198. set.setRegexp()
  199. }
  200. return
  201. }
  202. // AddAll adds the given masks to this set.
  203. func (set *UserMaskSet) AddAll(masks []string) (added bool) {
  204. set.Lock()
  205. defer set.Unlock()
  206. for _, mask := range masks {
  207. if !added && !set.masks[mask] {
  208. added = true
  209. }
  210. set.masks[mask] = true
  211. }
  212. if added {
  213. set.setRegexp()
  214. }
  215. return
  216. }
  217. // Remove removes the given mask from this set.
  218. func (set *UserMaskSet) Remove(mask string) (removed bool) {
  219. set.Lock()
  220. removed = set.masks[mask]
  221. if removed {
  222. delete(set.masks, mask)
  223. }
  224. set.Unlock()
  225. if removed {
  226. set.setRegexp()
  227. }
  228. return
  229. }
  230. // Match matches the given n!u@h.
  231. func (set *UserMaskSet) Match(userhost string) bool {
  232. set.RLock()
  233. regexp := set.regexp
  234. set.RUnlock()
  235. if regexp == nil {
  236. return false
  237. }
  238. return regexp.MatchString(userhost)
  239. }
  240. // String returns the masks in this set.
  241. func (set *UserMaskSet) String() string {
  242. set.RLock()
  243. masks := make([]string, len(set.masks))
  244. index := 0
  245. for mask := range set.masks {
  246. masks[index] = mask
  247. index++
  248. }
  249. set.RUnlock()
  250. return strings.Join(masks, " ")
  251. }
  252. func (set *UserMaskSet) Length() int {
  253. set.RLock()
  254. defer set.RUnlock()
  255. return len(set.masks)
  256. }
  257. // setRegexp generates a regular expression from the set of user mask
  258. // strings. Masks are split at the two types of wildcards, `*` and
  259. // `?`. All the pieces are meta-escaped. `*` is replaced with `.*`,
  260. // the regexp equivalent. Likewise, `?` is replaced with `.`. The
  261. // parts are re-joined and finally all masks are joined into a big
  262. // or-expression.
  263. func (set *UserMaskSet) setRegexp() {
  264. var re *regexp.Regexp
  265. set.RLock()
  266. maskExprs := make([]string, len(set.masks))
  267. index := 0
  268. for mask := range set.masks {
  269. manyParts := strings.Split(mask, "*")
  270. manyExprs := make([]string, len(manyParts))
  271. for mindex, manyPart := range manyParts {
  272. oneParts := strings.Split(manyPart, "?")
  273. oneExprs := make([]string, len(oneParts))
  274. for oindex, onePart := range oneParts {
  275. oneExprs[oindex] = regexp.QuoteMeta(onePart)
  276. }
  277. manyExprs[mindex] = strings.Join(oneExprs, ".")
  278. }
  279. maskExprs[index] = strings.Join(manyExprs, ".*")
  280. index++
  281. }
  282. set.RUnlock()
  283. if index > 0 {
  284. expr := "^" + strings.Join(maskExprs, "|") + "$"
  285. re, _ = regexp.Compile(expr)
  286. }
  287. set.Lock()
  288. set.regexp = re
  289. set.Unlock()
  290. }