2019-07-08 14:36:56 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"errors"
|
2019-07-11 10:20:08 +00:00
|
|
|
"fmt"
|
2019-07-08 14:36:56 +00:00
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"net"
|
2019-07-08 20:32:12 +00:00
|
|
|
"os"
|
2019-07-10 13:47:55 +00:00
|
|
|
"regexp"
|
|
|
|
"strconv"
|
2019-07-08 14:36:56 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
2019-07-11 06:14:38 +00:00
|
|
|
|
|
|
|
hibp "github.com/mattevans/pwned-passwords"
|
2019-07-08 14:36:56 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Server represents the server
|
|
|
|
type OpenVpnMgt struct {
|
2019-07-11 06:14:38 +00:00
|
|
|
port string
|
2019-07-10 13:47:55 +00:00
|
|
|
buf map[string]*bufio.ReadWriter
|
2019-07-09 10:34:45 +00:00
|
|
|
m sync.RWMutex
|
|
|
|
ret chan []string
|
|
|
|
ldap map[string]ldapConfig
|
2019-07-10 13:47:55 +00:00
|
|
|
clients map[string]map[int]*vpnSession
|
2019-07-09 10:34:45 +00:00
|
|
|
authCa string
|
|
|
|
vpnlogUrl string
|
|
|
|
mailRelay string
|
|
|
|
MailFrom string
|
|
|
|
CcPwnPassword string
|
|
|
|
pwnTemplate string
|
|
|
|
newAsTemplate string
|
|
|
|
cacheDir string
|
|
|
|
syslog bool
|
2019-07-11 06:14:38 +00:00
|
|
|
ipRouteScript string
|
2019-07-09 10:34:45 +00:00
|
|
|
otpMasterSecrets []string
|
2019-07-11 06:14:38 +00:00
|
|
|
hibpClient *hibp.Client
|
|
|
|
debug bool
|
2019-07-08 14:36:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewServer returns a pointer to a new server
|
|
|
|
func NewVPNServer(port string) *OpenVpnMgt {
|
|
|
|
return &OpenVpnMgt{
|
2019-07-11 06:14:38 +00:00
|
|
|
port: port,
|
|
|
|
ret: make(chan []string),
|
|
|
|
ldap: make(map[string]ldapConfig),
|
|
|
|
buf: make(map[string]*bufio.ReadWriter),
|
|
|
|
clients: make(map[string]map[int]*vpnSession),
|
|
|
|
hibpClient: hibp.NewClient(),
|
2019-07-08 14:36:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run starts a the server
|
|
|
|
func (s *OpenVpnMgt) Run() {
|
|
|
|
// Resolve the passed port into an address
|
2019-07-11 06:14:38 +00:00
|
|
|
addrs, err := net.ResolveTCPAddr("tcp", s.port)
|
2019-07-08 14:36:56 +00:00
|
|
|
if err != nil {
|
2019-07-08 20:32:12 +00:00
|
|
|
log.Println(err)
|
|
|
|
os.Exit(1)
|
2019-07-08 14:36:56 +00:00
|
|
|
}
|
|
|
|
// start listening to client connections
|
|
|
|
listener, err := net.ListenTCP("tcp", addrs)
|
|
|
|
if err != nil {
|
2019-07-08 20:32:12 +00:00
|
|
|
log.Println(err)
|
|
|
|
os.Exit(1)
|
2019-07-08 14:36:56 +00:00
|
|
|
}
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-11 06:14:38 +00:00
|
|
|
func (s *OpenVpnMgt) CheckPwn(c *vpnSession) error {
|
|
|
|
c.LogPrintln("checking pwn password")
|
|
|
|
pwned, err := s.hibpClient.Pwned.Compromised(c.password)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
c.PwnedPasswd = pwned
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-10 15:47:43 +00:00
|
|
|
// send a command to the server. Set the channel to receive the response
|
2019-07-10 13:47:55 +00:00
|
|
|
func (s *OpenVpnMgt) sendCommand(msg []string, remote string) (error, []string) {
|
|
|
|
if len(s.buf) == 0 {
|
|
|
|
return errors.New("No openvpn server present"), nil
|
2019-07-09 21:37:37 +00:00
|
|
|
}
|
2019-07-10 13:47:55 +00:00
|
|
|
for _, line := range msg {
|
2019-07-11 06:14:38 +00:00
|
|
|
if s.debug {
|
|
|
|
log.Println(line)
|
|
|
|
}
|
2019-07-10 13:47:55 +00:00
|
|
|
if _, err := s.buf[remote].WriteString(line + "\r\n"); err != nil {
|
|
|
|
return err, nil
|
|
|
|
}
|
2019-07-08 23:44:18 +00:00
|
|
|
}
|
|
|
|
|
2019-07-10 13:47:55 +00:00
|
|
|
if err := s.buf[remote].Flush(); err != nil {
|
|
|
|
return err, nil
|
2019-07-08 23:44:18 +00:00
|
|
|
}
|
|
|
|
|
2019-07-10 13:47:55 +00:00
|
|
|
// wait for the response
|
|
|
|
ret := <-s.ret
|
2019-07-11 06:14:38 +00:00
|
|
|
|
|
|
|
if s.debug {
|
|
|
|
for _, line := range ret {
|
|
|
|
log.Println(line)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-10 13:47:55 +00:00
|
|
|
return nil, ret
|
|
|
|
}
|
|
|
|
|
2019-07-11 10:20:08 +00:00
|
|
|
// send the list of all connected clients
|
|
|
|
func (s *OpenVpnMgt) Stats() map[string]map[int]*vpnSession {
|
|
|
|
return s.clients
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OpenVpnMgt) Kill(session string, id int) error {
|
|
|
|
if _, ok := s.clients[session]; !ok {
|
|
|
|
return errors.New("unknown session")
|
|
|
|
}
|
|
|
|
if _, ok := s.clients[session][id]; !ok {
|
|
|
|
return errors.New("unknown session id")
|
|
|
|
}
|
|
|
|
err, msg := s.sendCommand([]string{fmt.Sprintf("client-kill %d", id)}, session)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-07-10 15:47:43 +00:00
|
|
|
// send the help command on all vpn servers. Kind of useless
|
2019-07-10 13:47:55 +00:00
|
|
|
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)
|
2019-07-09 10:34:45 +00:00
|
|
|
if err != nil {
|
2019-07-10 13:47:55 +00:00
|
|
|
return err, ret
|
2019-07-09 10:34:45 +00:00
|
|
|
}
|
2019-07-10 13:47:55 +00:00
|
|
|
for _, line := range msg {
|
|
|
|
match := re.FindStringSubmatch(line)
|
|
|
|
if len(match) == 0 {
|
2019-07-08 23:44:18 +00:00
|
|
|
continue
|
|
|
|
}
|
2019-07-10 13:47:55 +00:00
|
|
|
help[match[1]] = match[2]
|
2019-07-08 23:44:18 +00:00
|
|
|
}
|
2019-07-10 13:47:55 +00:00
|
|
|
ret[remote] = help
|
2019-07-09 21:37:37 +00:00
|
|
|
}
|
2019-07-10 13:47:55 +00:00
|
|
|
return nil, ret
|
2019-07-08 23:44:18 +00:00
|
|
|
}
|
|
|
|
|
2019-07-11 10:20:08 +00:00
|
|
|
// send the version command on all vpn servers. Kind of useless
|
2019-07-10 13:47:55 +00:00
|
|
|
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
|
2019-07-08 14:36:56 +00:00
|
|
|
}
|
2019-07-10 13:47:55 +00:00
|
|
|
ret[remote] = msg
|
2019-07-08 14:36:56 +00:00
|
|
|
}
|
|
|
|
return nil, ret
|
|
|
|
}
|
|
|
|
|
2019-07-10 15:47:43 +00:00
|
|
|
// called after a client is confirmed connected and authenticated
|
2019-07-10 13:47:55 +00:00
|
|
|
func (s *OpenVpnMgt) ClientValidated(line, remote string) {
|
|
|
|
err, c := s.getClient(line, remote)
|
2019-07-08 14:36:56 +00:00
|
|
|
if err != nil {
|
2019-07-10 13:47:55 +00:00
|
|
|
log.Println(err, line)
|
|
|
|
return
|
2019-07-08 14:36:56 +00:00
|
|
|
}
|
2019-07-10 13:47:55 +00:00
|
|
|
c.Status = "success"
|
2019-07-10 15:47:43 +00:00
|
|
|
infos := <-s.ret
|
2019-07-10 13:47:55 +00:00
|
|
|
|
2019-07-11 06:14:38 +00:00
|
|
|
if err := c.ParseEnv(s, &infos); err != nil {
|
2019-07-10 15:47:43 +00:00
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
s.Log(c)
|
2019-07-08 14:36:56 +00:00
|
|
|
}
|
|
|
|
|
2019-07-10 15:47:43 +00:00
|
|
|
// called after a client is disconnected, including for auth issues
|
2019-07-10 13:47:55 +00:00
|
|
|
func (s *OpenVpnMgt) ClientDisconnect(line, remote string) {
|
|
|
|
err, c := s.getClient(line, remote)
|
2019-07-08 14:36:56 +00:00
|
|
|
if err != nil {
|
2019-07-10 13:47:55 +00:00
|
|
|
log.Println(err)
|
|
|
|
return
|
2019-07-08 14:36:56 +00:00
|
|
|
}
|
|
|
|
|
2019-07-09 21:37:37 +00:00
|
|
|
<-s.ret
|
|
|
|
|
2019-07-10 13:47:55 +00:00
|
|
|
// 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
|
2019-07-11 06:14:38 +00:00
|
|
|
// And don't log the auth failure during re auth
|
|
|
|
if c.Operation != "re auth" && c.Status != "Need OTP Code" {
|
2019-07-10 15:47:43 +00:00
|
|
|
s.Log(c)
|
2019-07-10 13:47:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
defer delete(s.clients[remote], c.cID)
|
2019-07-09 21:37:37 +00:00
|
|
|
}
|
|
|
|
|
2019-07-10 15:47:43 +00:00
|
|
|
// called at the initial connexion
|
2019-07-10 13:47:55 +00:00
|
|
|
func (s *OpenVpnMgt) ClientConnect(line, remote string) {
|
2019-07-11 06:14:38 +00:00
|
|
|
c := NewVPNSession()
|
2019-07-10 15:47:43 +00:00
|
|
|
c.vpnserver = remote
|
|
|
|
c.ParseSessionId(line)
|
|
|
|
s.clients[remote][c.cID] = c
|
2019-07-08 16:41:04 +00:00
|
|
|
infos := <-s.ret
|
2019-07-11 06:14:38 +00:00
|
|
|
if err := c.ParseEnv(s, &infos); err != nil {
|
2019-07-09 21:37:37 +00:00
|
|
|
log.Println(err)
|
|
|
|
return
|
|
|
|
}
|
2019-07-08 16:41:04 +00:00
|
|
|
|
2019-07-10 15:47:43 +00:00
|
|
|
c.Auth(s)
|
2019-07-10 13:47:55 +00:00
|
|
|
}
|
2019-07-08 23:44:18 +00:00
|
|
|
|
2019-07-11 06:14:38 +00:00
|
|
|
func (s *OpenVpnMgt) ClientReAuth(line, remote string) {
|
|
|
|
err, c := s.getClient(line, remote)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err, line)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.ParseSessionId(line)
|
|
|
|
infos := <-s.ret
|
|
|
|
if err := c.ParseEnv(s, &infos); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset some values
|
|
|
|
c.Profile = ""
|
|
|
|
c.Status = "system failure"
|
|
|
|
c.Operation = "re auth"
|
|
|
|
|
|
|
|
c.Auth(s)
|
|
|
|
}
|
|
|
|
|
2019-07-10 13:47:55 +00:00
|
|
|
// 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
|
2019-07-08 23:44:18 +00:00
|
|
|
}
|
2019-07-10 13:47:55 +00:00
|
|
|
id, err := strconv.Atoi(match[1])
|
|
|
|
if err != nil {
|
|
|
|
return err, nil
|
2019-07-08 23:44:18 +00:00
|
|
|
}
|
2019-07-10 13:47:55 +00:00
|
|
|
if _, ok := s.clients[remote]; !ok {
|
|
|
|
return errors.New("unknown vpn server"), nil
|
2019-07-09 21:37:37 +00:00
|
|
|
}
|
2019-07-10 13:47:55 +00:00
|
|
|
if c, ok := s.clients[remote][id]; ok {
|
|
|
|
return nil, c
|
|
|
|
}
|
|
|
|
return errors.New("unknown vpn client"), nil
|
2019-07-08 14:36:56 +00:00
|
|
|
}
|
|
|
|
|
2019-07-11 06:14:38 +00:00
|
|
|
// update counters
|
|
|
|
func (s *OpenVpnMgt) updateCounters(line, remote string) {
|
|
|
|
p := strings.Split(strings.Replace(line, ":", ",", 1), ",")
|
|
|
|
err, c := s.getClient(p[0]+","+p[1], remote)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err, line)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if c.BwWrite, err = strconv.Atoi(p[2]); err != nil {
|
|
|
|
c.LogPrintln(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if c.BwRead, err = strconv.Atoi(p[3]); err != nil {
|
|
|
|
c.LogPrintln(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-07-10 15:47:43 +00:00
|
|
|
// main loop for a given openvpn server
|
2019-07-08 14:36:56 +00:00
|
|
|
func (s *OpenVpnMgt) handleConn(conn net.Conn) {
|
2019-07-10 13:47:55 +00:00
|
|
|
remote := conn.RemoteAddr().String()
|
2019-07-08 14:36:56 +00:00
|
|
|
|
2019-07-10 13:47:55 +00:00
|
|
|
defer conn.Close()
|
|
|
|
defer delete(s.buf, remote)
|
|
|
|
defer delete(s.clients, remote)
|
2019-07-08 14:36:56 +00:00
|
|
|
|
|
|
|
// we store the buffer pointer in the struct, to be accessed from other methods
|
2019-07-10 13:47:55 +00:00
|
|
|
s.buf[remote] = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
|
|
|
s.clients[remote] = make(map[int]*vpnSession)
|
2019-07-08 14:36:56 +00:00
|
|
|
|
|
|
|
// most response are multilined, use response to concatenate them
|
|
|
|
response := []string{}
|
|
|
|
|
2019-07-10 13:47:55 +00:00
|
|
|
// 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
|
2019-07-11 10:20:08 +00:00
|
|
|
if _, err := s.buf[remote].WriteString("bytecount 30\r\n"); err != nil {
|
2019-07-10 13:47:55 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-07-10 15:47:43 +00:00
|
|
|
log.Printf("Valid openvpn connected from %s\n", remote)
|
2019-07-10 13:47:55 +00:00
|
|
|
|
2019-07-08 14:36:56 +00:00
|
|
|
for {
|
2019-07-10 13:47:55 +00:00
|
|
|
line, err := s.buf[remote].ReadString('\n')
|
2019-07-08 14:36:56 +00:00
|
|
|
|
|
|
|
// 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 ")
|
|
|
|
|
2019-07-10 13:47:55 +00:00
|
|
|
// manage exit commands
|
|
|
|
for _, terminator := range []string{"quit", "exit"} {
|
|
|
|
if line == terminator || strings.HasPrefix(line, terminator+" ") {
|
|
|
|
log.Println("server disconnected")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-09 21:37:37 +00:00
|
|
|
// manage all "terminator" lines
|
2019-07-11 10:20:08 +00:00
|
|
|
for _, terminator := range []string{"END", ">CLIENT:ENV,END", "SUCCESS", "ERROR"} {
|
2019-07-09 21:37:37 +00:00
|
|
|
if strings.HasPrefix(line, terminator) {
|
|
|
|
s.ret <- response
|
|
|
|
response = nil
|
2019-07-10 13:47:55 +00:00
|
|
|
line = ""
|
2019-07-09 21:37:37 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-08 14:36:56 +00:00
|
|
|
switch {
|
2019-07-10 13:47:55 +00:00
|
|
|
// command successfull, we can ignore
|
2019-07-08 16:41:04 +00:00
|
|
|
case strings.HasPrefix(line, ">SUCCESS: client-deny command succeeded"):
|
2019-07-08 14:36:56 +00:00
|
|
|
|
2019-07-10 13:47:55 +00:00
|
|
|
// trafic stats
|
|
|
|
case strings.HasPrefix(line, ">BYTECOUNT_CLI"):
|
2019-07-11 06:14:38 +00:00
|
|
|
go s.updateCounters(line, remote)
|
2019-07-10 13:47:55 +00:00
|
|
|
|
2019-07-08 14:36:56 +00:00
|
|
|
// new bloc for a disconnect event.
|
|
|
|
// We start the receiving handler, which will wait for the Channel message
|
|
|
|
case strings.HasPrefix(line, ">CLIENT:DISCONNECT"):
|
2019-07-10 13:47:55 +00:00
|
|
|
go s.ClientDisconnect(line, remote)
|
2019-07-08 14:36:56 +00:00
|
|
|
|
|
|
|
// new bloc for a connect event.
|
|
|
|
// We start the receiving handler, which will wait for the Channel message
|
2019-07-09 21:37:37 +00:00
|
|
|
case strings.HasPrefix(line, ">CLIENT:ADDRESS"):
|
2019-07-11 10:20:08 +00:00
|
|
|
case strings.HasPrefix(line, ">CLIENT:ESTABLISHED"):
|
2019-07-10 13:47:55 +00:00
|
|
|
go s.ClientValidated(line, remote)
|
2019-07-09 21:37:37 +00:00
|
|
|
|
2019-07-08 14:36:56 +00:00
|
|
|
case strings.HasPrefix(line, ">CLIENT:CONNECT"):
|
2019-07-10 13:47:55 +00:00
|
|
|
go s.ClientConnect(line, remote)
|
2019-07-08 14:36:56 +00:00
|
|
|
|
2019-07-11 06:14:38 +00:00
|
|
|
case strings.HasPrefix(line, ">CLIENT:REAUTH"):
|
|
|
|
go s.ClientReAuth(line, remote)
|
|
|
|
|
2019-07-08 14:36:56 +00:00
|
|
|
default:
|
|
|
|
response = append(response, line)
|
|
|
|
}
|
2019-07-11 06:14:38 +00:00
|
|
|
if s.debug && strings.Index(line, "password") == -1 {
|
|
|
|
log.Print(line)
|
|
|
|
}
|
2019-07-08 14:36:56 +00:00
|
|
|
}
|
|
|
|
}
|