github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/services/openvpn/core/options_node.go (about)

     1  /*
     2   * Copyright (C) 2018 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package core
    19  
    20  import (
    21  	"bufio"
    22  	"bytes"
    23  	"io"
    24  	"net/textproto"
    25  	"os/exec"
    26  	"strconv"
    27  	"syscall"
    28  
    29  	"github.com/pkg/errors"
    30  	"github.com/rs/zerolog/log"
    31  )
    32  
    33  // NodeOptions describes possible parameters of Openvpn configuration
    34  type NodeOptions struct {
    35  	BinaryPath string
    36  }
    37  
    38  // Check function checks that openvpn is available, given path to openvpn binary
    39  func (options *NodeOptions) Check() error {
    40  	command := exec.Command(options.BinaryPath, "--version")
    41  	outputBuffer, cmdResult := command.Output()
    42  	exitCode, err := extractExitCodeFromCmdResult(cmdResult)
    43  	if err != nil {
    44  		return err
    45  	}
    46  	// openvpn returns exit code 1 in case of --version parameter, if anything else is returned - treat as error
    47  	// with newer versions openvpn seems to have fixed the exit code 1 mistake, they now return a zero as they should.
    48  	if exitCode != 1 || exitCode == 0 {
    49  		log.Error().Msg("Check failed. Output of executed command: " + string(outputBuffer))
    50  		return errors.New("unexpected openvpn exit code: " + strconv.Itoa(exitCode))
    51  	}
    52  
    53  	stringReader := textproto.NewReader(bufio.NewReader(bytes.NewReader(outputBuffer)))
    54  	//openvpn --version produces 5 (and optional 6th) strings as output
    55  	//see testdata/openvpn-version-custom-tag.sh for output example
    56  	for i := 0; i < 5; i++ {
    57  		str, err := stringReader.ReadLine()
    58  		if err != nil {
    59  			return err
    60  		}
    61  		log.Info().Msg(str)
    62  	}
    63  
    64  	//optional custom tag
    65  	str, err := stringReader.ReadLine()
    66  	if err == nil {
    67  		log.Info().Msg("Custom tag: " + str)
    68  	} else if err != io.EOF {
    69  		//EOF is expected here and it doesn't fail openvpn check
    70  		return err
    71  	}
    72  	return nil
    73  }
    74  
    75  // given error value from cmd.Wait() extract exit code if possible, otherwise returns error itself
    76  // this is ugly but there is no standart and os independent way to extract exit status in golang
    77  func extractExitCodeFromCmdResult(cmdResult error) (int, error) {
    78  	exitError, ok := cmdResult.(*exec.ExitError)
    79  	if !ok {
    80  		return 0, cmdResult
    81  	}
    82  
    83  	exitStatus, ok := exitError.Sys().(syscall.WaitStatus)
    84  	if !ok {
    85  		return 0, cmdResult
    86  	}
    87  	return exitStatus.ExitStatus(), nil
    88  }