Skip to content

Commit

Permalink
load a configuration file
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxKellermann committed Oct 22, 2023
1 parent 6c6ebbb commit 478a07d
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 11 deletions.
7 changes: 7 additions & 0 deletions config/lukko.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#listener {
# bind "*"
# zeroconf_service "lukko"
#}

@include_optional "local.conf"
@include "conf.d/*.conf"
1 change: 1 addition & 0 deletions debian/cm4all-lukko.dirs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
etc/cm4all/lukko/conf.d
1 change: 1 addition & 0 deletions debian/cm4all-lukko.install
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
config/lukko.conf etc/cm4all/lukko
usr/sbin/cm4all-lukko
55 changes: 55 additions & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,58 @@ What is Lukko?
Lukko is a SSH server.

This project is work in progress.


Configuration
=============

Lukko loads the configuration file
:file:`/etc/cm4all/lukko/lukko.conf`.


Listener
--------

The ``listener`` section describes how Lukko listens for incoming SSH
connections. Example::

listener {
bind "*:22"
#interface "eth0"
#zeroconf_service "lukko"
}

Known attributes:

- ``bind``: an adddress to bind to. May be the wildcard ``*`` or an
IPv4/IPv6 address followed by a port. IPv6 addresses should be
enclosed in square brackets to disambiguate the port
separator. Local sockets start with a slash :file:`/`, and abstract
sockets start with the symbol ``@``.

- ``interface``: limit this listener to the given network interface.

- ``mode``: for local socket files, this specifies the octal file
mode.

- ``mptcp``: ``yes`` enables Multi-Path TCP

- ``ack_timeout``: close the connection if transmitted data remains
unacknowledged by the client for this number of seconds. By default,
dead connections can remain open for up to 20 minutes.

- ``keepalive``: ``yes`` enables the socket option ``SO_KEEPALIVE``.
This causes some traffic for the keepalive probes, but allows
detecting disappeared clients even when there is no traffic.

- ``v6only``: ``no`` disables IPv4 support on IPv6 listeners
(``IPV6_V6ONLY``). The default is ``yes``.

- ``reuse_port``: ``yes`` enables the socket option ``SO_REUSEPORT``,
which allows multiple sockets to bind to the same port.

- ``zeroconf_service``: if specified, then register this listener as
Zeroconf service in the local Avahi daemon.

- ``zeroconf_interface``: publish the Zeroconf service only on the
given interface.
3 changes: 3 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ subdir('libcommon/src/lib/fmt')
subdir('libcommon/src/lib/sodium')
subdir('libcommon/src/lib/openssl')
subdir('libcommon/src/io')
subdir('libcommon/src/io/config')
subdir('libcommon/src/system')
subdir('libcommon/src/event')
subdir('libcommon/src/net')
Expand Down Expand Up @@ -143,6 +144,7 @@ executable(
'cm4all-lukko',
sources,
'src/Main.cxx',
'src/Config.cxx',
'src/Instance.cxx',
'src/Listener.cxx',
'src/Connection.cxx',
Expand All @@ -166,6 +168,7 @@ executable(
event_net_dep,
system_dep,
io_dep,
io_config_dep,
avahi_dep,
crypto_dep,
sodium_dep,
Expand Down
143 changes: 143 additions & 0 deletions src/Config.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright CM4all GmbH
// author: Max Kellermann <[email protected]>

#include "Config.hxx"
#include "net/IPv6Address.hxx"
#include "net/Parser.hxx"
#include "io/config/FileLineParser.hxx"
#include "io/config/ConfigParser.hxx"
#include "util/StringAPI.hxx"

#ifdef HAVE_AVAHI
#include "lib/avahi/Check.hxx"
#endif

// not defaulting to 22 until this project is fully-featured
static constexpr unsigned LUKKO_DEFAULT_PORT = 2200;

void
Config::Check()
{
if (listeners.empty()) {
listeners.emplace_front();
auto &l = listeners.front();
l.bind_address = IPv6Address{LUKKO_DEFAULT_PORT};
l.listen = 256;
l.tcp_user_timeout = 60000;
l.tcp_no_delay = true;
l.keepalive = true;
}
}

class LukkoConfigParser final : public NestedConfigParser {
Config &config;

class Listener final : public ConfigParser {
Config &parent;
ListenerConfig config;

public:
explicit Listener(Config &_parent):parent(_parent) {}

protected:
/* virtual methods from class ConfigParser */
void ParseLine(FileLineParser &line) override;
void Finish() override;
};

public:
explicit LukkoConfigParser(Config &_config) noexcept
:config(_config) {}

protected:
/* virtual methods from class NestedConfigParser */
void ParseLine2(FileLineParser &line) override;
};

void
LukkoConfigParser::Listener::ParseLine(FileLineParser &line)
{
const char *word = line.ExpectWord();

if (StringIsEqual(word, "bind")) {
config.bind_address = ParseSocketAddress(line.ExpectValueAndEnd(),
LUKKO_DEFAULT_PORT, true);
} else if (StringIsEqual(word, "interface")) {
config.interface = line.ExpectValueAndEnd();
} else if (StringIsEqual(word, "mode")) {
if (config.bind_address.IsNull() ||
config.bind_address.GetFamily() != AF_LOCAL)
throw LineParser::Error("'mode' works only with local sockets");

const char *s = line.ExpectValueAndEnd();
char *endptr;
const unsigned long value = strtoul(s, &endptr, 8);
if (endptr == s || *endptr != 0)
throw LineParser::Error("Not a valid octal value");

if (value & ~0777ULL)
throw LineParser::Error("Not a valid mode");

config.mode = value;
} else if (StringIsEqual(word, "mptcp")) {
config.mptcp = line.NextBool();
line.ExpectEnd();
} else if (StringIsEqual(word, "ack_timeout")) {
config.tcp_user_timeout = line.NextPositiveInteger() * 1000;
line.ExpectEnd();
} else if (StringIsEqual(word, "keepalive")) {
config.keepalive = line.NextBool();
line.ExpectEnd();
} else if (StringIsEqual(word, "v6only")) {
config.v6only = line.NextBool();
line.ExpectEnd();
} else if (StringIsEqual(word, "reuse_port")) {
config.reuse_port = line.NextBool();
line.ExpectEnd();
} else if (StringIsEqual(word, "zeroconf_service")) {
#ifdef HAVE_AVAHI
config.zeroconf_service = MakeZeroconfServiceType(line.ExpectValueAndEnd(),
"_tcp");
#else
throw std::runtime_error{"Zeroconf support is disabled"};
#endif // HAVE_AVAHI
} else
throw LineParser::Error("Unknown option");
}

void
LukkoConfigParser::Listener::Finish()
{
if (config.bind_address.IsNull())
throw LineParser::Error("Listener has no bind address");

config.Fixup();

parent.listeners.emplace_front(std::move(config));

ConfigParser::Finish();
}

void
LukkoConfigParser::ParseLine2(FileLineParser &line)
{
const char *word = line.ExpectWord();

if (StringIsEqual(word, "listener")) {
line.ExpectSymbolAndEol('{');
SetChild(std::make_unique<Listener>(config));
} else
throw LineParser::Error("Unknown option");
}

void
LoadConfigFile(Config &config, const char *path)
{
LukkoConfigParser parser(config);
VariableConfigParser v_parser(parser);
CommentConfigParser parser2(v_parser);
IncludeConfigParser parser3(path, parser2);

ParseConfigFile(path, parser3);
}
34 changes: 34 additions & 0 deletions src/Config.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright CM4all GmbH
// author: Max Kellermann <[email protected]>

#pragma once

#include "net/SocketConfig.hxx"
#include "config.h"

#include <forward_list>

struct ListenerConfig : SocketConfig {
#ifdef HAVE_AVAHI
std::string zeroconf_service;
#endif

ListenerConfig() {
listen = 256;
tcp_no_delay = true;
}
};

struct Config {
std::forward_list<ListenerConfig> listeners;

void Check();
};

/**
* Load and parse the specified configuration file. Throws an
* exception on error.
*/
void
LoadConfigFile(Config &config, const char *path);
5 changes: 3 additions & 2 deletions src/Instance.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// author: Max Kellermann <[email protected]>

#include "Instance.hxx"
#include "Config.hxx"
#include "Listener.hxx"
#include "Connection.hxx"
#include "key/Key.hxx"
Expand Down Expand Up @@ -72,9 +73,9 @@ Instance::DisableZeroconf() noexcept
#endif // HAVE_AVAHI

void
Instance::AddListener(UniqueSocketDescriptor s)
Instance::AddListener(const ListenerConfig &config)
{
listeners.emplace_front(*this, std::move(s));
listeners.emplace_front(*this, config.Create(SOCK_STREAM));

#ifdef HAVE_AVAHI
// TODO
Expand Down
3 changes: 2 additions & 1 deletion src/Instance.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <stdint.h>

struct ListenerConfig;
class Key;
class UniqueSocketDescriptor;
class Listener;
Expand Down Expand Up @@ -73,7 +74,7 @@ public:
void DisableZeroconf() noexcept;
#endif // HAVE_AVAHI

void AddListener(UniqueSocketDescriptor s);
void AddListener(const ListenerConfig &config);
void AddConnection(UniqueSocketDescriptor s) noexcept;

void Run() noexcept {
Expand Down
15 changes: 7 additions & 8 deletions src/Main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// author: Max Kellermann <[email protected]>

#include "Instance.hxx"
#include "Config.hxx"
#include "key/Ed25519Key.hxx"
#include "lib/avahi/Service.hxx"
#include "system/SetupProcess.hxx"
Expand Down Expand Up @@ -44,6 +45,10 @@ LoadHostKey(bool use_ed25519_host_key)
int
main(int, char **) noexcept
try {
Config config;
LoadConfigFile(config, "/etc/cm4all/lukko/lukko.conf");
config.Check();

const bool use_ed25519_host_key = true;

SetupProcess();
Expand All @@ -52,14 +57,8 @@ try {
LoadHostKey(use_ed25519_host_key),
};

{
SocketConfig config{IPv6Address{2200}};
config.listen = 256;
config.tcp_user_timeout = 60000;
config.tcp_no_delay = true;
config.keepalive = true;
instance.AddListener(config.Create(SOCK_STREAM));
}
for (const auto &i : config.listeners)
instance.AddListener(i);

#ifdef HAVE_LIBSYSTEMD
/* tell systemd we're ready */
Expand Down

0 comments on commit 478a07d

Please sign in to comment.