]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cnid_metad.c
2fc1e8debe0c10ebf5846676f31bb6c10a8ca00d
[netatalk.git] / etc / cnid_dbd / cnid_metad.c
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/compat.h>
93 #include <atalk/errchk.h>
94 #include <atalk/bstrlib.h>
95 #include <atalk/netatalk_conf.h>
96 #include <atalk/volume.h>
97
98 #include "usockfd.h"
99
100 #define DBHOME        ".AppleDB"
101 #define DBHOMELEN    8
102
103 static int srvfd;
104 static int rqstfd;
105 static volatile sig_atomic_t sigchild = 0;
106 static uint maxvol;
107
108 #define MAXSPAWN   3                   /* Max times respawned in.. */
109 #define TESTTIME   10                  /* this much seconds apfd client tries to  *
110                                         * to reconnect every 5 secondes, catch it */
111 #define MAXVOLS    4096
112 #define DEFAULTHOST  "localhost"
113 #define DEFAULTPORT  "4700"
114
115 struct server {
116     char *v_path;
117     pid_t pid;
118     time_t tm;                    /* When respawned last */
119     unsigned int count;           /* Times respawned in the last TESTTIME secondes */
120     int control_fd;               /* file descriptor to child cnid_dbd process */
121 };
122
123 static struct server srv[MAXVOLS];
124
125 static void daemon_exit(int i)
126 {
127     exit(i);
128 }
129
130 /* ------------------ */
131 static void sig_handler(int sig)
132 {
133     switch( sig ) {
134     case SIGTERM:
135     case SIGQUIT:
136         LOG(log_note, logtype_afpd, "shutting down on %s",
137             sig == SIGTERM ? "SIGTERM" : "SIGQUIT");
138         break;
139     default :
140         LOG(log_error, logtype_afpd, "unexpected signal: %d", sig);
141     }
142     daemon_exit(0);
143 }
144
145 static struct server *test_usockfn(const char *path)
146 {
147     int i;
148
149     for (i = 0; i < maxvol; i++) {
150         if (srv[i].v_path && STRCMP(path, ==, srv[i].v_path))
151             return &srv[i];
152     }
153
154     return NULL;
155 }
156
157 /* -------------------- */
158 static int maybe_start_dbd(const AFPObj *obj, char *dbdpn, const char *volpath)
159 {
160     pid_t pid;
161     struct server *up;
162     int sv[2];
163     int i;
164     time_t t;
165     char buf1[8];
166     char buf2[8];
167
168     LOG(log_debug, logtype_cnid, "maybe_start_dbd(\"%s\"): BEGIN", volpath);
169
170     up = test_usockfn(volpath);
171     if (up && up->pid) {
172         /* we already have a process, send our fd */
173         LOG(log_debug, logtype_cnid, "maybe_start_dbd: cnid_dbd[%d] already serving", up->pid);
174         if (send_fd(up->control_fd, rqstfd) < 0) {
175             /* FIXME */
176             return -1;
177         }
178         return 0;
179     }
180
181     LOG(log_debug, logtype_cnid, "maybe_start_dbd: no cnid_dbd serving yet");
182
183     time(&t);
184     if (!up) {
185         /* find an empty slot (i < maxvol) or the first free slot (i == maxvol)*/
186         for (i = 0; i <= maxvol; i++) {
187             if (srv[i].v_path == NULL && i < MAXVOLS) {
188                 up = &srv[i];
189                 if ((up->v_path = strdup(volpath)) == NULL)
190                     return -1;
191                 up->tm = t;
192                 up->count = 0;
193                 if (i == maxvol)
194                     maxvol++;
195                 break;
196             }
197         }
198         if (!up) {
199             LOG(log_error, logtype_cnid, "no free slot for cnid_dbd child. Configured maximum: %d. Do you have so many volumes?", MAXVOLS);
200             return -1;
201         }
202     } else {
203         /* we have a slot but no process */
204         if (up->count > 0) {
205             /* check for respawn too fast */
206             if (t < (up->tm + TESTTIME)) {
207                 /* We're in the respawn time window */
208                 if (up->count > MAXSPAWN) {
209                     /* ...and already tried to fork too often */
210                     LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawning too fast");
211                     return -1; /* just exit, dont sleep, because we might have work to do for another client  */
212                 }
213             } else {
214                 /* out of respawn too fast windows reset the count */
215                 LOG(log_info, logtype_cnid, "maybe_start_dbd: respawn window ended");
216                 up->count = 0;
217             }
218         }
219         up->count++;
220         up->tm = t;
221         LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn count: %u", up->count);
222         if (up->count > MAXSPAWN) {
223             /* We spawned too fast. From now until the first time we tried + TESTTIME seconds
224                we will just return -1 above */
225             LOG(log_info, logtype_cnid, "maybe_start_dbd: reached MAXSPAWN threshhold");
226        }
227     }
228
229     /* 
230        Create socketpair for comm between parent and child.
231        We use it to pass fds from connecting afpd processes to our
232        cnid_dbd child via fd passing.
233     */
234     if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) {
235         LOG(log_error, logtype_cnid, "error in socketpair: %s", strerror(errno));
236         return -1;
237     }
238
239     if ((pid = fork()) < 0) {
240         LOG(log_error, logtype_cnid, "error in fork: %s", strerror(errno));
241         return -1;
242     }
243     if (pid == 0) {
244         int ret;
245         /*
246          *  Child. Close descriptors and start the daemon. If it fails
247          *  just log it. The client process will fail connecting
248          *  afterwards anyway.
249          */
250
251         close(srvfd);
252         close(sv[0]);
253
254         for (i = 0; i < MAXVOLS; i++) {
255             if (srv[i].pid && up != &srv[i]) {
256                 close(srv[i].control_fd);
257             }
258         }
259
260         sprintf(buf1, "%i", sv[1]);
261         sprintf(buf2, "%i", rqstfd);
262
263         if (up->count == MAXSPAWN) {
264             /* there's a pb with the db inform child, it will delete the db */
265             LOG(log_warning, logtype_cnid,
266                 "Multiple attempts to start CNID db daemon for \"%s\" failed, wiping the slate clean...",
267                 up->v_path);
268             ret = execlp(dbdpn, dbdpn, "-F", obj->options.configfile, "-p", volpath, "-t", buf1, "-l", buf2, "-d", NULL);
269         } else {
270             ret = execlp(dbdpn, dbdpn, "-F", obj->options.configfile, "-p", volpath, "-t", buf1, "-l", buf2, NULL);
271         }
272         /* Yikes! We're still here, so exec failed... */
273         LOG(log_error, logtype_cnid, "Fatal error in exec: %s", strerror(errno));
274         daemon_exit(0);
275     }
276     /*
277      *  Parent.
278      */
279     up->pid = pid;
280     close(sv[1]);
281     up->control_fd = sv[0];
282     return 0;
283 }
284
285 /* ------------------ */
286 static int set_dbdir(const char *dbdir, const char *vpath)
287 {
288     EC_INIT;
289     int status;
290     struct stat st;
291     bstring oldpath, newpath;
292     char *cmd_argv[4];
293
294     LOG(log_debug, logtype_cnid, "set_dbdir: volume: %s, db path: %s", vpath, dbdir);
295
296     EC_NULL_LOG( oldpath = bformat("%s/%s/", vpath, DBHOME) );
297     EC_NULL_LOG( newpath = bformat("%s/%s/", dbdir, DBHOME) );
298
299     if (lstat(dbdir, &st) < 0 && mkdir(dbdir, 0755) < 0) {
300         LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir);
301         EC_FAIL;
302     }
303
304     if (lstat(bdata(oldpath), &st) == 0 && lstat(bdata(newpath), &st) != 0 && errno == ENOENT) {
305         /* There's an .AppleDB in the volume root, we move it */
306         cmd_argv[0] = "mv";
307         cmd_argv[1] = bdata(oldpath);
308         cmd_argv[2] = (char *)dbdir;
309         cmd_argv[3] = NULL;
310         if (run_cmd("mv", cmd_argv) != 0) {
311             LOG(log_error, logtype_cnid, "set_dbdir: moving CNID db from \"%s\" to \"%s\" failed",
312                 bdata(oldpath), dbdir);
313             EC_FAIL;
314         }
315
316     }
317
318     if (lstat(bdata(newpath), &st) < 0 && mkdir(bdata(newpath), 0755 ) < 0) {
319         LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", bdata(newpath));
320         EC_FAIL;
321     }
322
323 EC_CLEANUP:
324     bdestroy(oldpath);
325     bdestroy(newpath);
326     EC_EXIT;
327 }
328
329 /* ------------------ */
330 static uid_t user_to_uid (char *username)
331 {
332     struct passwd *this_passwd;
333
334     /* check for anything */
335     if ( !username || strlen ( username ) < 1 ) return 0;
336
337     /* grab the /etc/passwd record relating to username */
338     this_passwd = getpwnam ( username );
339
340     /* return false if there is no structure returned */
341     if (this_passwd == NULL) return 0;
342
343     /* return proper uid */
344     return this_passwd->pw_uid;
345
346 }
347
348 /* ------------------ */
349 static gid_t group_to_gid ( char *group)
350 {
351     struct group *this_group;
352
353     /* check for anything */
354     if ( !group || strlen ( group ) < 1 ) return 0;
355
356     /* grab the /etc/groups record relating to group */
357     this_group = getgrnam ( group );
358
359     /* return false if there is no structure returned */
360     if (this_group == NULL) return 0;
361
362     /* return proper gid */
363     return this_group->gr_gid;
364
365 }
366
367 /* ------------------ */
368 static void catch_child(int sig _U_) 
369 {
370     sigchild = 1;
371 }
372
373 /* ----------------------- */
374 static void set_signal(void)
375 {
376     struct sigaction sv;
377     sigset_t set;
378
379     memset(&sv, 0, sizeof(sv));
380
381     /* Catch SIGCHLD */
382     sv.sa_handler = catch_child;
383     sv.sa_flags = SA_NOCLDSTOP;
384     sigemptyset(&sv.sa_mask);
385     if (sigaction(SIGCHLD, &sv, NULL) < 0) {
386         LOG(log_error, logtype_cnid, "cnid_metad: sigaction: %s", strerror(errno));
387         daemon_exit(EXITERR_SYS);
388     }
389
390     /* Catch SIGTERM and SIGQUIT */
391     sv.sa_handler = sig_handler;
392     sigfillset(&sv.sa_mask );
393     if (sigaction(SIGTERM, &sv, NULL ) < 0 ) {
394         LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
395         daemon_exit(EXITERR_SYS);
396     }
397     if (sigaction(SIGQUIT, &sv, NULL ) < 0 ) {
398         LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
399         daemon_exit(EXITERR_SYS);
400     }
401
402     /* Ignore the rest */
403     sv.sa_handler = SIG_IGN;
404     sigemptyset(&sv.sa_mask );
405     if (sigaction(SIGALRM, &sv, NULL ) < 0 ) {
406         LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
407         daemon_exit(EXITERR_SYS);
408     }
409     sv.sa_handler = SIG_IGN;
410     sigemptyset(&sv.sa_mask );
411     if (sigaction(SIGHUP, &sv, NULL ) < 0 ) {
412         LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
413         daemon_exit(EXITERR_SYS);
414     }
415     sv.sa_handler = SIG_IGN;
416     sigemptyset(&sv.sa_mask );
417     if (sigaction(SIGUSR1, &sv, NULL ) < 0 ) {
418         LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
419         daemon_exit(EXITERR_SYS);
420     }
421     sv.sa_handler = SIG_IGN;
422     sigemptyset(&sv.sa_mask );
423     if (sigaction(SIGUSR2, &sv, NULL ) < 0 ) {
424         LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
425         daemon_exit(EXITERR_SYS);
426     }
427     sv.sa_handler = SIG_IGN;
428     sigemptyset(&sv.sa_mask );
429     if (sigaction(SIGPIPE, &sv, NULL ) < 0 ) {
430         LOG(log_error, logtype_afpd, "sigaction: %s", strerror(errno) );
431         daemon_exit(EXITERR_SYS);
432     }
433
434     /* block everywhere but in pselect */
435     sigemptyset(&set);
436     sigaddset(&set, SIGCHLD);
437     sigprocmask(SIG_SETMASK, &set, NULL);
438 }
439
440 static int setlimits(void)
441 {
442     struct rlimit rlim;
443
444     if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
445         LOG(log_error, logtype_afpd, "setlimits: %s", strerror(errno));
446         exit(1);
447     }
448     if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < 65535) {
449         rlim.rlim_cur = 65535;
450         if (rlim.rlim_max != RLIM_INFINITY && rlim.rlim_max < 65535)
451             rlim.rlim_max = 65535;
452         if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) {
453             LOG(log_error, logtype_afpd, "setlimits: %s", strerror(errno));
454             exit(1);
455         }
456     }
457     return 0;
458 }
459
460 /* ------------------ */
461 int main(int argc, char *argv[])
462 {
463     char  volpath[MAXPATHLEN + 1];
464     int   len, actual_len;
465     pid_t pid;
466     int   status;
467     char  *dbdpn = _PATH_CNID_DBD;
468     char  *host;
469     char  *port;
470     int    i;
471     int    cc;
472     uid_t  uid = 0;
473     gid_t  gid = 0;
474     int    err = 0;
475     int    debug = 0;
476     int    ret;
477     sigset_t set;
478     AFPObj obj = { 0 };
479     struct vol *vol;
480
481     while (( cc = getopt( argc, argv, "dF:vV")) != -1 ) {
482         switch (cc) {
483         case 'd':
484             debug = 1;
485             break;
486         case 'F':
487             obj.cmdlineconfigfile = strdup(optarg);
488             break;
489         case 'v':
490         case 'V':
491             printf("cnid_metad (Netatalk %s)\n", VERSION);
492             return -1;
493         default:
494             printf("cnid_metad [-dvV] [-F alternate configfile ]\n");
495             return -1;
496         }
497     }
498
499     if (!debug && daemonize(0, 0) != 0)
500         exit(EXITERR_SYS);
501
502     if (afp_config_parse(&obj, "cnid_metad") != 0)
503         daemon_exit(1);
504
505     if (load_volumes(&obj) != 0)
506         daemon_exit(1);
507
508     (void)setlimits();
509
510     host = iniparser_getstrdup(obj.iniconfig, INISEC_GLOBAL, "cnid listen", "localhost:4700");
511     if (port = strrchr(host, ':'))
512         *port++ = 0;
513     else
514         port = DEFAULTPORT;
515     if ((srvfd = tsockfd_create(host, port, 10)) < 0)
516         daemon_exit(1);
517
518     LOG(log_note, logtype_afpd, "CNID Server listening on %s:%s", host, port);
519
520     /* switch uid/gid */
521     if (uid || gid) {
522         LOG(log_debug, logtype_cnid, "Setting uid/gid to %i/%i", uid, gid);
523         if (gid) {
524             if (SWITCH_TO_GID(gid) < 0) {
525                 LOG(log_info, logtype_cnid, "unable to switch to group %d", gid);
526                 daemon_exit(1);
527             }
528         }
529         if (uid) {
530             if (SWITCH_TO_UID(uid) < 0) {
531                 LOG(log_info, logtype_cnid, "unable to switch to user %d", uid);
532                 daemon_exit(1);
533             }
534         }
535     }
536
537     set_signal();
538
539     sigemptyset(&set);
540     sigprocmask(SIG_SETMASK, NULL, &set);
541     sigdelset(&set, SIGCHLD);
542
543     while (1) {
544         rqstfd = usockfd_check(srvfd, &set);
545         /* Collect zombie processes and log what happened to them */
546         if (sigchild) while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
547             for (i = 0; i < maxvol; i++) {
548                 if (srv[i].pid == pid) {
549                     srv[i].pid = 0;
550                     close(srv[i].control_fd);
551                     break;
552                 }
553             }
554             if (WIFEXITED(status)) {
555                 LOG(log_info, logtype_cnid, "cnid_dbd[%i] exited with exit code %i",
556                     pid, WEXITSTATUS(status));
557             } else {
558                 /* cnid_dbd did a clean exit probably on idle timeout, reset bookkeeping */
559                 srv[i].tm = 0;
560                 srv[i].count = 0;
561             }
562             if (WIFSIGNALED(status)) {
563                 LOG(log_info, logtype_cnid, "cnid_dbd[%i] got signal %i",
564                     pid, WTERMSIG(status));
565             }
566             sigchild = 0;
567         }
568         if (rqstfd <= 0)
569             continue;
570
571         ret = readt(rqstfd, &len, sizeof(int), 1, 4);
572
573         if (!ret) {
574             /* already close */
575             goto loop_end;
576         }
577         else if (ret < 0) {
578             LOG(log_severe, logtype_cnid, "error read: %s", strerror(errno));
579             goto loop_end;
580         }
581         else if (ret != sizeof(int)) {
582             LOG(log_error, logtype_cnid, "short read: got %d", ret);
583             goto loop_end;
584         }
585         /*
586          *  checks for buffer overruns. The client libatalk side does it too
587          *  before handing the dir path over but who trusts clients?
588          */
589         if (!len || len +DBHOMELEN +2 > MAXPATHLEN) {
590             LOG(log_error, logtype_cnid, "wrong len parameter: %d", len);
591             goto loop_end;
592         }
593
594         actual_len = readt(rqstfd, volpath, len, 1, 5);
595         if (actual_len < 0) {
596             LOG(log_severe, logtype_cnid, "Read(2) error : %s", strerror(errno));
597             goto loop_end;
598         }
599         if (actual_len != len) {
600             LOG(log_error, logtype_cnid, "error/short read (dir): %s", strerror(errno));
601             goto loop_end;
602         }
603         volpath[len] = '\0';
604
605         LOG(log_debug, logtype_cnid, "main: request for volume: %s", volpath);
606
607         if (load_volumes(&obj) != 0) {
608             LOG(log_severe, logtype_cnid, "main: error reloading config");
609             goto loop_end;
610         }
611
612         if ((vol = getvolbypath(&obj, volpath)) == NULL) {
613             LOG(log_severe, logtype_cnid, "main: no volume for path \"%s\"", volpath);
614             goto loop_end;
615         }
616
617         LOG(log_maxdebug, logtype_cnid, "main: dbpath: %s", vol->v_dbpath);
618
619         if (set_dbdir(vol->v_dbpath, volpath) < 0) {
620             goto loop_end;
621         }
622
623         maybe_start_dbd(&obj, dbdpn, vol->v_path);
624     loop_end:
625         close(rqstfd);
626     }
627 }