package main import ( "errors" "fmt" "io" "log" "net" "strconv" "strings" "sync" "github.com/pyke369/golang-support/rcache" ) // Server represents the server type OpenVpnMgt struct { port string m sync.RWMutex debug bool VpnRemotes []string `json:"remotes"` vpnServers map[int]*OpenVpnSrv `json:"sessions"` } // 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), } } func (s *OpenVpnMgt) Lock() { s.m.Lock() } func (s *OpenVpnMgt) Unlock() { s.m.Unlock() } // Run starts a the server func (s *OpenVpnMgt) Run() { // get the endpoint list if err := s.getServerList(); err != nil { log.Println(err) return } // 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(remote int) (error, *OpenVpnSrv) { if vpnServer, ok := s.vpnServers[remote]; ok { return nil, vpnServer } return errors.New(fmt.Sprintf("unknown session %d", vpnServers)), nil } func (s *OpenVpnMgt) SetRemote(server string, remote int) error { // check if the session is valid err, session := s.GetSession(remote) if err != nil { return err } for _, r := range s.VpnRemotes { if r != server { continue } return session.SetRemote(server) } return errors.New(fmt.Sprintf("unknown remote %s", server)) } // 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 remote, srv := range s.vpnServers { err, msg := srv.Version() if err == nil { ret[remote] = msg } } return err, ret } // main loop for a given openvpn server func (s *OpenVpnMgt) handleConn(conn net.Conn) { remote := conn.RemoteAddr().String() pidRegexp := rcache.Get("^SUCCESS: pid=([0-9]+)$") // >REMOTE:vpn.example.com,1194,udp remoteRegexp := rcache.Get("^>REMOTE:(.*),([0-9]*),(.*)$") defer conn.Close() vpnServer := NewOpenVpnSrv(conn) // most response are multilined, use response to concatenate them response := []string{} // remove bogus clients line, err := vpnServer.session.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 session.sendCommand([]string{"pid"}) for { line, err := session.GetLine() // manage basic errors switch { case err == io.EOF: log.Println("Reached EOF - close this connection.\n") return case err != nil: log.Println("Error reading line. Got: '"+line+"'\n", err) return } line = strings.Trim(line, "\n\r ") if s.debug && strings.Index(line, "password") == -1 { log.Print(line) } // manage exit commands for _, terminator := range []string{"quit", "exit"} { if line == terminator || strings.HasPrefix(line, terminator+" ") { log.Println("server disconnected") return } } // get the PID match := pidRegexp.FindStringSubmatch(line) if len(match) == 2 { pid, _ := strconv.Atoi(match[1]) s.Lock() s.vpnServers[pid] = vpnServer s.Unlock() defer delete(s.vpnServers, pid) } // manage all "terminator" lines for _, terminator := range []string{"END", ">CLIENT:ENV,END", "SUCCESS", "ERROR"} { if strings.HasPrefix(line, terminator) { vpnServer.Response(response) response = nil line = "" break } } remoteMatch := remoteRegexp.FindStringSubmatch(line) switch { // command successfull, we can ignore case strings.HasPrefix(line, ">SUCCESS: client-deny command succeeded"): case strings.HasPrefix(line, ">HOLD"): go vpnServer.waitForRelase() case len(remoteMatch) > 0: go vpnServer.ValidRemote(remoteMatch[1], remoteMatch[2], remoteMatch[3]) default: response = append(response, line) } } }