/* This file is part of Mailfromd.
   Copyright (C) 2005-2025 Sergey Poznyakoff

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <syslog.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <mailutils/mailutils.h>
#include <mailutils/alloc.h>

#include "libmf.h"
#include "srvman.h"

struct mfd_server {
	struct mfd_server *prev;    /* Link to the previous server */
	struct mfd_server *next;    /* Link to the next server */
	char *id;                   /* Server ID */
	struct sockaddr *sa;        /* Socket address */
	socklen_t salen;            /* Length of the sa */
	int backlog;                /* Backlog value for listen(2) */
	int fd;                     /* Socket descriptor */
	int flags;                  /* SRV_* flags */
	mfd_server_prefork_hook_t prefork_hook;  /* Pre-fork function */
	mfd_server_func_t conn;     /* Connection handler */
	mfd_srvman_hook_t free_hook;
	void *data;            /* Server-specific data */
	mu_acl_t acl;          /* Access Control List */
	size_t max_children;   /* Maximum number of sub-processes to run. */
	size_t num_children;   /* Current number of running sub-processes. */
	pid_t *pidtab;         /* Array of child PIDs */
	size_t pidtab_size;    /* Number of elements in pidtab */
};


struct srvman_param srvman_param;


typedef void (*sig_handler_t) (int);

union srvman_sockaddr {
	struct sockaddr sa;
	struct sockaddr_in s_in;
	struct sockaddr_un s_un;
};

struct srvman {
	struct mfd_server *head, *tail; /* List of servers */
	size_t num_children;        /* Current number of running
				       sub-processes. */
	sigset_t sigmask;           /* A set of signals to handle by the
				       manager.  */
	sig_handler_t sigtab[NSIG]; /* Keeps old signal handlers. */
};

static struct srvman srvman;
static mu_debug_handle_t debug_handle;

static sig_handler_t
set_signal(int sig, sig_handler_t handler)
{
	struct sigaction act, oldact;
	act.sa_handler = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(sig, &act, &oldact);
	return oldact.sa_handler;
}

static int volatile need_cleanup;
static int volatile stop;

void
mfd_srvman_stop()
{
	stop = 1;
}
	
static void
srvman_signal (int signo)
{
	switch (signo) {
	case SIGCHLD:
		need_cleanup = 1;
		break;
		
	default:
		/* FIXME: */
		stop = 1;
		break;
	}
}

static void
set_signal_handlers()
{
	int i;
	
	for (i = 1; i < NSIG; i++)
		if (sigismember (&srvman.sigmask, i))
			srvman.sigtab[i] = set_signal(i, srvman_signal);
}

static void
restore_signal_handlers()
{
	int i;
	
	for (i = 1; i < NSIG; i++)
		if (sigismember (&srvman.sigmask, i))
			set_signal(i, srvman.sigtab[i]);
}


struct sockaddr *
srvman_url_to_sockaddr(mu_url_t url, socklen_t *psalen)
{
	int rc;
	const char *sval;
	struct sockaddr *sa;
	socklen_t socklen;
	
	if (rc = mu_url_sget_scheme(url, &sval)) {
		mu_error(_("cannot get URL scheme: %s"), mu_strerror(rc));
		return NULL;
	}

	if (strcmp(sval, "unix") == 0
	    || strcmp(sval, "local") == 0) {
		struct sockaddr_un *s_un;
		size_t slen;
		
		rc = mu_url_sget_path(url, &sval);
		if (rc) {
			if (rc == MU_ERR_NOENT)
				rc = mu_url_sget_host(url, &sval);
			if (rc) {
				mu_error(_("cannot get URL path: %s"),
					 mu_strerror(rc));
				return NULL;
			}
		}
		slen = strlen(sval);
		if (slen >= sizeof s_un->sun_path) {
			mu_error(_("socket path name too long: %s"),
				 sval);
			return NULL;
		}
		socklen = sizeof (s_un[0]);
		sa = mu_alloc (socklen);
		s_un = (struct sockaddr_un *)sa;
		s_un->sun_family = AF_UNIX;
		strcpy(s_un->sun_path, sval);
	} else if (strcmp(sval, "inet") == 0) {
		struct sockaddr_in *s_in;
		unsigned n;
		short port;
		struct hostent *hp;

		if (rc = mu_url_get_port(url, &n)) {
			mu_error(_("cannot get URL port: %s"),
				 mu_strerror(rc));
			return NULL;
		}

		if (n == 0 || (port = n) != n) {
			mu_error(_("port out of range: %u"), n);
			return NULL;
		}

		if (rc = mu_url_sget_host(url, &sval)) {
			mu_error(_("cannot get URL host: %s"),
				 mu_strerror(rc));
			return NULL;
		}

		hp = gethostbyname(sval);
		if (!hp) {
			mu_error(_("unknown host name: %s"), sval);
			return NULL;
		}

		if (hp->h_addrtype != AF_INET || hp->h_length != 4) {
			mu_error(_("unsupported address family"));
			return NULL;
		}

		socklen = sizeof s_in[0];
		sa = mu_alloc(socklen);
		s_in = (struct sockaddr_in *)sa;
		s_in->sin_family = AF_INET;
		memcpy(&s_in->sin_addr, hp->h_addr, 4);
		s_in->sin_port = htons(port);
	} else if (strcmp(sval, "inet6") == 0) {
		struct addrinfo hints;
		struct addrinfo *res;
		const char *host = NULL;
		const char *port;

		memset(&hints, 0, sizeof(hints));
		hints.ai_family = AF_INET6;
		hints.ai_socktype = SOCK_STREAM;

		rc = mu_url_sget_host(url, &host);
		if (rc == MU_ERR_NOENT)
			hints.ai_flags |= AI_PASSIVE;
		else if (rc) {
			mu_error(_("cannot get URL host: %s"),
				 mu_strerror(rc));
			return NULL;
		}

		if (mu_url_sget_portstr(url, &port)) {
			mu_error(_("cannot get URL port: %s"),
				 mu_strerror(rc));
			return NULL;
		}

		rc = getaddrinfo(host, port, &hints, &res);

		switch (rc) {
		case 0:
			break;
		case EAI_SYSTEM:
			mu_error(_("%s:%s: cannot parse address: %s"),
				 host ? host : "NULL", port, strerror(errno));
			return NULL;
			
		case EAI_BADFLAGS:
		case EAI_SOCKTYPE:
			mu_error(_("%s:%d: internal error converting %s:%s"),
				 __FILE__, __LINE__,
				 host ? host : "NULL", port);
			return NULL;
			
		case EAI_MEMORY:
			mu_alloc_die();
			
		default:
			mu_error("%s:%s: %s",
				 host ? host : "NULL", port,
				 gai_strerror(rc));
			return NULL;
		}

		if (res->ai_next) {
			char hostbuf[NI_MAXHOST], servbuf[NI_MAXSERV];
			rc = getnameinfo(res->ai_addr, res->ai_addrlen,
					 hostbuf, sizeof hostbuf,
					 servbuf, sizeof servbuf,
					 NI_NUMERICHOST|NI_NUMERICSERV);
			
			mu_diag_output(MU_DIAG_WARNING, 
				    _("%s:%s resolves to several addresses; "
				      "using %s:%s"),
				       host, port, hostbuf, servbuf);
		}
	
		socklen = res->ai_addrlen;
		sa = mu_alloc(socklen);
		memcpy(sa, res->ai_addr, res->ai_addrlen);
		freeaddrinfo(res);
	} else {
		mu_error(_("unsupported protocol: %s"), sval);
		return NULL;
	}

	if (psalen)
		*psalen = socklen;
	return sa;
}



/* Yield 1 if SRV has run out of the children limit */
#define SERVER_BUSY(srv)			\
	((srv)->max_children && (srv)->num_children >= (srv)->max_children)

static void
register_child(struct mfd_server *srv, pid_t pid)
{
	size_t i;

	mu_debug(debug_handle, MU_DEBUG_TRACE5, 
		 ("registering child %lu", (unsigned long)pid));
	for (i = 0; i < srv->pidtab_size; i++)
		if (srv->pidtab[i] == 0)
			break;
	if (i == srv->pidtab_size) {
		int j;
		if (srv->pidtab_size == 0)
			srv->pidtab_size = DEFAULT_PIDTAB_SIZE;
		srv->pidtab = mu_2nrealloc(srv->pidtab,
					 &srv->pidtab_size,
					 sizeof(srv->pidtab[0]));
		for (j = i; j < srv->pidtab_size; j++)
			srv->pidtab[j] = 0;
			
	}
	srv->pidtab[i] = pid;
	srv->num_children++;
	srvman.num_children++;
}

static void
report_exit_status(const char *tag, pid_t pid, int status, int expect_term)
{
	if (WIFEXITED(status)) {
		if (WEXITSTATUS(status) == 0)
			mu_debug(debug_handle, MU_DEBUG_TRACE0, 
                              ("%s [%lu] exited successfully",
			       tag, (unsigned long) pid));
		else
			mu_error(_("%s [%lu] failed with status %d"),
				 tag, (unsigned long) pid,
				 WEXITSTATUS(status));
	} else if (WIFSIGNALED (status)) {
		if (expect_term && WTERMSIG(status) == SIGTERM)
			mu_debug(debug_handle, MU_DEBUG_TRACE0, 
				 ("%s [%lu] terminated on signal %d",
				  tag, (unsigned long) pid,
				  WTERMSIG(status)));
		else
			mu_error(_("%s [%lu] terminated on signal %d"),
				 tag, (unsigned long) pid,
				 WTERMSIG(status));
	} else if (WIFSTOPPED(status))
		mu_error(_("%s [%lu] stopped on signal %d"),
			 tag, (unsigned long) pid,
			 WSTOPSIG(status));
	else
		mu_error(_("%s [%lu] terminated with unrecognized status"),
			 tag, (unsigned long) pid);
}

/* Remove (unregister) PID from the list of running instances and
   log its exit STATUS.
   Return 1 to command main loop to recompute the set of active
   descriptors. */
static int
unregister_child(pid_t pid, int status)
{
	struct mfd_server *srv;

	mu_debug(debug_handle, MU_DEBUG_TRACE5, 
		 ("unregistering child %lu (status %x)",
		  (unsigned long)pid, status));
	for (srv = srvman.head; srv; srv = srv->next) {
		size_t i;

		for (i = 0; i < srv->pidtab_size; i++)
			if (srv->pidtab[i] == pid) {
				int rc = SERVER_BUSY(srv);
				srv->pidtab[i] = 0;
				srv->num_children--;
				srvman.num_children--;
				/* FIXME: expect_term? */
				report_exit_status(srv->id, pid, status, 0);
				return rc;
			}
	}
	/* FIXME */
	return 0;
}

static int
children_cleanup()
{
	int rc = 0;
	pid_t pid;
	int status;

	mu_debug(debug_handle, MU_DEBUG_TRACE1, ("cleaning up subprocesses"));
	while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
		rc |= unregister_child(pid, status);
	return rc;
}


static void
server_remove(struct mfd_server *srv)
{
	struct mfd_server *p;

	mu_debug(debug_handle, MU_DEBUG_TRACE4,
		 ("removing server %s", srv->id));
	if ((p = srv->prev) != NULL)
		p->next = srv->next;
	else
		srvman.head = srv->next;

	if ((p = srv->next) != NULL)
		p->prev = srv->prev;
	else
		srvman.tail = srv->prev;
}

static void
server_signal_children(struct mfd_server *srv, int sig)
{
	int i;

	mu_debug(debug_handle, MU_DEBUG_TRACE4, 
		 ("server %s: sending children signal %d", srv->id, sig));
	for (i = 0; i < srv->pidtab_size; i++)
		if (srv->pidtab[i])
			kill(srv->pidtab[i], sig);
}

void
mfd_server_shutdown(struct mfd_server *srv)
{
	if (srv->fd == -1)
		return;
			     
	mu_debug(debug_handle, MU_DEBUG_TRACE1,
		 ("shutting down %s", srv->id));
	close(srv->fd);
	srv->fd = -1;
}

struct mfd_server *
mfd_server_new(const char *id, mu_url_t url, mfd_server_func_t conn, int flags)
{
	struct mfd_server *srv;
	struct sockaddr *sa;
	socklen_t salen;

	sa = srvman_url_to_sockaddr(url, &salen);
	if (!sa)
		return NULL;
	
	srv = mu_zalloc(sizeof(*srv));
	srv->id = mu_strdup(id);
	srv->sa = sa;
	srv->salen = salen;
	srv->backlog = 8;
	srv->conn = conn;
	srv->flags = flags;
	return srv;
}

void
mfd_server_free(struct mfd_server *srv)
{
	server_remove(srv);
	if (srv->free_hook)
		srv->free_hook(srv->data);
	free(srv->id);
	free(srv->sa);
	mu_acl_destroy(&srv->acl);
	free(srv->pidtab);
	free(srv);
}

void
mfd_server_set_prefork_hook(struct mfd_server *srv,
			    mfd_server_prefork_hook_t hook)
{
	srv->prefork_hook = hook;
}

void
mfd_server_set_data(struct mfd_server *srv,
		    void *data,
		    mfd_srvman_hook_t free_hook)
{
	srv->data = data;
	srv->free_hook = free_hook;
}

void
mfd_server_set_max_children(struct mfd_server *srv, size_t n)
{
	srv->max_children = n;
}

void
mfd_server_set_acl(struct mfd_server *srv, mu_acl_t acl)
{
	srv->acl = acl;
}

void
mfd_server_set_backlog(struct mfd_server *srv, int value)
{
	srv->backlog = value;
}

void
mfd_srvman_attach_server(struct mfd_server *srv)
{
	srv->next = NULL;
	srv->prev = srvman.tail;
	if (srvman.tail)
		srvman.tail->next = srv;
	else
		srvman.head = srv;
	srvman.tail = srv;
}


static int
check_acl(const char *id, mu_acl_t acl, struct sockaddr *sa, socklen_t salen)
{
	mu_acl_result_t res;
	int rc = mu_acl_check_sockaddr(acl, sa, salen, &res);
	char *p = mu_sys_sockaddr_to_astr(sa, salen);
	
	if (rc) {
		mu_error(_("server %s: access from %s blocked: "
			   "cannot check ACLs: %s"),
			 id, p, mu_strerror(rc));
	} else {
		switch (res) {
		case mu_acl_result_undefined:
			mu_diag_output(MU_DIAG_INFO,
				       _("server %s: undefined ACL result; "
					 "access from %s allowed"),
				       id, p);
			break;
			
		case mu_acl_result_accept:
			mu_debug(debug_handle, MU_DEBUG_TRACE0, 
                              ("server %s: allowed access from %s", id, p));
			break;
	      
		case mu_acl_result_deny:
			mu_error(_("server %s: access from %s blocked"),
				 id, p);
			rc = 1;
		}
	}
	free(p);
	return rc;
}

static void
server_run(int connfd, struct mfd_server *srv,
	   struct sockaddr *sa, socklen_t salen)
{
	if (srvman_param.acl && check_acl(srv->id, srvman_param.acl,
					  sa, salen)) 
		return;
	if (srv->acl && check_acl(srv->id, srv->acl, sa, salen)) 
		return;

	if (((srvman_param.flags | srv->flags) & SRV_SINGLE_PROCESS)) {
		if ((!srvman_param.prefork_hook
		     || srvman_param.prefork_hook(sa, salen,
						  srvman_param.data) == 0)
		    && (!srv->prefork_hook
			|| srv->prefork_hook(srv->id,
					     sa, salen,
					     srv->data,
					     srvman_param.data) == 0))
			srv->conn(srv->id,
				  connfd, sa, salen,
				  srv->data,
				  srvman_param.data);
	} else {
		pid_t pid;

		if (srv->prefork_hook
		    && srv->prefork_hook(srv->id,
					 sa, salen,
					 srv->data, srvman_param.data))
			return;
		
		pid = fork();
		if (pid == -1)
			mu_error("fork: %s", strerror(errno));
		else if (pid == 0) {
			/* Child.  */
			FD_SET(connfd, &srvman_param.keepfds);
			close_fds_except(&srvman_param.keepfds);
			restore_signal_handlers();
			mu_onexit_reset();
			exit(srv->conn(srv->id,
				       connfd,
				       sa, salen,
				       srv->data, srvman_param.data));
		} else
			register_child(srv, pid);
	}
}

/* Accept incoming connection for server SRV.
   Return 1 to command main loop to recompute the set of active
   descriptors. */
static int
server_accept(struct mfd_server *srv)
{
	int connfd;
	union srvman_sockaddr client;
	socklen_t size = sizeof(client);

	if (srv->fd == -1) {
		mu_error(_("removing shut down server %s"),
			 srv->id);
		mfd_server_free(srv);
		return 1;
	}
	
	if (SERVER_BUSY(srv)) {
		mu_error(_("server %s: too many children (%lu)"),
			 srv->id, (unsigned long) srv->num_children);
		return 1;
	}
	
	connfd = accept(srv->fd, &client.sa, &size);
	if (connfd == -1) {
		if (errno != EINTR) {
			mu_error(_("server %s: accept failed: %s"),
				 srv->id, mu_strerror(errno));
			mfd_server_shutdown(srv);
			mfd_server_free(srv);
			return 1;
		} /* FIXME: Call srv->intr otherwise? */
	} else {
		server_run(connfd, srv, &client.sa, size);
		close(connfd);
	}
	return 0;
}

static int
connection_loop(fd_set *fdset)
{
	struct mfd_server *srv;
	int rc = 0;
	for (srv = srvman.head; srv; ) {
		struct mfd_server *next = srv->next;
		if (FD_ISSET(srv->fd, fdset))
			rc |= server_accept(srv);
		srv = next;
	}
	return rc;
}

int
compute_fdset(fd_set *fdset)
{
	struct mfd_server *p;
	int maxfd = 0;
	FD_ZERO(fdset);
	for (p = srvman.head; p; p = p->next) {
		if (SERVER_BUSY(p))
			continue;
		FD_SET(p->fd, fdset);
		if (p->fd > maxfd)
			maxfd = p->fd;
	}
	mu_debug(debug_handle, MU_DEBUG_TRACE3,
		 ("recomputed fdset: %d fds", maxfd));
	return maxfd;
}

void
mfd_srvman_run(sigset_t *set)
{
	int recompute_fd = 1;
	int maxfd;
	fd_set fdset;

	if (!srvman.head)
		return;

	mu_debug(debug_handle, MU_DEBUG_TRACE1, ("server manager starting"));
	if (set)
		srvman.sigmask = *set;
	else
		sigemptyset(&srvman.sigmask);
	sigaddset(&srvman.sigmask, SIGCHLD);
	set_signal_handlers();
	if (srvman_param.shutdown_timeout == 0)
		srvman_param.shutdown_timeout = DEFAULT_SHUTDOWN_TIMEOUT;

	for (stop = 0; srvman.head && !stop;) {
		int rc;
		struct timeval *to;
		fd_set rdset;

		if (need_cleanup) {
			need_cleanup = 0;
			recompute_fd = children_cleanup();
		}

		if (recompute_fd) {
			maxfd = compute_fdset(&fdset);
			recompute_fd = 0;
		}

		if (!maxfd) {
			mu_debug(debug_handle,
				 MU_DEBUG_TRACE1, ("no active fds, pausing"));
			pause();
			recompute_fd = 1;
			continue;
		}
		
		if (srvman_param.max_children
		    && srvman.num_children >= srvman_param.max_children) {
			mu_error(_("too many children (%lu)"),
				 (unsigned long) srvman.num_children);
			pause();
			continue;
		}
		
		if (srvman_param.idle_hook
		    && srvman_param.idle_hook(srvman_param.data)) {
			mu_debug(debug_handle, MU_DEBUG_TRACE1, 
				 ("break requested by idle hook"));
			break;
		}
		
		if (stop)
			break;
		    
		rdset = fdset;
		to = NULL; /* FIXME */
		rc = select(maxfd + 1, &rdset, NULL, NULL, to);
		if (rc == -1 && errno == EINTR)
			continue;
		if (rc < 0) {
			mu_error(_("select failed: %s"),
				 mu_strerror(errno));
			break;
		}
		recompute_fd = connection_loop(&rdset);
	}

	restore_signal_handlers();
	mu_debug(debug_handle, MU_DEBUG_TRACE1, ("server manager finishing"));
}

static int
server_prep(struct mfd_server *srv, int fd)
{
	struct stat st;
	struct sockaddr_un *s_un;
	int t;

	switch (srv->sa->sa_family) {
	case AF_UNIX:
		s_un = (struct sockaddr_un *) srv->sa;
	
		if (stat(s_un->sun_path, &st)) {
			if (errno != ENOENT) {
				mu_error(_("%s: file %s exists but cannot be stat'd: %s"),
					 srv->id,
					 s_un->sun_path,
					 mu_strerror(errno));
				return 1;
			}
		} else if (!S_ISSOCK(st.st_mode)) {
			mu_error(_("%s: file %s is not a socket"),
				 srv->id, s_un->sun_path);
			return 1;
		} else if (!(srvman_param.flags & SRV_KEEP_EXISTING)
			   && !(srv->flags & SRV_KEEP_EXISTING)) {
			if (unlink(s_un->sun_path)) {
				mu_error(_("%s: cannot unlink file %s: %s"),
					 srv->id, s_un->sun_path,
					 mu_strerror(errno));
				return 1;
			}
		} else {
			mu_error(_("socket `%s' already exists"),
				 s_un->sun_path);
			return 1;
		}
		break;

	case AF_INET:
		if (!(srvman_param.flags & SRV_KEEP_EXISTING)
		    && !(srv->flags & SRV_KEEP_EXISTING)) {
			t = 1;	 
			setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
				   &t, sizeof(t));
		}
	}
  
	if (bind(fd, srv->sa, srv->salen) == -1) {
		char *p = mu_sys_sockaddr_to_astr(srv->sa, srv->salen);
		mu_error(_("%s: cannot bind to %s: %s"),
			   srv->id, p, mu_strerror(errno));
		free(p);
		return 1;
	}

	if (listen(fd, srv->backlog) == -1) {
		char *p = mu_sys_sockaddr_to_astr(srv->sa, srv->salen);
		mu_error(_("%s: listen on %s failed: %s"),
			   srv->id, p, mu_strerror(errno));
		free(p);
		return 1;
	}
	return 0;
}	

static int
server_open(struct mfd_server *srv)
{
	int fd = socket(srv->sa->sa_family, SOCK_STREAM, 0);
	if (fd == -1) {
		mu_error("%s: socket: %s", srv->id, mu_strerror(errno));
		return 1;
	}

	if (server_prep(srv, fd)) {
		close(fd);
		return 1;
	}
	srv->fd = fd;
	return 0;
}


int
mfd_srvman_open()
{
	struct mfd_server *p;

	mu_debug(debug_handle, MU_DEBUG_TRACE1, ("opening servers"));
	if (!srvman.head)
		mu_error(_("no servers configured"));
	for (p = srvman.head; p; ) {
		struct mfd_server *next = p->next;
		if (server_open(p))
			server_remove(p);
		p = next;
	}
	return srvman.head == NULL;
}

void
mfd_srvman_shutdown()
{
	struct mfd_server *p;
	time_t start = time(NULL);
	
	for (p = srvman.head; p; p = p->next)
		server_signal_children(p, SIGTERM);

	do {
		children_cleanup();
		if (srvman.num_children == 0)
			break;
		sleep(1);
	} while (time(NULL) - start < srvman_param.shutdown_timeout);
	
	mu_debug(debug_handle, MU_DEBUG_TRACE1, ("shutting down servers"));
	for (p = srvman.head; p; p = p->next) {
		server_signal_children(p, SIGKILL);
		mfd_server_shutdown(p);
	}
}
		
void
mfd_srvman_free()
{
	struct mfd_server *p;

	for (p = srvman.head; p; ) {
		struct mfd_server *next = p->next;
		mfd_server_free(p);
		p = next;
	}
	if (srvman_param.free_hook)
		srvman_param.free_hook(srvman_param.data);
	mu_acl_destroy(&srvman_param.acl);
}

size_t
mfd_srvman_count_servers()
{
	size_t count = 0;
	struct mfd_server *p;

	for (p = srvman.head; p; p = p->next)
		count++;
	return count;
}

void
srvman_init()
{
	debug_handle = mu_debug_register_category("srvman");
}
