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.

server.go 38KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210
  1. // Copyright (c) 2012-2014 Jeremy Latt
  2. // Copyright (c) 2014-2015 Edmund Huber
  3. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  4. // released under the MIT license
  5. package irc
  6. import (
  7. "bufio"
  8. "crypto/tls"
  9. "encoding/base64"
  10. "fmt"
  11. "log"
  12. "math/rand"
  13. "net"
  14. "os"
  15. "os/signal"
  16. "strconv"
  17. "strings"
  18. "sync"
  19. "syscall"
  20. "time"
  21. "github.com/goshuirc/irc-go/ircfmt"
  22. "github.com/goshuirc/irc-go/ircmsg"
  23. "github.com/oragono/oragono/irc/caps"
  24. "github.com/oragono/oragono/irc/connection_limits"
  25. "github.com/oragono/oragono/irc/isupport"
  26. "github.com/oragono/oragono/irc/languages"
  27. "github.com/oragono/oragono/irc/logger"
  28. "github.com/oragono/oragono/irc/modes"
  29. "github.com/oragono/oragono/irc/passwd"
  30. "github.com/oragono/oragono/irc/sno"
  31. "github.com/oragono/oragono/irc/utils"
  32. "github.com/tidwall/buntdb"
  33. )
  34. var (
  35. // common error line to sub values into
  36. errorMsg, _ = (&[]ircmsg.IrcMessage{ircmsg.MakeMessage(nil, "", "ERROR", "%s ")}[0]).Line()
  37. // common error responses
  38. couldNotParseIPMsg, _ = (&[]ircmsg.IrcMessage{ircmsg.MakeMessage(nil, "", "ERROR", "Unable to parse your IP address")}[0]).Line()
  39. // supportedUserModesString acts as a cache for when we introduce users
  40. supportedUserModesString = modes.SupportedUserModes.String()
  41. // supportedChannelModesString acts as a cache for when we introduce users
  42. supportedChannelModesString = modes.SupportedChannelModes.String()
  43. // SupportedCapabilities are the caps we advertise.
  44. // MaxLine, SASL and STS are set during server startup.
  45. SupportedCapabilities = caps.NewSet(caps.AccountTag, caps.AccountNotify, caps.AwayNotify, caps.Batch, caps.CapNotify, caps.ChgHost, caps.EchoMessage, caps.ExtendedJoin, caps.InviteNotify, caps.LabeledResponse, caps.Languages, caps.MessageTags, caps.MultiPrefix, caps.Rename, caps.Resume, caps.ServerTime, caps.UserhostInNames)
  46. // CapValues are the actual values we advertise to v3.2 clients.
  47. // actual values are set during server startup.
  48. CapValues = caps.NewValues()
  49. )
  50. // Limits holds the maximum limits for various things such as topic lengths.
  51. type Limits struct {
  52. AwayLen int
  53. ChannelLen int
  54. KickLen int
  55. MonitorEntries int
  56. NickLen int
  57. TopicLen int
  58. ChanListModes int
  59. LineLen LineLenLimits
  60. }
  61. // LineLenLimits holds the maximum limits for IRC lines.
  62. type LineLenLimits struct {
  63. Tags int
  64. Rest int
  65. }
  66. // ListenerWrapper wraps a listener so it can be safely reconfigured or stopped
  67. type ListenerWrapper struct {
  68. listener net.Listener
  69. tlsConfig *tls.Config
  70. shouldStop bool
  71. // protects atomic update of tlsConfig and shouldStop:
  72. configMutex sync.Mutex // tier 1
  73. }
  74. // Server is the main Oragono server.
  75. type Server struct {
  76. accountConfig *AccountConfig
  77. accounts *AccountManager
  78. batches *BatchManager
  79. channelRegistrationEnabled bool
  80. channels *ChannelManager
  81. channelRegistry *ChannelRegistry
  82. checkIdent bool
  83. clients *ClientManager
  84. configFilename string
  85. configurableStateMutex sync.RWMutex // tier 1; generic protection for server state modified by rehash()
  86. connectionLimiter *connection_limits.Limiter
  87. connectionThrottler *connection_limits.Throttler
  88. ctime time.Time
  89. defaultChannelModes modes.Modes
  90. dlines *DLineManager
  91. loggingRawIO bool
  92. isupport *isupport.List
  93. klines *KLineManager
  94. languages *languages.Manager
  95. limits Limits
  96. listeners map[string]*ListenerWrapper
  97. logger *logger.Manager
  98. MaxSendQBytes uint64
  99. monitorManager *MonitorManager
  100. motdLines []string
  101. name string
  102. nameCasefolded string
  103. networkName string
  104. operators map[string]Oper
  105. operclasses map[string]OperClass
  106. password []byte
  107. passwords *passwd.SaltedManager
  108. recoverFromErrors bool
  109. rehashMutex sync.Mutex // tier 4
  110. rehashSignal chan os.Signal
  111. proxyAllowedFrom []string
  112. signals chan os.Signal
  113. snomasks *SnoManager
  114. store *buntdb.DB
  115. storeFilename string
  116. stsEnabled bool
  117. webirc []webircConfig
  118. whoWas *WhoWasList
  119. }
  120. var (
  121. // ServerExitSignals are the signals the server will exit on.
  122. ServerExitSignals = []os.Signal{
  123. syscall.SIGINT,
  124. syscall.SIGTERM,
  125. syscall.SIGQUIT,
  126. }
  127. )
  128. type clientConn struct {
  129. Conn net.Conn
  130. IsTLS bool
  131. }
  132. // NewServer returns a new Oragono server.
  133. func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
  134. // initialize data structures
  135. server := &Server{
  136. batches: NewBatchManager(),
  137. channels: NewChannelManager(),
  138. clients: NewClientManager(),
  139. connectionLimiter: connection_limits.NewLimiter(),
  140. connectionThrottler: connection_limits.NewThrottler(),
  141. languages: languages.NewManager(config.Languages.Default, config.Languages.Data),
  142. listeners: make(map[string]*ListenerWrapper),
  143. logger: logger,
  144. monitorManager: NewMonitorManager(),
  145. rehashSignal: make(chan os.Signal, 1),
  146. signals: make(chan os.Signal, len(ServerExitSignals)),
  147. snomasks: NewSnoManager(),
  148. whoWas: NewWhoWasList(config.Limits.WhowasEntries),
  149. }
  150. if err := server.applyConfig(config, true); err != nil {
  151. return nil, err
  152. }
  153. // generate help info
  154. if err := GenerateHelpIndices(server.languages); err != nil {
  155. return nil, err
  156. }
  157. // Attempt to clean up when receiving these signals.
  158. signal.Notify(server.signals, ServerExitSignals...)
  159. signal.Notify(server.rehashSignal, syscall.SIGHUP)
  160. return server, nil
  161. }
  162. // setISupport sets up our RPL_ISUPPORT reply.
  163. func (server *Server) setISupport() {
  164. maxTargetsString := strconv.Itoa(maxTargets)
  165. server.configurableStateMutex.RLock()
  166. // add RPL_ISUPPORT tokens
  167. isupport := isupport.NewList()
  168. isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen))
  169. isupport.Add("CASEMAPPING", "ascii")
  170. isupport.Add("CHANMODES", strings.Join([]string{modes.Modes{modes.BanMask, modes.ExceptMask, modes.InviteMask}.String(), "", modes.Modes{modes.UserLimit, modes.Key}.String(), modes.Modes{modes.InviteOnly, modes.Moderated, modes.NoOutside, modes.OpOnlyTopic, modes.ChanRoleplaying, modes.Secret}.String()}, ","))
  171. isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen))
  172. isupport.Add("CHANTYPES", "#")
  173. isupport.Add("ELIST", "U")
  174. isupport.Add("EXCEPTS", "")
  175. isupport.Add("INVEX", "")
  176. isupport.Add("KICKLEN", strconv.Itoa(server.limits.KickLen))
  177. isupport.Add("MAXLIST", fmt.Sprintf("beI:%s", strconv.Itoa(server.limits.ChanListModes)))
  178. isupport.Add("MAXTARGETS", maxTargetsString)
  179. isupport.Add("MODES", "")
  180. isupport.Add("MONITOR", strconv.Itoa(server.limits.MonitorEntries))
  181. isupport.Add("NETWORK", server.networkName)
  182. isupport.Add("NICKLEN", strconv.Itoa(server.limits.NickLen))
  183. isupport.Add("PREFIX", "(qaohv)~&@%+")
  184. isupport.Add("RPCHAN", "E")
  185. isupport.Add("RPUSER", "E")
  186. isupport.Add("STATUSMSG", "~&@%+")
  187. isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:", maxTargetsString, maxTargetsString, maxTargetsString))
  188. isupport.Add("TOPICLEN", strconv.Itoa(server.limits.TopicLen))
  189. isupport.Add("UTF8MAPPING", casemappingName)
  190. // account registration
  191. if server.accountConfig.Registration.Enabled {
  192. // 'none' isn't shown in the REGCALLBACKS vars
  193. var enabledCallbacks []string
  194. for _, name := range server.accountConfig.Registration.EnabledCallbacks {
  195. if name != "*" {
  196. enabledCallbacks = append(enabledCallbacks, name)
  197. }
  198. }
  199. isupport.Add("REGCOMMANDS", "CREATE,VERIFY")
  200. isupport.Add("REGCALLBACKS", strings.Join(enabledCallbacks, ","))
  201. isupport.Add("REGCREDTYPES", "passphrase,certfp")
  202. }
  203. server.configurableStateMutex.RUnlock()
  204. isupport.RegenerateCachedReply()
  205. server.configurableStateMutex.Lock()
  206. server.isupport = isupport
  207. server.configurableStateMutex.Unlock()
  208. }
  209. func loadChannelList(channel *Channel, list string, maskMode modes.Mode) {
  210. if list == "" {
  211. return
  212. }
  213. channel.lists[maskMode].AddAll(strings.Split(list, " "))
  214. }
  215. // Shutdown shuts down the server.
  216. func (server *Server) Shutdown() {
  217. //TODO(dan): Make sure we disallow new nicks
  218. for _, client := range server.clients.AllClients() {
  219. client.Notice("Server is shutting down")
  220. }
  221. if err := server.store.Close(); err != nil {
  222. server.logger.Error("shutdown", fmt.Sprintln("Could not close datastore:", err))
  223. }
  224. }
  225. // Run starts the server.
  226. func (server *Server) Run() {
  227. // defer closing db/store
  228. defer server.store.Close()
  229. for {
  230. select {
  231. case <-server.signals:
  232. server.Shutdown()
  233. return
  234. case <-server.rehashSignal:
  235. go func() {
  236. server.logger.Info("rehash", "Rehashing due to SIGHUP")
  237. err := server.rehash()
  238. if err != nil {
  239. server.logger.Error("rehash", fmt.Sprintln("Failed to rehash:", err.Error()))
  240. }
  241. }()
  242. }
  243. }
  244. }
  245. func (server *Server) acceptClient(conn clientConn) {
  246. // check IP address
  247. ipaddr := utils.AddrToIP(conn.Conn.RemoteAddr())
  248. if ipaddr != nil {
  249. isBanned, banMsg := server.checkBans(ipaddr)
  250. if isBanned {
  251. // this might not show up properly on some clients, but our objective here is just to close the connection out before it has a load impact on us
  252. conn.Conn.Write([]byte(fmt.Sprintf(errorMsg, banMsg)))
  253. conn.Conn.Close()
  254. return
  255. }
  256. }
  257. server.logger.Debug("localconnect-ip", fmt.Sprintf("Client connecting from %v", ipaddr))
  258. // prolly don't need to alert snomasks on this, only on connection reg
  259. NewClient(server, conn.Conn, conn.IsTLS)
  260. }
  261. func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) {
  262. // check DLINEs
  263. isBanned, info := server.dlines.CheckIP(ipaddr)
  264. if isBanned {
  265. server.logger.Info("localconnect-ip", fmt.Sprintf("Client from %v rejected by d-line", ipaddr))
  266. return true, info.BanMessage("You are banned from this server (%s)")
  267. }
  268. // check connection limits
  269. err := server.connectionLimiter.AddClient(ipaddr, false)
  270. if err != nil {
  271. // too many connections from one client, tell the client and close the connection
  272. server.logger.Info("localconnect-ip", fmt.Sprintf("Client from %v rejected for connection limit", ipaddr))
  273. return true, "Too many clients from your network"
  274. }
  275. // check connection throttle
  276. err = server.connectionThrottler.AddClient(ipaddr)
  277. if err != nil {
  278. // too many connections too quickly from client, tell them and close the connection
  279. duration := server.connectionThrottler.BanDuration()
  280. length := &IPRestrictTime{
  281. Duration: duration,
  282. Expires: time.Now().Add(duration),
  283. }
  284. server.dlines.AddIP(ipaddr, length, server.connectionThrottler.BanMessage(), "Exceeded automated connection throttle", "auto.connection.throttler")
  285. // they're DLINE'd for 15 minutes or whatever, so we can reset the connection throttle now,
  286. // and once their temporary DLINE is finished they can fill up the throttler again
  287. server.connectionThrottler.ResetFor(ipaddr)
  288. // this might not show up properly on some clients, but our objective here is just to close it out before it has a load impact on us
  289. server.logger.Info(
  290. "localconnect-ip",
  291. fmt.Sprintf("Client from %v exceeded connection throttle, d-lining for %v", ipaddr, duration))
  292. return true, server.connectionThrottler.BanMessage()
  293. }
  294. return false, ""
  295. }
  296. //
  297. // IRC protocol listeners
  298. //
  299. // createListener starts the given listeners.
  300. func (server *Server) createListener(addr string, tlsConfig *tls.Config) *ListenerWrapper {
  301. // make listener
  302. var listener net.Listener
  303. var err error
  304. addr = strings.TrimPrefix(addr, "unix:")
  305. if strings.HasPrefix(addr, "/") {
  306. // https://stackoverflow.com/a/34881585
  307. os.Remove(addr)
  308. listener, err = net.Listen("unix", addr)
  309. } else {
  310. listener, err = net.Listen("tcp", addr)
  311. }
  312. if err != nil {
  313. log.Fatal(server, "listen error: ", err)
  314. }
  315. // throw our details to the server so we can be modified/killed later
  316. wrapper := ListenerWrapper{
  317. listener: listener,
  318. tlsConfig: tlsConfig,
  319. shouldStop: false,
  320. }
  321. var shouldStop bool
  322. // setup accept goroutine
  323. go func() {
  324. for {
  325. conn, err := listener.Accept()
  326. // synchronously access config data:
  327. // whether TLS is enabled and whether we should stop listening
  328. wrapper.configMutex.Lock()
  329. shouldStop = wrapper.shouldStop
  330. tlsConfig = wrapper.tlsConfig
  331. wrapper.configMutex.Unlock()
  332. if err == nil {
  333. if tlsConfig != nil {
  334. conn = tls.Server(conn, tlsConfig)
  335. }
  336. newConn := clientConn{
  337. Conn: conn,
  338. IsTLS: tlsConfig != nil,
  339. }
  340. // hand off the connection
  341. go server.acceptClient(newConn)
  342. }
  343. if shouldStop {
  344. listener.Close()
  345. return
  346. }
  347. }
  348. }()
  349. return &wrapper
  350. }
  351. // generateMessageID returns a network-unique message ID.
  352. func (server *Server) generateMessageID() string {
  353. // we don't need the full like 30 chars since the unixnano below handles
  354. // most of our uniqueness requirements, so just truncate at 5
  355. lastbit := strconv.FormatInt(rand.Int63(), 36)
  356. if 5 < len(lastbit) {
  357. lastbit = lastbit[:4]
  358. }
  359. return fmt.Sprintf("%s%s", strconv.FormatInt(time.Now().UTC().UnixNano(), 36), lastbit)
  360. }
  361. //
  362. // server functionality
  363. //
  364. func (server *Server) tryRegister(c *Client) {
  365. if c.Registered() {
  366. return
  367. }
  368. preregNick := c.PreregNick()
  369. if preregNick == "" || !c.HasUsername() || c.capState == caps.NegotiatingState {
  370. return
  371. }
  372. // client MUST send PASS (or AUTHENTICATE, if skip-server-password is set)
  373. // before completing the other registration commands
  374. if !c.Authorized() {
  375. c.Quit(c.t("Bad password"))
  376. c.destroy(false)
  377. return
  378. }
  379. rb := NewResponseBuffer(c)
  380. nickAssigned := performNickChange(server, c, c, preregNick, rb)
  381. rb.Send()
  382. if !nickAssigned {
  383. c.SetPreregNick("")
  384. return
  385. }
  386. // check KLINEs
  387. isBanned, info := server.klines.CheckMasks(c.AllNickmasks()...)
  388. if isBanned {
  389. reason := info.Reason
  390. if info.Time != nil {
  391. reason += fmt.Sprintf(" [%s]", info.Time.Duration.String())
  392. }
  393. c.Quit(fmt.Sprintf(c.t("You are banned from this server (%s)"), reason))
  394. c.destroy(false)
  395. return
  396. }
  397. // continue registration
  398. server.logger.Debug("localconnect", fmt.Sprintf("Client registered [%s] [u:%s] [r:%s]", c.nick, c.username, c.realname))
  399. server.snomasks.Send(sno.LocalConnects, fmt.Sprintf(ircfmt.Unescape("Client registered $c[grey][$r%s$c[grey]] [u:$r%s$c[grey]] [h:$r%s$c[grey]] [r:$r%s$c[grey]]"), c.nick, c.username, c.rawHostname, c.realname))
  400. c.Register()
  401. // send welcome text
  402. //NOTE(dan): we specifically use the NICK here instead of the nickmask
  403. // see http://modern.ircdocs.horse/#rplwelcome-001 for details on why we avoid using the nickmask
  404. c.Send(nil, server.name, RPL_WELCOME, c.nick, fmt.Sprintf(c.t("Welcome to the Internet Relay Network %s"), c.nick))
  405. c.Send(nil, server.name, RPL_YOURHOST, c.nick, fmt.Sprintf(c.t("Your host is %[1]s, running version %[2]s"), server.name, Ver))
  406. c.Send(nil, server.name, RPL_CREATED, c.nick, fmt.Sprintf(c.t("This server was created %s"), server.ctime.Format(time.RFC1123)))
  407. //TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter
  408. c.Send(nil, server.name, RPL_MYINFO, c.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString)
  409. rb = NewResponseBuffer(c)
  410. c.RplISupport(rb)
  411. server.MOTD(c, rb)
  412. rb.Send()
  413. c.Send(nil, c.nickMaskString, RPL_UMODEIS, c.nick, c.ModeString())
  414. if server.logger.IsLoggingRawIO() {
  415. c.Notice(c.t("This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect."))
  416. }
  417. // if resumed, send fake channel joins
  418. if c.resumeDetails != nil {
  419. for _, name := range c.resumeDetails.SendFakeJoinsFor {
  420. channel := server.channels.Get(name)
  421. if channel == nil {
  422. continue
  423. }
  424. if c.capabilities.Has(caps.ExtendedJoin) {
  425. c.Send(nil, c.nickMaskString, "JOIN", channel.name, c.AccountName(), c.realname)
  426. } else {
  427. c.Send(nil, c.nickMaskString, "JOIN", channel.name)
  428. }
  429. // reuse the last rb
  430. channel.SendTopic(c, rb)
  431. channel.Names(c, rb)
  432. rb.Send()
  433. // construct and send fake modestring if necessary
  434. c.stateMutex.RLock()
  435. myModes := channel.members[c]
  436. c.stateMutex.RUnlock()
  437. if myModes == nil {
  438. continue
  439. }
  440. oldModes := myModes.String()
  441. if 0 < len(oldModes) {
  442. params := []string{channel.name, "+" + oldModes}
  443. for range oldModes {
  444. params = append(params, c.nick)
  445. }
  446. c.Send(nil, server.name, "MODE", params...)
  447. }
  448. }
  449. }
  450. }
  451. // t returns the translated version of the given string, based on the languages configured by the client.
  452. func (client *Client) t(originalString string) string {
  453. // grab this mutex to protect client.languages
  454. client.stateMutex.RLock()
  455. defer client.stateMutex.RUnlock()
  456. return client.server.languages.Translate(client.languages, originalString)
  457. }
  458. // MOTD serves the Message of the Day.
  459. func (server *Server) MOTD(client *Client, rb *ResponseBuffer) {
  460. server.configurableStateMutex.RLock()
  461. motdLines := server.motdLines
  462. server.configurableStateMutex.RUnlock()
  463. if len(motdLines) < 1 {
  464. rb.Add(nil, server.name, ERR_NOMOTD, client.nick, client.t("MOTD File is missing"))
  465. return
  466. }
  467. rb.Add(nil, server.name, RPL_MOTDSTART, client.nick, fmt.Sprintf(client.t("- %s Message of the day - "), server.name))
  468. for _, line := range motdLines {
  469. rb.Add(nil, server.name, RPL_MOTD, client.nick, line)
  470. }
  471. rb.Add(nil, server.name, RPL_ENDOFMOTD, client.nick, client.t("End of MOTD command"))
  472. }
  473. // wordWrap wraps the given text into a series of lines that don't exceed lineWidth characters.
  474. func wordWrap(text string, lineWidth int) []string {
  475. var lines []string
  476. var cacheLine, cacheWord string
  477. for _, char := range text {
  478. if char == '\r' {
  479. continue
  480. } else if char == '\n' {
  481. cacheLine += cacheWord
  482. lines = append(lines, cacheLine)
  483. cacheWord = ""
  484. cacheLine = ""
  485. } else if (char == ' ' || char == '-') && len(cacheLine)+len(cacheWord)+1 < lineWidth {
  486. // natural word boundary
  487. cacheLine += cacheWord + string(char)
  488. cacheWord = ""
  489. } else if lineWidth <= len(cacheLine)+len(cacheWord)+1 {
  490. // time to wrap to next line
  491. if len(cacheLine) < (lineWidth / 2) {
  492. // this word takes up more than half a line... just split in the middle of the word
  493. cacheLine += cacheWord + string(char)
  494. cacheWord = ""
  495. } else {
  496. cacheWord += string(char)
  497. }
  498. lines = append(lines, cacheLine)
  499. cacheLine = ""
  500. } else {
  501. // normal character
  502. cacheWord += string(char)
  503. }
  504. }
  505. if 0 < len(cacheWord) {
  506. cacheLine += cacheWord
  507. }
  508. if 0 < len(cacheLine) {
  509. lines = append(lines, cacheLine)
  510. }
  511. return lines
  512. }
  513. // SplitMessage represents a message that's been split for sending.
  514. type SplitMessage struct {
  515. For512 []string
  516. ForMaxLine string
  517. }
  518. func (server *Server) splitMessage(original string, origIs512 bool) SplitMessage {
  519. var newSplit SplitMessage
  520. newSplit.ForMaxLine = original
  521. if !origIs512 {
  522. newSplit.For512 = wordWrap(original, 400)
  523. } else {
  524. newSplit.For512 = []string{original}
  525. }
  526. return newSplit
  527. }
  528. // WhoisChannelsNames returns the common channel names between two users.
  529. func (client *Client) WhoisChannelsNames(target *Client) []string {
  530. isMultiPrefix := target.capabilities.Has(caps.MultiPrefix)
  531. var chstrs []string
  532. for _, channel := range client.Channels() {
  533. // channel is secret and the target can't see it
  534. if !target.flags[modes.Operator] && channel.HasMode(modes.Secret) && !channel.hasClient(target) {
  535. continue
  536. }
  537. chstrs = append(chstrs, channel.ClientPrefixes(client, isMultiPrefix)+channel.name)
  538. }
  539. return chstrs
  540. }
  541. func (client *Client) getWhoisOf(target *Client, rb *ResponseBuffer) {
  542. target.stateMutex.RLock()
  543. defer target.stateMutex.RUnlock()
  544. rb.Add(nil, client.server.name, RPL_WHOISUSER, client.nick, target.nick, target.username, target.hostname, "*", target.realname)
  545. whoischannels := client.WhoisChannelsNames(target)
  546. if whoischannels != nil {
  547. rb.Add(nil, client.server.name, RPL_WHOISCHANNELS, client.nick, target.nick, strings.Join(whoischannels, " "))
  548. }
  549. if target.class != nil {
  550. rb.Add(nil, client.server.name, RPL_WHOISOPERATOR, client.nick, target.nick, target.whoisLine)
  551. }
  552. if client.flags[modes.Operator] || client == target {
  553. rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, client.nick, target.nick, fmt.Sprintf("%s@%s", target.username, utils.LookupHostname(target.IPString())), target.IPString(), client.t("Actual user@host, Actual IP"))
  554. }
  555. if target.flags[modes.TLS] {
  556. rb.Add(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, client.t("is using a secure connection"))
  557. }
  558. if target.LoggedIntoAccount() {
  559. rb.Add(nil, client.server.name, RPL_WHOISACCOUNT, client.nick, client.AccountName(), client.t("is logged in as"))
  560. }
  561. if target.flags[modes.Bot] {
  562. rb.Add(nil, client.server.name, RPL_WHOISBOT, client.nick, target.nick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.networkName)))
  563. }
  564. if 0 < len(target.languages) {
  565. params := []string{client.nick, target.nick}
  566. for _, str := range client.server.languages.Codes(target.languages) {
  567. params = append(params, str)
  568. }
  569. params = append(params, client.t("can speak these languages"))
  570. rb.Add(nil, client.server.name, RPL_WHOISLANGUAGE, params...)
  571. }
  572. if target.certfp != "" && (client.flags[modes.Operator] || client == target) {
  573. rb.Add(nil, client.server.name, RPL_WHOISCERTFP, client.nick, target.nick, fmt.Sprintf(client.t("has client certificate fingerprint %s"), target.certfp))
  574. }
  575. rb.Add(nil, client.server.name, RPL_WHOISIDLE, client.nick, target.nick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), client.t("seconds idle, signon time"))
  576. }
  577. // rplWhoReply returns the WHO reply between one user and another channel/user.
  578. // <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ]
  579. // :<hopcount> <real name>
  580. func (target *Client) rplWhoReply(channel *Channel, client *Client, rb *ResponseBuffer) {
  581. channelName := "*"
  582. flags := ""
  583. if client.HasMode(modes.Away) {
  584. flags = "G"
  585. } else {
  586. flags = "H"
  587. }
  588. if client.HasMode(modes.Operator) {
  589. flags += "*"
  590. }
  591. if channel != nil {
  592. flags += channel.ClientPrefixes(client, target.capabilities.Has(caps.MultiPrefix))
  593. channelName = channel.name
  594. }
  595. rb.Add(nil, target.server.name, RPL_WHOREPLY, target.nick, channelName, client.Username(), client.Hostname(), client.server.name, client.Nick(), flags, strconv.Itoa(client.hops)+" "+client.Realname())
  596. }
  597. func whoChannel(client *Client, channel *Channel, friends ClientSet, rb *ResponseBuffer) {
  598. for _, member := range channel.Members() {
  599. if !client.flags[modes.Invisible] || friends[client] {
  600. client.rplWhoReply(channel, member, rb)
  601. }
  602. }
  603. }
  604. // rehash reloads the config and applies the changes from the config file.
  605. func (server *Server) rehash() error {
  606. server.logger.Debug("rehash", "Starting rehash")
  607. // only let one REHASH go on at a time
  608. server.rehashMutex.Lock()
  609. defer server.rehashMutex.Unlock()
  610. server.logger.Debug("rehash", "Got rehash lock")
  611. config, err := LoadConfig(server.configFilename)
  612. if err != nil {
  613. return fmt.Errorf("Error loading config file config: %s", err.Error())
  614. }
  615. err = server.applyConfig(config, false)
  616. if err != nil {
  617. return fmt.Errorf("Error applying config changes: %s", err.Error())
  618. }
  619. return nil
  620. }
  621. func (server *Server) applyConfig(config *Config, initial bool) error {
  622. if initial {
  623. server.ctime = time.Now()
  624. server.configFilename = config.Filename
  625. } else {
  626. // enforce configs that can't be changed after launch:
  627. if server.limits.LineLen.Tags != config.Limits.LineLen.Tags || server.limits.LineLen.Rest != config.Limits.LineLen.Rest {
  628. return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted")
  629. } else if server.name != config.Server.Name {
  630. return fmt.Errorf("Server name cannot be changed after launching the server, rehash aborted")
  631. } else if server.storeFilename != config.Datastore.Path {
  632. return fmt.Errorf("Datastore path cannot be changed after launching the server, rehash aborted")
  633. }
  634. }
  635. server.logger.Info("rehash", "Using config file", server.configFilename)
  636. casefoldedName, err := Casefold(config.Server.Name)
  637. if err != nil {
  638. return fmt.Errorf("Server name isn't valid [%s]: %s", config.Server.Name, err.Error())
  639. }
  640. // confirm operator stuff all exists and is fine
  641. operclasses, err := config.OperatorClasses()
  642. if err != nil {
  643. return fmt.Errorf("Error rehashing config file operclasses: %s", err.Error())
  644. }
  645. opers, err := config.Operators(operclasses)
  646. if err != nil {
  647. return fmt.Errorf("Error rehashing config file opers: %s", err.Error())
  648. }
  649. // TODO: support rehash of existing operator perms?
  650. // sanity checks complete, start modifying server state
  651. if initial {
  652. server.name = config.Server.Name
  653. server.nameCasefolded = casefoldedName
  654. }
  655. server.configurableStateMutex.Lock()
  656. server.networkName = config.Network.Name
  657. if config.Server.Password != "" {
  658. server.password = config.Server.PasswordBytes()
  659. } else {
  660. server.password = nil
  661. }
  662. // apply new WebIRC command restrictions
  663. server.webirc = config.Server.WebIRC
  664. // apply new PROXY command restrictions
  665. server.proxyAllowedFrom = config.Server.ProxyAllowedFrom
  666. server.recoverFromErrors = true
  667. if config.Debug.RecoverFromErrors != nil {
  668. server.recoverFromErrors = *config.Debug.RecoverFromErrors
  669. }
  670. server.configurableStateMutex.Unlock()
  671. err = server.connectionLimiter.ApplyConfig(config.Server.ConnectionLimiter)
  672. if err != nil {
  673. return err
  674. }
  675. err = server.connectionThrottler.ApplyConfig(config.Server.ConnectionThrottler)
  676. if err != nil {
  677. return err
  678. }
  679. // setup new and removed caps
  680. addedCaps := caps.NewSet()
  681. removedCaps := caps.NewSet()
  682. updatedCaps := caps.NewSet()
  683. // Translations
  684. currentLanguageValue, _ := CapValues.Get(caps.Languages)
  685. langCodes := []string{strconv.Itoa(len(config.Languages.Data) + 1), "en"}
  686. for _, info := range config.Languages.Data {
  687. if info.Incomplete {
  688. langCodes = append(langCodes, "~"+info.Code)
  689. } else {
  690. langCodes = append(langCodes, info.Code)
  691. }
  692. }
  693. newLanguageValue := strings.Join(langCodes, ",")
  694. server.logger.Debug("rehash", "Languages:", newLanguageValue)
  695. if currentLanguageValue != newLanguageValue {
  696. updatedCaps.Add(caps.Languages)
  697. CapValues.Set(caps.Languages, newLanguageValue)
  698. }
  699. lm := languages.NewManager(config.Languages.Default, config.Languages.Data)
  700. server.logger.Debug("rehash", "Regenerating HELP indexes for new languages")
  701. GenerateHelpIndices(lm)
  702. server.languages = lm
  703. // SASL
  704. oldAccountConfig := server.AccountConfig()
  705. authPreviouslyEnabled := oldAccountConfig != nil && oldAccountConfig.AuthenticationEnabled
  706. if config.Accounts.AuthenticationEnabled && !authPreviouslyEnabled {
  707. // enabling SASL
  708. SupportedCapabilities.Enable(caps.SASL)
  709. CapValues.Set(caps.SASL, "PLAIN,EXTERNAL")
  710. addedCaps.Add(caps.SASL)
  711. } else if !config.Accounts.AuthenticationEnabled && authPreviouslyEnabled {
  712. // disabling SASL
  713. SupportedCapabilities.Disable(caps.SASL)
  714. removedCaps.Add(caps.SASL)
  715. }
  716. server.configurableStateMutex.Lock()
  717. server.accountConfig = &config.Accounts
  718. server.configurableStateMutex.Unlock()
  719. nickReservationPreviouslyDisabled := oldAccountConfig != nil && !oldAccountConfig.NickReservation.Enabled
  720. nickReservationNowEnabled := config.Accounts.NickReservation.Enabled
  721. if nickReservationPreviouslyDisabled && nickReservationNowEnabled {
  722. server.accounts.buildNickToAccountIndex()
  723. }
  724. // STS
  725. stsValue := config.Server.STS.Value()
  726. var stsDisabled bool
  727. stsCurrentCapValue, _ := CapValues.Get(caps.STS)
  728. server.logger.Debug("rehash", "STS Vals", stsCurrentCapValue, stsValue, fmt.Sprintf("server[%v] config[%v]", server.stsEnabled, config.Server.STS.Enabled))
  729. if config.Server.STS.Enabled && !server.stsEnabled {
  730. // enabling STS
  731. SupportedCapabilities.Enable(caps.STS)
  732. addedCaps.Add(caps.STS)
  733. CapValues.Set(caps.STS, stsValue)
  734. } else if !config.Server.STS.Enabled && server.stsEnabled {
  735. // disabling STS
  736. SupportedCapabilities.Disable(caps.STS)
  737. removedCaps.Add(caps.STS)
  738. stsDisabled = true
  739. } else if config.Server.STS.Enabled && server.stsEnabled && stsValue != stsCurrentCapValue {
  740. // STS policy updated
  741. CapValues.Set(caps.STS, stsValue)
  742. updatedCaps.Add(caps.STS)
  743. }
  744. server.stsEnabled = config.Server.STS.Enabled
  745. // burst new and removed caps
  746. var capBurstClients ClientSet
  747. added := make(map[caps.Version]string)
  748. var removed string
  749. // updated caps get DEL'd and then NEW'd
  750. // so, we can just add updated ones to both removed and added lists here and they'll be correctly handled
  751. server.logger.Debug("rehash", "Updated Caps", updatedCaps.String(caps.Cap301, CapValues), strconv.Itoa(updatedCaps.Count()))
  752. for _, capab := range updatedCaps.List() {
  753. addedCaps.Enable(capab)
  754. removedCaps.Enable(capab)
  755. }
  756. if 0 < addedCaps.Count() || 0 < removedCaps.Count() {
  757. capBurstClients = server.clients.AllWithCaps(caps.CapNotify)
  758. added[caps.Cap301] = addedCaps.String(caps.Cap301, CapValues)
  759. added[caps.Cap302] = addedCaps.String(caps.Cap302, CapValues)
  760. // removed never has values, so we leave it as Cap301
  761. removed = removedCaps.String(caps.Cap301, CapValues)
  762. }
  763. for sClient := range capBurstClients {
  764. if stsDisabled {
  765. // remove STS policy
  766. //TODO(dan): this is an ugly hack. we can write this better.
  767. stsPolicy := "sts=duration=0"
  768. if 0 < addedCaps.Count() {
  769. added[caps.Cap302] = added[caps.Cap302] + " " + stsPolicy
  770. } else {
  771. addedCaps.Enable(caps.STS)
  772. added[caps.Cap302] = stsPolicy
  773. }
  774. }
  775. // DEL caps and then send NEW ones so that updated caps get removed/added correctly
  776. if 0 < removedCaps.Count() {
  777. sClient.Send(nil, server.name, "CAP", sClient.nick, "DEL", removed)
  778. }
  779. if 0 < addedCaps.Count() {
  780. sClient.Send(nil, server.name, "CAP", sClient.nick, "NEW", added[sClient.capVersion])
  781. }
  782. }
  783. // set server options
  784. server.configurableStateMutex.Lock()
  785. lineLenConfig := LineLenLimits{
  786. Tags: config.Limits.LineLen.Tags,
  787. Rest: config.Limits.LineLen.Rest,
  788. }
  789. server.limits = Limits{
  790. AwayLen: int(config.Limits.AwayLen),
  791. ChannelLen: int(config.Limits.ChannelLen),
  792. KickLen: int(config.Limits.KickLen),
  793. MonitorEntries: int(config.Limits.MonitorEntries),
  794. NickLen: int(config.Limits.NickLen),
  795. TopicLen: int(config.Limits.TopicLen),
  796. ChanListModes: int(config.Limits.ChanListModes),
  797. LineLen: lineLenConfig,
  798. }
  799. server.operclasses = *operclasses
  800. server.operators = opers
  801. server.checkIdent = config.Server.CheckIdent
  802. // registration
  803. server.channelRegistrationEnabled = config.Channels.Registration.Enabled
  804. server.defaultChannelModes = ParseDefaultChannelModes(config)
  805. server.configurableStateMutex.Unlock()
  806. // set new sendqueue size
  807. if config.Server.MaxSendQBytes != server.MaxSendQBytes {
  808. server.configurableStateMutex.Lock()
  809. server.MaxSendQBytes = config.Server.MaxSendQBytes
  810. server.configurableStateMutex.Unlock()
  811. // update on all clients
  812. for _, sClient := range server.clients.AllClients() {
  813. sClient.socket.MaxSendQBytes = config.Server.MaxSendQBytes
  814. }
  815. }
  816. // set RPL_ISUPPORT
  817. var newISupportReplies [][]string
  818. oldISupportList := server.isupport
  819. server.setISupport()
  820. if oldISupportList != nil {
  821. newISupportReplies = oldISupportList.GetDifference(server.isupport)
  822. }
  823. server.loadMOTD(config.Server.MOTD, config.Server.MOTDFormatting)
  824. // reload logging config
  825. err = server.logger.ApplyConfig(config.Logging)
  826. if err != nil {
  827. return err
  828. }
  829. nowLoggingRawIO := server.logger.IsLoggingRawIO()
  830. // notify clients if raw i/o logging was enabled by a rehash
  831. sendRawOutputNotice := !initial && !server.loggingRawIO && nowLoggingRawIO
  832. server.loggingRawIO = nowLoggingRawIO
  833. server.storeFilename = config.Datastore.Path
  834. server.logger.Info("rehash", "Using datastore", server.storeFilename)
  835. if initial {
  836. if err := server.loadDatastore(server.storeFilename); err != nil {
  837. return err
  838. }
  839. }
  840. // we are now open for business
  841. server.setupListeners(config)
  842. if !initial {
  843. // push new info to all of our clients
  844. for _, sClient := range server.clients.AllClients() {
  845. for _, tokenline := range newISupportReplies {
  846. sClient.Send(nil, server.name, RPL_ISUPPORT, append([]string{sClient.nick}, tokenline...)...)
  847. }
  848. if sendRawOutputNotice {
  849. sClient.Notice(sClient.t("This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect."))
  850. }
  851. }
  852. }
  853. return nil
  854. }
  855. func (server *Server) loadMOTD(motdPath string, useFormatting bool) error {
  856. server.logger.Info("rehash", "Using MOTD", motdPath)
  857. motdLines := make([]string, 0)
  858. if motdPath != "" {
  859. file, err := os.Open(motdPath)
  860. if err == nil {
  861. defer file.Close()
  862. reader := bufio.NewReader(file)
  863. for {
  864. line, err := reader.ReadString('\n')
  865. if err != nil {
  866. break
  867. }
  868. line = strings.TrimRight(line, "\r\n")
  869. if useFormatting {
  870. line = ircfmt.Unescape(line)
  871. }
  872. // "- " is the required prefix for MOTD, we just add it here to make
  873. // bursting it out to clients easier
  874. line = fmt.Sprintf("- %s", line)
  875. motdLines = append(motdLines, line)
  876. }
  877. } else {
  878. return err
  879. }
  880. }
  881. server.configurableStateMutex.Lock()
  882. server.motdLines = motdLines
  883. server.configurableStateMutex.Unlock()
  884. return nil
  885. }
  886. func (server *Server) loadDatastore(datastorePath string) error {
  887. // open the datastore and load server state for which it (rather than config)
  888. // is the source of truth
  889. db, err := OpenDatabase(datastorePath)
  890. if err == nil {
  891. server.store = db
  892. } else {
  893. return fmt.Errorf("Failed to open datastore: %s", err.Error())
  894. }
  895. // load *lines (from the datastores)
  896. server.logger.Debug("startup", "Loading D/Klines")
  897. server.loadDLines()
  898. server.loadKLines()
  899. // load password manager
  900. server.logger.Debug("startup", "Loading passwords")
  901. err = server.store.View(func(tx *buntdb.Tx) error {
  902. saltString, err := tx.Get(keySalt)
  903. if err != nil {
  904. return fmt.Errorf("Could not retrieve salt string: %s", err.Error())
  905. }
  906. salt, err := base64.StdEncoding.DecodeString(saltString)
  907. if err != nil {
  908. return err
  909. }
  910. pwm := passwd.NewSaltedManager(salt)
  911. server.passwords = &pwm
  912. return nil
  913. })
  914. if err != nil {
  915. return fmt.Errorf("Could not load salt: %s", err.Error())
  916. }
  917. server.channelRegistry = NewChannelRegistry(server)
  918. server.accounts = NewAccountManager(server)
  919. return nil
  920. }
  921. func (server *Server) setupListeners(config *Config) {
  922. logListener := func(addr string, tlsconfig *tls.Config) {
  923. server.logger.Info("listeners",
  924. fmt.Sprintf("now listening on %s, tls=%t.", addr, (tlsconfig != nil)),
  925. )
  926. }
  927. // update or destroy all existing listeners
  928. tlsListeners := config.TLSListeners()
  929. for addr := range server.listeners {
  930. currentListener := server.listeners[addr]
  931. var stillConfigured bool
  932. for _, newaddr := range config.Server.Listen {
  933. if newaddr == addr {
  934. stillConfigured = true
  935. break
  936. }
  937. }
  938. // pass new config information to the listener, to be picked up after
  939. // its next Accept(). this is like sending over a buffered channel of
  940. // size 1, but where sending a second item overwrites the buffered item
  941. // instead of blocking.
  942. currentListener.configMutex.Lock()
  943. currentListener.shouldStop = !stillConfigured
  944. currentListener.tlsConfig = tlsListeners[addr]
  945. currentListener.configMutex.Unlock()
  946. if stillConfigured {
  947. logListener(addr, currentListener.tlsConfig)
  948. } else {
  949. // tell the listener it should stop by interrupting its Accept() call:
  950. currentListener.listener.Close()
  951. delete(server.listeners, addr)
  952. server.logger.Info("listeners", fmt.Sprintf("stopped listening on %s.", addr))
  953. }
  954. }
  955. // create new listeners that were not previously configured
  956. for _, newaddr := range config.Server.Listen {
  957. _, exists := server.listeners[newaddr]
  958. if !exists {
  959. // make new listener
  960. tlsConfig := tlsListeners[newaddr]
  961. server.listeners[newaddr] = server.createListener(newaddr, tlsConfig)
  962. logListener(newaddr, tlsConfig)
  963. }
  964. }
  965. if len(tlsListeners) == 0 {
  966. server.logger.Warning("startup", "You are not exposing an SSL/TLS listening port. You should expose at least one port (typically 6697) to accept TLS connections")
  967. }
  968. var usesStandardTLSPort bool
  969. for addr := range config.TLSListeners() {
  970. if strings.Contains(addr, "6697") {
  971. usesStandardTLSPort = true
  972. break
  973. }
  974. }
  975. if 0 < len(tlsListeners) && !usesStandardTLSPort {
  976. server.logger.Warning("startup", "Port 6697 is the standard TLS port for IRC. You should (also) expose port 6697 as a TLS port to ensure clients can connect securely")
  977. }
  978. }
  979. // elistMatcher takes and matches ELIST conditions
  980. type elistMatcher struct {
  981. MinClientsActive bool
  982. MinClients int
  983. MaxClientsActive bool
  984. MaxClients int
  985. }
  986. // Matches checks whether the given channel matches our matches.
  987. func (matcher *elistMatcher) Matches(channel *Channel) bool {
  988. if matcher.MinClientsActive {
  989. if len(channel.Members()) < matcher.MinClients {
  990. return false
  991. }
  992. }
  993. if matcher.MaxClientsActive {
  994. if len(channel.Members()) < len(channel.members) {
  995. return false
  996. }
  997. }
  998. return true
  999. }
  1000. // RplList returns the RPL_LIST numeric for the given channel.
  1001. func (target *Client) RplList(channel *Channel, rb *ResponseBuffer) {
  1002. // get the correct number of channel members
  1003. var memberCount int
  1004. if target.flags[modes.Operator] || channel.hasClient(target) {
  1005. memberCount = len(channel.Members())
  1006. } else {
  1007. for _, member := range channel.Members() {
  1008. if !member.HasMode(modes.Invisible) {
  1009. memberCount++
  1010. }
  1011. }
  1012. }
  1013. rb.Add(nil, target.server.name, RPL_LIST, target.nick, channel.name, strconv.Itoa(memberCount), channel.topic)
  1014. }
  1015. // ResumeDetails are the details that we use to resume connections.
  1016. type ResumeDetails struct {
  1017. OldNick string
  1018. Timestamp *time.Time
  1019. SendFakeJoinsFor []string
  1020. }
  1021. var (
  1022. infoString1 = strings.Split(` ▄▄▄ ▄▄▄· ▄▄ • ▐ ▄
  1023. ▪ ▀▄ █·▐█ ▀█ ▐█ ▀ ▪▪ •█▌▐█▪
  1024. ▄█▀▄ ▐▀▀▄ ▄█▀▀█ ▄█ ▀█▄ ▄█▀▄▪▐█▐▐▌ ▄█▀▄
  1025. ▐█▌.▐▌▐█•█▌▐█ ▪▐▌▐█▄▪▐█▐█▌ ▐▌██▐█▌▐█▌.▐▌
  1026. ▀█▄▀▪.▀ ▀ ▀ ▀ ·▀▀▀▀ ▀█▄▀ ▀▀ █▪ ▀█▄▀▪
  1027. https://oragono.io/
  1028. https://github.com/oragono/oragono
  1029. https://crowdin.com/project/oragono
  1030. `, "\n")
  1031. infoString2 = strings.Split(` Daniel Oakley, DanielOaks, <daniel@danieloaks.net>
  1032. Shivaram Lingamneni, slingamn, <slingamn@cs.stanford.edu>
  1033. `, "\n")
  1034. infoString3 = strings.Split(` 3onyc
  1035. Edmund Huber
  1036. Euan Kemp (euank)
  1037. Jeremy Latt
  1038. Martin Lindhe (martinlindhe)
  1039. Roberto Besser (besser)
  1040. Robin Burchell (rburchell)
  1041. Sean Enck (enckse)
  1042. soul9
  1043. Vegax
  1044. `, "\n")
  1045. )