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