package main import ( "bufio" "errors" "io" "log" "net" "os" "strings" "sync" ) // Server represents the server type OpenVpnMgt struct { Port string buf *bufio.ReadWriter connected bool m sync.RWMutex ret chan []string ldap map[string]ldapConfig authCa string vpnlogUrl string mailRelay string MailFrom string CcPwnPassword string pwnTemplate string newAsTemplate string slackTemplate string slackTemplate2 string cacheDir string syslog bool } // 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), } } // 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) } } func (s *OpenVpnMgt) TokenPassword(c *vpnSession) bool { return false } func (s *OpenVpnMgt) Auth(c *vpnSession) (error, bool) { // an empty password is not good if c.password == "" { return nil, false } // check if the password is a valid token validated for TOTP 2FA tokenPassword := s.TokenPassword(c) // If this is the case, empty the password to avoid checking it against the // ldap server if tokenPassword { c.password = "" } c.Profile = "" login := []string{c.Login} pass := c.password for { n := c.Profile for k, ldap := range s.ldap { if ldap.upgradeFrom != c.Profile { continue } log.Printf("try %s with login %s\n", k, login) err, userOk, passOk, secondary := ldap.Auth(login, pass) // if there is an error, try the other configurations if err != nil { log.Println(err) continue } // we did find a valid User if userOk { // the login for the new auth level is given by the current one login = secondary if c.Mail == "" { c.Mail = secondary[0] } if passOk && c.Profile != "" { // it's at least the second auth level, and we have a valid // password on 2 different auth system. It's a dupplicate // password, let's log it log.Printf("User %s has a dupplicate password\n", c.Login) } // we have either a positive auth ok a previous valid one if passOk || c.Profile != "" || tokenPassword { c.Profile = k } } } if n == c.Profile { break } } log.Println(c) log.Println(s.ldap[c.Profile]) return nil, false } func (s *OpenVpnMgt) sendCommand(msg []string) (error, []string) { if !s.connected { return errors.New("No openvpn server present"), nil } for _, line := range msg { if _, err := s.buf.WriteString(line + "\r\n"); err != nil { return err, nil } } if err := s.buf.Flush(); err != nil { return err, nil } // wait for the response ret := <-s.ret return nil, ret } func (s *OpenVpnMgt) Help() (error, []string) { err, msg := s.sendCommand([]string{"help"}) if err != nil { return err, nil } return nil, msg } func (s *OpenVpnMgt) Version() (error, []string) { err, msg := s.sendCommand([]string{"version"}) if err != nil { return err, nil } return nil, msg } func (s *OpenVpnMgt) ClientDisconnect(line string) { msg := <-s.ret log.Println(msg) } func (s *OpenVpnMgt) ClientConnect(line string) { client := NewVPNSession("log in") client.ParseSessionId(line) infos := <-s.ret client.ParseEnv(&infos) err, ok := s.Auth(client) if err != nil { log.Println(err) } if ok { log.Println("auth ok") } // err, msg := s.sendCommand([]string{fmt.Sprintf("client-deny %d %d \"Need OTP\" \"CRV1:R:blabla:eC5oZW5uZXI=:OTP Code \"", client.cID, client.kID)}) // if err != nil { // return // } // log.Println(msg) } func (s *OpenVpnMgt) handleConn(conn net.Conn) { defer conn.Close() // we don't want multiple connexions, only one openvpn server at a time s.m.Lock() if s.connected { conn.Write([]byte("Sorry, only one server allowed\n")) s.m.Unlock() return } s.connected = true s.m.Unlock() // we store the buffer pointer in the struct, to be accessed from other methods s.buf = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) // most response are multilined, use response to concatenate them response := []string{} for { line, err := s.buf.ReadString('\n') // manage basic errors switch { case err == io.EOF: log.Println("Reached EOF - close this connection.\n") s.connected = false return case err != nil: log.Println("Error reading line. Got: '"+line+"'\n", err) s.connected = false return } line = strings.Trim(line, "\n\r ") switch { // a new openvpn server is connected case strings.HasPrefix(line, ">INFO"): // command sucessfull, we can ignore case strings.HasPrefix(line, ">SUCCESS: client-deny command succeeded"): // 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) // new bloc for a connect event. // We start the receiving handler, which will wait for the Channel message case strings.HasPrefix(line, ">CLIENT:CONNECT"): go s.ClientConnect(line) // write the cumulated lines into the channel to the current handler case strings.HasPrefix(line, "END") || strings.HasPrefix(line, ">CLIENT:ENV,END"): s.ret <- response response = nil default: response = append(response, line) } //log.Print(line) } }