1/*
2 * Copyright (C) Joerg Lenneis 2003
3 * Copyright (C) Frank Lahm 2009, 2010
4 *
5 * All Rights Reserved.  See COPYING.
6 */
7
8/*
9   cnid_dbd metadaemon to start up cnid_dbd upon request from afpd.
10   Here is how it works:
11
12                       via TCP socket
13   1.       afpd          ------->        cnid_metad
14
15                   via UNIX domain socket
16   2.   cnid_metad        ------->         cnid_dbd
17
18                    passes afpd client fd
19   3.   cnid_metad        ------->         cnid_dbd
20
21   Result:
22                       via TCP socket
23   4.       afpd          ------->         cnid_dbd
24
25   cnid_metad and cnid_dbd have been converted to non-blocking IO in 2010.
26 */
27
28
29#ifdef HAVE_CONFIG_H
30#include "config.h"
31#endif /* HAVE_CONFIG_H */
32
33#include <unistd.h>
34#undef __USE_GNU
35
36#include <stdlib.h>
37#include <sys/param.h>
38#include <errno.h>
39#include <string.h>
40#include <signal.h>
41#include <sys/types.h>
42#include <sys/time.h>
43#include <sys/resource.h>
44#include <sys/wait.h>
45#include <sys/uio.h>
46#include <sys/un.h>
47#define _XPG4_2 1
48#include <sys/socket.h>
49#include <stdio.h>
50#include <time.h>
51
52#ifndef WEXITSTATUS
53#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
54#endif /* ! WEXITSTATUS */
55#ifndef WIFEXITED
56#define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
57#endif /* ! WIFEXITED */
58#ifndef WIFSTOPPED
59#define WIFSTOPPED(status) (((status) & 0xff) == 0x7f)
60#endif
61
62#ifndef WIFSIGNALED
63#define WIFSIGNALED(status) (!WIFSTOPPED(status) && !WIFEXITED(status))
64#endif
65#ifndef WTERMSIG
66#define WTERMSIG(status)      ((status) & 0x7f)
67#endif
68
69/* functions for username and group */
70#include <pwd.h>
71#include <grp.h>
72
73/* FIXME */
74#ifdef linux
75#ifndef USE_SETRESUID
76#define USE_SETRESUID 1
77#define SWITCH_TO_GID(gid)  ((setresgid(gid,gid,gid) < 0 || setgid(gid) < 0) ? -1 : 0)
78#define SWITCH_TO_UID(uid)  ((setresuid(uid,uid,uid) < 0 || setuid(uid) < 0) ? -1 : 0)
79#endif  /* USE_SETRESUID */
80#else   /* ! linux */
81#ifndef USE_SETEUID
82#define USE_SETEUID 1
83#define SWITCH_TO_GID(gid)  ((setegid(gid) < 0 || setgid(gid) < 0) ? -1 : 0)
84#define SWITCH_TO_UID(uid)  ((setuid(uid) < 0 || seteuid(uid) < 0 || setuid(uid) < 0) ? -1 : 0)
85#endif  /* USE_SETEUID */
86#endif  /* linux */
87
88#include <atalk/util.h>
89#include <atalk/logger.h>
90#include <atalk/cnid_dbd_private.h>
91#include <atalk/paths.h>
92#include <atalk/volinfo.h>
93
94#include "usockfd.h"
95
96#define DBHOME        ".AppleDB"
97#define DBHOMELEN    8
98
99static int srvfd;
100static int rqstfd;
101static volatile sig_atomic_t sigchild = 0;
102static uint maxvol;
103
104#define MAXSPAWN   3                   /* Max times respawned in.. */
105#define TESTTIME   10                  /* this much seconds apfd client tries to  *
106                                        * to reconnect every 5 secondes, catch it */
107#define MAXVOLS    4096
108#define DEFAULTHOST  "localhost"
109#define DEFAULTPORT  "4700"
110
111struct server {
112    struct volinfo *volinfo;
113    pid_t pid;
114    time_t tm;                    /* When respawned last */
115    unsigned int count;           /* Times respawned in the last TESTTIME secondes */
116    int control_fd;               /* file descriptor to child cnid_dbd process */
117};
118
119static struct server srv[MAXVOLS];
120
121/* Default logging config: log to syslog with level log_note */
122static char logconfig[MAXPATHLEN + 21 + 1] = "default log_note";
123
124static void daemon_exit(int i)
125{
126    server_unlock(_PATH_CNID_METAD_LOCK);
127    exit(i);
128}
129
130/* ------------------ */
131static void sigterm_handler(int sig)
132{
133    switch( sig ) {
134    case SIGTERM :
135        LOG(log_info, logtype_afpd, "shutting down on signal %d", sig );
136        break;
137    default :
138        LOG(log_error, logtype_afpd, "unexpected signal: %d", sig);
139    }
140    daemon_exit(0);
141}
142
143static struct server *test_usockfn(struct volinfo *volinfo)
144{
145    int i;
146    for (i = 0; i < maxvol; i++) {
147        if ((srv[i].volinfo) && (strcmp(srv[i].volinfo->v_path, volinfo->v_path) == 0)) {
148            return &srv[i];
149        }
150    }
151    return NULL;
152}
153
154/* -------------------- */
155static int maybe_start_dbd(char *dbdpn, struct volinfo *volinfo)
156{
157    pid_t pid;
158    struct server *up;
159    int sv[2];
160    int i;
161    time_t t;
162    char buf1[8];
163    char buf2[8];
164    char *volpath = volinfo->v_path;
165
166    LOG(log_debug, logtype_cnid, "maybe_start_dbd: Volume: \"%s\"", volpath);
167
168    up = test_usockfn(volinfo);
169    if (up && up->pid) {
170        /* we already have a process, send our fd */
171        if (send_fd(up->control_fd, rqstfd) < 0) {
172            /* FIXME */
173            return -1;
174        }
175        return 0;
176    }
177
178    LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: no cnid_dbd for that volume yet");
179
180    time(&t);
181    if (!up) {
182        /* find an empty slot (i < maxvol) or the first free slot (i == maxvol)*/
183        for (i = 0; i <= maxvol; i++) {
184            if (srv[i].volinfo == NULL && i < MAXVOLS) {
185                up = &srv[i];
186                up->volinfo = volinfo;
187                retainvolinfo(volinfo);
188                up->tm = t;
189                up->count = 0;
190                if (i == maxvol)
191                    maxvol++;
192                break;
193            }
194        }
195        if (!up) {
196            LOG(log_error, logtype_cnid, "no free slot for cnid_dbd child. Configured maximum: %d. Do you have so many volumes?", MAXVOLS);
197            return -1;
198        }
199    } else {
200        /* we have a slot but no process */
201        if (up->count > 0) {
202            /* check for respawn too fast */
203            if (t < (up->tm + TESTTIME)) {
204                /* We're in the respawn time window */
205                if (up->count > MAXSPAWN) {
206                    /* ...and already tried to fork too often */
207                    LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawning too fast");
208                    return -1; /* just exit, dont sleep, because we might have work to do for another client  */
209                }
210            } else {
211                /* out of respawn too fast windows reset the count */
212                LOG(log_info, logtype_cnid, "maybe_start_dbd: respawn window ended");
213                up->count = 0;
214            }
215        }
216        up->count++;
217        up->tm = t;
218        LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn count: %u", up->count);
219        if (up->count > MAXSPAWN) {
220            /* We spawned too fast. From now until the first time we tried + TESTTIME seconds
221               we will just return -1 above */
222            LOG(log_info, logtype_cnid, "maybe_start_dbd: reached MAXSPAWN threshhold");
223       }
224    }
225
226    /*
227       Create socketpair for comm between parent and child.
228       We use it to pass fds from connecting afpd processes to our
229       cnid_dbd child via fd passing.
230    */
231    if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) {
232        LOG(log_error, logtype_cnid, "error in socketpair: %s", strerror(errno));
233        return -1;
234    }
235
236    if ((pid = fork()) < 0) {
237        LOG(log_error, logtype_cnid, "error in fork: %s", strerror(errno));
238        return -1;
239    }
240    if (pid == 0) {
241        int ret;
242        /*
243         *  Child. Close descriptors and start the daemon. If it fails
244         *  just log it. The client process will fail connecting
245         *  afterwards anyway.
246         */
247
248        close(srvfd);
249        close(sv[0]);
250
251        for (i = 0; i < MAXVOLS; i++) {
252            if (srv[i].pid && up != &srv[i]) {
253                close(srv[i].control_fd);
254            }
255        }
256
257        sprintf(buf1, "%i", sv[1]);
258        sprintf(buf2, "%i", rqstfd);
259
260        if (up->count == MAXSPAWN) {
261            /* there's a pb with the db inform child, it will delete the db */
262            LOG(log_warning, logtype_cnid,
263                "Multiple attempts to start CNID db daemon for \"%s\" failed, wiping the slate clean...",
264                up->volinfo->v_path);
265            ret = execlp(dbdpn, dbdpn, "-d", volpath, buf1, buf2, logconfig, NULL);
266        } else {
267            ret = execlp(dbdpn, dbdpn, volpath, buf1, buf2, logconfig, NULL);
268        }
269        /* Yikes! We're still here, so exec failed... */
270        LOG(log_error, logtype_cnid, "Fatal error in exec: %s", strerror(errno));
271        daemon_exit(0);
272    }
273    /*
274     *  Parent.
275     */
276    up->pid = pid;
277    close(sv[1]);
278    up->control_fd = sv[0];
279    return 0;
280}
281
282/* ------------------ */
283static int set_dbdir(char *dbdir)
284{
285    int len;
286    struct stat st;
287
288    len = strlen(dbdir);
289
290    if (stat(dbdir, &st) < 0 && mkdir(dbdir, 0755) < 0) {
291        LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir);
292        return -1;
293    }
294
295    if (dbdir[len - 1] != '/') {
296        strcat(dbdir, "/");
297        len++;
298    }
299    strcpy(dbdir + len, DBHOME);
300    if (stat(dbdir, &st) < 0 && mkdir(dbdir, 0755 ) < 0) {
301        LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir);
302        return -1;
303    }
304    return 0;
305}
306
307/* ------------------ */
308static uid_t user_to_uid (char *username)
309{
310    struct passwd *this_passwd;
311
312    /* check for anything */
313    if ( !username || strlen ( username ) < 1 ) return 0;
314
315    /* grab the /etc/passwd record relating to username */
316    this_passwd = getpwnam ( username );
317
318    /* return false if there is no structure returned */
319    if (this_passwd == NULL) return 0;
320
321    /* return proper uid */
322    return this_passwd->pw_uid;
323
324}
325
326/* ------------------ */
327static gid_t group_to_gid ( char *group)
328{
329    struct group *this_group;
330
331    /* check for anything */
332    if ( !group || strlen ( group ) < 1 ) return 0;
333
334    /* grab the /etc/groups record relating to group */
335    this_group = getgrnam ( group );
336
337    /* return false if there is no structure returned */
338    if (this_group == NULL) return 0;
339
340    /* return proper gid */
341    return this_group->gr_gid;
342
343}
344
345/* ------------------ */
346static void catch_child(int sig _U_)
347{
348    sigchild = 1;
349}
350
351/* ----------------------- */
352static void set_signal(void)
353{
354    struct sigaction sv;
355    sigset_t set;
356
357    memset(&sv, 0, sizeof(sv));
358
359    /* Catch SIGCHLD */
360    sv.sa_handler = catch_child;
361    sv.sa_flags = SA_NOCLDSTOP;
362    sigemptyset(&sv.sa_mask);
363    if (sigaction(SIGCHLD, &sv, NULL) < 0) {
364        LOG(log_error, logtype_cnid, "cnid_metad: sigaction: %s", strerror(errno));
365        daemon_exit(EXITERR_SYS);
366    }
367
368    /* Catch SIGTERM */
369    sv.sa_handler = sigterm_handler;
370    sigfillset(&sv.sa_mask );
371    if (sigaction(SIGTERM, &sv, NULL ) < 0 ) {
372        LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
373        daemon_exit(EXITERR_SYS);
374    }
375
376    /* Ignore the rest */
377    sv.sa_handler = SIG_IGN;
378    sigemptyset(&sv.sa_mask );
379    if (sigaction(SIGALRM, &sv, NULL ) < 0 ) {
380        LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
381        daemon_exit(EXITERR_SYS);
382    }
383    sv.sa_handler = SIG_IGN;
384    sigemptyset(&sv.sa_mask );
385    if (sigaction(SIGHUP, &sv, NULL ) < 0 ) {
386        LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
387        daemon_exit(EXITERR_SYS);
388    }
389    sv.sa_handler = SIG_IGN;
390    sigemptyset(&sv.sa_mask );
391    if (sigaction(SIGUSR1, &sv, NULL ) < 0 ) {
392        LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
393        daemon_exit(EXITERR_SYS);
394    }
395    sv.sa_handler = SIG_IGN;
396    sigemptyset(&sv.sa_mask );
397    if (sigaction(SIGUSR2, &sv, NULL ) < 0 ) {
398        LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
399        daemon_exit(EXITERR_SYS);
400    }
401    sv.sa_handler = SIG_IGN;
402    sigemptyset(&sv.sa_mask );
403    if (sigaction(SIGPIPE, &sv, NULL ) < 0 ) {
404        LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
405        daemon_exit(EXITERR_SYS);
406    }
407
408    /* block everywhere but in pselect */
409    sigemptyset(&set);
410    sigaddset(&set, SIGCHLD);
411    sigprocmask(SIG_BLOCK, &set, NULL);
412}
413
414static int setlimits(void)
415{
416    struct rlimit rlim;
417
418    if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
419        LOG(log_error, logtype_afpd, "setlimits: %s", strerror(errno));
420        exit(1);
421    }
422    if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < 65535) {
423        rlim.rlim_cur = 65535;
424        if (rlim.rlim_max != RLIM_INFINITY && rlim.rlim_max < 65535)
425            rlim.rlim_max = 65535;
426        if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) {
427            LOG(log_error, logtype_afpd, "setlimits: %s", strerror(errno));
428            exit(1);
429        }
430    }
431    return 0;
432}
433
434/* ------------------ */
435int main(int argc, char *argv[])
436{
437    char  volpath[MAXPATHLEN + 1];
438    int   len, actual_len;
439    pid_t pid;
440    int   status;
441    char  *dbdpn = _PATH_CNID_DBD;
442    char  *host = DEFAULTHOST;
443    char  *port = DEFAULTPORT;
444    int    i;
445    int    cc;
446    uid_t  uid = 0;
447    gid_t  gid = 0;
448    int    err = 0;
449    int    debug = 0;
450    int    ret;
451    char   *loglevel = NULL;
452    char   *logfile  = NULL;
453    sigset_t set;
454    struct volinfo *volinfo;
455
456    set_processname("cnid_metad");
457
458    while (( cc = getopt( argc, argv, "vVds:p:h:u:g:l:f:")) != -1 ) {
459        switch (cc) {
460        case 'v':
461        case 'V':
462            printf("cnid_metad (Netatalk %s)\n", VERSION);
463            return -1;
464        case 'd':
465            debug = 1;
466            break;
467        case 'h':
468            host = strdup(optarg);
469            break;
470        case 'u':
471            uid = user_to_uid (optarg);
472            if (!uid) {
473                LOG(log_error, logtype_cnid, "main: bad user %s", optarg);
474                err++;
475            }
476            break;
477        case 'g':
478            gid =group_to_gid (optarg);
479            if (!gid) {
480                LOG(log_error, logtype_cnid, "main: bad group %s", optarg);
481                err++;
482            }
483            break;
484        case 'p':
485            port = strdup(optarg);
486            break;
487        case 's':
488            dbdpn = strdup(optarg);
489            break;
490        case 'l':
491            loglevel = strdup(optarg);
492            break;
493        case 'f':
494            logfile = strdup(optarg);
495            break;
496        default:
497            err++;
498            break;
499        }
500    }
501
502    /* Check for PID lockfile */
503    if (check_lockfile("cnid_metad", _PATH_CNID_METAD_LOCK))
504        return -1;
505
506    if (!debug && daemonize(0, 0) != 0)
507        exit(EXITERR_SYS);
508
509    /* Create PID lockfile */
510    if (create_lockfile("cnid_metad", _PATH_CNID_METAD_LOCK))
511        return -1;
512
513    if (loglevel) {
514        strlcpy(logconfig + 8, loglevel, 13);
515        free(loglevel);
516        strcat(logconfig, " ");
517    }
518    if (logfile) {
519        strlcat(logconfig, logfile, MAXPATHLEN);
520        free(logfile);
521    }
522    setuplog(logconfig);
523
524    if (err) {
525        LOG(log_error, logtype_cnid, "main: bad arguments");
526        daemon_exit(1);
527    }
528
529    (void)setlimits();
530
531    if ((srvfd = tsockfd_create(host, port, 10)) < 0)
532        daemon_exit(1);
533
534    /* switch uid/gid */
535    if (uid || gid) {
536        LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", uid, gid);
537        if (gid) {
538            if (SWITCH_TO_GID(gid) < 0) {
539                LOG(log_info, logtype_cnid, "unable to switch to group %d", gid);
540                daemon_exit(1);
541            }
542        }
543        if (uid) {
544            if (SWITCH_TO_UID(uid) < 0) {
545                LOG(log_info, logtype_cnid, "unable to switch to user %d", uid);
546                daemon_exit(1);
547            }
548        }
549    }
550
551    set_signal();
552
553    sigemptyset(&set);
554    sigprocmask(SIG_SETMASK, NULL, &set);
555    sigdelset(&set, SIGCHLD);
556
557    while (1) {
558        rqstfd = usockfd_check(srvfd, &set);
559        /* Collect zombie processes and log what happened to them */
560        if (sigchild) while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
561            for (i = 0; i < maxvol; i++) {
562                if (srv[i].pid == pid) {
563                    srv[i].pid = 0;
564                    close(srv[i].control_fd);
565                    break;
566                }
567            }
568            if (WIFEXITED(status)) {
569                LOG(log_info, logtype_cnid, "cnid_dbd[%i] exited with exit code %i",
570                    pid, WEXITSTATUS(status));
571            } else {
572                /* cnid_dbd did a clean exit probably on idle timeout, reset bookkeeping */
573                srv[i].tm = 0;
574                srv[i].count = 0;
575            }
576            if (WIFSIGNALED(status)) {
577                LOG(log_info, logtype_cnid, "cnid_dbd[%i] received signal %i",
578                    pid, WTERMSIG(status));
579            }
580            sigchild = 0;
581        }
582        if (rqstfd <= 0)
583            continue;
584
585        ret = readt(rqstfd, &len, sizeof(int), 1, 4);
586
587        if (!ret) {
588            /* already close */
589            goto loop_end;
590        }
591        else if (ret < 0) {
592            LOG(log_severe, logtype_cnid, "error read: %s", strerror(errno));
593            goto loop_end;
594        }
595        else if (ret != sizeof(int)) {
596            LOG(log_error, logtype_cnid, "short read: got %d", ret);
597            goto loop_end;
598        }
599        /*
600         *  checks for buffer overruns. The client libatalk side does it too
601         *  before handing the dir path over but who trusts clients?
602         */
603        if (!len || len +DBHOMELEN +2 > MAXPATHLEN) {
604            LOG(log_error, logtype_cnid, "wrong len parameter: %d", len);
605            goto loop_end;
606        }
607
608        actual_len = readt(rqstfd, volpath, len, 1, 5);
609        if (actual_len < 0) {
610            LOG(log_severe, logtype_cnid, "Read(2) error : %s", strerror(errno));
611            goto loop_end;
612        }
613        if (actual_len != len) {
614            LOG(log_error, logtype_cnid, "error/short read (dir): %s", strerror(errno));
615            goto loop_end;
616        }
617        volpath[len] = '\0';
618
619        /* Load .volinfo file */
620        if ((volinfo = allocvolinfo(volpath)) == NULL) {
621            LOG(log_severe, logtype_cnid, "allocvolinfo(\"%s\"): %s",
622                volpath, strerror(errno));
623            goto loop_end;
624        }
625
626        if (set_dbdir(volinfo->v_dbpath) < 0) {
627            goto loop_end;
628        }
629
630        maybe_start_dbd(dbdpn, volinfo);
631
632        (void)closevolinfo(volinfo);
633
634    loop_end:
635        close(rqstfd);
636    }
637}
638