]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cnid_metad.c
709e694facecf3a55f9ab02d426671d8c75e2eaa
[netatalk.git] / etc / cnid_dbd / cnid_metad.c
1 /*
2  * $Id: cnid_metad.c,v 1.16 2009-10-14 01:38:28 didg Exp $
3  *
4  * Copyright (C) Joerg Lenneis 2003
5  * All Rights Reserved.  See COPYING.
6  *
7  */
8
9 /* 
10    cnid_dbd metadaemon to start up cnid_dbd upon request from afpd.
11    Here is how it works:
12    
13                        via TCP socket
14    1.       afpd          ------->        cnid_metad
15
16                    via UNIX domain socket
17    2.   cnid_metad        ------->         cnid_dbd
18
19                     passes afpd client fd
20    3.   cnid_metad        ------->         cnid_dbd
21
22    Result:
23                        via TCP socket
24    4.       afpd          ------->         cnid_dbd
25  */
26
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif /* HAVE_CONFIG_H */
31
32 #include <stdlib.h>
33
34 #ifdef HAVE_UNISTD_H
35 #define __USE_GNU
36 #include <unistd.h>
37 #undef __USE_GNU
38 #endif /* HAVE_UNISTD_H */
39 #include <sys/param.h>
40 #include <errno.h>
41 #include <string.h>
42 #include <signal.h>
43 #ifdef HAVE_SYS_TYPES_H
44 #include <sys/types.h>
45 #endif
46 #ifdef HAVE_SYS_TIME_H
47 #include <sys/time.h>
48 #endif
49 #ifdef HAVE_SYS_WAIT_H
50 #include <sys/wait.h>
51 #endif
52 #ifdef HAVE_SYS_UIO_H
53 #include <sys/uio.h>
54 #endif
55 #include <sys/un.h>
56 #define _XPG4_2 1
57 #include <sys/socket.h>
58 #include <stdio.h>
59 #include <time.h>
60 #include <sys/ioctl.h>
61
62 #ifndef WEXITSTATUS
63 #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
64 #endif /* ! WEXITSTATUS */
65 #ifndef WIFEXITED
66 #define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
67 #endif /* ! WIFEXITED */
68 #ifndef WIFSTOPPED
69 #define WIFSTOPPED(status) (((status) & 0xff) == 0x7f)
70 #endif
71
72 #ifndef WIFSIGNALED
73 #define WIFSIGNALED(status) (!WIFSTOPPED(status) && !WIFEXITED(status))
74 #endif
75 #ifndef WTERMSIG
76 #define WTERMSIG(status)      ((status) & 0x7f)
77 #endif
78
79 /* functions for username and group */
80 #include <pwd.h>
81 #include <grp.h>
82
83 /* FIXME */
84 #ifdef linux
85 #ifndef USE_SETRESUID
86 #define USE_SETRESUID 1
87 #define SWITCH_TO_GID(gid)  ((setresgid(gid,gid,gid) < 0 || setgid(gid) < 0) ? -1 : 0)
88 #define SWITCH_TO_UID(uid)  ((setresuid(uid,uid,uid) < 0 || setuid(uid) < 0) ? -1 : 0)
89 #endif
90 #else
91 #ifndef USE_SETEUID
92 #define USE_SETEUID 1
93 #define SWITCH_TO_GID(gid)  ((setegid(gid) < 0 || setgid(gid) < 0) ? -1 : 0)
94 #define SWITCH_TO_UID(uid)  ((setuid(uid) < 0 || seteuid(uid) < 0 || setuid(uid) < 0) ? -1 : 0)
95 #endif
96 #endif
97
98 #include <atalk/util.h>
99 #include <atalk/logger.h>
100 #include <atalk/cnid_dbd_private.h>
101
102 #include "db_param.h"
103 #include "usockfd.h"
104
105 #define DBHOME        ".AppleDB"
106 #define DBHOMELEN    8
107
108 static int srvfd;
109 static int rqstfd;
110 static volatile sig_atomic_t alarmed = 0;
111
112 #define MAXSPAWN   3                   /* Max times respawned in.. */
113 #define TESTTIME   42                  /* this much seconds apfd client tries to  *
114                                         * to reconnect every 5 secondes, catch it */
115 #define MAXVOLS    512
116 #define DEFAULTHOST  "localhost"
117 #define DEFAULTPORT  4700
118
119 struct server {
120     char  *name;
121     pid_t pid;
122     time_t tm;                    /* When respawned last */
123     int count;                    /* Times respawned in the last TESTTIME secondes */
124     int control_fd;               /* file descriptor to child cnid_dbd process */
125 };
126
127 static struct server srv[MAXVOLS];
128
129 /* Default logging config: log to syslog with level log_note */
130 static char logconfig[MAXPATHLEN + 21 + 1] = "default log_note";
131
132 static struct server *test_usockfn(char *dir)
133 {
134     int i;
135     for (i = 0; i < MAXVOLS; i++) {
136         if (srv[i].name && !strcmp(srv[i].name, dir)) {
137             return &srv[i];
138         }
139     }
140     return NULL;
141 }
142
143 /* -------------------- */
144 static int send_cred(int socket, int fd)
145 {
146     int ret;
147     struct msghdr msgh;
148     struct iovec iov[1];
149     struct cmsghdr *cmsgp = NULL;
150     char *buf;
151     size_t size;
152     int er=0;
153
154     size = CMSG_SPACE(sizeof fd);
155     buf = malloc(size);
156     if (!buf) {
157         LOG(log_error, logtype_cnid, "error in sendmsg: %s", strerror(errno));
158         return -1;
159     }
160
161     memset(&msgh,0,sizeof (msgh));
162     memset(buf,0, size);
163
164     msgh.msg_name = NULL;
165     msgh.msg_namelen = 0;
166
167     msgh.msg_iov = iov;
168     msgh.msg_iovlen = 1;
169
170     iov[0].iov_base = &er;
171     iov[0].iov_len = sizeof(er);
172
173     msgh.msg_control = buf;
174     msgh.msg_controllen = size;
175
176     cmsgp = CMSG_FIRSTHDR(&msgh);
177     cmsgp->cmsg_level = SOL_SOCKET;
178     cmsgp->cmsg_type = SCM_RIGHTS;
179     cmsgp->cmsg_len = CMSG_LEN(sizeof(fd));
180
181     *((int *)CMSG_DATA(cmsgp)) = fd;
182     msgh.msg_controllen = cmsgp->cmsg_len;
183
184     do  {
185         ret = sendmsg(socket,&msgh, 0);
186     } while ( ret == -1 && errno == EINTR );
187     if (ret == -1) {
188         LOG(log_error, logtype_cnid, "error in sendmsg: %s", strerror(errno));
189         free(buf);
190         return -1;
191     }
192     free(buf);
193     return 0;
194 }
195
196 /* -------------------- */
197 static int maybe_start_dbd(char *dbdpn, char *dbdir, char *usockfn)
198 {
199     pid_t pid;
200     struct server *up;
201     int sv[2];
202     int i;
203     time_t t;
204     char buf1[8];
205     char buf2[8];
206
207     LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: dbdir: '%s', UNIX socket file: '%s'", 
208         dbdir, usockfn);
209
210     up = test_usockfn(dbdir);
211     if (up && up->pid) {
212         /* we already have a process, send our fd */
213         if (send_cred(up->control_fd, rqstfd) < 0) {
214             /* FIXME */
215             return -1;
216         }
217         return 0;
218     }
219
220     LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: no cnid_dbd for that volume yet. Starting one ...");
221
222     time(&t);
223     if (!up) {
224         /* find an empty slot */
225         for (i = 0; i < MAXVOLS; i++) {
226             if ( !srv[i].name ) {
227                 up = &srv[i];
228                 up->tm = t;
229                 up->count = 0;
230                 up->name = strdup(dbdir);
231                 break;
232             }
233         }
234         if (!up) {
235             LOG(log_error, logtype_cnid, "no free slot for cnid_dbd child. Configured maximum: %d. Do you have so many volumes?", MAXVOLS);
236             return -1;
237         }
238     }
239     else {
240         /* we have a slot but no process, check for respawn too fast */
241         if ( (t < (up->tm + TESTTIME)) /* We're in the respawn time window */
242              &&
243              (up->count > MAXSPAWN) ) { /* ...and already tried to fork too often */
244             LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn too fast just exiting");
245             return -1; /* just exit, dont sleep, because we might have work to do for another client  */
246         }
247         if ( t >= (up->tm + TESTTIME) ) { /* drop slot */
248             LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn window ended, dropping slot");
249             free(up->name);
250             up->name = NULL;
251             return -1; /* next time we'll try again with a new slot */
252         }
253         up->count++;
254         LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: respawn count now is: %u", up->count);
255         if (up->count > MAXSPAWN) {
256             /* We spawned too fast. From now until the first time we tried + TESTTIME seconds
257                we will just return -1 above */
258             LOG(log_maxdebug, logtype_cnid, "maybe_start_dbd: reached MAXSPAWN threshhold");
259         }
260     }
261
262     /* 
263        Create socketpair for comm between parent and child.
264        We use it to pass fds from connecting afpd processes to our
265        cnid_dbd child via fd passing.
266     */
267     if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) {
268         LOG(log_error, logtype_cnid, "error in socketpair: %s", strerror(errno));
269         return -1;
270     }
271
272     if ((pid = fork()) < 0) {
273         LOG(log_error, logtype_cnid, "error in fork: %s", strerror(errno));
274         return -1;
275     }
276     if (pid == 0) {
277         int ret;
278         /*
279          *  Child. Close descriptors and start the daemon. If it fails
280          *  just log it. The client process will fail connecting
281          *  afterwards anyway.
282          */
283
284         close(srvfd);
285         close(sv[0]);
286
287         for (i = 0; i < MAXVOLS; i++) {
288             if (srv[i].pid && up != &srv[i]) {
289                 close(srv[i].control_fd);
290             }
291         }
292
293         sprintf(buf1, "%i", sv[1]);
294         sprintf(buf2, "%i", rqstfd);
295
296         if (up->count == MAXSPAWN) {
297             /* there's a pb with the db inform child
298              * it will run recover, delete the db whatever
299              */
300             LOG(log_error, logtype_cnid, "try with -d %s", up->name);
301             ret = execlp(dbdpn, dbdpn, "-d", dbdir, buf1, buf2, logconfig, NULL);
302         }
303         else {
304             ret = execlp(dbdpn, dbdpn, dbdir, buf1, buf2, logconfig, NULL);
305         }
306         if (ret < 0) {
307             LOG(log_error, logtype_cnid, "Fatal error in exec: %s", strerror(errno));
308             exit(0);
309         }
310     }
311     /*
312      *  Parent.
313      */
314     up->pid = pid;
315     close(sv[1]);
316     up->control_fd = sv[0];
317     return 0;
318 }
319
320 /* ------------------ */
321 static int set_dbdir(char *dbdir, int len)
322 {
323     struct stat st;
324
325     if (!len)
326         return -1;
327
328     if (stat(dbdir, &st) < 0 && mkdir(dbdir, 0755) < 0) {
329         LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir);
330         return -1;
331     }
332
333     if (dbdir[len - 1] != '/') {
334         strcat(dbdir, "/");
335         len++;
336     }
337     strcpy(dbdir + len, DBHOME);
338     if (stat(dbdir, &st) < 0 && mkdir(dbdir, 0755 ) < 0) {
339         LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir);
340         return -1;
341     }
342     return 0;
343 }
344
345 /* ------------------ */
346 static uid_t user_to_uid (char *username)
347 {
348     struct passwd *this_passwd;
349
350     /* check for anything */
351     if ( !username || strlen ( username ) < 1 ) return 0;
352
353     /* grab the /etc/passwd record relating to username */
354     this_passwd = getpwnam ( username );
355
356     /* return false if there is no structure returned */
357     if (this_passwd == NULL) return 0;
358
359     /* return proper uid */
360     return this_passwd->pw_uid;
361
362 }
363
364 /* ------------------ */
365 static gid_t group_to_gid ( char *group)
366 {
367     struct group *this_group;
368
369     /* check for anything */
370     if ( !group || strlen ( group ) < 1 ) return 0;
371
372     /* grab the /etc/groups record relating to group */
373     this_group = getgrnam ( group );
374
375     /* return false if there is no structure returned */
376     if (this_group == NULL) return 0;
377
378     /* return proper gid */
379     return this_group->gr_gid;
380
381 }
382
383 /* ------------------ */
384 static void catch_alarm(int sig _U_) {
385     alarmed = 1;
386 }
387
388 /* ------------------ */
389 int main(int argc, char *argv[])
390 {
391     char  dbdir[MAXPATHLEN + 1];
392     int   len, actual_len;
393     pid_t pid;
394     int   status;
395     char  *dbdpn = _PATH_CNID_DBD;
396     char  *host = DEFAULTHOST;
397     u_int16_t   port = DEFAULTPORT;
398     struct db_param *dbp;
399     int    i;
400     int    cc;
401     uid_t  uid = 0;
402     gid_t  gid = 0;
403     int    err = 0;
404     int    debug = 0;
405     int    ret;
406     char   *loglevel = NULL;
407     char   *logfile  = NULL;
408
409     set_processname("cnid_metad");
410
411     while (( cc = getopt( argc, argv, "ds:p:h:u:g:l:f:")) != -1 ) {
412         switch (cc) {
413         case 'd':
414             debug = 1;
415             break;
416         case 'h':
417             host = strdup(optarg);
418             break;
419         case 'u':
420             uid = user_to_uid (optarg);
421             if (!uid) {
422                 LOG(log_error, logtype_cnid, "main: bad user %s", optarg);
423                 err++;
424             }
425             break;
426         case 'g':
427             gid =group_to_gid (optarg);
428             if (!gid) {
429                 LOG(log_error, logtype_cnid, "main: bad group %s", optarg);
430                 err++;
431             }
432             break;
433         case 'p':
434             port = atoi(optarg);
435             break;
436         case 's':
437             dbdpn = strdup(optarg);
438             break;
439         case 'l':
440             loglevel = strdup(optarg);
441             break;
442         case 'f':
443             logfile = strdup(optarg);
444             break;
445         default:
446             err++;
447             break;
448         }
449     }
450
451     if (loglevel) {
452         strlcpy(logconfig + 8, loglevel, 13);
453         free(loglevel);
454         strcat(logconfig, " ");
455     }
456     if (logfile) {
457         strlcat(logconfig, logfile, MAXPATHLEN);
458         free(logfile);
459     }
460     setuplog(logconfig);
461
462     if (err) {
463         LOG(log_error, logtype_cnid, "main: bad arguments");
464         exit(1);
465     }
466
467     if (!debug) {
468
469         switch (fork()) {
470         case 0 :
471             fclose(stdin);
472             fclose(stdout);
473             fclose(stderr);
474
475 #ifdef TIOCNOTTY
476             {
477                 int i;
478                 if (( i = open( "/dev/tty", O_RDWR )) >= 0 ) {
479                     (void)ioctl( i, TIOCNOTTY, 0 );
480                     setpgid( 0, getpid());
481                     (void) close(i);
482                 }
483             }
484 #else
485             setpgid( 0, getpid());
486 #endif
487             break;
488         case -1 :  /* error */
489             LOG(log_error, logtype_cnid, "detach from terminal: %s", strerror(errno));
490             exit(1);
491         default :  /* server */
492             exit(0);
493         }
494     }
495
496     if ((srvfd = tsockfd_create(host, port, 10)) < 0)
497         exit(1);
498
499     /* switch uid/gid */
500     if (uid || gid) {
501         LOG(log_info, logtype_cnid, "Setting uid/gid to %i/%i", uid, gid);
502         if (gid) {
503             if (SWITCH_TO_GID(gid) < 0) {
504                 LOG(log_info, logtype_cnid, "unable to switch to group %d", gid);
505                 exit(1);
506             }
507         }
508         if (uid) {
509             if (SWITCH_TO_UID(uid) < 0) {
510                 LOG(log_info, logtype_cnid, "unable to switch to user %d", uid);
511                 exit(1);
512             }
513         }
514     }
515
516     signal(SIGPIPE, SIG_IGN);
517     signal(SIGALRM, catch_alarm);
518
519     while (1) {
520         rqstfd = usockfd_check(srvfd, 10000000);
521         /* Collect zombie processes and log what happened to them */
522         while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
523             for (i = 0; i < MAXVOLS; i++) {
524                 if (srv[i].pid == pid) {
525                     srv[i].pid = 0;
526                     close(srv[i].control_fd);
527                     break;
528                 }
529             }
530             if (WIFEXITED(status)) {
531                 LOG(log_info, logtype_cnid, "cnid_dbd pid %i exited with exit code %i",
532                     pid, WEXITSTATUS(status));
533             }
534             else if (WIFSIGNALED(status)) {
535                 LOG(log_info, logtype_cnid, "cnid_dbd pid %i exited with signal %i",
536                     pid, WTERMSIG(status));
537             }
538             /* FIXME should */
539
540         }
541         if (rqstfd <= 0)
542             continue;
543
544         /* TODO: Check out read errors, broken pipe etc. in libatalk. Is
545            SIGIPE ignored there? Answer: Ignored for dsi, but not for asp ... */
546         alarm(5); /* to prevent read from getting stuck */
547         ret = read(rqstfd, &len, sizeof(int));
548         alarm(0);
549         if (alarmed) {
550             alarmed = 0;
551             LOG(log_severe, logtype_cnid, "Read(1) bailed with alarm (timeout)");
552             goto loop_end;
553         }
554
555         if (!ret) {
556             /* already close */
557             goto loop_end;
558         }
559         else if (ret < 0) {
560             LOG(log_error, logtype_cnid, "error read: %s", strerror(errno));
561             goto loop_end;
562         }
563         else if (ret != sizeof(int)) {
564             LOG(log_error, logtype_cnid, "short read: got %d", ret);
565             goto loop_end;
566         }
567         /*
568          *  checks for buffer overruns. The client libatalk side does it too
569          *  before handing the dir path over but who trusts clients?
570          */
571         if (!len || len +DBHOMELEN +2 > MAXPATHLEN) {
572             LOG(log_error, logtype_cnid, "wrong len parameter: %d", len);
573             goto loop_end;
574         }
575
576         alarm(5);
577         actual_len = read(rqstfd, dbdir, len);
578         alarm(0);
579         if (alarmed) {
580             alarmed = 0;
581             LOG(log_severe, logtype_cnid, "Read(2) bailed with alarm (timeout)");
582             goto loop_end;
583         }
584         if (actual_len != len) {
585             LOG(log_error, logtype_cnid, "error/short read (dir): %s", strerror(errno));
586             goto loop_end;
587         }
588         dbdir[len] = '\0';
589
590         if (set_dbdir(dbdir, len) < 0) {
591             goto loop_end;
592         }
593
594         if ((dbp = db_param_read(dbdir, METAD)) == NULL) {
595             LOG(log_error, logtype_cnid, "Error reading config file");
596             goto loop_end;
597         }
598         maybe_start_dbd(dbdpn, dbdir, dbp->usock_file);
599
600     loop_end:
601         close(rqstfd);
602     }
603 }