X-Git-Url: http://www.git.cypherpunks.ru/?p=goircd.git;a=blobdiff_plain;f=daemon.go;h=9145ba08dd950e345b2df1c7843b93817de17d24;hp=2bf696ce31229c86a744f0ee64d4c9f287894d32;hb=171c6d6df3afbd6d0895528b2a8a38cb3d25d5f0;hpb=f46c678ce03915d6f081cf2f78ac7fe885cdb001 diff --git a/daemon.go b/daemon.go index 2bf696c..9145ba0 100644 --- a/daemon.go +++ b/daemon.go @@ -1,6 +1,6 @@ /* goircd -- minimalistic simple Internet Relay Chat (IRC) server -Copyright (C) 2014 Sergey Matveev +Copyright (C) 2014-2015 Sergey Matveev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,6 +15,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ + package main import ( @@ -25,40 +26,45 @@ import ( "regexp" "sort" "strings" - "sync" "time" ) const ( - PingTimeout = time.Second * 180 // Max time deadline for client's unresponsiveness - PingThreshold = time.Second * 90 // Max idle client's time before PING are sent - AlivenessCheck = time.Second * 10 // Client's aliveness check period + // Max time deadline for client's unresponsiveness + PingTimeout = time.Second * 180 + // Max idle client's time before PING are sent + PingThreshold = time.Second * 90 + // Client's aliveness check period + AlivenessCheck = time.Second * 10 ) var ( - RENickname = regexp.MustCompile("^[a-zA-Z0-9-]{1,9}$") + RENickname = regexp.MustCompile("^[a-zA-Z0-9-]{1,24}$") ) -var passwordsRefreshLock sync.Mutex - type Daemon struct { Verbose bool version string - hostname string - motd string - clients map[*Client]bool + hostname *string + motd *string + passwords *string + clients map[*Client]struct{} clientAliveness map[*Client]*ClientAlivenessState rooms map[string]*Room roomSinks map[*Room]chan ClientEvent lastAlivenessCheck time.Time logSink chan<- LogEvent stateSink chan<- StateEvent - passwords map[string]string } -func NewDaemon(version, hostname, motd string, logSink chan<- LogEvent, stateSink chan<- StateEvent) *Daemon { - daemon := Daemon{version: version, hostname: hostname, motd: motd} - daemon.clients = make(map[*Client]bool) +func NewDaemon(version string, hostname, motd, passwords *string, logSink chan<- LogEvent, stateSink chan<- StateEvent) *Daemon { + daemon := Daemon{ + version: version, + hostname: hostname, + motd: motd, + passwords: passwords, + } + daemon.clients = make(map[*Client]struct{}) daemon.clientAliveness = make(map[*Client]*ClientAlivenessState) daemon.rooms = make(map[string]*Room) daemon.roomSinks = make(map[*Room]chan ClientEvent) @@ -78,19 +84,19 @@ func (daemon *Daemon) SendLusers(client *Client) { } func (daemon *Daemon) SendMotd(client *Client) { - if len(daemon.motd) == 0 { + if daemon.motd == nil || *daemon.motd == "" { client.ReplyNicknamed("422", "MOTD File is missing") return } - motd, err := ioutil.ReadFile(daemon.motd) + motd, err := ioutil.ReadFile(*daemon.motd) if err != nil { - log.Printf("Can not read motd file %s: %v", daemon.motd, err) + log.Printf("Can not read motd file %s: %v", *daemon.motd, err) client.ReplyNicknamed("422", "Error reading MOTD File") return } - client.ReplyNicknamed("375", "- "+daemon.hostname+" Message of the day -") + client.ReplyNicknamed("375", "- "+*daemon.hostname+" Message of the day -") for _, s := range strings.Split(strings.Trim(string(motd), "\n"), "\n") { client.ReplyNicknamed("372", "- "+string(s)) } @@ -113,7 +119,10 @@ func (daemon *Daemon) SendWhois(client *Client, nicknames []string) { h = "Unknown" } client.ReplyNicknamed("311", c.nickname, c.username, h, "*", c.realname) - client.ReplyNicknamed("312", c.nickname, daemon.hostname, daemon.hostname) + client.ReplyNicknamed("312", c.nickname, *daemon.hostname, *daemon.hostname) + if c.away != nil { + client.ReplyNicknamed("301", c.nickname, *c.away) + } subscriptions := []string{} for _, room := range daemon.rooms { for subscriber := range room.members { @@ -171,6 +180,8 @@ func (daemon *Daemon) ClientRegister(client *Client, command string, cols []stri return } nickname := cols[1] + // Compatibility with some clients prepending colons to nickname + nickname = strings.TrimPrefix(nickname, ":") for existingClient := range daemon.clients { if existingClient.nickname == nickname { client.ReplyParts("433", "*", nickname, "Nickname is already in use") @@ -196,19 +207,34 @@ func (daemon *Daemon) ClientRegister(client *Client, command string, cols []stri client.realname = strings.TrimLeft(args[3], ":") } if client.nickname != "*" && client.username != "" { - passwordsRefreshLock.Lock() - if daemon.passwords != nil && (client.password == "" || daemon.passwords[client.nickname] != client.password) { - passwordsRefreshLock.Unlock() - client.ReplyParts("462", "You may not register") - client.conn.Close() - return + if daemon.passwords != nil && *daemon.passwords != "" { + if client.password == "" { + client.ReplyParts("462", "You may not register") + client.conn.Close() + return + } + contents, err := ioutil.ReadFile(*daemon.passwords) + if err != nil { + log.Fatalf("Can no read passwords file %s: %s", *daemon.passwords, err) + return + } + for _, entry := range strings.Split(string(contents), "\n") { + if entry == "" { + continue + } + if lp := strings.Split(entry, ":"); lp[0] == client.nickname && lp[1] != client.password { + client.ReplyParts("462", "You may not register") + client.conn.Close() + return + } + } } - passwordsRefreshLock.Unlock() + client.registered = true client.ReplyNicknamed("001", "Hi, welcome to IRC") - client.ReplyNicknamed("002", "Your host is "+daemon.hostname+", running goircd "+daemon.version) + client.ReplyNicknamed("002", "Your host is "+*daemon.hostname+", running goircd "+daemon.version) client.ReplyNicknamed("003", "This server was created sometime") - client.ReplyNicknamed("004", daemon.hostname+" goircd o o") + client.ReplyNicknamed("004", *daemon.hostname+" goircd o o") daemon.SendLusers(client) daemon.SendMotd(client) log.Println(client, "logged in") @@ -277,8 +303,9 @@ func (daemon *Daemon) HandlerJoin(client *Client, cmd string) { } func (daemon *Daemon) Processor(events <-chan ClientEvent) { + var now time.Time for event := range events { - now := time.Now() + now = time.Now() client := event.client // Check for clients aliveness @@ -295,7 +322,7 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) { } if !aliveness.pingSent && aliveness.timestamp.Add(PingThreshold).Before(now) { if c.registered { - c.Msg("PING :" + daemon.hostname) + c.Msg("PING :" + *daemon.hostname) aliveness.pingSent = true } else { log.Println(c, "ping timeout") @@ -308,8 +335,11 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) { switch event.eventType { case EventNew: - daemon.clients[client] = true - daemon.clientAliveness[client] = &ClientAlivenessState{pingSent: false, timestamp: now} + daemon.clients[client] = struct{}{} + daemon.clientAliveness[client] = &ClientAlivenessState{ + pingSent: false, + timestamp: now, + } case EventDel: delete(daemon.clients, client) delete(daemon.clientAliveness, client) @@ -335,7 +365,14 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) { } switch command { case "AWAY": - continue + if len(cols) == 1 { + client.away = nil + client.ReplyNicknamed("305", "You are no longer marked as being away") + continue + } + msg := strings.TrimLeft(cols[1], ":") + client.away = &msg + client.ReplyNicknamed("306", "You have been marked as being away") case "JOIN": if len(cols) == 1 || len(cols[1]) < 1 { client.ReplyNotEnoughParameters("JOIN") @@ -378,7 +415,8 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) { client.ReplyNotEnoughParameters("PART") continue } - for _, room := range strings.Split(cols[1], ",") { + rooms := strings.Split(cols[1], " ")[0] + for _, room := range strings.Split(rooms, ",") { r, found := daemon.rooms[room] if !found { client.ReplyNoChannel(room) @@ -391,7 +429,7 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) { client.ReplyNicknamed("409", "No origin specified") continue } - client.Reply(fmt.Sprintf("PONG %s :%s", daemon.hostname, cols[1])) + client.Reply(fmt.Sprintf("PONG %s :%s", *daemon.hostname, cols[1])) case "PONG": continue case "NOTICE", "PRIVMSG": @@ -410,6 +448,9 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) { if c.nickname == target { msg = fmt.Sprintf(":%s %s %s %s", client, command, c.nickname, cols[1]) c.Msg(msg) + if c.away != nil { + client.ReplyNicknamed("301", c.nickname, *c.away) + } break } } @@ -419,8 +460,13 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) { r, found := daemon.rooms[target] if !found { client.ReplyNoNickChan(target) + continue + } + daemon.roomSinks[r] <- ClientEvent{ + client, + EventMsg, + command + " " + strings.TrimLeft(cols[1], ":"), } - daemon.roomSinks[r] <- ClientEvent{client, EventMsg, command + " " + strings.TrimLeft(cols[1], ":")} case "TOPIC": if len(cols) == 1 { client.ReplyNotEnoughParameters("TOPIC") @@ -466,7 +512,7 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) { } else { debug = "" } - client.ReplyNicknamed("351", fmt.Sprintf("%s.%s %s :", daemon.version, debug, daemon.hostname)) + client.ReplyNicknamed("351", fmt.Sprintf("%s.%s %s :", daemon.version, debug, *daemon.hostname)) default: client.ReplyNicknamed("421", command, "Unknown command") } @@ -477,22 +523,3 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) { } } } - -func (daemon *Daemon) PasswordsRefresh() { - contents, err := ioutil.ReadFile(*passwords) - if err != nil { - log.Fatalf("Can no read passwords file %s: %s", *passwords, err) - return - } - processed := make(map[string]string) - for _, entry := range strings.Split(string(contents), "\n") { - loginAndPassword := strings.Split(entry, ":") - if len(loginAndPassword) == 2 { - processed[loginAndPassword[0]] = loginAndPassword[1] - } - } - log.Printf("Read %d passwords", len(processed)) - passwordsRefreshLock.Lock() - daemon.passwords = processed - passwordsRefreshLock.Unlock() -}