diff --git a/configuration.c b/configuration.c index abf59d4..7fce1d5 100644 --- a/configuration.c +++ b/configuration.c @@ -77,6 +77,7 @@ static int readConfigTime(xmlTextReaderPtr reader, time_t * value, const char * static int configListen(WebdavdConfiguration * config, xmlTextReaderPtr reader, const char * configFile) { //80localhostdisabled + //http int index = config->daemonCount++; config->daemons = reallocSafe(config->daemons, sizeof(*config->daemons) * config->daemonCount); memset(&config->daemons[index], 0, sizeof(config->daemons[index])); @@ -90,6 +91,8 @@ static int configListen(WebdavdConfiguration * config, xmlTextReaderPtr reader, result = readConfigInt(reader, &config->daemons[index].port, configFile); } else if (!strcmp(xmlTextReaderConstLocalName(reader), "host")) { result = readConfigString(reader, &config->daemons[index].host); + } else if (!strcmp(xmlTextReaderConstLocalName(reader), "name")) { + result = readConfigString(reader, &config->daemons[index].name); } else if (!strcmp(xmlTextReaderConstLocalName(reader), "encryption")) { const char * encryptionString; result = stepOverText(reader, &encryptionString); @@ -148,7 +151,7 @@ static int configListen(WebdavdConfiguration * config, xmlTextReaderPtr reader, result = stepOver(reader); } } - if (config->daemons[index].port == -1) { + if (!config->socketActivation && config->daemons[index].port == -1) { stdLogError(0, "port not specified for listen in %s", configFile); exit(1); } @@ -282,6 +285,17 @@ static int configUnprotectOptions(WebdavdConfiguration * config, xmlTextReaderPt return result; } +static int configSocketActivation(WebdavdConfiguration * config, xmlTextReaderPtr reader, const char * configFile) { + // +#ifdef HAVE_SYSTEMD + config->socketActivation = true; + return stepOver(reader); +#else + stdLogError(0, "Socket activation support not enable at build time"); + return false; +#endif +} + /////////////////////////// // End Handler Functions // /////////////////////////// @@ -314,6 +328,7 @@ static const ConfigurationFunction configFunctions[] = { { .nodeName = "rap-timeout", .func = &configRapTimeout }, // { .nodeName = "restricted", .func = &configRestricted }, // { .nodeName = "session-timeout", .func = &configSessionTimeout }, // + { .nodeName = "socket-activation", .func = &configSocketActivation }, // { .nodeName = "ssl-cert", .func = &configConfigSSLCert }, // { .nodeName = "static-response-dir", .func = &configResponseDir }, // { .nodeName = "unprotect-options", .func = &configUnprotectOptions } // diff --git a/configuration.h b/configuration.h index 881a02c..704e26c 100644 --- a/configuration.h +++ b/configuration.h @@ -2,6 +2,7 @@ #define WEBDAV_CONFIGURATION_H #include +#include ////////////////////////////////////// // Webdavd Configuration Structures // @@ -9,7 +10,8 @@ typedef struct DaemonConfig { int port; - const char * host; + const char * host; // For opening socket ourselves + const char * name; // For socket activation, i.e. open socket passed to daemon int sslEnabled; int forwardToIsEncrypted; int forwardToPort; @@ -55,6 +57,9 @@ typedef struct WebdavdConfiguration { // OPTIONS Requests int unprotectOptions; + // Use systemd/xinetd style socket based activation + bool socketActivation; + } WebdavdConfiguration; extern WebdavdConfiguration config; diff --git a/makefile b/makefile index 83a9dc5..c12a713 100644 --- a/makefile +++ b/makefile @@ -1,17 +1,22 @@ CFLAGS=-O3 -s STATIC_FLAGS=-Werror -Wall -Wno-pointer-sign -Wno-unused-result -std=gnu99 -pthread +ifdef HAVE_SYSTEMD +DEFS+=-DHAVE_SYSTEMD=1 +LIBSYSTEMD=-lsystemd +endif + all: build/rap build/webdavd ls -lh $^ build/webdavd: build/webdavd.o build/shared.o build/configuration.o build/xml.o - gcc ${CFLAGS} ${STATIC_FLAGS} -o $@ $(filter %.o,$^) -lmicrohttpd -lxml2 -lgnutls -luuid + gcc ${CFLAGS} ${STATIC_FLAGS} -o $@ $(filter %.o,$^) -lmicrohttpd -lxml2 -lgnutls -luuid ${LIBSYSTEMD} build/rap: build/rap.o build/shared.o build/xml.o gcc ${CFLAGS} ${STATIC_FLAGS} -o $@ $(filter %.o,$^) -lpam -lxml2 build/%.o: %.c makefile | build - gcc ${CFLAGS} ${STATIC_FLAGS} -MMD -o $@ $(filter %.c,$^) -I/usr/include/libxml2 -c + gcc ${CFLAGS} ${STATIC_FLAGS} ${DEFS} -MMD -o $@ $(filter %.c,$^) -I/usr/include/libxml2 -c build: mkdir $@ diff --git a/package-control/webdavd.spec b/package-control/webdavd.spec index fc356e5..430ccf4 100644 --- a/package-control/webdavd.spec +++ b/package-control/webdavd.spec @@ -13,6 +13,7 @@ BuildRequires: libxml2-devel BuildRequires: pam-devel BuildRequires: libuuid-devel BuildRequires: make +BuildRequires: systemd-devel Requires: gnutls Requires: libmicrohttpd @@ -36,7 +37,7 @@ webdavd is a WebDAV server designed to be a replace for SMBA providing access to %setup -n WebDAV-Daemon-%{version} %build -%make_build +%make_build HAVE_SYSTEMD=1 %install install -Dpm 755 build/webdavd %{buildroot}%{_sbindir}/webdavd @@ -46,6 +47,7 @@ install -Dpm 644 package-with/conf.xml %{buildroot}%{_sysconfdir}/webdavd install -d %{buildroot}%{_datadir}/webdavd install -Dpm 644 package-with/share/* %{buildroot}%{_datadir}/webdavd install -Dpm 644 package-with/systemd.service %{buildroot}%{_prefix}/lib/systemd/system/webdavd.service +install -Dpm 644 package-with/webdavd-*.socket %{buildroot}%{_prefix}/lib/systemd/system install -Dpm 644 package-with/logrotate.conf %{buildroot}%{_sysconfdir}/logrotate.d/webdavd @@ -57,6 +59,7 @@ install -Dpm 644 package-with/logrotate.conf %{buildroot}%{_sysconfdir}/logrotat %{_prefix}/lib/webdavd/webdav-worker %{_datadir}/webdavd/* %{_prefix}/lib/systemd/system/webdavd.service +%{_prefix}/lib/systemd/system/webdavd-*.socket %config %{_sysconfdir}/pam.d/webdavd %config %{_sysconfdir}/webdavd %config %{_sysconfdir}/logrotate.d/webdavd diff --git a/package-with/conf.xml b/package-with/conf.xml index b7bce47..2555f92 100644 --- a/package-with/conf.xml +++ b/package-with/conf.xml @@ -9,6 +9,18 @@ default port. Default host "any ip". Default encryption is "none". The following will listening on all ips and be unencrypted --> + + + 80 @@ -17,6 +29,11 @@ be convertied to an IP before binding. --> + + http + none + https + diff --git a/package-with/webdavd-http.socket b/package-with/webdavd-http.socket new file mode 100644 index 0000000..c446cbb --- /dev/null +++ b/package-with/webdavd-http.socket @@ -0,0 +1,14 @@ +[Unit] +Description=WebDAV server on HTTP socket + +[Socket] +Accept=no +ListenStream=80 +FileDescriptorName=http +DeferAcceptSec=1 +Service=webdavd.service +# Add this to only allow connections over localhost +# BindToDevice=lo + +[Install] +WantedBy=sockets.target diff --git a/package-with/webdavd-https.socket b/package-with/webdavd-https.socket new file mode 100644 index 0000000..6ea5108 --- /dev/null +++ b/package-with/webdavd-https.socket @@ -0,0 +1,14 @@ +[Unit] +Description=WebDAV server on HTTPS socket + +[Socket] +Accept=no +ListenStream=443 +FileDescriptorName=https +DeferAcceptSec=1 +Service=webdavd.service +# Add this to only allow connections over localhost +# BindToDevice=lo + +[Install] +WantedBy=sockets.target diff --git a/webdavd.c b/webdavd.c index e9c288c..8315e71 100644 --- a/webdavd.c +++ b/webdavd.c @@ -20,6 +20,10 @@ #include #include +#if HAVE_SYSTEMD +#include +#endif + //////////////// // Structures // //////////////// @@ -90,6 +94,16 @@ typedef struct FDResponseData { RAP * session; } FDResponseData; +// Sockets passed to us by systemd +typedef struct PassedSockets { + int count; + struct { + int fd; + char * name; + bool used; // For us to mark socket as in use + } socket[]; +} PassedSockets; + //////////////////// // End Structures // //////////////////// @@ -1912,6 +1926,61 @@ static void initializeEnvVariables() { else unsetenv("WEBDAVD_CHROOT_PATH"); } +static void freePassedSockets(PassedSockets * ps) { + if (ps) { + for (int i = 0; i < ps->count; i++) + free(ps->socket[i].name); // Allocated by libsystemd + freeSafe(ps); // Allocated by us + } +} + +#if HAVE_SYSTEMD +static void initializeSocketActivation(PassedSockets ** socketsRet) { + if (!config.socketActivation) { + *socketsRet = NULL; + return; + } + + char **names; + // This will set close on exec on all sockets + int n = sd_listen_fds_with_names(1, &names); + if (n <= 0) { + *socketsRet = NULL; + return; + } + PassedSockets * ps = mallocSafe(sizeof(*ps) + sizeof(*ps->socket) * n); + ps->count = n; + + bool error = false; + for (int i = 0; i < n; i++) { + ps->socket[i].fd = SD_LISTEN_FDS_START + i; + ps->socket[i].name = names[i]; // Take ownership of string, need to free on exit + ps->socket[i].used = false; + int ret = sd_is_socket(ps->socket[i].fd, AF_UNSPEC, SOCK_STREAM, true); + if (ret <= 0) { + stdLogError(-ret, "Socket #%d '%s' unstuitable for WebDAV", i, names[i]); + error = true; + } + } + free(names); + + if (error) { + // Just clean up stuct now and refuse to use any sockets + freePassedSockets(ps); + ps = NULL; + } + + *socketsRet = ps; + return; +} +#else +// Only supported with systemd. The old inetd system on stdin could work too, +// but would need some more code to support it. +static void initializeSocketActivation(PassedSockets ** socketsRet) { + *socketsRet = NULL; +} +#endif + //////////////////////// // End Initialisation // //////////////////////// @@ -1960,6 +2029,7 @@ static void runServer() { if (!lockToUser(config.restrictedUser, NULL)) { exit(1); } + PassedSockets *passedSocks; initializeLogs(); initializeStaticResponses(); @@ -1967,50 +2037,92 @@ static void runServer() { initializeLockDB(); initializeSSL(); initializeEnvVariables(); + initializeSocketActivation(&passedSocks); + + if (config.socketActivation && !passedSocks) { + stdLogError(0, "Configured to use systemd-socket activation but no sockets passed"); + return; + } // Start up the daemons daemons = mallocSafe(sizeof(*daemons) * config.daemonCount); for (int i = 0; i < config.daemonCount; i++) { - struct sockaddr_in6 address; - if (getBindAddress(&address, &config.daemons[i])) { - MHD_AccessHandlerCallback callback; - if (config.daemons[i].forwardToPort) { - callback = (MHD_AccessHandlerCallback) &answerForwardToRequest; - } else { - callback = (MHD_AccessHandlerCallback) &answerToRequest; - } - - if (config.daemons[i].sslEnabled) { - // https - if (sslCertificateCount == 0) { - stdLogError(0, "No certificates available for ssl %s:%d", - config.daemons[i].host ? config.daemons[i].host : "", config.daemons[i].port); + unsigned int flags = MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DUAL_STACK | MHD_USE_PEDANTIC_CHECKS; + // Addtional options of MHD_start_daemon. Needs terminating MHD_OPTION_END entry. + struct MHD_OptionItem ops[3]; + int nops = 0; + + if (config.socketActivation) { + int listenFd = -1; + // Socket should have been passed to us already + if (config.daemons[i].name) { + // Find by name + for (int j = 0; j < passedSocks->count; j++) { + if (!strcmp(passedSocks->socket[j].name, config.daemons[i].name)) { + if (passedSocks->socket[j].used) { + stdLogError(0, "Socket '%s' for listening port #%d has already been used", config.daemons[i].name, i); + continue; + } + listenFd = passedSocks->socket[j].fd; + passedSocks->socket[j].used = true; + } + } + if (listenFd == -1) { + stdLogError(0, "No socket named '%s' was passed to webdavd by systemd", config.daemons[i].name); continue; } - daemons[i] = MHD_start_daemon( - MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DUAL_STACK | MHD_USE_PEDANTIC_CHECKS - | MHD_USE_SSL, 0 /* ignored */, NULL, NULL, // - callback, &config.daemons[i], // - MHD_OPTION_SOCK_ADDR, &address, // Specifies both host and port - MHD_OPTION_HTTPS_CERT_CALLBACK, &sslSNICallback, // enable ssl - MHD_OPTION_PER_IP_CONNECTION_LIMIT, config.maxConnectionsPerIp, // - MHD_OPTION_END); } else { - // http - daemons[i] = MHD_start_daemon( - MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DUAL_STACK | MHD_USE_PEDANTIC_CHECKS, - 0 /* ignored */, - NULL, NULL, // - callback, &config.daemons[i], // - MHD_OPTION_SOCK_ADDR, &address, // Specifies both host and port - MHD_OPTION_PER_IP_CONNECTION_LIMIT, config.maxConnectionsPerIp, // - MHD_OPTION_END); + // Assign sockets to daemons in order listed + if (i >= passedSocks->count || passedSocks->socket[i].used) { + stdLogError(0, "Socket for listening port #%d not passed via socket activation", i); + continue; + } + listenFd = passedSocks->socket[i].fd; + passedSocks->socket[i].used = true; + } + ops[nops++] = (struct MHD_OptionItem){ MHD_OPTION_LISTEN_SOCKET, (MHD_socket)listenFd }; + } else { + struct sockaddr_in6 address; + if (!getBindAddress(&address, &config.daemons[i])) { + // Already printed error message in getBindAddress + continue; } - if (!daemons[i]) { - stdLogError(errno, "Unable to initialise daemon on port %d", config.daemons[i].port); + // Specifies both host and port + ops[nops++] = (struct MHD_OptionItem){ MHD_OPTION_SOCK_ADDR, 0, &address }; + } + + MHD_AccessHandlerCallback callback; + if (config.daemons[i].forwardToPort) { + callback = (MHD_AccessHandlerCallback) &answerForwardToRequest; + } else { + callback = (MHD_AccessHandlerCallback) &answerToRequest; + } + + if (config.daemons[i].sslEnabled) { + // https + if (sslCertificateCount == 0) { + stdLogError(0, "No certificates available for ssl %s:%d", + config.daemons[i].name ? config.daemons[i].name : + config.daemons[i].host ? config.daemons[i].host : "", + config.daemons[i].port); + continue; } + flags |= MHD_USE_SSL; + ops[nops++] = (struct MHD_OptionItem){ MHD_OPTION_HTTPS_CERT_CALLBACK, 0, &sslSNICallback }; + } + + ops[nops++] = (struct MHD_OptionItem){ MHD_OPTION_END }; + daemons[i] = MHD_start_daemon(flags, 0 /* ignored */, NULL, NULL, + callback, &config.daemons[i], // + MHD_OPTION_PER_IP_CONNECTION_LIMIT, config.maxConnectionsPerIp, // + MHD_OPTION_ARRAY, ops, + MHD_OPTION_END); + if (!daemons[i]) { + stdLogError(errno, "Unable to initialise daemon on port %d", config.daemons[i].port); } } + + freePassedSockets(passedSocks); } int main(int argCount, char ** args) {