]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/casperd/casperd.c
MFV r267843: update file/libmagic to 5.19.
[FreeBSD/FreeBSD.git] / sbin / casperd / casperd.c
1 /*-
2  * Copyright (c) 2012 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by Pawel Jakub Dawidek under sponsorship from
6  * the FreeBSD Foundation.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32
33 #include <sys/types.h>
34 #include <sys/capsicum.h>
35 #include <sys/queue.h>
36 #include <sys/socket.h>
37 #include <sys/stat.h>
38 #include <sys/un.h>
39
40 #include <assert.h>
41 #include <dirent.h>
42 #include <err.h>
43 #include <errno.h>
44 #include <fcntl.h>
45 #include <libutil.h>
46 #include <paths.h>
47 #include <stdbool.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <strings.h>
52 #include <unistd.h>
53
54 #include <libcapsicum.h>
55 #include <libcapsicum_impl.h>
56 #include <libcasper.h>
57 #include <libcasper_impl.h>
58 #include <msgio.h>
59 #include <nv.h>
60 #include <pjdlog.h>
61
62 #include "msgio.h"
63
64 #include "zygote.h"
65
66 #define CASPERD_PIDFILE         "/var/run/casperd.pid"
67 #define CASPERD_SERVCONFDIR     "/etc/casper"
68 #define CASPERD_SOCKPATH        "/var/run/casper"
69
70 typedef void service_function_t(struct service_connection *, const nvlist_t *,
71     nvlist_t *);
72
73 struct casper_service {
74         const char      *cs_name;
75         const char      *cs_execpath;
76         struct service  *cs_service;
77         nvlist_t        *cs_attrs;
78         TAILQ_ENTRY(casper_service) cs_next;
79 };
80
81 static TAILQ_HEAD(, casper_service) casper_services =
82     TAILQ_HEAD_INITIALIZER(casper_services);
83
84 #define SERVICE_IS_CORE(service)        ((service)->cs_execpath == NULL)
85
86 static void service_external_execute(int chanfd);
87
88 #define KEEP_ERRNO(work)        do {                                    \
89         int _serrno;                                                    \
90                                                                         \
91         _serrno = errno;                                                \
92         work;                                                           \
93         errno = _serrno;                                                \
94 } while (0)
95
96 static struct casper_service *
97 service_find(const char *name)
98 {
99         struct casper_service *casserv;
100
101         TAILQ_FOREACH(casserv, &casper_services, cs_next) {
102                 if (strcmp(casserv->cs_name, name) == 0)
103                         break;
104         }
105         return (casserv);
106 }
107
108 /*
109  * Function always consumes the given attrs.
110  */
111 static void
112 service_register(nvlist_t *attrs)
113 {
114         struct casper_service *casserv;
115         const char *name;
116
117         PJDLOG_ASSERT(nvlist_exists_string(attrs, "name"));
118         PJDLOG_ASSERT(nvlist_exists_string(attrs, "execpath") ||
119             (nvlist_exists_number(attrs, "commandfunc") &&
120              nvlist_exists_number(attrs, "limitfunc")));
121
122         name = nvlist_get_string(attrs, "name");
123         PJDLOG_ASSERT(name != NULL);
124         if (name[0] == '\0') {
125                 pjdlog_error("Unable to register service with an empty name.");
126                 nvlist_destroy(attrs);
127                 return;
128         }
129         if (service_find(name) != NULL) {
130                 pjdlog_error("Service \"%s\" is already registered.", name);
131                 nvlist_destroy(attrs);
132                 return;
133         }
134
135         casserv = malloc(sizeof(*casserv));
136         if (casserv == NULL) {
137                 pjdlog_errno(LOG_ERR, "Unable to register service \"%s\"",
138                     name);
139                 nvlist_destroy(attrs);
140                 return;
141         }
142         casserv->cs_name = name;
143         if (nvlist_exists_string(attrs, "execpath")) {
144                 struct stat sb;
145
146                 PJDLOG_ASSERT(!nvlist_exists_number(attrs, "commandfunc"));
147                 PJDLOG_ASSERT(!nvlist_exists_number(attrs, "limitfunc"));
148
149                 casserv->cs_service = NULL;
150
151                 casserv->cs_execpath = nvlist_get_string(attrs, "execpath");
152                 if (casserv->cs_execpath == NULL ||
153                     casserv->cs_execpath[0] == '\0') {
154                         pjdlog_error("Unable to register service with an empty execpath.");
155                         free(casserv);
156                         nvlist_destroy(attrs);
157                         return;
158                 }
159                 if (stat(casserv->cs_execpath, &sb) == -1) {
160                         pjdlog_errno(LOG_ERR,
161                             "Unable to register service \"%s\", problem with executable \"%s\"",
162                             name, casserv->cs_execpath);
163                         free(casserv);
164                         nvlist_destroy(attrs);
165                         return;
166                 }
167         } else /* if (nvlist_exists_number(attrs, "commandfunc")) */ {
168                 PJDLOG_ASSERT(!nvlist_exists_string(attrs, "execpath"));
169
170                 casserv->cs_execpath = NULL;
171
172                 casserv->cs_service = service_alloc(name,
173                     (void *)(uintptr_t)nvlist_get_number(attrs, "limitfunc"),
174                     (void *)(uintptr_t)nvlist_get_number(attrs, "commandfunc"));
175                 if (casserv->cs_service == NULL) {
176                         pjdlog_errno(LOG_ERR,
177                             "Unable to register service \"%s\"", name);
178                         free(casserv);
179                         nvlist_destroy(attrs);
180                         return;
181                 }
182         }
183         casserv->cs_attrs = attrs;
184         TAILQ_INSERT_TAIL(&casper_services, casserv, cs_next);
185         pjdlog_debug(1, "Service %s successfully registered.",
186             casserv->cs_name);
187 }
188
189 static bool
190 casper_allowed_service(const nvlist_t *limits, const char *service)
191 {
192
193         if (limits == NULL)
194                 return (true);
195
196         if (nvlist_exists_null(limits, service))
197                 return (true);
198
199         return (false);
200 }
201
202 static int
203 casper_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
204 {
205         const char *name;
206         int type;
207         void *cookie;
208
209         cookie = NULL;
210         while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
211                 if (type != NV_TYPE_NULL)
212                         return (EINVAL);
213                 if (!casper_allowed_service(oldlimits, name))
214                         return (ENOTCAPABLE);
215         }
216
217         return (0);
218 }
219
220 static int
221 casper_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
222     nvlist_t *nvlout)
223 {
224         struct casper_service *casserv;
225         const char *servname;
226         nvlist_t *nvl;
227         int chanfd, execfd, procfd, error;
228
229         if (strcmp(cmd, "open") != 0)
230                 return (EINVAL);
231         if (!nvlist_exists_string(nvlin, "service"))
232                 return (EINVAL);
233
234         servname = nvlist_get_string(nvlin, "service");
235
236         casserv = service_find(servname);
237         if (casserv == NULL)
238                 return (ENOENT);
239
240         if (!casper_allowed_service(limits, servname))
241                 return (ENOTCAPABLE);
242
243 #ifdef O_EXEC_WORKING
244         execfd = open(casserv->cs_execpath, O_EXEC);
245 #else
246         execfd = open(casserv->cs_execpath, O_RDONLY);
247 #endif
248         if (execfd < -1) {
249                 error = errno;
250                 pjdlog_errno(LOG_ERR,
251                     "Unable to open executable '%s' of service '%s'",
252                     casserv->cs_execpath, casserv->cs_name);
253                 return (error);
254         }
255
256         if (zygote_clone(service_external_execute, 0, &chanfd, &procfd) == -1) {
257                 error = errno;
258                 close(execfd);
259                 return (error);
260         }
261
262         nvl = nvlist_create(0);
263         nvlist_add_string(nvl, "service", casserv->cs_name);
264         if (nvlist_exists_descriptor(nvlin, "stderrfd")) {
265                 nvlist_move_descriptor(nvl, "stderrfd",
266                     nvlist_take_descriptor(nvlin, "stderrfd"));
267         }
268         nvlist_move_descriptor(nvl, "execfd", execfd);
269         nvlist_move_descriptor(nvl, "procfd", procfd);
270         if (nvlist_send(chanfd, nvl) == -1) {
271                 error = errno;
272                 pjdlog_errno(LOG_ERR, "Unable to send nvlist");
273                 nvlist_destroy(nvl);
274                 close(chanfd);
275                 return (error);
276         }
277         nvlist_destroy(nvl);
278
279         nvlist_move_descriptor(nvlout, "chanfd", chanfd);
280
281         return (0);
282 }
283
284 static void
285 fdswap(int *fd0, int *fd1)
286 {
287         int tmpfd;
288
289         PJDLOG_VERIFY((tmpfd = dup(*fd0)) != -1);
290         PJDLOG_VERIFY(dup2(*fd1, *fd0) != -1);
291         PJDLOG_VERIFY(dup2(tmpfd, *fd1) != -1);
292         close(tmpfd);
293         tmpfd = *fd0;
294         *fd0 = *fd1;
295         *fd1 = tmpfd;
296 }
297
298 static void
299 fdmove(int *oldfdp, int newfd)
300 {
301
302         if (*oldfdp != newfd) {
303                 PJDLOG_VERIFY(dup2(*oldfdp, newfd) != -1);
304                 close(*oldfdp);
305                 *oldfdp = newfd;
306         }
307 }
308
309 static void
310 fdcloexec(int fd)
311 {
312         int flags;
313
314         flags = fcntl(fd, F_GETFD);
315         PJDLOG_ASSERT(flags != -1);
316         if ((flags & FD_CLOEXEC) != 0)
317                 PJDLOG_VERIFY(fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC) != -1);
318 }
319
320 static void
321 service_register_core(void)
322 {
323         nvlist_t *nvl;
324
325         nvl = nvlist_create(0);
326         nvlist_add_string(nvl, "name", "core.casper");
327         nvlist_add_number(nvl, "limitfunc", (uint64_t)(uintptr_t)casper_limit);
328         nvlist_add_number(nvl, "commandfunc",
329             (uint64_t)(uintptr_t)casper_command);
330         service_register(nvl);
331 }
332
333 static int
334 setup_creds(int sock)
335 {
336         struct cmsgcred cred;
337
338         if (cred_recv(sock, &cred) == -1)
339                 return (-1);
340
341         if (setgroups((int)cred.cmcred_ngroups, cred.cmcred_groups) == -1)
342                 return (-1);
343
344         if (setgid(cred.cmcred_groups[0]) == -1)
345                 return (-1);
346
347         if (setuid(cred.cmcred_euid) == -1)
348                 return (-1);
349
350         return (0);
351 }
352
353 static void
354 service_external_execute(int chanfd)
355 {
356         char *service, *argv[3];
357         int stderrfd, execfd, procfd;
358         nvlist_t *nvl;
359
360         nvl = nvlist_recv(chanfd);
361         if (nvl == NULL)
362                 pjdlog_exit(1, "Unable to receive nvlist");
363         service = nvlist_take_string(nvl, "service");
364         PJDLOG_ASSERT(service != NULL);
365         if (nvlist_exists_descriptor(nvl, "stderrfd")) {
366                 stderrfd = nvlist_take_descriptor(nvl, "stderrfd");
367         } else {
368                 stderrfd = open(_PATH_DEVNULL, O_RDWR);
369                 if (stderrfd < 0)
370                         pjdlog_exit(1, "Unable to open %s", _PATH_DEVNULL);
371         }
372         execfd = nvlist_take_descriptor(nvl, "execfd");
373         procfd = nvlist_take_descriptor(nvl, "procfd");
374         nvlist_destroy(nvl);
375
376         /*
377          * Move all descriptors into right slots.
378          */
379
380         if (stderrfd != STDERR_FILENO) {
381                 if (chanfd == STDERR_FILENO)
382                         fdswap(&stderrfd, &chanfd);
383                 else if (execfd == STDERR_FILENO)
384                         fdswap(&stderrfd, &execfd);
385                 else if (procfd == STDERR_FILENO)
386                         fdswap(&stderrfd, &procfd);
387                 fdmove(&stderrfd, STDERR_FILENO);
388         }
389         fdcloexec(stderrfd);
390
391         if (chanfd != PARENT_FILENO) {
392                 if (execfd == PARENT_FILENO)
393                         fdswap(&chanfd, &execfd);
394                 else if (procfd == PARENT_FILENO)
395                         fdswap(&chanfd, &procfd);
396                 fdmove(&chanfd, PARENT_FILENO);
397         }
398         fdcloexec(chanfd);
399
400         if (execfd != EXECUTABLE_FILENO) {
401                 if (procfd == EXECUTABLE_FILENO)
402                         fdswap(&execfd, &procfd);
403                 fdmove(&execfd, EXECUTABLE_FILENO);
404         }
405         fdcloexec(execfd);
406
407         if (procfd != PROC_FILENO)
408                 fdmove(&procfd, PROC_FILENO);
409         fdcloexec(procfd);
410
411         /*
412          * Use credentials of the caller process.
413          */
414         setup_creds(chanfd);
415
416         argv[0] = service;
417         asprintf(&argv[1], "%d", pjdlog_debug_get());
418         argv[2] = NULL;
419
420         fexecve(execfd, argv, NULL);
421         pjdlog_exit(1, "Unable to execute service %s", service);
422 }
423
424 static void
425 service_register_external_one(const char *dirpath, int dfd,
426     const char *filename)
427 {
428         char execpath[FILENAME_MAX];
429         nvlist_t *nvl;
430         ssize_t done;
431         int fd;
432
433         fd = openat(dfd, filename, O_RDONLY);
434         if (fd == -1) {
435                 pjdlog_errno(LOG_ERR, "Unable to open \"%s/%s\"", dirpath,
436                     filename);
437                 return;
438         }
439
440         done = read(fd, execpath, sizeof(execpath));
441         if (done == -1) {
442                 pjdlog_errno(LOG_ERR, "Unable to read content of \"%s/%s\"",
443                     dirpath, filename);
444                 close(fd);
445                 return;
446         }
447         close(fd);
448         if (done == sizeof(execpath)) {
449                 pjdlog_error("Executable path too long in \"%s/%s\".", dirpath,
450                     filename);
451                 return;
452         }
453         execpath[done] = '\0';
454         while (done > 0) {
455                 if (execpath[--done] == '\n')
456                         execpath[done] = '\0';
457         }
458
459         nvl = nvlist_create(0);
460         nvlist_add_string(nvl, "name", filename);
461         nvlist_add_string(nvl, "execpath", execpath);
462         if (nvlist_error(nvl) != 0) {
463                 pjdlog_common(LOG_ERR, 0, nvlist_error(nvl),
464                     "Unable to allocate attributes for service \"%s/%s\"",
465                     dirpath, filename);
466                 nvlist_destroy(nvl);
467                 return;
468         }
469
470         service_register(nvl);
471         /* service_register() consumed nvl. */
472 }
473
474 static uint8_t
475 file_type(int dfd, const char *filename)
476 {
477         struct stat sb;
478
479         if (fstatat(dfd, filename, &sb, AT_SYMLINK_NOFOLLOW) == -1) {
480                 pjdlog_errno(LOG_ERR, "Unable to stat \"%s\"", filename);
481                 return (DT_UNKNOWN);
482         }
483         return (IFTODT(sb.st_mode));
484 }
485
486 static void
487 service_register_external(const char *dirpath)
488 {
489         DIR *dirp;
490         struct dirent *dp;
491         int dfd;
492
493         dirp = opendir(dirpath);
494         if (dirp == NULL) {
495                 pjdlog_errno(LOG_WARNING, "Unable to open \"%s\"", dirpath);
496                 return;
497         }
498         dfd = dirfd(dirp);
499         PJDLOG_ASSERT(dfd >= 0);
500         while ((dp = readdir(dirp)) != NULL) {
501                 dp->d_type = file_type(dfd, dp->d_name);
502                 /* We are only interested in regular files, skip the rest. */
503                 if (dp->d_type != DT_REG) {
504                         pjdlog_debug(1,
505                             "File \"%s/%s\" is not a regular file, skipping.",
506                             dirpath, dp->d_name);
507                         continue;
508                 }
509                 service_register_external_one(dirpath, dfd, dp->d_name);
510         }
511         closedir(dirp);
512 }
513
514 static void
515 casper_accept(int lsock)
516 {
517         struct casper_service *casserv;
518         struct service_connection *sconn;
519         int sock;
520
521         sock = accept(lsock, NULL, NULL);
522         if (sock == -1) {
523                 pjdlog_errno(LOG_ERR, "Unable to accept casper connection");
524                 return;
525         }
526         casserv = service_find("core.casper");
527         PJDLOG_ASSERT(casserv != NULL);
528
529         sconn = service_connection_add(casserv->cs_service, sock, NULL);
530         if (sconn == NULL) {
531                 close(sock);
532                 return;
533         }
534 }
535
536 static void
537 main_loop(const char *sockpath, struct pidfh *pfh)
538 {
539         fd_set fds;
540         struct sockaddr_un sun;
541         struct casper_service *casserv;
542         struct service_connection *sconn, *sconntmp;
543         int lsock, sock, maxfd, ret;
544         mode_t oldumask;
545
546         lsock = socket(AF_UNIX, SOCK_STREAM, 0);
547         if (lsock == -1)
548                 pjdlog_exit(1, "Unable to create socket");
549
550         (void)unlink(sockpath);
551
552         bzero(&sun, sizeof(sun));
553         sun.sun_family = AF_UNIX;
554         PJDLOG_VERIFY(strlcpy(sun.sun_path, sockpath, sizeof(sun.sun_path)) <
555             sizeof(sun.sun_path));
556         sun.sun_len = SUN_LEN(&sun);
557
558         oldumask = umask(S_IXUSR | S_IXGRP | S_IXOTH);
559         if (bind(lsock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
560                 pjdlog_exit(1, "Unable to bind to %s", sockpath);
561         (void)umask(oldumask);
562         if (listen(lsock, 8) == -1)
563                 pjdlog_exit(1, "Unable to listen on %s", sockpath);
564
565         for (;;) {
566                 FD_ZERO(&fds);
567                 FD_SET(lsock, &fds);
568                 maxfd = lsock;
569                 TAILQ_FOREACH(casserv, &casper_services, cs_next) {
570                         /* We handle only core services. */
571                         if (!SERVICE_IS_CORE(casserv))
572                                 continue;
573                         for (sconn = service_connection_first(casserv->cs_service);
574                             sconn != NULL;
575                             sconn = service_connection_next(sconn)) {
576                                 sock = service_connection_get_sock(sconn);
577                                 FD_SET(sock, &fds);
578                                 maxfd = sock > maxfd ? sock : maxfd;
579                         }
580                 }
581                 maxfd++;
582
583                 PJDLOG_ASSERT(maxfd <= (int)FD_SETSIZE);
584                 ret = select(maxfd, &fds, NULL, NULL, NULL);
585                 PJDLOG_ASSERT(ret == -1 || ret > 0);    /* select() cannot timeout */
586                 if (ret == -1) {
587                         if (errno == EINTR)
588                                 continue;
589                         KEEP_ERRNO((void)pidfile_remove(pfh));
590                         pjdlog_exit(1, "select() failed");
591                 }
592
593                 if (FD_ISSET(lsock, &fds))
594                         casper_accept(lsock);
595                 TAILQ_FOREACH(casserv, &casper_services, cs_next) {
596                         /* We handle only core services. */
597                         if (!SERVICE_IS_CORE(casserv))
598                                 continue;
599                         for (sconn = service_connection_first(casserv->cs_service);
600                             sconn != NULL; sconn = sconntmp) {
601                                 /*
602                                  * Prepare for connection to be removed from
603                                  * the list on failure.
604                                  */
605                                 sconntmp = service_connection_next(sconn);
606                                 sock = service_connection_get_sock(sconn);
607                                 if (FD_ISSET(sock, &fds)) {
608                                         service_message(casserv->cs_service,
609                                             sconn);
610                                 }
611                         }
612                 }
613         }
614 }
615
616 static void
617 usage(void)
618 {
619
620         pjdlog_exitx(1,
621             "usage: casperd [-Fhv] [-D servconfdir] [-P pidfile] [-S sockpath]");
622 }
623
624 int
625 main(int argc, char *argv[])
626 {
627         struct pidfh *pfh;
628         const char *pidfile, *servconfdir, *sockpath;
629         pid_t otherpid;
630         int ch, debug;
631         bool foreground;
632
633         pjdlog_init(PJDLOG_MODE_STD);
634
635         debug = 0;
636         foreground = false;
637         pidfile = CASPERD_PIDFILE;
638         servconfdir = CASPERD_SERVCONFDIR;
639         sockpath = CASPERD_SOCKPATH;
640
641         while ((ch = getopt(argc, argv, "D:FhP:S:v")) != -1) {
642                 switch (ch) {
643                 case 'D':
644                         servconfdir = optarg;
645                         break;
646                 case 'F':
647                         foreground = true;
648                         break;
649                 case 'P':
650                         pidfile = optarg;
651                         break;
652                 case 'S':
653                         sockpath = optarg;
654                         break;
655                 case 'v':
656                         debug++;
657                         break;
658                 case 'h':
659                 default:
660                         usage();
661                 }
662         }
663         argc -= optind;
664         argv += optind;
665
666         if (argc != 0)
667                 usage();
668
669         if (!foreground)
670                 pjdlog_mode_set(PJDLOG_MODE_SYSLOG);
671         pjdlog_prefix_set("(casperd) ");
672         pjdlog_debug_set(debug);
673
674         if (zygote_init() < 0)
675                 pjdlog_exit(1, "Unable to create zygote process");
676
677         pfh = pidfile_open(pidfile, 0600, &otherpid);
678         if (pfh == NULL) {
679                 if (errno == EEXIST) {
680                         pjdlog_exitx(1, "casperd already running, pid: %jd.",
681                             (intmax_t)otherpid);
682                 }
683                 pjdlog_errno(LOG_WARNING, "Cannot open or create pidfile %s",
684                     pidfile);
685         }
686
687         if (!foreground) {
688                 if (daemon(0, 0) == -1) {
689                         KEEP_ERRNO((void)pidfile_remove(pfh));
690                         pjdlog_exit(1, "Unable to go into background");
691                 }
692         }
693
694         /* Write PID to a file. */
695         if (pidfile_write(pfh) == -1) {
696                 pjdlog_errno(LOG_WARNING, "Unable to write to pidfile %s",
697                     pidfile);
698         } else {
699                 pjdlog_debug(1, "PID stored in %s.", pidfile);
700         }
701
702         /*
703          * Register core services.
704          */
705         service_register_core();
706         /*
707          * Register external services.
708          */
709         service_register_external(servconfdir);
710
711         /*
712          * Wait for connections.
713          */
714         main_loop(sockpath, pfh);
715 }