package main import ( "errors" "fmt" "io" "log" "net" "strings" "sync" "time" "github.com/pyke369/golang-support/rcache" ) // Server represents the server type OpenVpnMgt struct { port string m sync.RWMutex debug bool VpnRemotes map[string]*map[string]string `json:"remotes"` VpnServers map[int]*OpenVpnSrv `json:"sessions"` LastChange time.Time `json:"last_change"` } // NewServer returns a pointer to a new server func NewVPNServer(port string, debug bool) *OpenVpnMgt { return &OpenVpnMgt{ port: port, debug: debug, VpnServers: make(map[int]*OpenVpnSrv), VpnRemotes: make(map[string]*map[string]string), } } func (s *OpenVpnMgt) Lock() { s.m.Lock() } func (s *OpenVpnMgt) Unlock() { s.m.Unlock() } func (s *OpenVpnMgt) Change() { s.LastChange = time.Now().Round(time.Second) } func (s *OpenVpnMgt) Debug(v ...interface{}) { if s.debug { log.Println(v...) } } // Run starts a the server func (s *OpenVpnMgt) Run() { // Resolve the passed port into an address addrs, err := net.ResolveTCPAddr("tcp", s.port) if err != nil { log.Println(err) return } // start listening to client connections listener, err := net.ListenTCP("tcp", addrs) if err != nil { log.Println(err) return } // Infinite loop since we dont want the server to shut down for { // Accept the incomming connections conn, err := listener.Accept() if err != nil { // continue accepting connection even if an error occurs (if error occurs dont shut down) continue } // run it as a go routine to allow multiple clients to connect at the same time go s.handleConn(conn) } } func (s *OpenVpnMgt) GetSession(pid int) (error, *OpenVpnSrv) { if openvpn, ok := s.VpnServers[pid]; ok { return nil, openvpn } // if there is only 1 session, ignore the pid parameter if len(s.VpnServers) == 1 { for _, openvpn := range s.VpnServers { return nil, openvpn } } return errors.New(fmt.Sprintf("unknown session %d", pid)), nil } func (s *OpenVpnMgt) Restart(pid int) error { // check if the session is valid err, session := s.GetSession(pid) if err != nil { return err } session.Signal("SIGHUP") return nil } func (s *OpenVpnMgt) AuthUserPass(pid int, user, pass, otp string) error { // check if the session is valid err, session := s.GetSession(pid) if err != nil { return err } session.AuthUserPass(user, pass, otp) return nil } func (s *OpenVpnMgt) Kill(pid int) error { // check if the session is valid err, session := s.GetSession(pid) if err != nil { return err } session.Signal("SIGTERM") return nil } func (s *OpenVpnMgt) SetRemote(server string, pid int) error { // check if the session is valid err, session := s.GetSession(pid) if err != nil { return err } if session.Provider == "" { return errors.New("No server list for this config") } if _, ok := s.VpnRemotes[session.Provider]; !ok { return errors.New("No server list for this provider") } for _, r := range *(s.VpnRemotes[session.Provider]) { if r != server { continue } return session.SetRemote(server) } return errors.New(fmt.Sprintf("unknown session %s", server)) } func (s *OpenVpnMgt) SetPid(openvpn *OpenVpnSrv, pid int) { s.Lock() defer s.Unlock() s.VpnServers[pid] = openvpn } func (s *OpenVpnMgt) Remove(openvpn *OpenVpnSrv) { for pid, v := range s.VpnServers { if v == openvpn { delete(s.VpnServers, pid) } } } // send the version command on all vpn servers. Kind of useless func (s *OpenVpnMgt) Version() (error, map[int][]string) { var err error ret := make(map[int][]string) for pid, srv := range s.VpnServers { err, msg := srv.Version() if err == nil { ret[pid] = msg } } return err, ret } // main loop for a given openvpn server func (s *OpenVpnMgt) handleConn(conn net.Conn) { remote := conn.RemoteAddr().String() remoteRegexp := rcache.Get("^>REMOTE:(.*),([0-9]*),(.*)$") defer conn.Close() openvpn := NewOpenVpnSrv(conn, s) defer s.Remove(openvpn) // most response are multilined, use response to concatenate them response := []string{} // remove bogus clients line, err := openvpn.GetLine() if err != nil { log.Println(err) return } if line != ">INFO:OpenVPN Management Interface Version 1 -- type 'help' for more info\r\n" { log.Println("Bogus Client") return } log.Printf("Valid openvpn connected from %s\n", remote) go openvpn.GetEcho() go openvpn.GetPid() s.Change() defer s.Change() for { line, err := openvpn.GetLine() // manage basic errors switch { case err == io.EOF: log.Println("Reached EOF - close this connection") return case err != nil: log.Printf("Error reading line. Got: '"+line+"'\n", err) return } line = strings.Trim(line, "\n\r ") if strings.Index(line, "password") == -1 { s.Debug(line) } // manage exit commands for _, terminator := range []string{"quit", "exit"} { if line == terminator || strings.HasPrefix(line, terminator+" ") { log.Println("server disconnected") return } } // manage all "terminator" lines for _, terminator := range []string{"END", ">CLIENT:ENV,END", "SUCCESS", "ERROR"} { if strings.HasPrefix(line, terminator) { openvpn.Response(append(response, line)) response = nil line = "" s.Change() break } } remoteMatch := remoteRegexp.FindStringSubmatch(line) switch { // command successfull, we can ignore case strings.HasPrefix(line, ">PASSWORD:"): go openvpn.NeedPassword(line) case strings.HasPrefix(line, ">HOLD"): go openvpn.waitForRelase() case len(remoteMatch) > 0: go openvpn.ValidRemote(remoteMatch[1], remoteMatch[2], remoteMatch[3]) default: response = append(response, line) } } }