/* goircd -- minimalistic simple Internet Relay Chat (IRC) server Copyright (C) 2014-2020 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 the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 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 ( "crypto/tls" "flag" "io/ioutil" "log" "net" "os" "os/signal" "path" "path/filepath" "strconv" "strings" "syscall" ) const ( Version = "1.9.1" StateTopicFilename = "topic" StateKeyFilename = "key" EventNew = iota EventDel = iota EventMsg = iota EventTopic = iota EventWho = iota EventMode = iota EventTerm = iota EventTick = iota ) type ClientEvent struct { client *Client eventType int text string } type StateEvent struct { where string topic string key string } var ( hostname = flag.String("hostname", "localhost", "hostname") bind = flag.String("bind", "[::1]:6667", "address to bind to") cloak = flag.String("cloak", "", "cloak user's host with the given hostname") motd = flag.String("motd", "", "path to MOTD file") logdir = flag.String("logdir", "", "absolute path to directory for logs") statedir = flag.String("statedir", "", "absolute path to directory for states") passwords = flag.String("passwd", "", "optional path to passwords file") tlsBind = flag.String("tlsbind", "", "TLS address to bind to") tlsPEM = flag.String("tlspem", "", "path to TLS certificat+key PEM file") permStateDirS = flag.String("perm-state-dir", "755", "state directory permissions") permStateFileS = flag.String("perm-state-file", "600", "state files permissions") permLogFileS = flag.String("perm-log-file", "644", "log files permissions") timestamped = flag.Bool("timestamped", false, "enable timestamps on stderr messages") verbose = flag.Bool("verbose", false, "enable verbose logging") debug = flag.Bool("debug", false, "enable debug (traffic) logging") permStateDir os.FileMode permStateFile os.FileMode permLogFile os.FileMode stateSink chan StateEvent = make(chan StateEvent) ) func permParse(s string) os.FileMode { r, err := strconv.ParseUint(s, 8, 16) if err != nil { log.Fatalln(err) } return os.FileMode(r) } func listenerLoop(ln net.Listener, events chan ClientEvent) { for { conn, err := ln.Accept() if err != nil { log.Println("error during accept", err) continue } NewClient(conn, events) } } func main() { flag.Parse() permStateDir = permParse(*permStateDirS) permStateFile = permParse(*permStateFileS) permLogFile = permParse(*permLogFileS) if *timestamped { log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) } else { log.SetFlags(log.Lshortfile) } log.SetOutput(os.Stdout) if *logdir == "" { // Dummy logger go func() { for range logSink { } }() } else { if !path.IsAbs(*logdir) { log.Fatalln("need absolute path for logdir") } go Logger(*logdir, logSink) } if *statedir == "" { // Dummy statekeeper go func() { for range stateSink { } }() } else { if !path.IsAbs(*statedir) { log.Fatalln("need absolute path for statedir") } states, err := filepath.Glob(path.Join(*statedir, "#*")) if err != nil { log.Fatalln("can not read statedir", err) } for _, state := range states { buf, err := ioutil.ReadFile(path.Join(state, StateTopicFilename)) if err != nil { log.Fatalf( "can not read state %s/%s: %v", state, StateTopicFilename, err, ) } room := RoomRegister(path.Base(state)) room.topic = strings.TrimRight(string(buf), "\n") buf, err = ioutil.ReadFile(path.Join(state, StateKeyFilename)) if err == nil { room.key = strings.TrimRight(string(buf), "\n") } else { if !os.IsNotExist(err) { log.Fatalf( "can not read state %s/%s: %v", state, StateKeyFilename, err, ) } } log.Println("loaded state for room:", room.name) } go func() { for event := range stateSink { statePath := path.Join(*statedir, event.where) if _, err := os.Stat(statePath); os.IsNotExist(err) { if err := os.Mkdir(statePath, permStateDir); err != nil { log.Printf("can not create state %s: %v", statePath, err) continue } } topicPath := path.Join(statePath, StateTopicFilename) if err := ioutil.WriteFile( topicPath, []byte(event.topic+"\n"), permStateFile, ); err != nil { log.Printf("can not write statefile %s: %v", topicPath, err) continue } keyPath := path.Join(statePath, StateKeyFilename) if err := ioutil.WriteFile( keyPath, []byte(event.key+"\n"), permStateFile, ); err != nil { log.Printf("can not write statefile %s: %v", keyPath, err) } } }() } events := make(chan ClientEvent) if *bind != "" { ln, err := net.Listen("tcp", *bind) if err != nil { log.Fatalf("can not listen on %s: %v", *bind, err) } go listenerLoop(ln, events) } if *tlsBind != "" { cert, err := tls.LoadX509KeyPair(*tlsPEM, *tlsPEM) if err != nil { log.Fatalf("can not load TLS keys from %s: %s", *tlsPEM, err) } config := tls.Config{Certificates: []tls.Certificate{cert}} ln, err := tls.Listen("tcp", *tlsBind, &config) if err != nil { log.Fatalf("can not listen on %s: %v", *tlsBind, err) } go listenerLoop(ln, events) } log.Println("goircd", Version, "started") needsShutdown := make(chan os.Signal, 0) signal.Notify(needsShutdown, syscall.SIGTERM, syscall.SIGINT) go func() { <-needsShutdown events <- ClientEvent{eventType: EventTerm} log.Println("goircd shutting down") }() finished := make(chan struct{}) go Processor(events, finished) <-finished }