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