FortiClient writes /etc/resolv.conf itself on connect and never tells resolved a link's DNS. The module now requires split-dns and runs an adapter as root that reads the client's servers and domains from its write, routes them over the client's tunnel through the resolver's socket on the machine, takes the write so the resolver's file is back at once, and takes the route away when the tunnel goes. Nothing of it crosses the bus.
66 lines
2.7 KiB
Go
66 lines
2.7 KiB
Go
// The forticlient module's Go tools bundle (novox/hq ADR 0188, ADR 0193, ADR 0208): the FortiClient
|
|
// VPN client's tray in the operator's session and the vendor's service behind it, served by the node's
|
|
// runtime as the operator account, and its adapter to the machine's own resolver (splitdns.go, hq ADR
|
|
// 0247). The module holds no seat, so every tool is its own. The tools
|
|
// report running and connected state only: never a profile, a credential, a gateway or a certificate.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
func main() {
|
|
// Started as `forticlient-tools split-dns` it is the module's adapter to the machine's own resolver,
|
|
// a long-running process as root (splitdns.go). With no argument, the runtime's tools bundle.
|
|
if len(os.Args) > 1 {
|
|
if os.Args[1] != "split-dns" || len(os.Args) != 2 {
|
|
fmt.Fprintln(os.Stderr, "usage: forticlient-tools [split-dns]")
|
|
os.Exit(2)
|
|
}
|
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
|
|
defer stop()
|
|
NewAdapter().Run(ctx)
|
|
return
|
|
}
|
|
if err := stdio.Serve("", tools()); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
var machine = NewMachine()
|
|
|
|
func tools() []stdio.Tool {
|
|
return []stdio.Tool{
|
|
{
|
|
Name: "forticlient_status",
|
|
Description: "The VPN client: the installed version and whether it came from outside the official " +
|
|
"repositories, the vendor's service (active, enabled), whether the tray and its launcher run " +
|
|
"(pid, since, scope or unit), what starts the tray at login, and whether a tunnel is up (a " +
|
|
"count; never a name, an address, a gateway or a profile). (r)",
|
|
Run: func(map[string]any) (any, error) { return machine.Status() },
|
|
},
|
|
{
|
|
Name: "forticlient_restart",
|
|
Description: "End the tray and its launcher (asked first, then forced after 5 s) and start the " +
|
|
"launcher again in the operator's desktop session, under the account's service manager. The " +
|
|
"tunnel is the service's and is not touched. Needs someone logged in to the desktop. (a)",
|
|
Run: func(map[string]any) (any, error) { return machine.Restart() },
|
|
},
|
|
{
|
|
Name: "forticlient_check",
|
|
Description: "Check what the module promises and relies on: the package is installed (by hand: it is " +
|
|
"outside the official repositories); the service runs and is enabled; the tray has exactly one " +
|
|
"start (the vendor's XDG autostart entry, which the session's dex runs; no window-manager exec); " +
|
|
"and the tray and its launcher run once in a desktop session. Being connected is not checked. (r)",
|
|
Run: func(map[string]any) (any, error) { return machine.Check() },
|
|
},
|
|
}
|
|
}
|