[feat] send password to management interface

- Resolves: #104
merge-requests/148/merge
kali 2 years ago committed by kali kaneko (leap communications)
parent d1252ef5b9
commit a85959a43e

@ -8,9 +8,13 @@ require (
0xacab.org/leap/obfsvpn v0.0.0-20220626143947-feff527c00e5
git.torproject.org/pluggable-transports/goptlib.git v1.2.0
git.torproject.org/pluggable-transports/snowflake.git v1.1.0
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
github.com/apparentlymart/go-openvpn-mgmt v0.0.0-20200929191752-4d2ce95ae600
github.com/cretz/bine v0.2.0
github.com/dchest/siphash v1.2.1 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19
github.com/pion/webrtc/v3 v3.1.41
github.com/sevlyar/go-daemon v0.1.5

@ -142,6 +142,7 @@ func maybeStartVPN(b Bitmask, conf *config.Config) error {
}
if b.CanStartVPN() {
log.Println("DEBUG starting")
err := b.StartVPN(config.Provider)
conf.SetUserStoppedVPN(false)
return err

@ -0,0 +1,83 @@
// Copyright (c) 2016 Martin Atkins
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package demux
import (
"bufio"
"io"
)
var readErrSynthEvent = []byte("FATAL:Error reading from OpenVPN")
// Demultiplex reads from the given io.Reader, assumed to be the client
// end of an OpenVPN Management Protocol connection, and splits it into
// distinct messages from OpenVPN.
//
// It then writes the raw message buffers into either replyCh or eventCh
// depending on whether each message is a reply to a client command or
// an asynchronous event notification.
//
// The buffers written to replyCh are entire raw message lines (without the
// trailing newlines), while the buffers written to eventCh are the raw
// event strings with the prototcol's leading '>' indicator omitted.
//
// The caller should usually provide buffered channels of sufficient buffer
// depth so that the reply channel will not be starved by slow event
// processing.
//
// Once the io.Reader signals EOF, eventCh will be closed, then replyCh
// will be closed, and then this function will return.
//
// As a special case, if a non-EOF error occurs while reading from the
// io.Reader then a synthetic "FATAL" event will be written to eventCh
// before the two buffers are closed and the function returns. This
// synthetic message will have the error message "Error reading from OpenVPN".
func Demultiplex(r io.Reader, replyCh, eventCh chan<- []byte) {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
buf := scanner.Bytes()
if len(buf) < 1 {
// Should never happen but we'll be robust and ignore this,
// rather than crashing below.
continue
}
// Asynchronous messages always start with > to differentiate
// them from replies.
if buf[0] == '>' {
// Trim off the > when we post the message, since it's
// redundant after we've demuxed.
eventCh <- buf[1:]
} else {
replyCh <- buf
}
}
if err := scanner.Err(); err != nil {
// Generate a synthetic FATAL event so that the caller can
// see that the connection was not gracefully closed.
eventCh <- readErrSynthEvent
}
close(eventCh)
close(replyCh)
}

@ -0,0 +1,238 @@
// Copyright (c) 2016 Martin Atkins
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package demux
import (
"bytes"
"fmt"
"io"
"reflect"
"testing"
)
func TestDemultiplex(t *testing.T) {
type TestCase struct {
Input []string
ExpectedReplies []string
ExpectedEvents []string
}
testCases := []TestCase{
{
Input: []string{},
ExpectedReplies: []string{},
ExpectedEvents: []string{},
},
{
Input: []string{
"SUCCESS: foo bar baz",
},
ExpectedReplies: []string{
"SUCCESS: foo bar baz",
},
ExpectedEvents: []string{},
},
{
Input: []string{
">STATE:1234,ASSIGN_IP,,10.0.0.1,",
},
ExpectedReplies: []string{},
ExpectedEvents: []string{
"STATE:1234,ASSIGN_IP,,10.0.0.1,",
},
},
{
Input: []string{
">STATE:1234,ASSIGN_IP,,10.0.0.1,",
">STATE:5678,ASSIGN_IP,,10.0.0.1,",
">STATE:9012,ASSIGN_IP,,10.0.0.1,",
},
ExpectedReplies: []string{},
ExpectedEvents: []string{
"STATE:1234,ASSIGN_IP,,10.0.0.1,",
"STATE:5678,ASSIGN_IP,,10.0.0.1,",
"STATE:9012,ASSIGN_IP,,10.0.0.1,",
},
},
{
Input: []string{
">STATE:1234,ASSIGN_IP,,10.0.0.1,",
"SUCCESS: foo bar baz",
">STATE:5678,ASSIGN_IP,,10.0.0.1,",
},
ExpectedReplies: []string{
"SUCCESS: foo bar baz",
},
ExpectedEvents: []string{
"STATE:1234,ASSIGN_IP,,10.0.0.1,",
"STATE:5678,ASSIGN_IP,,10.0.0.1,",
},
},
{
Input: []string{
"SUCCESS: foo bar baz",
">STATE:1234,ASSIGN_IP,,10.0.0.1,",
"SUCCESS: baz bar foo",
},
ExpectedReplies: []string{
"SUCCESS: foo bar baz",
"SUCCESS: baz bar foo",
},
ExpectedEvents: []string{
"STATE:1234,ASSIGN_IP,,10.0.0.1,",
},
},
}
for i, testCase := range testCases {
r := mockReader(testCase.Input)
gotReplies, gotEvents := captureMsgs(r)
if !reflect.DeepEqual(gotReplies, testCase.ExpectedReplies) {
t.Errorf(
"test %d returned incorrect replies\ngot %#v\nwant %#v",
i, gotReplies, testCase.ExpectedReplies,
)
}
if !reflect.DeepEqual(gotEvents, testCase.ExpectedEvents) {
t.Errorf(
"test %d returned incorrect events\ngot %#v\nwant %#v",
i, gotEvents, testCase.ExpectedEvents,
)
}
}
}
func TestDemultiplex_error(t *testing.T) {
r := &alwaysErroringReader{}
gotReplies, gotEvents := captureMsgs(r)
expectedReplies := []string{}
expectedEvents := []string{
"FATAL:Error reading from OpenVPN",
}
if !reflect.DeepEqual(gotReplies, expectedReplies) {
t.Errorf(
"incorrect replies\ngot %#v\nwant %#v",
gotReplies, expectedReplies,
)
}
if !reflect.DeepEqual(gotEvents, expectedEvents) {
t.Errorf(
"incorrect events\ngot %#v\nwant %#v",
gotEvents, expectedEvents,
)
}
}
func mockReader(msgs []string) io.Reader {
var buf []byte
for _, msg := range msgs {
buf = append(buf, []byte(msg)...)
buf = append(buf, '\n')
}
return bytes.NewReader(buf)
}
func captureMsgs(r io.Reader) (replies, events []string) {
replyCh := make(chan []byte)
eventCh := make(chan []byte)
replies = make([]string, 0)
events = make([]string, 0)
go Demultiplex(r, replyCh, eventCh)
for replyCh != nil || eventCh != nil {
select {
case msg, ok := <-replyCh:
if ok {
replies = append(replies, string(msg))
} else {
replyCh = nil
}
case msg, ok := <-eventCh:
if ok {
events = append(events, string(msg))
} else {
eventCh = nil
}
}
}
return replies, events
}
type alwaysErroringReader struct{}
func (r *alwaysErroringReader) Read(buf []byte) (int, error) {
return 0, fmt.Errorf("mock error")
}
// Somewhat-contrived example of blocking for a reply while concurrently
// processing asynchronous events.
func ExampleDemultiplex() {
// In a real caller we would have a net.IPConn as our reader,
// but we'll use a bytes reader here as a test.
r := bytes.NewReader([]byte(
// A reply to a hypothetical command interspersed between
// two asynchronous events.
">HOLD:Waiting for hold release\nSUCCESS: foo\n>FATAL:baz\n",
))
// No strong need for buffering on this channel because usually
// a message sender will immediately block waiting for the
// associated response message.
replyCh := make(chan []byte)
// Make sure the event channel buffer is deep enough that slow event
// processing won't significantly delay synchronous replies. If you
// process events quickly, or if you aren't sending any commands
// concurrently with acting on events, then this is not so important.
eventCh := make(chan []byte, 10)
// Start demultiplexing the message stream in the background.
// This goroutine will exit once the reader signals EOF.
go Demultiplex(r, replyCh, eventCh)
// Some coroutine has sent a hypothetical message to OpenVPN,
// and it can directly block until the associated reply arrives.
// The events will be concurrently handled by our event loop
// below while we wait for the reply to show up.
go func() {
replyMsgBuf := <-replyCh
fmt.Printf("Command reply: %s\n", string(replyMsgBuf))
}()
// Main event loop deals with the async events as they arrive,
// independently of any commands that are pending.
for msgBuf := range eventCh {
fmt.Printf("Event: %s\n", string(msgBuf))
}
}

@ -0,0 +1,12 @@
// Package demux implements low-level demultiplexing of the stream of
// messages sent from OpenVPN on the management channel.
//
// OpenVPN's protocol includes two different kinds of message from the OpenVPN
// process: replies to commands sent by the management client, and asynchronous
// event notifications.
//
// This package's purpose is to split these messages into two separate streams,
// so that functions executing command/response sequences can just block
// on the reply channel while an event loop elsewhere deals with any async
// events that might show up.
package demux

@ -1,4 +1,4 @@
// +build windows
// +build windows
// Copyright (C) 2018-2021 LEAP
//
// This program is free software: you can redistribute it and/or modify
@ -17,19 +17,19 @@
package vpn
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"log"
"os"
"strings"
"bufio"
"fmt"
"unicode/utf16"
"bytes"
"time"
"encoding/binary"
"time"
"unicode/utf16"
"github.com/natefinch/npipe"
"0xacab.org/leap/bitmask-vpn/pkg/vpn/bonafide"
"github.com/natefinch/npipe"
)
const pipeName = `\\.\pipe\openvpn\service`
@ -49,49 +49,52 @@ func (l *launcher) close() error {
func (l *launcher) check() (helpers bool, privilege bool, err error) {
// TODO check if the named pipe exists
log.Println("bogus check on windows")
return true, true, nil
}
func (l *launcher) openvpnStart(flags ...string) error {
var b bytes.Buffer
var b bytes.Buffer
/* DELETE-ME
var filtered []string
for _, v := range flags {
if v != "--tun-ipv6" {
filtered = append(filtered, v)
}
}
*/
cwd, _ := os.Getwd()
opts := `--client --dev tun --block-outside-dns --redirect-gateway --script-security 0 ` + strings.Join(filtered, " ")
cwd, _ := os.Getwd()
opts := `--client --dev tun --block-outside-dns --redirect-gateway --script-security 0 ` + strings.Join(flags, " ")
log.Println("openvpn start: ", opts)
timeout := 3 * time.Second
conn, err := npipe.DialTimeout(pipeName, timeout)
if err != nil {
fmt.Println("ERROR opening pipe")
return errors.New("cannot open openvpn pipe")
}
defer conn.Close()
writeUTF16Bytes(&b, cwd)
writeUTF16Bytes(&b, opts)
writeUTF16Bytes(&b, `\n`)
encoded := b.Bytes()
rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
_, err = rw.Write(encoded)
if err != nil {
fmt.Println("ERROR writing to pipe")
return errors.New("cannot write to openvpn pipe")
}
rw.Flush()
pid, err := getCommandResponse(rw)
if err != nil {
fmt.Println("ERROR getting pid")
}
fmt.Println("OpenVPN PID:", pid)
timeout := 3 * time.Second
conn, err := npipe.DialTimeout(pipeName, timeout)
if err != nil {
fmt.Println("ERROR opening pipe")
return errors.New("cannot open openvpn pipe")
}
defer conn.Close()
writeUTF16Bytes(&b, cwd)
writeUTF16Bytes(&b, opts)
writeUTF16Bytes(&b, `\n`)
encoded := b.Bytes()
rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
_, err = rw.Write(encoded)
if err != nil {
log.Println("ERROR writing to pipe")
return errors.New("cannot write to openvpn pipe")
}
rw.Flush()
pid, err := getCommandResponse(rw)
if err != nil {
log.Println("ERROR getting pid")
}
log.Println("OpenVPN PID:", pid)
return nil
}
@ -102,57 +105,56 @@ func (l *launcher) openvpnStop() error {
// TODO we will have to bring our helper back to do firewall
func (l *launcher) firewallStart(gateways []bonafide.Gateway) error {
log.Println("NO firewall in windows")
log.Println("start: no firewall in windows")
return nil
}
func (l *launcher) firewallStop() error {
log.Println("NO firewall in windows")
log.Println("stop: no firewall in windows")
return nil
}
func (l *launcher) firewallIsUp() bool {
log.Println("NO firewall in windows")
return true
log.Println("up: no firewall in windows")
return false
}
func writeUTF16Bytes(b *bytes.Buffer, in string) {
var u16 []uint16 = utf16.Encode([]rune(in + "\x00"))
binary.Write(b, binary.LittleEndian, u16)
var u16 []uint16 = utf16.Encode([]rune(in + "\x00"))
binary.Write(b, binary.LittleEndian, u16)
}
func decodeUTF16String(s string) int {
var code int
var dec []byte
for _, v := range []byte(s) {
if byte(v) != byte(0) {
dec = append(dec, v)
}
}
_, err := fmt.Sscanf(string(dec), "%v", &code)
if err != nil {
fmt.Println("ERROR decoding")
}
return code
var code int
var dec []byte
for _, v := range []byte(s) {
if byte(v) != byte(0) {
dec = append(dec, v)
}
}
_, err := fmt.Sscanf(string(dec), "%v", &code)
if err != nil {
fmt.Println("ERROR decoding")
}
return code
}
func getCommandResponse(rw *bufio.ReadWriter) (int, error) {
msg, err := rw.ReadString('\n')
if err != nil {
fmt.Println("ERROR reading")
}
ok := decodeUTF16String(msg)
if ok != 0 {
return -1, errors.New("command failed")
}
msg, err = rw.ReadString('\n')
if err != nil {
fmt.Println("ERROR reading")
}
pid := decodeUTF16String(msg)
if pid == 0 {
return -1, errors.New("command failed")
}
return pid, nil
msg, err := rw.ReadString('\n')
if err != nil {
fmt.Println("ERROR reading")
}
ok := decodeUTF16String(msg)
if ok != 0 {
return -1, errors.New("command failed")
}
msg, err = rw.ReadString('\n')
if err != nil {
fmt.Println("ERROR reading")
}
pid := decodeUTF16String(msg)
if pid == 0 {
return -1, errors.New("command failed")
}
return pid, nil
}

@ -27,8 +27,8 @@ import (
"0xacab.org/leap/bitmask-vpn/pkg/motd"
"0xacab.org/leap/bitmask-vpn/pkg/snowflake"
"0xacab.org/leap/bitmask-vpn/pkg/vpn/bonafide"
"0xacab.org/leap/bitmask-vpn/pkg/vpn/management"
obfsvpn "0xacab.org/leap/obfsvpn/client"
"github.com/apparentlymart/go-openvpn-mgmt/openvpn"
)
@ -38,7 +38,7 @@ type Bitmask struct {
onGateway bonafide.Gateway
ptGateway bonafide.Gateway
statusCh chan string
managementClient *openvpn.MgmtClient
managementClient *management.MgmtClient
bonafide *bonafide.Bonafide
launch *launcher
transport string
@ -138,6 +138,7 @@ func (b *Bitmask) Close() {
if err != nil {
log.Printf("There was an error closing the launcher: %v", err)
}
time.Sleep(1 * time.Second)
err = os.RemoveAll(b.tempdir)
if err != nil {
log.Printf("There was an error removing temp dir: %v", err)

@ -0,0 +1,362 @@
// Copyright (c) 2016 Martin Atkins
// Copyright (c) 2021 LEAP Encryption Access Project
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package management
import (
"bytes"
"fmt"
"io"
"net"
"strconv"
"time"
"0xacab.org/leap/bitmask-vpn/pkg/vpn/demux"
)
var newline = []byte{'\n'}
var successPrefix = []byte("SUCCESS: ")
var errorPrefix = []byte("ERROR: ")
var endMessage = []byte("END")
// StatusFormat enum type
type StatusFormat string
// StatusFormatDefault openvpn default status format
// StatusFormatV3 openvpn version 3 status format
const (
StatusFormatDefault StatusFormat = ""
StatusFormatV3 StatusFormat = "3"
)
// MgmtClient .
type MgmtClient struct {
wr io.Writer
replies <-chan []byte
}
// NewClient creates a new MgmtClient that communicates via the given
// io.ReadWriter and emits events on the given channel.
//
// eventCh should be a buffered channel with a sufficient buffer depth
// such that it cannot be filled under the expected event volume. Event
// volume depends on which events are enabled and how they are configured;
// some of the event-enabling functions have further discussion how frequently
// events are likely to be emitted, but the caller should also factor in
// how long its own event *processing* will take, since slow event
// processing will create back-pressure that could cause this buffer to
// fill faster.
//
// It probably goes without saying given the previous paragraph, but the
// caller *must* constantly read events from eventCh to avoid its buffer
// becoming full. Events and replies are received on the same channel
// from OpenVPN, so if writing to eventCh blocks then this will also block
// responses from the client's various command methods.
//
// eventCh will be closed to signal the closing of the client connection,
// whether due to graceful shutdown or to an error. In the case of error,
// a FatalEvent will be emitted on the channel as the last event before it
// is closed. Connection errors may also concurrently surface as error
// responses from the client's various command methods, should an error
// occur while we await a reply.
func NewClient(conn io.ReadWriter, eventCh chan<- Event) *MgmtClient {
replyCh := make(chan []byte)
rawEventCh := make(chan []byte) // not buffered because eventCh should be
go demux.Demultiplex(conn, replyCh, rawEventCh)
// Get raw events and upgrade them into proper event types before
// passing them on to the caller's event channel.
go func() {
for raw := range rawEventCh {
eventCh <- upgradeEvent(raw)
}
close(eventCh)
}()
return &MgmtClient{
// replyCh acts as the reader for our ReadWriter, so we only
// need to retain the io.Writer for it, so we can send commands.
wr: conn,
replies: replyCh,
}
}
// Dial is a convenience wrapper around NewClient that handles the common
// case of opening an TCP/IP socket to an OpenVPN management port and creating
// a client for it.
//
// See the NewClient docs for discussion about the requirements for eventCh.
//
// OpenVPN will create a suitable management port if launched with the
// following command line option:
//
// --management <ipaddr> <port>
//
// Address may an IPv4 address, an IPv6 address, or a hostname that resolves
// to either of these, followed by a colon and then a port number.
//
// When running on Unix systems it's possible to instead connect to a Unix
// domain socket. To do this, pass an absolute path to the socket as
// the target address, having run OpenVPN with the following options:
//
// --management /path/to/socket unix
//
func Dial(addr string, eventCh chan<- Event) (*MgmtClient, error) {
proto := "tcp"
if len(addr) > 0 && addr[0] == '/' {
proto = "unix"
}
conn, err := net.Dial(proto, addr)
if err != nil {
return nil, err
}
return NewClient(conn, eventCh), nil
}
// HoldRelease instructs OpenVPN to release any management hold preventing
// it from proceeding, but to retain the state of the hold flag such that
// the daemon will hold again if it needs to reconnect for any reason.
//
// OpenVPN can be instructed to activate a management hold on startup by
// running it with the following option:
//
// --management-hold
//
// Instructing OpenVPN to hold gives your client a chance to connect and
// do any necessary configuration before a connection proceeds, thus avoiding
// the problem of missed events.
//
// When OpenVPN begins holding, or when a new management client connects while
// a hold is already in effect, a HoldEvent will be emitted on the event
// channel.
func (c *MgmtClient) HoldRelease() error {
_, err := c.simpleCommand("hold release")
return err
}
// SetStateEvents either enables or disables asynchronous events for changes
// in the OpenVPN connection state.
//
// When enabled, a StateEvent will be emitted from the event channel each
// time the connection state changes. See StateEvent for more information
// on the event structure.
func (c *MgmtClient) SetStateEvents(on bool) error {
var err error
if on {
_, err = c.simpleCommand("state on")
} else {
_, err = c.simpleCommand("state off")
}
return err
}
// SetEchoEvents either enables or disables asynchronous events for "echo"
// commands sent from a remote server to our managed OpenVPN client.
//
// When enabled, an EchoEvent will be emitted from the event channel each
// time the server sends an echo command. See EchoEvent for more information.
func (c *MgmtClient) SetEchoEvents(on bool) error {
var err error
if on {
_, err = c.simpleCommand("echo on")
} else {
_, err = c.simpleCommand("echo off")
}
return err
}
// SetByteCountEvents either enables or disables ongoing asynchronous events
// for information on OpenVPN bandwidth usage.
//
// When enabled, a ByteCountEvent will be emitted at given time interval,
// (which may only be whole seconds) describing how many bytes have been
// transferred in each direction See ByteCountEvent for more information.
//
// Set the time interval to zero in order to disable byte count events.
func (c *MgmtClient) SetByteCountEvents(interval time.Duration) error {
msg := fmt.Sprintf("bytecount %d", int(interval.Seconds()))
_, err := c.simpleCommand(msg)
return err
}
// SendSignal sends a signal to the OpenVPN process via the management
// channel. In effect this causes the OpenVPN process to send a signal to
// itself on our behalf.
//
// OpenVPN accepts a subset of the usual UNIX signal names, including
// "SIGHUP", "SIGTERM", "SIGUSR1" and "SIGUSR2". See the OpenVPN manual
// page for the meaning of each.
//
// Behavior is undefined if the given signal name is not entirely uppercase
// letters. In particular, including newlines in the string is likely to
// cause very unpredictable behavior.
func (c *MgmtClient) SendSignal(name string) error {
msg := fmt.Sprintf("signal %q", name)
_, err := c.simpleCommand(msg)
return err
}
// LatestState retrieves the most recent StateEvent from the server. This
// can either be used to poll the state or it can be used to determine the
// initial state after calling SetStateEvents(true) but before the first
// state event is delivered.
func (c *MgmtClient) LatestState() (*StateEvent, error) {
err := c.sendCommand([]byte("state"))
if err != nil {
return nil, err
}
payload, err := c.readCommandResponsePayload()
if err != nil {
return nil, err
}
if len(payload) != 1 {
return nil, fmt.Errorf("Malformed OpenVPN 'state' response")
}
return &StateEvent{
body: payload[0],
}, nil
}
// LatestStatus retrieves the current daemon status information, in the same format as that produced by the OpenVPN --status directive.
func (c *MgmtClient) LatestStatus(statusFormat StatusFormat) ([][]byte, error) {
var cmd []byte
if statusFormat == StatusFormatDefault {
cmd = []byte("status")
} else if statusFormat == StatusFormatV3 {
cmd = []byte("status 3")
} else {
return nil, fmt.Errorf("Incorrect 'status' format option")
}
err := c.sendCommand(cmd)
if err != nil {
return nil, err
}
payload, err := c.readCommandResponsePayload()
if err != nil {
return nil, err
}
return payload, nil
}
// Pid retrieves the process id of the connected OpenVPN process.
func (c *MgmtClient) Pid() (int, error) {
raw, err := c.simpleCommand("pid")
if err != nil {
return 0, err
}
if !bytes.HasPrefix(raw, []byte("pid=")) {
return 0, fmt.Errorf("malformed response from OpenVPN")
}
pid, err := strconv.Atoi(string(raw[4:]))
if err != nil {
return 0, fmt.Errorf("error parsing pid from OpenVPN: %s", err)
}
return pid, nil
}
func (c *MgmtClient) SendPassword(pass string) ([]byte, error) {
return c.simpleCommand(pass + "\n")
}
func (c *MgmtClient) sendCommand(cmd []byte) error {
_, err := c.wr.Write(cmd)
if err != nil {
return err
}
_, err = c.wr.Write(newline)
return err
}
// sendCommandPayload can be called after sendCommand for
// commands that expect a multi-line input payload.
//
// The buffer given in 'payload' *must* end with a newline,
// or else the protocol will be broken.
func (c *MgmtClient) sendCommandPayload(payload []byte) error {
_, err := c.wr.Write(payload)
if err != nil {
return err
}
_, err = c.wr.Write(endMessage)
if err != nil {
return err
}
_, err = c.wr.Write(newline)
return err
}
func (c *MgmtClient) readCommandResult() ([]byte, error) {
reply, ok := <-c.replies
if !ok {
return nil, fmt.Errorf("connection closed while awaiting result")
}
if bytes.HasPrefix(reply, successPrefix) {
result := reply[len(successPrefix):]
return result, nil
}
if bytes.HasPrefix(reply, errorPrefix) {
message := reply[len(errorPrefix):]
return nil, ErrorFromServer(message)
}
return nil, fmt.Errorf("malformed result message")
}
func (c *MgmtClient) readCommandResponsePayload() ([][]byte, error) {
lines := make([][]byte, 0, 10)
for {
line, ok := <-c.replies
if !ok {
// We'll give the caller whatever we got before the connection
// closed, in case it's useful for debugging.
return lines, fmt.Errorf("connection closed before END recieved")
}
if bytes.Equal(line, endMessage) {
break
}
lines = append(lines, line)
}
return lines, nil
}
func (c *MgmtClient) simpleCommand(cmd string) ([]byte, error) {
err := c.sendCommand([]byte(cmd))
if err != nil {
return nil, err
}
return c.readCommandResult()
}

@ -0,0 +1,32 @@
// Copyright (c) 2016 Martin Atkins
// Copyright (c) 2021 LEAP Encryption Access Project
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package management
type ErrorFromServer []byte
func (err ErrorFromServer) Error() string {
return string(err)
}
func (err ErrorFromServer) String() string {
return string(err)
}

@ -0,0 +1,320 @@
// Copyright (c) 2016 Martin Atkins
// Copyright (c) 2021 LEAP Encryption Access Project
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package management
import (
"bytes"
"fmt"
"strconv"
)
var eventSep = []byte(":")
var fieldSep = []byte(",")
var byteCountEventKW = []byte("BYTECOUNT")
var byteCountCliEventKW = []byte("BYTECOUNT_CLI")
var clientEventKW = []byte("CLIENT")
var echoEventKW = []byte("ECHO")
var fatalEventKW = []byte("FATAL")
var holdEventKW = []byte("HOLD")
var infoEventKW = []byte("INFO")
var logEventKW = []byte("LOG")
var needOkEventKW = []byte("NEED-OK")
var needStrEventKW = []byte("NEED-STR")
var passwordEventKW = []byte("PASSWORD")
var stateEventKW = []byte("STATE")
type Event interface {
String() string
}
// UnknownEvent represents an event of a type that this package doesn't
// know about.
//
// Future versions of this library may learn about new event types, so a
// caller should exercise caution when making use of events of this type
// to access unsupported behavior. Backward-compatibility is *not*
// guaranteed for events of this type.
type UnknownEvent struct {
keyword []byte
body []byte
}
func (e *UnknownEvent) Type() string {
return string(e.keyword)
}
func (e *UnknownEvent) Body() string {
return string(e.body)
}
func (e *UnknownEvent) String() string {
return fmt.Sprintf("%s: %s", e.keyword, e.body)
}
// MalformedEvent represents a message from the OpenVPN process that is
// presented as an event but does not comply with the expected event syntax.
//
// Events of this type should never be seen but robust callers will accept
// and ignore them, possibly generating some kind of debugging message.
//
// One reason for potentially seeing events of this type is when the target
// program is actually not an OpenVPN process at all, but in fact this client
// has been connected to a different sort of server by mistake.
type MalformedEvent struct {
raw []byte
}
func (e *MalformedEvent) String() string {
return fmt.Sprintf("Malformed Event %q", e.raw)
}
// HoldEvent is a notification that the OpenVPN process is in a management
// hold and will not continue connecting until the hold is released, e.g.
// by calling client.HoldRelease()
type HoldEvent struct {
body []byte
}
func (e *HoldEvent) String() string {
return string(e.body)
}
// StateEvent is a notification of a change of connection state. It can be
// used, for example, to detect if the OpenVPN connection has been interrupted
// and the OpenVPN process is attempting to reconnect.
type StateEvent struct {
body []byte
// bodyParts is populated only on first request, giving us the
// separate comma-separated elements of the message. Not all
// fields are populated for all states.
bodyParts [][]byte
}
func (e *StateEvent) RawTimestamp() string {
parts := e.parts()
return string(parts[0])
}
func (e *StateEvent) NewState() string {
parts := e.parts()
return string(parts[1])
}
func (e *StateEvent) Description() string {
parts := e.parts()
return string(parts[2])
}
// LocalTunnelAddr returns the IP address of the local interface within
// the tunnel, as a string that can be parsed using net.ParseIP.
//
// This field is only populated for events whose NewState returns
// either ASSIGN_IP or CONNECTED.
func (e *StateEvent) LocalTunnelAddr() string {
parts := e.parts()
return string(parts[3])
}
// RemoteAddr returns the non-tunnel IP address of the remote
// system that has connected to the local OpenVPN process.
//
// This field is only populated for events whose NewState returns
// CONNECTED.
func (e *StateEvent) RemoteAddr() string {
parts := e.parts()
return string(parts[4])
}
func (e *StateEvent) String() string {
newState := e.NewState()
switch newState {
case "ASSIGN_IP":
return fmt.Sprintf("%s: %s", newState, e.LocalTunnelAddr())
case "CONNECTED":
return fmt.Sprintf("%s: %s", newState, e.RemoteAddr())
default:
desc := e.Description()
if desc != "" {
return fmt.Sprintf("%s: %s", newState, desc)
} else {
return newState
}
}
}
func (e *StateEvent) parts() [][]byte {
if e.bodyParts == nil {
// State messages currently have only five segments, but
// we'll ask for 5 so any additional fields that might show
// up in newer versions will gather in an element we're
// not actually using.
e.bodyParts = bytes.SplitN(e.body, fieldSep, 6)
// Prevent crash if the server has sent us a malformed
// status message. This should never actually happen if
// the server is behaving itself.
if len(e.bodyParts) < 5 {
expanded := make([][]byte, 5)
copy(expanded, e.bodyParts)
e.bodyParts = expanded
}
}
return e.bodyParts
}
// EchoEvent is emitted by an OpenVPN process running in client mode when
// an "echo" command is pushed to it by the server it has connected to.
//
// The format of the echo message is free-form, since this message type is
// intended to pass application-specific data from the server-side config
// into whatever client is consuming the management prototcol.
//
// This event is emitted only if the management client has turned on events
// of this type using client.SetEchoEvents(true)
type EchoEvent struct {
body []byte
}
func (e *EchoEvent) RawTimestamp() string {
sepIndex := bytes.Index(e.body, fieldSep)
if sepIndex == -1 {
return ""
}
return string(e.body[:sepIndex])
}
func (e *EchoEvent) Message() string {
sepIndex := bytes.Index(e.body, fieldSep)
if sepIndex == -1 {
return ""
}
return string(e.body[sepIndex+1:])
}
func (e *EchoEvent) String() string {
return fmt.Sprintf("ECHO: %s", e.Message())
}
// ByteCountEvent represents a periodic snapshot of data transfer in bytes
// on a VPN connection.
//
// For OpenVPN *servers*, events are emitted for each client and the method
// ClientId identifies thet client.
//
// For other OpenVPN modes, events are emitted only once per interval for the
// single connection managed by the target process, and ClientId returns
// the empty string.
type ByteCountEvent struct {
hasClient bool
body []byte
// populated on first call to parts()
bodyParts [][]byte
}
func (e *ByteCountEvent) ClientId() string {
if !e.hasClient {
return ""
}
return string(e.parts()[0])
}
func (e *ByteCountEvent) BytesIn() int {
index := 0
if e.hasClient {
index = 1
}
str := string(e.parts()[index])
val, _ := strconv.Atoi(str)
// Ignore error, since this should never happen if OpenVPN is
// behaving itself.
return val
}
func (e *ByteCountEvent) BytesOut() int {
index := 1
if e.hasClient {
index = 2
}
str := string(e.parts()[index])
val, _ := strconv.Atoi(str)
// Ignore error, since this should never happen if OpenVPN is
// behaving itself.
return val
}
func (e *ByteCountEvent) String() string {
if e.hasClient {
return fmt.Sprintf("Client %s: %d in, %d out", e.ClientId(), e.BytesIn(), e.BytesOut())
} else {
return fmt.Sprintf("%d in, %d out", e.BytesIn(), e.BytesOut())
}
}
func (e *ByteCountEvent) parts() [][]byte {
if e.bodyParts == nil {
e.bodyParts = bytes.SplitN(e.body, fieldSep, 4)
wantCount := 2
if e.hasClient {
wantCount = 3
}
// Prevent crash if the server has sent us a malformed
// message. This should never actually happen if the
// server is behaving itself.
if len(e.bodyParts) < wantCount {
expanded := make([][]byte, wantCount)
copy(expanded, e.bodyParts)
e.bodyParts = expanded
}
}
return e.bodyParts
}
func upgradeEvent(raw []byte) Event {
splitIdx := bytes.Index(raw, eventSep)
if splitIdx == -1 {
// Should never happen, but we'll handle it robustly if it does.
return &MalformedEvent{raw}
}
keyword := raw[:splitIdx]
body := raw[splitIdx+1:]
switch {
case bytes.Equal(keyword, stateEventKW):
return &StateEvent{body: body}
case bytes.Equal(keyword, holdEventKW):
return &HoldEvent{body}
case bytes.Equal(keyword, echoEventKW):
return &EchoEvent{body}
case bytes.Equal(keyword, byteCountEventKW):
return &ByteCountEvent{hasClient: false, body: body}
case bytes.Equal(keyword, byteCountCliEventKW):
return &ByteCountEvent{hasClient: true, body: body}
default:
return &UnknownEvent{keyword, body}
}
}

@ -0,0 +1,367 @@
// Copyright (c) 2016 Martin Atkins
// Copyright (c) 2021 LEAP Encryption Access Project
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package management
import (
"fmt"
"testing"
)
// A key requirement of our event parsing is that it must never cause a
// panic, even if the OpenVPN process sends us malformed garbage.
//
// Therefore most of the tests in here are testing various tortured error
// cases, which are all expected to produce an event object, though the
// contents of that event object will be nonsensical if the OpenVPN server
// sends something nonsensical.
func TestMalformedEvent(t *testing.T) {
testCases := [][]byte{
[]byte(""),
[]byte("HTTP/1.1 200 OK"),
[]byte(" "),
[]byte("\x00"),
}
for i, testCase := range testCases {
event := upgradeEvent(testCase)
var malformed *MalformedEvent
var ok bool
if malformed, ok = event.(*MalformedEvent); !ok {
t.Errorf("test %d got %T; want %T", i, event, malformed)
continue
}
wantString := fmt.Sprintf("Malformed Event %q", testCase)
if gotString := malformed.String(); gotString != wantString {
t.Errorf("test %d String returned %q; want %q", i, gotString, wantString)
}
}
}
func TestUnknownEvent(t *testing.T) {
type TestCase struct {
Input []byte
WantType string
WantBody string
}
testCases := []TestCase{
{
Input: []byte("DUMMY:baz"),
WantType: "DUMMY",
WantBody: "baz",
},
{
Input: []byte("DUMMY:"),
WantType: "DUMMY",
WantBody: "",
},
{
Input: []byte("DUMMY:abc,123,456"),
WantType: "DUMMY",
WantBody: "abc,123,456",
},
}
for i, testCase := range testCases {
event := upgradeEvent(testCase.Input)
var unk *UnknownEvent
var ok bool
if unk, ok = event.(*UnknownEvent); !ok {
t.Errorf("test %d got %T; want %T", i, event, unk)
continue
}
if got, want := unk.Type(), testCase.WantType; got != want {
t.Errorf("test %d Type returned %q; want %q", i, got, want)
}
if got, want := unk.Body(), testCase.WantBody; got != want {
t.Errorf("test %d Body returned %q; want %q", i, got, want)
}
}
}
func TestHoldEvent(t *testing.T) {
testCases := [][]byte{
[]byte("HOLD:"),
[]byte("HOLD:waiting for hold release"),
}
for i, testCase := range testCases {
event := upgradeEvent(testCase)
var hold *HoldEvent
var ok bool
if hold, ok = event.(*HoldEvent); !ok {
t.Errorf("test %d got %T; want %T", i, event, hold)
continue
}
}
}
func TestEchoEvent(t *testing.T) {
type TestCase struct {
Input []byte
WantTimestamp string
WantMessage string
}
testCases := []TestCase{
{
Input: []byte("ECHO:123,foo"),
WantTimestamp: "123",
WantMessage: "foo",
},
{
Input: []byte("ECHO:123,"),
WantTimestamp: "123",
WantMessage: "",
},
{
Input: []byte("ECHO:,foo"),
WantTimestamp: "",
WantMessage: "foo",
},
{
Input: []byte("ECHO:,"),
WantTimestamp: "",
WantMessage: "",
},
{
Input: []byte("ECHO:"),
WantTimestamp: "",
WantMessage: "",
},
}
for i, testCase := range testCases {
event := upgradeEvent(testCase.Input)
var echo *EchoEvent
var ok bool
if echo, ok = event.(*EchoEvent); !ok {
t.Errorf("test %d got %T; want %T", i, event, echo)
continue
}
if got, want := echo.RawTimestamp(), testCase.WantTimestamp; got != want {
t.Errorf("test %d RawTimestamp returned %q; want %q", i, got, want)
}
if got, want := echo.Message(), testCase.WantMessage; got != want {
t.Errorf("test %d Message returned %q; want %q", i, got, want)
}
}
}
func TestStateEvent(t *testing.T) {
type TestCase struct {
Input []byte
WantTimestamp string
WantState string
WantDesc string
WantLocalAddr string
WantRemoteAddr string
}
testCases := []TestCase{
{
Input: []byte("STATE:"),
WantTimestamp: "",
WantState: "",
WantDesc: "",
WantLocalAddr: "",
WantRemoteAddr: "",
},
{
Input: []byte("STATE:,"),
WantTimestamp: "",
WantState: "",
WantDesc: "",
WantLocalAddr: "",
WantRemoteAddr: "",
},
{
Input: []byte("STATE:,,,,"),
WantTimestamp: "",
WantState: "",
WantDesc: "",
WantLocalAddr: "",
WantRemoteAddr: "",
},
{
Input: []byte("STATE:123,CONNECTED,good,172.16.0.1,192.168.4.1"),
WantTimestamp: "123",
WantState: "CONNECTED",
WantDesc: "good",
WantLocalAddr: "172.16.0.1",
WantRemoteAddr: "192.168.4.1",
},
{
Input: []byte("STATE:123,RECONNECTING,SIGHUP,,"),
WantTimestamp: "123",
WantState: "RECONNECTING",
WantDesc: "SIGHUP",
WantLocalAddr: "",
WantRemoteAddr: "",
},
{
Input: []byte("STATE:123,RECONNECTING,SIGHUP,,,extra"),
WantTimestamp: "123",
WantState: "RECONNECTING",
WantDesc: "SIGHUP",
WantLocalAddr: "",
WantRemoteAddr: "",
},
}
for i, testCase := range testCases {
event := upgradeEvent(testCase.Input)
var st *StateEvent
var ok bool
if st, ok = event.(*StateEvent); !ok {
t.Errorf("test %d got %T; want %T", i, event, st)
continue
}
if got, want := st.RawTimestamp(), testCase.WantTimestamp; got != want {
t.Errorf("test %d RawTimestamp returned %q; want %q", i, got, want)
}
if got, want := st.NewState(), testCase.WantState; got != want {
t.Errorf("test %d NewState returned %q; want %q", i, got, want)
}
if got, want := st.Description(), testCase.WantDesc; got != want {
t.Errorf("test %d Description returned %q; want %q", i, got, want)
}
if got, want := st.LocalTunnelAddr(), testCase.WantLocalAddr; got != want {
t.Errorf("test %d LocalTunnelAddr returned %q; want %q", i