package main import ( "bufio" "errors" "io" "log" "net" "os" "regexp" "strconv" "strings" "sync" ) // Server represents the server type OpenVpnMgt struct { Port string buf map[string]*bufio.ReadWriter m sync.RWMutex ret chan []string ldap map[string]ldapConfig clients map[string]map[int]*vpnSession authCa string vpnlogUrl string mailRelay string MailFrom string CcPwnPassword string pwnTemplate string newAsTemplate string cacheDir string syslog bool otpMasterSecrets []string } // NewServer returns a pointer to a new server func NewVPNServer(port string) *OpenVpnMgt { return &OpenVpnMgt{ Port: port, ret: make(chan []string), ldap: make(map[string]ldapConfig), buf: make(map[string]*bufio.ReadWriter), clients: make(map[string]map[int]*vpnSession), } } // 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 _, 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 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 := regexp.MustCompile("^(.*[^ ]) *: (.*)$") 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 verson 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 } // internal DHCP func (s *OpenVpnMgt) getIP(c *vpnSession) (string, error) { // TODO implement ip := s.ldap[c.Profile].ipMin return ip.String(), nil } // called after a client is confirmed connected and authenticated func (s *OpenVpnMgt) ClientValidated(line, remote string) { err, c := s.getClient(line, remote) if err != nil { log.Println(err, line) return } c.Status = "success" infos := <-s.ret if err := c.ParseEnv(&infos); err != nil { log.Println(err) } s.Log(c) } // called after a client is disconnected, including for auth issues func (s *OpenVpnMgt) ClientDisconnect(line, remote string) { //TODO free the IP err, c := s.getClient(line, remote) if err != nil { log.Println(err) return } <-s.ret // if the disconnect is due to an auth failure, don't change the status if c.Status == "success" { c.Operation = "log out" } // Don't log the initial auth failure due to absence of OTP code if c.Status != "Need OTP Code" { s.Log(c) } defer delete(s.clients[remote], c.cID) } // called at the initial connexion func (s *OpenVpnMgt) ClientConnect(line, remote string) { c := NewVPNSession("log in") c.vpnserver = remote c.ParseSessionId(line) s.clients[remote][c.cID] = c infos := <-s.ret if err := c.ParseEnv(&infos); err != nil { log.Println(err) return } c.Auth(s) } // find a client among all registered sessions func (s *OpenVpnMgt) getClient(line, remote string) (error, *vpnSession) { re := regexp.MustCompile("^[^0-9]*,([0-9]+)[^0-9]*") match := re.FindStringSubmatch(line) if len(match) == 0 { return errors.New("invalid message"), nil } id, err := strconv.Atoi(match[1]) if err != nil { return err, nil } if _, ok := s.clients[remote]; !ok { return errors.New("unknown vpn server"), nil } if c, ok := s.clients[remote][id]; ok { return nil, c } return errors.New("unknown vpn client"), nil } // 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) defer delete(s.clients, remote) // TODO : free all IPs if disconnected // 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)) s.clients[remote] = make(map[int]*vpnSession) // 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 } // ask for statistics if _, err := s.buf[remote].WriteString("bytecount 10\r\n"); err != nil { log.Println(err) return } if err := s.buf[remote].Flush(); err != nil { log.Println(err) return } if line, err := s.buf[remote].ReadString('\n'); err != nil || line != "SUCCESS: bytecount interval changed\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"} { 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"): // trafic stats case strings.HasPrefix(line, ">BYTECOUNT_CLI"): //TODO use that // new bloc for a disconnect event. // We start the receiving handler, which will wait for the Channel message case strings.HasPrefix(line, ">CLIENT:DISCONNECT"): go s.ClientDisconnect(line, remote) // new bloc for a connect event. // We start the receiving handler, which will wait for the Channel message case strings.HasPrefix(line, ">CLIENT:ADDRESS"): go s.ClientValidated(line, remote) case strings.HasPrefix(line, ">CLIENT:CONNECT"): go s.ClientConnect(line, remote) default: response = append(response, line) } // TODO remove this if false && strings.Index(line, "password") == -1 { log.Print(line) } } }