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 6package irc 7 8import ( 9 "fmt" 10 "net" 11 "net/http" 12 _ "net/http/pprof" 13 "os" 14 "os/signal" 15 "strconv" 16 "strings" 17 "sync" 18 "syscall" 19 "time" 20 "unsafe" 21 22 "github.com/ergochat/irc-go/ircfmt" 23 "github.com/okzk/sdnotify" 24 25 "github.com/ergochat/ergo/irc/caps" 26 "github.com/ergochat/ergo/irc/connection_limits" 27 "github.com/ergochat/ergo/irc/flatip" 28 "github.com/ergochat/ergo/irc/history" 29 "github.com/ergochat/ergo/irc/logger" 30 "github.com/ergochat/ergo/irc/modes" 31 "github.com/ergochat/ergo/irc/mysql" 32 "github.com/ergochat/ergo/irc/sno" 33 "github.com/ergochat/ergo/irc/utils" 34 "github.com/tidwall/buntdb" 35) 36 37const ( 38 alwaysOnExpirationPollPeriod = time.Hour 39) 40 41var ( 42 // common error line to sub values into 43 errorMsg = "ERROR :%s\r\n" 44 45 // three final parameters of 004 RPL_MYINFO, enumerating our supported modes 46 rplMyInfo1, rplMyInfo2, rplMyInfo3 = modes.RplMyInfo() 47 48 // CHANMODES isupport token 49 chanmodesToken = modes.ChanmodesToken() 50 51 // whitelist of caps to serve on the STS-only listener. In particular, 52 // never advertise SASL, to discourage people from sending their passwords: 53 stsOnlyCaps = caps.NewSet(caps.STS, caps.MessageTags, caps.ServerTime, caps.Batch, caps.LabeledResponse, caps.EchoMessage, caps.Nope) 54 55 // we only have standard channels for now. TODO: any updates to this 56 // will also need to be reflected in CasefoldChannel 57 chanTypes = "#" 58 59 throttleMessage = "You have attempted to connect too many times within a short duration. Wait a while, and you will be able to connect." 60) 61 62// Server is the main Oragono server. 63type Server struct { 64 accounts AccountManager 65 channels ChannelManager 66 channelRegistry ChannelRegistry 67 clients ClientManager 68 config unsafe.Pointer 69 configFilename string 70 connectionLimiter connection_limits.Limiter 71 ctime time.Time 72 dlines *DLineManager 73 helpIndexManager HelpIndexManager 74 klines *KLineManager 75 listeners map[string]IRCListener 76 logger *logger.Manager 77 monitorManager MonitorManager 78 name string 79 nameCasefolded string 80 rehashMutex sync.Mutex // tier 4 81 rehashSignal chan os.Signal 82 pprofServer *http.Server 83 exitSignals chan os.Signal 84 snomasks SnoManager 85 store *buntdb.DB 86 historyDB mysql.MySQL 87 torLimiter connection_limits.TorLimiter 88 whoWas WhoWasList 89 stats Stats 90 semaphores ServerSemaphores 91 defcon uint32 92} 93 94// NewServer returns a new Oragono server. 95func NewServer(config *Config, logger *logger.Manager) (*Server, error) { 96 // initialize data structures 97 server := &Server{ 98 ctime: time.Now().UTC(), 99 listeners: make(map[string]IRCListener), 100 logger: logger, 101 rehashSignal: make(chan os.Signal, 1), 102 exitSignals: make(chan os.Signal, len(utils.ServerExitSignals)), 103 defcon: 5, 104 } 105 106 server.clients.Initialize() 107 server.semaphores.Initialize() 108 server.whoWas.Initialize(config.Limits.WhowasEntries) 109 server.monitorManager.Initialize() 110 server.snomasks.Initialize() 111 112 if err := server.applyConfig(config); err != nil { 113 return nil, err 114 } 115 116 // Attempt to clean up when receiving these signals. 117 signal.Notify(server.exitSignals, utils.ServerExitSignals...) 118 signal.Notify(server.rehashSignal, syscall.SIGHUP) 119 120 time.AfterFunc(alwaysOnExpirationPollPeriod, server.handleAlwaysOnExpirations) 121 122 return server, nil 123} 124 125// Shutdown shuts down the server. 126func (server *Server) Shutdown() { 127 sdnotify.Stopping() 128 server.logger.Info("server", "Stopping server") 129 130 //TODO(dan): Make sure we disallow new nicks 131 for _, client := range server.clients.AllClients() { 132 client.Notice("Server is shutting down") 133 if client.AlwaysOn() { 134 client.Store(IncludeLastSeen) 135 } 136 } 137 138 if err := server.store.Close(); err != nil { 139 server.logger.Error("shutdown", fmt.Sprintln("Could not close datastore:", err)) 140 } 141 142 server.historyDB.Close() 143 server.logger.Info("server", fmt.Sprintf("%s exiting", Ver)) 144} 145 146// Run starts the server. 147func (server *Server) Run() { 148 defer server.Shutdown() 149 150 for { 151 select { 152 case <-server.exitSignals: 153 return 154 case <-server.rehashSignal: 155 server.logger.Info("server", "Rehashing due to SIGHUP") 156 go server.rehash() 157 } 158 } 159} 160 161func (server *Server) checkBans(config *Config, ipaddr net.IP, checkScripts bool) (banned bool, requireSASL bool, message string) { 162 // #671: do not enforce bans against loopback, as a failsafe 163 // note that this function is not used for Tor connections (checkTorLimits is used instead) 164 if ipaddr.IsLoopback() { 165 return 166 } 167 168 if server.Defcon() == 1 { 169 if !utils.IPInNets(ipaddr, server.Config().Server.secureNets) { 170 return true, false, "New connections to this server are temporarily restricted" 171 } 172 } 173 174 flat := flatip.FromNetIP(ipaddr) 175 176 // check DLINEs 177 isBanned, info := server.dlines.CheckIP(flat) 178 if isBanned { 179 if info.RequireSASL { 180 server.logger.Info("connect-ip", "Requiring SASL from client due to d-line", ipaddr.String()) 181 return false, true, info.BanMessage("You must authenticate with SASL to connect from this IP (%s)") 182 } else { 183 server.logger.Info("connect-ip", "Client rejected by d-line", ipaddr.String()) 184 return true, false, info.BanMessage("You are banned from this server (%s)") 185 } 186 } 187 188 // check connection limits 189 err := server.connectionLimiter.AddClient(flat) 190 if err == connection_limits.ErrLimitExceeded { 191 // too many connections from one client, tell the client and close the connection 192 server.logger.Info("connect-ip", "Client rejected for connection limit", ipaddr.String()) 193 return true, false, "Too many clients from your network" 194 } else if err == connection_limits.ErrThrottleExceeded { 195 server.logger.Info("connect-ip", "Client exceeded connection throttle", ipaddr.String()) 196 return true, false, throttleMessage 197 } else if err != nil { 198 server.logger.Warning("internal", "unexpected ban result", err.Error()) 199 } 200 201 if checkScripts && config.Server.IPCheckScript.Enabled { 202 output, err := CheckIPBan(server.semaphores.IPCheckScript, config.Server.IPCheckScript, ipaddr) 203 if err != nil { 204 server.logger.Error("internal", "couldn't check IP ban script", ipaddr.String(), err.Error()) 205 return false, false, "" 206 } 207 // TODO: currently no way to cache IPAccepted 208 if (output.Result == IPBanned || output.Result == IPRequireSASL) && output.CacheSeconds != 0 { 209 network, err := flatip.ParseToNormalizedNet(output.CacheNet) 210 if err != nil { 211 server.logger.Error("internal", "invalid dline net from IP ban script", ipaddr.String(), output.CacheNet) 212 } else { 213 dlineDuration := time.Duration(output.CacheSeconds) * time.Second 214 err := server.dlines.AddNetwork(network, dlineDuration, output.Result == IPRequireSASL, output.BanMessage, "", "") 215 if err != nil { 216 server.logger.Error("internal", "couldn't set dline from IP ban script", ipaddr.String(), err.Error()) 217 } 218 } 219 } 220 if output.Result == IPBanned { 221 // XXX roll back IP connection/throttling addition for the IP 222 server.connectionLimiter.RemoveClient(flat) 223 server.logger.Info("connect-ip", "Rejected client due to ip-check-script", ipaddr.String()) 224 return true, false, output.BanMessage 225 } else if output.Result == IPRequireSASL { 226 server.logger.Info("connect-ip", "Requiring SASL from client due to ip-check-script", ipaddr.String()) 227 return false, true, output.BanMessage 228 } 229 } 230 231 return false, false, "" 232} 233 234func (server *Server) checkTorLimits() (banned bool, message string) { 235 switch server.torLimiter.AddClient() { 236 case connection_limits.ErrLimitExceeded: 237 return true, "Too many clients from the Tor network" 238 case connection_limits.ErrThrottleExceeded: 239 return true, "Exceeded connection throttle for the Tor network" 240 default: 241 return false, "" 242 } 243} 244 245func (server *Server) handleAlwaysOnExpirations() { 246 defer func() { 247 // reschedule whether or not there was a panic 248 time.AfterFunc(alwaysOnExpirationPollPeriod, server.handleAlwaysOnExpirations) 249 }() 250 251 defer server.HandlePanic() 252 253 config := server.Config() 254 deadline := time.Duration(config.Accounts.Multiclient.AlwaysOnExpiration) 255 if deadline == 0 { 256 return 257 } 258 server.logger.Info("accounts", "Checking always-on clients for expiration") 259 for _, client := range server.clients.AllClients() { 260 if client.IsExpiredAlwaysOn(config) { 261 // TODO save the channels list, use it for autojoin if/when they return? 262 server.logger.Info("accounts", "Expiring always-on client", client.AccountName()) 263 client.destroy(nil) 264 } 265 } 266} 267 268// 269// server functionality 270// 271 272func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) { 273 // XXX PROXY or WEBIRC MUST be sent as the first line of the session; 274 // if we are here at all that means we have the final value of the IP 275 if session.rawHostname == "" { 276 session.client.lookupHostname(session, false) 277 } 278 279 // try to complete registration normally 280 // XXX(#1057) username can be filled in by an ident query without the client 281 // having sent USER: check for both username and realname to ensure they did 282 if c.preregNick == "" || c.username == "" || c.realname == "" || session.capState == caps.NegotiatingState { 283 return 284 } 285 286 if c.isSTSOnly { 287 server.playSTSBurst(session) 288 return true 289 } 290 291 // client MUST send PASS if necessary, or authenticate with SASL if necessary, 292 // before completing the other registration commands 293 config := server.Config() 294 authOutcome := c.isAuthorized(server, config, session, c.requireSASL) 295 var quitMessage string 296 switch authOutcome { 297 case authFailPass: 298 quitMessage = c.t("Password incorrect") 299 c.Send(nil, server.name, ERR_PASSWDMISMATCH, "*", quitMessage) 300 case authFailSaslRequired, authFailTorSaslRequired: 301 quitMessage = c.requireSASLMessage 302 if quitMessage == "" { 303 quitMessage = c.t("You must log in with SASL to join this server") 304 } 305 c.Send(nil, c.server.name, "FAIL", "*", "ACCOUNT_REQUIRED", quitMessage) 306 } 307 if authOutcome != authSuccess { 308 c.Quit(quitMessage, nil) 309 return true 310 } 311 c.requireSASLMessage = "" 312 313 rb := NewResponseBuffer(session) 314 nickError := performNickChange(server, c, c, session, c.preregNick, rb) 315 rb.Send(true) 316 if nickError == errInsecureReattach { 317 c.Quit(c.t("You can't mix secure and insecure connections to this account"), nil) 318 return true 319 } else if nickError != nil { 320 c.preregNick = "" 321 return false 322 } 323 324 if session.client != c { 325 // reattached, bail out. 326 // we'll play the reg burst later, on the new goroutine associated with 327 // (thisSession, otherClient). This is to avoid having to transfer state 328 // like nickname, hostname, etc. to show the correct values in the reg burst. 329 return false 330 } 331 332 // Apply default user modes (without updating the invisible counter) 333 // The number of invisible users will be updated by server.stats.Register 334 // if we're using default user mode +i. 335 for _, defaultMode := range config.Accounts.defaultUserModes { 336 c.SetMode(defaultMode, true) 337 } 338 339 // count new user in statistics (before checking KLINEs, see #1303) 340 server.stats.Register(c.HasMode(modes.Invisible)) 341 342 // check KLINEs (#671: ignore KLINEs for loopback connections) 343 if !session.IP().IsLoopback() || session.isTor { 344 isBanned, info := server.klines.CheckMasks(c.AllNickmasks()...) 345 if isBanned { 346 c.Quit(info.BanMessage(c.t("You are banned from this server (%s)")), nil) 347 return true 348 } 349 } 350 351 server.playRegistrationBurst(session) 352 return false 353} 354 355func (server *Server) playSTSBurst(session *Session) { 356 nick := utils.SafeErrorParam(session.client.preregNick) 357 session.Send(nil, server.name, RPL_WELCOME, nick, fmt.Sprintf("Welcome to the Internet Relay Network %s", nick)) 358 session.Send(nil, server.name, RPL_YOURHOST, nick, fmt.Sprintf("Your host is %[1]s, running version %[2]s", server.name, "ergo")) 359 session.Send(nil, server.name, RPL_CREATED, nick, fmt.Sprintf("This server was created %s", time.Time{}.Format(time.RFC1123))) 360 session.Send(nil, server.name, RPL_MYINFO, nick, server.name, "ergo", "o", "o", "o") 361 session.Send(nil, server.name, RPL_ISUPPORT, nick, "CASEMAPPING=ascii", "are supported by this server") 362 session.Send(nil, server.name, ERR_NOMOTD, nick, "MOTD is unavailable") 363 for _, line := range server.Config().Server.STS.bannerLines { 364 session.Send(nil, server.name, "NOTICE", nick, line) 365 } 366} 367 368func (server *Server) playRegistrationBurst(session *Session) { 369 c := session.client 370 // continue registration 371 d := c.Details() 372 server.logger.Info("connect", fmt.Sprintf("Client connected [%s] [u:%s] [r:%s]", d.nick, d.username, d.realname)) 373 server.snomasks.Send(sno.LocalConnects, fmt.Sprintf("Client connected [%s] [u:%s] [h:%s] [ip:%s] [r:%s]", d.nick, d.username, session.rawHostname, session.IP().String(), d.realname)) 374 if d.account != "" { 375 server.sendLoginSnomask(d.nickMask, d.accountName) 376 } 377 378 // send welcome text 379 //NOTE(dan): we specifically use the NICK here instead of the nickmask 380 // see http://modern.ircdocs.horse/#rplwelcome-001 for details on why we avoid using the nickmask 381 config := server.Config() 382 session.Send(nil, server.name, RPL_WELCOME, d.nick, fmt.Sprintf(c.t("Welcome to the %s IRC Network %s"), config.Network.Name, d.nick)) 383 session.Send(nil, server.name, RPL_YOURHOST, d.nick, fmt.Sprintf(c.t("Your host is %[1]s, running version %[2]s"), server.name, Ver)) 384 session.Send(nil, server.name, RPL_CREATED, d.nick, fmt.Sprintf(c.t("This server was created %s"), server.ctime.Format(time.RFC1123))) 385 session.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, rplMyInfo1, rplMyInfo2, rplMyInfo3) 386 387 rb := NewResponseBuffer(session) 388 server.RplISupport(c, rb) 389 server.Lusers(c, rb) 390 server.MOTD(c, rb) 391 rb.Send(true) 392 393 modestring := c.ModeString() 394 if modestring != "+" { 395 session.Send(nil, server.name, RPL_UMODEIS, d.nick, modestring) 396 } 397 398 c.attemptAutoOper(session) 399 400 if server.logger.IsLoggingRawIO() { 401 session.Send(nil, c.server.name, "NOTICE", d.nick, 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.")) 402 } 403} 404 405// RplISupport outputs our ISUPPORT lines to the client. This is used on connection and in VERSION responses. 406func (server *Server) RplISupport(client *Client, rb *ResponseBuffer) { 407 translatedISupport := client.t("are supported by this server") 408 nick := client.Nick() 409 config := server.Config() 410 for _, cachedTokenLine := range config.Server.isupport.CachedReply { 411 length := len(cachedTokenLine) + 2 412 tokenline := make([]string, length) 413 tokenline[0] = nick 414 copy(tokenline[1:], cachedTokenLine) 415 tokenline[length-1] = translatedISupport 416 rb.Add(nil, server.name, RPL_ISUPPORT, tokenline...) 417 } 418} 419 420func (server *Server) Lusers(client *Client, rb *ResponseBuffer) { 421 nick := client.Nick() 422 config := server.Config() 423 var stats StatsValues 424 var numChannels int 425 if !config.Server.SuppressLusers || client.HasRoleCapabs("ban") { 426 stats = server.stats.GetValues() 427 numChannels = server.channels.Len() 428 } 429 430 rb.Add(nil, server.name, RPL_LUSERCLIENT, nick, fmt.Sprintf(client.t("There are %[1]d users and %[2]d invisible on %[3]d server(s)"), stats.Total-stats.Invisible, stats.Invisible, 1)) 431 rb.Add(nil, server.name, RPL_LUSEROP, nick, strconv.Itoa(stats.Operators), client.t("IRC Operators online")) 432 rb.Add(nil, server.name, RPL_LUSERUNKNOWN, nick, strconv.Itoa(stats.Unknown), client.t("unregistered connections")) 433 rb.Add(nil, server.name, RPL_LUSERCHANNELS, nick, strconv.Itoa(numChannels), client.t("channels formed")) 434 rb.Add(nil, server.name, RPL_LUSERME, nick, fmt.Sprintf(client.t("I have %[1]d clients and %[2]d servers"), stats.Total, 0)) 435 total := strconv.Itoa(stats.Total) 436 max := strconv.Itoa(stats.Max) 437 rb.Add(nil, server.name, RPL_LOCALUSERS, nick, total, max, fmt.Sprintf(client.t("Current local users %[1]s, max %[2]s"), total, max)) 438 rb.Add(nil, server.name, RPL_GLOBALUSERS, nick, total, max, fmt.Sprintf(client.t("Current global users %[1]s, max %[2]s"), total, max)) 439} 440 441// MOTD serves the Message of the Day. 442func (server *Server) MOTD(client *Client, rb *ResponseBuffer) { 443 motdLines := server.Config().Server.motdLines 444 445 if len(motdLines) < 1 { 446 rb.Add(nil, server.name, ERR_NOMOTD, client.nick, client.t("MOTD File is missing")) 447 return 448 } 449 450 rb.Add(nil, server.name, RPL_MOTDSTART, client.nick, fmt.Sprintf(client.t("- %s Message of the day - "), server.name)) 451 for _, line := range motdLines { 452 rb.Add(nil, server.name, RPL_MOTD, client.nick, line) 453 } 454 rb.Add(nil, server.name, RPL_ENDOFMOTD, client.nick, client.t("End of MOTD command")) 455} 456 457func (client *Client) whoisChannelsNames(target *Client, multiPrefix bool, hasPrivs bool) []string { 458 var chstrs []string 459 targetInvis := target.HasMode(modes.Invisible) 460 for _, channel := range target.Channels() { 461 if !hasPrivs && (targetInvis || channel.flags.HasMode(modes.Secret)) && !channel.hasClient(client) { 462 // client can't see *this* channel membership 463 continue 464 } 465 chstrs = append(chstrs, channel.ClientPrefixes(target, multiPrefix)+channel.name) 466 } 467 return chstrs 468} 469 470func (client *Client) getWhoisOf(target *Client, hasPrivs bool, rb *ResponseBuffer) { 471 oper := client.Oper() 472 cnick := client.Nick() 473 targetInfo := target.Details() 474 rb.Add(nil, client.server.name, RPL_WHOISUSER, cnick, targetInfo.nick, targetInfo.username, targetInfo.hostname, "*", targetInfo.realname) 475 tnick := targetInfo.nick 476 477 whoischannels := client.whoisChannelsNames(target, rb.session.capabilities.Has(caps.MultiPrefix), oper.HasRoleCapab("sajoin")) 478 if whoischannels != nil { 479 rb.Add(nil, client.server.name, RPL_WHOISCHANNELS, cnick, tnick, strings.Join(whoischannels, " ")) 480 } 481 if target.HasMode(modes.Operator) && operStatusVisible(client, target, oper != nil) { 482 tOper := target.Oper() 483 if tOper != nil { 484 rb.Add(nil, client.server.name, RPL_WHOISOPERATOR, cnick, tnick, tOper.WhoisLine) 485 } 486 } 487 if client == target || oper.HasRoleCapab("ban") { 488 ip, hostname := target.getWhoisActually() 489 rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, cnick, tnick, fmt.Sprintf("%s@%s", targetInfo.username, hostname), utils.IPStringToHostname(ip.String()), client.t("Actual user@host, Actual IP")) 490 } 491 if client == target || oper.HasRoleCapab("samode") { 492 rb.Add(nil, client.server.name, RPL_WHOISMODES, cnick, tnick, fmt.Sprintf(client.t("is using modes +%s"), target.modes.String())) 493 } 494 if target.HasMode(modes.TLS) { 495 rb.Add(nil, client.server.name, RPL_WHOISSECURE, cnick, tnick, client.t("is using a secure connection")) 496 } 497 if targetInfo.accountName != "*" { 498 rb.Add(nil, client.server.name, RPL_WHOISACCOUNT, cnick, tnick, targetInfo.accountName, client.t("is logged in as")) 499 } 500 if target.HasMode(modes.Bot) { 501 rb.Add(nil, client.server.name, RPL_WHOISBOT, cnick, tnick, fmt.Sprintf(ircfmt.Unescape(client.t("is a $bBot$b on %s")), client.server.Config().Network.Name)) 502 } 503 504 if client == target || oper.HasRoleCapab("ban") { 505 for _, session := range target.Sessions() { 506 if session.certfp != "" { 507 rb.Add(nil, client.server.name, RPL_WHOISCERTFP, cnick, tnick, fmt.Sprintf(client.t("has client certificate fingerprint %s"), session.certfp)) 508 } 509 } 510 } 511 rb.Add(nil, client.server.name, RPL_WHOISIDLE, cnick, tnick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), client.t("seconds idle, signon time")) 512 if away, awayMessage := target.Away(); away { 513 rb.Add(nil, client.server.name, RPL_AWAY, cnick, tnick, awayMessage) 514 } 515} 516 517// rehash reloads the config and applies the changes from the config file. 518func (server *Server) rehash() error { 519 // #1570; this needs its own panic handling because it can be invoked via SIGHUP 520 defer server.HandlePanic() 521 522 server.logger.Info("server", "Attempting rehash") 523 524 // only let one REHASH go on at a time 525 server.rehashMutex.Lock() 526 defer server.rehashMutex.Unlock() 527 528 sdnotify.Reloading() 529 defer sdnotify.Ready() 530 531 config, err := LoadConfig(server.configFilename) 532 if err != nil { 533 server.logger.Error("server", "failed to load config file", err.Error()) 534 return err 535 } 536 537 err = server.applyConfig(config) 538 if err != nil { 539 server.logger.Error("server", "Failed to rehash", err.Error()) 540 return err 541 } 542 543 server.logger.Info("server", "Rehash completed successfully") 544 return nil 545} 546 547func (server *Server) applyConfig(config *Config) (err error) { 548 oldConfig := server.Config() 549 initial := oldConfig == nil 550 551 if initial { 552 server.configFilename = config.Filename 553 server.name = config.Server.Name 554 server.nameCasefolded = config.Server.nameCasefolded 555 globalCasemappingSetting = config.Server.Casemapping 556 globalUtf8EnforcementSetting = config.Server.EnforceUtf8 557 MaxLineLen = config.Server.MaxLineLen 558 } else { 559 // enforce configs that can't be changed after launch: 560 if server.name != config.Server.Name { 561 return fmt.Errorf("Server name cannot be changed after launching the server, rehash aborted") 562 } else if oldConfig.Datastore.Path != config.Datastore.Path { 563 return fmt.Errorf("Datastore path cannot be changed after launching the server, rehash aborted") 564 } else if globalCasemappingSetting != config.Server.Casemapping { 565 return fmt.Errorf("Casemapping cannot be changed after launching the server, rehash aborted") 566 } else if globalUtf8EnforcementSetting != config.Server.EnforceUtf8 { 567 return fmt.Errorf("UTF-8 enforcement cannot be changed after launching the server, rehash aborted") 568 } else if oldConfig.Accounts.Multiclient.AlwaysOn != config.Accounts.Multiclient.AlwaysOn { 569 return fmt.Errorf("Default always-on setting cannot be changed after launching the server, rehash aborted") 570 } else if oldConfig.Server.Relaymsg.Enabled != config.Server.Relaymsg.Enabled { 571 return fmt.Errorf("Cannot enable or disable relaying after launching the server, rehash aborted") 572 } else if oldConfig.Server.Relaymsg.Separators != config.Server.Relaymsg.Separators { 573 return fmt.Errorf("Cannot change relaying separators after launching the server, rehash aborted") 574 } else if oldConfig.Server.IPCheckScript.MaxConcurrency != config.Server.IPCheckScript.MaxConcurrency || 575 oldConfig.Accounts.AuthScript.MaxConcurrency != config.Accounts.AuthScript.MaxConcurrency { 576 return fmt.Errorf("Cannot change max-concurrency for scripts after launching the server, rehash aborted") 577 } else if oldConfig.Server.OverrideServicesHostname != config.Server.OverrideServicesHostname { 578 return fmt.Errorf("Cannot change override-services-hostname after launching the server, rehash aborted") 579 } else if !oldConfig.Datastore.MySQL.Enabled && config.Datastore.MySQL.Enabled { 580 return fmt.Errorf("Cannot enable MySQL after launching the server, rehash aborted") 581 } else if oldConfig.Server.MaxLineLen != config.Server.MaxLineLen { 582 return fmt.Errorf("Cannot change max-line-len after launching the server, rehash aborted") 583 } 584 } 585 586 server.logger.Info("server", "Using config file", server.configFilename) 587 588 // first, reload config sections for functionality implemented in subpackages: 589 wasLoggingRawIO := !initial && server.logger.IsLoggingRawIO() 590 err = server.logger.ApplyConfig(config.Logging) 591 if err != nil { 592 return err 593 } 594 nowLoggingRawIO := server.logger.IsLoggingRawIO() 595 // notify existing clients if raw i/o logging was enabled by a rehash 596 sendRawOutputNotice := !wasLoggingRawIO && nowLoggingRawIO 597 598 server.connectionLimiter.ApplyConfig(&config.Server.IPLimits) 599 600 tlConf := &config.Server.TorListeners 601 server.torLimiter.Configure(tlConf.MaxConnections, tlConf.ThrottleDuration, tlConf.MaxConnectionsPerDuration) 602 603 // Translations 604 server.logger.Debug("server", "Regenerating HELP indexes for new languages") 605 server.helpIndexManager.GenerateIndices(config.languageManager) 606 607 if initial { 608 maxIPConc := int(config.Server.IPCheckScript.MaxConcurrency) 609 if maxIPConc != 0 { 610 server.semaphores.IPCheckScript = utils.NewSemaphore(maxIPConc) 611 } 612 maxAuthConc := int(config.Accounts.AuthScript.MaxConcurrency) 613 if maxAuthConc != 0 { 614 server.semaphores.AuthScript = utils.NewSemaphore(maxAuthConc) 615 } 616 617 if err := overrideServicePrefixes(config.Server.OverrideServicesHostname); err != nil { 618 return err 619 } 620 } 621 622 if oldConfig != nil { 623 // if certain features were enabled by rehash, we need to load the corresponding data 624 // from the store 625 if !oldConfig.Accounts.NickReservation.Enabled { 626 server.accounts.buildNickToAccountIndex(config) 627 } 628 if !oldConfig.Channels.Registration.Enabled { 629 server.channels.loadRegisteredChannels(config) 630 } 631 // resize history buffers as needed 632 if config.historyChangedFrom(oldConfig) { 633 for _, channel := range server.channels.Channels() { 634 channel.resizeHistory(config) 635 } 636 for _, client := range server.clients.AllClients() { 637 client.resizeHistory(config) 638 } 639 } 640 if oldConfig.Accounts.Registration.Throttling != config.Accounts.Registration.Throttling { 641 server.accounts.resetRegisterThrottle(config) 642 } 643 } 644 645 server.logger.Info("server", "Using datastore", config.Datastore.Path) 646 if initial { 647 if err := server.loadDatastore(config); err != nil { 648 return err 649 } 650 } else { 651 if config.Datastore.MySQL.Enabled && config.Datastore.MySQL != oldConfig.Datastore.MySQL { 652 server.historyDB.SetConfig(config.Datastore.MySQL) 653 } 654 } 655 656 // now that the datastore is initialized, we can load the cloak secret from it 657 // XXX this modifies config after the initial load, which is naughty, 658 // but there's no data race because we haven't done SetConfig yet 659 config.Server.Cloaks.SetSecret(LoadCloakSecret(server.store)) 660 661 // activate the new config 662 server.SetConfig(config) 663 664 // load [dk]-lines, registered users and channels, etc. 665 if initial { 666 if err := server.loadFromDatastore(config); err != nil { 667 return err 668 } 669 } 670 671 // burst new and removed caps 672 addedCaps, removedCaps := config.Diff(oldConfig) 673 var capBurstSessions []*Session 674 added := make(map[caps.Version][]string) 675 var removed []string 676 677 if !addedCaps.Empty() || !removedCaps.Empty() { 678 capBurstSessions = server.clients.AllWithCapsNotify() 679 680 added[caps.Cap301] = addedCaps.Strings(caps.Cap301, config.Server.capValues, 0) 681 added[caps.Cap302] = addedCaps.Strings(caps.Cap302, config.Server.capValues, 0) 682 // removed never has values, so we leave it as Cap301 683 removed = removedCaps.Strings(caps.Cap301, config.Server.capValues, 0) 684 } 685 686 for _, sSession := range capBurstSessions { 687 // DEL caps and then send NEW ones so that updated caps get removed/added correctly 688 if !removedCaps.Empty() { 689 for _, capStr := range removed { 690 sSession.Send(nil, server.name, "CAP", sSession.client.Nick(), "DEL", capStr) 691 } 692 } 693 if !addedCaps.Empty() { 694 for _, capStr := range added[sSession.capVersion] { 695 sSession.Send(nil, server.name, "CAP", sSession.client.Nick(), "NEW", capStr) 696 } 697 } 698 } 699 700 server.setupPprofListener(config) 701 702 // set RPL_ISUPPORT 703 var newISupportReplies [][]string 704 if oldConfig != nil { 705 newISupportReplies = oldConfig.Server.isupport.GetDifference(&config.Server.isupport) 706 } 707 708 if len(config.Server.ProxyAllowedFrom) != 0 { 709 server.logger.Info("server", "Proxied IPs will be accepted from", strings.Join(config.Server.ProxyAllowedFrom, ", ")) 710 } 711 712 // we are now ready to receive connections: 713 err = server.setupListeners(config) 714 715 if initial && err == nil { 716 server.logger.Info("server", "Server running") 717 sdnotify.Ready() 718 } 719 720 if !initial { 721 // push new info to all of our clients 722 for _, sClient := range server.clients.AllClients() { 723 for _, tokenline := range newISupportReplies { 724 sClient.Send(nil, server.name, RPL_ISUPPORT, append([]string{sClient.nick}, tokenline...)...) 725 } 726 727 if sendRawOutputNotice { 728 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.")) 729 } 730 } 731 } 732 733 // send other config warnings 734 if config.Accounts.RequireSasl.Enabled && config.Accounts.Registration.Enabled { 735 server.logger.Warning("server", "Warning: although require-sasl is enabled, users can still register accounts. If your server is not intended to be public, you must set accounts.registration.enabled to false.") 736 } 737 738 return err 739} 740 741func (server *Server) setupPprofListener(config *Config) { 742 pprofListener := config.Debug.PprofListener 743 if server.pprofServer != nil { 744 if pprofListener == "" || (pprofListener != server.pprofServer.Addr) { 745 server.logger.Info("server", "Stopping pprof listener", server.pprofServer.Addr) 746 server.pprofServer.Close() 747 server.pprofServer = nil 748 } 749 } 750 if pprofListener != "" && server.pprofServer == nil { 751 ps := http.Server{ 752 Addr: pprofListener, 753 } 754 go func() { 755 if err := ps.ListenAndServe(); err != nil { 756 server.logger.Error("server", "pprof listener failed", err.Error()) 757 } 758 }() 759 server.pprofServer = &ps 760 server.logger.Info("server", "Started pprof listener", server.pprofServer.Addr) 761 } 762} 763 764func (server *Server) loadDatastore(config *Config) error { 765 // open the datastore and load server state for which it (rather than config) 766 // is the source of truth 767 768 _, err := os.Stat(config.Datastore.Path) 769 if os.IsNotExist(err) { 770 server.logger.Warning("server", "database does not exist, creating it", config.Datastore.Path) 771 err = initializeDB(config.Datastore.Path) 772 if err != nil { 773 return err 774 } 775 } 776 777 db, err := OpenDatabase(config) 778 if err == nil { 779 server.store = db 780 return nil 781 } else { 782 return fmt.Errorf("Failed to open datastore: %s", err.Error()) 783 } 784} 785 786func (server *Server) loadFromDatastore(config *Config) (err error) { 787 // load *lines (from the datastores) 788 server.logger.Debug("server", "Loading D/Klines") 789 server.loadDLines() 790 server.loadKLines() 791 792 server.channelRegistry.Initialize(server) 793 server.channels.Initialize(server) 794 server.accounts.Initialize(server) 795 796 if config.Datastore.MySQL.Enabled { 797 server.historyDB.Initialize(server.logger, config.Datastore.MySQL) 798 err = server.historyDB.Open() 799 if err != nil { 800 server.logger.Error("internal", "could not connect to mysql", err.Error()) 801 return err 802 } 803 } 804 805 return nil 806} 807 808func (server *Server) setupListeners(config *Config) (err error) { 809 logListener := func(addr string, config utils.ListenerConfig) { 810 server.logger.Info("listeners", 811 fmt.Sprintf("now listening on %s, tls=%t, proxy=%t, tor=%t, websocket=%t.", addr, (config.TLSConfig != nil), config.RequireProxy, config.Tor, config.WebSocket), 812 ) 813 } 814 815 // update or destroy all existing listeners 816 for addr := range server.listeners { 817 currentListener := server.listeners[addr] 818 newConfig, stillConfigured := config.Server.trueListeners[addr] 819 820 if stillConfigured { 821 if reloadErr := currentListener.Reload(newConfig); reloadErr == nil { 822 logListener(addr, newConfig) 823 } else { 824 // stop the listener; we will attempt to replace it below 825 currentListener.Stop() 826 delete(server.listeners, addr) 827 } 828 } else { 829 currentListener.Stop() 830 delete(server.listeners, addr) 831 server.logger.Info("listeners", fmt.Sprintf("stopped listening on %s.", addr)) 832 } 833 } 834 835 publicPlaintextListener := "" 836 // create new listeners that were not previously configured, 837 // or that couldn't be reloaded above: 838 for newAddr, newConfig := range config.Server.trueListeners { 839 if strings.HasPrefix(newAddr, ":") && !newConfig.Tor && !newConfig.STSOnly && newConfig.TLSConfig == nil { 840 publicPlaintextListener = newAddr 841 } 842 _, exists := server.listeners[newAddr] 843 if !exists { 844 // make a new listener 845 newListener, newErr := NewListener(server, newAddr, newConfig, config.Server.UnixBindMode) 846 if newErr == nil { 847 server.listeners[newAddr] = newListener 848 logListener(newAddr, newConfig) 849 } else { 850 server.logger.Error("server", "couldn't listen on", newAddr, newErr.Error()) 851 err = newErr 852 } 853 } 854 } 855 856 if publicPlaintextListener != "" { 857 server.logger.Warning("listeners", fmt.Sprintf("Warning: your server is configured with public plaintext listener %s. Consider disabling it for improved security and privacy.", publicPlaintextListener)) 858 } 859 860 return 861} 862 863// Gets the abstract sequence from which we're going to query history; 864// we may already know the channel we're querying, or we may have 865// to look it up via a string query. This function is responsible for 866// privilege checking. 867// XXX: call this with providedChannel==nil and query=="" to get a sequence 868// suitable for ListCorrespondents (i.e., this function is still used to 869// decide whether the ringbuf or mysql is authoritative about the client's 870// message history). 871func (server *Server) GetHistorySequence(providedChannel *Channel, client *Client, query string) (channel *Channel, sequence history.Sequence, err error) { 872 config := server.Config() 873 // 4 cases: {persistent, ephemeral} x {normal, conversation} 874 // with ephemeral history, target is implicit in the choice of `hist`, 875 // and correspondent is "" if we're retrieving a channel or *, and the correspondent's name 876 // if we're retrieving a DM conversation ("query buffer"). with persistent history, 877 // target is always nonempty, and correspondent is either empty or nonempty as before. 878 var status HistoryStatus 879 var target, correspondent string 880 var hist *history.Buffer 881 restriction := HistoryCutoffNone 882 channel = providedChannel 883 if channel == nil { 884 if strings.HasPrefix(query, "#") { 885 channel = server.channels.Get(query) 886 if channel == nil { 887 return 888 } 889 } 890 } 891 var joinTimeCutoff time.Time 892 if channel != nil { 893 if present, cutoff := channel.joinTimeCutoff(client); present { 894 joinTimeCutoff = cutoff 895 } else { 896 err = errInsufficientPrivs 897 return 898 } 899 status, target, restriction = channel.historyStatus(config) 900 switch status { 901 case HistoryEphemeral: 902 hist = &channel.history 903 case HistoryPersistent: 904 // already set `target` 905 default: 906 return 907 } 908 } else { 909 status, target = client.historyStatus(config) 910 if query != "" { 911 correspondent, err = CasefoldName(query) 912 if err != nil { 913 return 914 } 915 } 916 switch status { 917 case HistoryEphemeral: 918 hist = &client.history 919 case HistoryPersistent: 920 // already set `target`, and `correspondent` if necessary 921 default: 922 return 923 } 924 } 925 926 var cutoff time.Time 927 if config.History.Restrictions.ExpireTime != 0 { 928 cutoff = time.Now().UTC().Add(-time.Duration(config.History.Restrictions.ExpireTime)) 929 } 930 // #836: registration date cutoff is always enforced for DMs 931 // either way, take the later of the two cutoffs 932 if restriction == HistoryCutoffRegistrationTime || channel == nil { 933 regCutoff := client.historyCutoff() 934 if regCutoff.After(cutoff) { 935 cutoff = regCutoff 936 } 937 } else if restriction == HistoryCutoffJoinTime { 938 if joinTimeCutoff.After(cutoff) { 939 cutoff = joinTimeCutoff 940 } 941 } 942 943 // #836 again: grace period is never applied to DMs 944 if !cutoff.IsZero() && channel != nil && restriction != HistoryCutoffJoinTime { 945 cutoff = cutoff.Add(-time.Duration(config.History.Restrictions.GracePeriod)) 946 } 947 948 if hist != nil { 949 sequence = hist.MakeSequence(correspondent, cutoff) 950 } else if target != "" { 951 sequence = server.historyDB.MakeSequence(target, correspondent, cutoff) 952 } 953 return 954} 955 956func (server *Server) ForgetHistory(accountName string) { 957 // sanity check 958 if accountName == "*" { 959 return 960 } 961 962 config := server.Config() 963 if !config.History.Enabled { 964 return 965 } 966 967 if cfAccount, err := CasefoldName(accountName); err == nil { 968 server.historyDB.Forget(cfAccount) 969 } 970 971 persistent := config.History.Persistent 972 if persistent.Enabled && persistent.UnregisteredChannels && persistent.RegisteredChannels == PersistentMandatory && persistent.DirectMessages == PersistentMandatory { 973 return 974 } 975 976 predicate := func(item *history.Item) bool { return item.AccountName == accountName } 977 978 for _, channel := range server.channels.Channels() { 979 channel.history.Delete(predicate) 980 } 981 982 for _, client := range server.clients.AllClients() { 983 client.history.Delete(predicate) 984 } 985} 986 987// deletes a message. target is a hint about what buffer it's in (not required for 988// persistent history, where all the msgids are indexed together). if accountName 989// is anything other than "*", it must match the recorded AccountName of the message 990func (server *Server) DeleteMessage(target, msgid, accountName string) (err error) { 991 config := server.Config() 992 var hist *history.Buffer 993 994 if target != "" { 995 if target[0] == '#' { 996 channel := server.channels.Get(target) 997 if channel != nil { 998 if status, _, _ := channel.historyStatus(config); status == HistoryEphemeral { 999 hist = &channel.history 1000 } 1001 } 1002 } else { 1003 client := server.clients.Get(target) 1004 if client != nil { 1005 if status, _ := client.historyStatus(config); status == HistoryEphemeral { 1006 hist = &client.history 1007 } 1008 } 1009 } 1010 } 1011 1012 if hist == nil { 1013 err = server.historyDB.DeleteMsgid(msgid, accountName) 1014 } else { 1015 count := hist.Delete(func(item *history.Item) bool { 1016 return item.Message.Msgid == msgid && (accountName == "*" || item.AccountName == accountName) 1017 }) 1018 if count == 0 { 1019 err = errNoop 1020 } 1021 } 1022 1023 return 1024} 1025 1026func (server *Server) UnfoldName(cfname string) (name string) { 1027 if strings.HasPrefix(cfname, "#") { 1028 return server.channels.UnfoldName(cfname) 1029 } 1030 return server.clients.UnfoldNick(cfname) 1031} 1032 1033// elistMatcher takes and matches ELIST conditions 1034type elistMatcher struct { 1035 MinClientsActive bool 1036 MinClients int 1037 MaxClientsActive bool 1038 MaxClients int 1039} 1040 1041// Matches checks whether the given channel matches our matches. 1042func (matcher *elistMatcher) Matches(channel *Channel) bool { 1043 if matcher.MinClientsActive { 1044 if len(channel.Members()) < matcher.MinClients { 1045 return false 1046 } 1047 } 1048 1049 if matcher.MaxClientsActive { 1050 if len(channel.Members()) < len(channel.members) { 1051 return false 1052 } 1053 } 1054 1055 return true 1056} 1057 1058var ( 1059 infoString1 = strings.Split(` 1060 __ __ ______ ___ ______ ___ 1061 __/ // /_/ ____/ __ \/ ____/ __ \ 1062 /_ // __/ __/ / /_/ / / __/ / / / 1063 /_ // __/ /___/ _, _/ /_/ / /_/ / 1064 /_//_/ /_____/_/ |_|\____/\____/ 1065 1066 https://ergo.chat/ 1067 https://github.com/ergochat/ergo 1068`, "\n")[1:] // XXX: cut off initial blank line 1069 infoString2 = strings.Split(` Daniel Oakley, DanielOaks, <daniel@danieloaks.net> 1070 Shivaram Lingamneni, slingamn, <slingamn@cs.stanford.edu> 1071`, "\n") 1072 infoString3 = strings.Split(` Jeremy Latt, jlatt 1073 Edmund Huber, edmund-huber 1074`, "\n") 1075) 1076