package main import ( "bufio" "errors" "io" "log" "net" "os" "strings" "sync" "github.com/pyke369/golang-support/rcache" ) // Server represents the server type OpenVpnMgt struct { port string buf map[string]*bufio.ReadWriter m sync.RWMutex ret chan []string syslog bool debug bool hold bool } // NewServer returns a pointer to a new server func NewVPNServer(port string) *OpenVpnMgt { return &OpenVpnMgt{ port: port, ret: make(chan []string), buf: make(map[string]*bufio.ReadWriter), } } // 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) os.Exit(1) } // start listening to client connections listener, err := net.ListenTCP("tcp", addrs) if err != nil { log.Println(err) os.Exit(1) } // 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) } } // send a command to the server. Set the channel to receive the response func (s *OpenVpnMgt) sendCommand(msg []string, remote string) (error, []string) { if len(s.buf) == 0 { return errors.New("No openvpn server present"), nil } for _, line := range msg { if s.debug { log.Println(line) } if _, err := s.buf[remote].WriteString(line + "\r\n"); err != nil { return err, nil } } if err := s.buf[remote].Flush(); err != nil { return err, nil } // wait for the response ret := <-s.ret if s.debug { for _, line := range ret { log.Println(line) } } return nil, ret } // send the help command on all vpn servers. Kind of useless func (s *OpenVpnMgt) Help() (error, map[string]map[string]string) { ret := make(map[string]map[string]string) re := rcache.Get("^(.*[^ ]) *: (.*)$") for remote := range s.buf { help := make(map[string]string) err, msg := s.sendCommand([]string{"help"}, remote) if err != nil { return err, ret } for _, line := range msg { match := re.FindStringSubmatch(line) if len(match) == 0 { continue } help[match[1]] = match[2] } ret[remote] = help } return nil, ret } // send the version command on all vpn servers. Kind of useless func (s *OpenVpnMgt) Version() (error, map[string][]string) { ret := make(map[string][]string) for remote := range s.buf { err, msg := s.sendCommand([]string{"version"}, remote) if err != nil { return err, ret } ret[remote] = msg } return nil, ret } // main loop for a given openvpn server func (s *OpenVpnMgt) handleConn(conn net.Conn) { remote := conn.RemoteAddr().String() defer conn.Close() defer delete(s.buf, remote) // we store the buffer pointer in the struct, to be accessed from other methods s.buf[remote] = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) // most response are multilined, use response to concatenate them response := []string{} // remove bogus clients line, err := s.buf[remote].ReadString('\n') 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) for { line, err := s.buf[remote].ReadString('\n') // 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 ") // 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) { s.ret <- response response = nil line = "" break } } switch { // command successfull, we can ignore case strings.HasPrefix(line, ">SUCCESS: client-deny command succeeded"): case strings.HasPrefix(line, ">HOLD"): s.sendCommand([]string{"hold release"}, remote) case strings.HasPrefix(line, ">REMOTE"): s.sendCommand([]string{"remote ACCEPT"}, remote) default: response = append(response, line) } if s.debug && strings.Index(line, "password") == -1 { log.Print(line) } } }