259 lines
5.6 KiB
Go
259 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/pyke369/golang-support/rcache"
|
|
)
|
|
|
|
// Server represents the server
|
|
type OpenVpnMgt struct {
|
|
port string
|
|
m sync.RWMutex
|
|
debug bool
|
|
VpnRemotes map[string]*map[string]string `json:"remotes"`
|
|
VpnServers map[int]*OpenVpnSrv `json:"sessions"`
|
|
LastChange time.Time `json:"last_change"`
|
|
}
|
|
|
|
// 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),
|
|
VpnRemotes: make(map[string]*map[string]string),
|
|
}
|
|
}
|
|
|
|
func (s *OpenVpnMgt) Lock() {
|
|
s.m.Lock()
|
|
}
|
|
|
|
func (s *OpenVpnMgt) Unlock() {
|
|
s.m.Unlock()
|
|
}
|
|
|
|
func (s *OpenVpnMgt) Change() {
|
|
s.LastChange = time.Now().Round(time.Second)
|
|
}
|
|
|
|
func (s *OpenVpnMgt) Debug(v ...interface{}) {
|
|
if s.debug {
|
|
log.Println(v...)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
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(pid int) (error, *OpenVpnSrv) {
|
|
if openvpn, ok := s.VpnServers[pid]; ok {
|
|
return nil, openvpn
|
|
}
|
|
// if there is only 1 session, ignore the pid parameter
|
|
if len(s.VpnServers) == 1 {
|
|
for _, openvpn := range s.VpnServers {
|
|
return nil, openvpn
|
|
}
|
|
}
|
|
return errors.New(fmt.Sprintf("unknown session %d", pid)), nil
|
|
}
|
|
|
|
func (s *OpenVpnMgt) Restart(pid int) error {
|
|
// check if the session is valid
|
|
err, session := s.GetSession(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
session.Signal("SIGHUP")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *OpenVpnMgt) AuthUserPass(pid int, user, pass, otp string) error {
|
|
// check if the session is valid
|
|
err, session := s.GetSession(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
session.AuthUserPass(user, pass, otp)
|
|
return nil
|
|
}
|
|
|
|
func (s *OpenVpnMgt) Kill(pid int) error {
|
|
// check if the session is valid
|
|
err, session := s.GetSession(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
session.Signal("SIGTERM")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *OpenVpnMgt) SetRemote(server string, pid int) error {
|
|
// check if the session is valid
|
|
err, session := s.GetSession(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if session.Provider == "" {
|
|
return errors.New("No server list for this config")
|
|
}
|
|
if _, ok := s.VpnRemotes[session.Provider]; !ok {
|
|
return errors.New("No server list for this provider")
|
|
}
|
|
|
|
for _, r := range *(s.VpnRemotes[session.Provider]) {
|
|
if r != server {
|
|
continue
|
|
}
|
|
return session.SetRemote(server)
|
|
}
|
|
|
|
return errors.New(fmt.Sprintf("unknown session %s", server))
|
|
}
|
|
|
|
func (s *OpenVpnMgt) SetPid(openvpn *OpenVpnSrv, pid int) {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
s.VpnServers[pid] = openvpn
|
|
}
|
|
|
|
func (s *OpenVpnMgt) Remove(openvpn *OpenVpnSrv) {
|
|
for pid, v := range s.VpnServers {
|
|
if v == openvpn {
|
|
delete(s.VpnServers, pid)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 pid, srv := range s.VpnServers {
|
|
err, msg := srv.Version()
|
|
if err == nil {
|
|
ret[pid] = msg
|
|
}
|
|
}
|
|
return err, ret
|
|
}
|
|
|
|
// main loop for a given openvpn server
|
|
func (s *OpenVpnMgt) handleConn(conn net.Conn) {
|
|
remote := conn.RemoteAddr().String()
|
|
remoteRegexp := rcache.Get("^>REMOTE:(.*),([0-9]*),(.*)$")
|
|
|
|
defer conn.Close()
|
|
openvpn := NewOpenVpnSrv(conn, s)
|
|
defer s.Remove(openvpn)
|
|
|
|
// most response are multilined, use response to concatenate them
|
|
response := []string{}
|
|
|
|
// remove bogus clients
|
|
line, err := openvpn.GetLine()
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
if line != ">INFO:OpenVPN Management Interface Version 3 -- type 'help' for more info\r\n" {
|
|
log.Println("Bogus Client")
|
|
log.Println(line)
|
|
return
|
|
}
|
|
|
|
log.Printf("Valid openvpn connected from %s\n", remote)
|
|
|
|
go openvpn.GetEcho()
|
|
go openvpn.GetPid()
|
|
s.Change()
|
|
defer s.Change()
|
|
|
|
for {
|
|
line, err := openvpn.GetLine()
|
|
|
|
// manage basic errors
|
|
switch {
|
|
case err == io.EOF:
|
|
log.Println("Reached EOF - close this connection")
|
|
return
|
|
case err != nil:
|
|
log.Printf("Error reading line. Got: '"+line+"'\n", err)
|
|
return
|
|
}
|
|
line = strings.Trim(line, "\n\r ")
|
|
|
|
if strings.Index(line, "password") == -1 {
|
|
s.Debug(line)
|
|
}
|
|
|
|
// 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) {
|
|
openvpn.Response(append(response, line))
|
|
response = nil
|
|
line = ""
|
|
s.Change()
|
|
break
|
|
}
|
|
}
|
|
|
|
remoteMatch := remoteRegexp.FindStringSubmatch(line)
|
|
switch {
|
|
// command successfull, we can ignore
|
|
case strings.HasPrefix(line, ">PASSWORD:"):
|
|
go openvpn.NeedPassword(line)
|
|
case strings.HasPrefix(line, ">HOLD"):
|
|
go openvpn.waitForRelase()
|
|
case len(remoteMatch) > 0:
|
|
go openvpn.ValidRemote(remoteMatch[1], remoteMatch[2], remoteMatch[3])
|
|
default:
|
|
response = append(response, line)
|
|
}
|
|
}
|
|
}
|