]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cnid_metad.c
affe50c17aaebdfc716333ae6a247d1291573231
[netatalk.git] / etc / cnid_dbd / cnid_metad.c
1 /*
2  * $Id: cnid_metad.c,v 1.1.4.7 2003-12-01 22:23:12 lenneis Exp $
3  *
4  * Copyright (C) Joerg Lenneis 2003
5  * All Rights Reserved.  See COPYRIGHT.
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 #define TESTTIME   20                  /* this much seconds */
100
101 #define DEFAULTHOST  "localhost"
102 #define DEFAULTPORT  4700
103
104 struct server {
105     char  *name;
106     pid_t pid;
107     time_t tm;                    /* When respawned last */
108     int count;                    /* Times respawned in the last TESTTIME secondes */
109     int   sv[2];
110 };
111
112 static struct server srv[MAXSRV +1];
113
114 static struct server *test_usockfn(char *dir, char *fn)
115 {
116 int i;
117     for (i = 1; i <= MAXSRV; i++) {
118         if (srv[i].name && !strcmp(srv[i].name, dir)) {
119             return &srv[i];
120         }
121     }
122     return NULL;
123 }
124
125 /* -------------------- */
126 static int send_cred(int socket, int fd)
127 {
128    int ret;
129    struct msghdr msgh; 
130    struct iovec iov[1];
131    struct cmsghdr *cmsgp = NULL;
132    char buf[CMSG_SPACE(sizeof fd)];
133    int er=0;
134
135    memset(&msgh,0,sizeof (msgh));
136    memset(buf,0,sizeof (buf));
137
138    msgh.msg_name = NULL;
139    msgh.msg_namelen = 0;
140
141    msgh.msg_iov = iov;
142    msgh.msg_iovlen = 1;
143
144    iov[0].iov_base = &er;
145    iov[0].iov_len = sizeof(er);
146
147    msgh.msg_control = buf;
148    msgh.msg_controllen = sizeof(buf);
149
150    cmsgp = CMSG_FIRSTHDR(&msgh);
151    cmsgp->cmsg_level = SOL_SOCKET;
152    cmsgp->cmsg_type = SCM_RIGHTS;
153    cmsgp->cmsg_len = CMSG_LEN(sizeof(fd));
154
155    *((int *)CMSG_DATA(cmsgp)) = fd;
156    msgh.msg_controllen = cmsgp->cmsg_len;
157
158    do  {
159        ret = sendmsg(socket,&msgh, 0);
160    } while ( ret == -1 && errno == EINTR );
161    if (ret == -1) {
162        LOG(log_error, logtype_cnid, "error in sendmsg: %s", strerror(errno));
163        return -1;
164    }
165    return 0;
166 }
167
168 /* -------------------- */
169 static int maybe_start_dbd(char *dbdpn, char *dbdir, char *usockfn)
170 {
171     pid_t pid;
172     struct server *up;
173     int i;
174     time_t t;
175
176     up = test_usockfn(dbdir, usockfn);
177     if (up && up->pid) {
178        /* we already have a process, send our fd */
179        if (send_cred(up->sv[0], rqstfd) < 0) {
180            /* FIXME */
181            return -1;
182        }
183        return 0;
184     }
185
186     time(&t);
187     if (!up) {
188         /* find an empty slot */
189         for (i = 1; i <= MAXSRV; i++) {
190             if (!srv[i].pid && srv[i].tm + TESTTIME < t) {
191                 up = &srv[i];
192                 free(up->name);
193                 up->tm = t;
194                 up->count = 0;
195                 /* copy name */
196                 up->name = strdup(dbdir);
197                 break;
198             }
199         }
200         if (!up) {
201             LOG(log_error, logtype_cnid, "no free slot");
202             return -1;
203         }
204     }
205     else {
206         /* we have a slot but no process, check for respawn too fast */
207         if (up->tm + TESTTIME > t) {
208             up->count++;
209         } else {
210             up->count = 0;
211             up->tm = t;
212         }
213         if (up->count >= MAXSPAWN) {
214             up->tm = t;
215             LOG(log_error, logtype_cnid, "respawn too fast %s", up->name);
216             /* FIXME should we sleep a little ? */
217             return -1;
218         }
219         
220     }
221     /* create socketpair for comm between parent and child 
222      * FIXME Do we really need a permanent pipe between them ?
223      */
224     if (socketpair(PF_UNIX, SOCK_STREAM, 0, up->sv) < 0) {
225         LOG(log_error, logtype_cnid, "error in fork: %s", strerror(errno));
226         return -1;
227     }
228         
229     if ((pid = fork()) < 0) {
230         LOG(log_error, logtype_cnid, "error in fork: %s", strerror(errno));
231         return -1;
232     }    
233     if (pid == 0) {
234         /*
235          *  Child. Close descriptors and start the daemon. If it fails
236          *  just log it. The client process will fail connecting
237          *  afterwards anyway.
238          */
239         close(0);
240         close(1);
241         close(srvfd);
242         dup2(up->sv[1], 0);
243         dup2(rqstfd, 1);
244
245         close(up->sv[0]);
246         close(up->sv[1]);
247         close(rqstfd);
248         if (execlp(dbdpn, dbdpn, dbdir, NULL) < 0) {
249             LOG(log_error, logtype_cnid, "Fatal error in exec: %s", strerror(errno));
250             exit(0);
251         }
252     }
253     /*
254      *  Parent.
255      */
256     up->pid = pid;
257     return 0;
258 }
259
260 /* ------------------ */
261 static int set_dbdir(char *dbdir, int len)
262 {
263    struct stat st;
264
265     if (!len)
266         return -1;
267
268     if (stat(dbdir, &st) < 0 && mkdir(dbdir, 0755) < 0) {
269         LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir);
270         return -1;
271     }
272
273     if (dbdir[len - 1] != '/') {
274          strcat(dbdir, "/");
275          len++;
276     }
277     strcpy(dbdir + len, DBHOME);
278     if (stat(dbdir, &st) < 0 && mkdir(dbdir, 0755 ) < 0) {
279         LOG(log_error, logtype_cnid, "set_dbdir: mkdir failed for %s", dbdir);
280         return -1;
281     }
282     return 0;   
283 }
284
285 /* ------------------ */
286 uid_t user_to_uid ( username )
287 char    *username;
288 {
289     struct passwd *this_passwd;
290  
291     /* check for anything */
292     if ( !username || strlen ( username ) < 1 ) return 0;
293  
294     /* grab the /etc/passwd record relating to username */
295     this_passwd = getpwnam ( username );
296  
297     /* return false if there is no structure returned */
298     if (this_passwd == NULL) return 0;
299  
300     /* return proper uid */
301     return this_passwd->pw_uid;
302  
303
304
305 /* ------------------ */
306 gid_t group_to_gid ( group )
307 char    *group;
308 {
309     struct group *this_group;
310  
311     /* check for anything */
312     if ( !group || strlen ( group ) < 1 ) return 0;
313  
314     /* grab the /etc/groups record relating to group */
315     this_group = getgrnam ( group );
316  
317     /* return false if there is no structure returned */
318     if (this_group == NULL) return 0;
319  
320     /* return proper gid */
321     return this_group->gr_gid;
322  
323 }
324
325 /* ------------------ */
326 int main(int argc, char *argv[])
327 {
328     char  dbdir[MAXPATHLEN + 1];
329     int   len;
330     pid_t pid;
331     int   status;
332     char  *dbdpn = _PATH_CNID_DBD;
333     char  *host = DEFAULTHOST;
334     int   port = DEFAULTPORT;
335     struct db_param *dbp;
336     int    i;
337     int    cc;
338     uid_t  uid = 0;
339     gid_t  gid = 0;
340     int    err = 0;
341     int    debug = 0;
342     int    ret;
343     
344     while (( cc = getopt( argc, argv, "ds:p:h:u:g:")) != -1 ) {
345         switch (cc) {
346         case 'd':
347             debug = 1;
348             break;
349         case 'h':
350             host = strdup(optarg);  
351             break;
352         case 'u':
353             uid = user_to_uid (optarg);
354             if (!uid) {
355                 LOG(log_error, logtype_cnid, "main: bad user %s", optarg);
356                 err++;
357             }
358             break;
359         case 'g':
360             gid =group_to_gid (optarg);
361             if (!gid) {
362                 LOG(log_error, logtype_cnid, "main: bad group %s", optarg);
363                 err++;
364             }
365             break;
366         case 'p':
367             port = atoi(optarg);
368             break;
369         case 's':
370             dbdpn = strdup(optarg);
371             break;
372         default:
373             err++;
374             break;
375         }
376     }
377     
378     if (err) {
379         LOG(log_error, logtype_cnid, "main: bad arguments");
380         exit(1);
381     }
382     
383     if (!debug) {
384  
385         switch (fork()) {
386         case 0 :
387             fclose(stdin);
388             fclose(stdout);
389             fclose(stderr);
390
391 #ifdef TIOCNOTTY
392             {
393                 int i;
394                 if (( i = open( "/dev/tty", O_RDWR )) >= 0 ) {
395                     (void)ioctl( i, TIOCNOTTY, 0 );
396                     setpgid( 0, getpid());
397                     (void) close(i);
398                 }
399             }
400 #else
401             setpgid( 0, getpid());
402 #endif
403            break;
404         case -1 :  /* error */
405             LOG(log_error, logtype_cnid, "detach from terminal: %s", strerror(errno));
406             exit(1);
407         default :  /* server */
408             exit(0);
409         }
410     }
411
412     if ((srvfd = tsockfd_create(host, port, 10)) < 0)
413         exit(1);
414
415     /* switch uid/gid */
416     if (uid || gid) {
417         LOG(log_info, logtype_cnid, "Setting uid/gid to %i/%i", uid, gid);
418         if (gid) {
419             if (SWITCH_TO_GID(gid) < 0) {
420                 LOG(log_info, logtype_cnid, "unable to switch to group %d", gid);
421                 exit(1);
422             }
423         }
424         if (uid) {
425             if (SWITCH_TO_UID(uid) < 0) {
426                 LOG(log_info, logtype_cnid, "unable to switch to user %d", uid);
427                 exit(1);
428             }
429         }
430     }
431
432     signal(SIGPIPE, SIG_IGN);
433
434     while (1) {
435         /* Collect zombie processes and log what happened to them */       
436         while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
437            for (i = 1; i <= MAXSRV; i++) {
438                if (srv[i].pid == pid) {
439                    srv[i].pid = 0;
440 #if 0                   
441                    free(srv[i].name);
442 #endif                   
443                    close(srv[i].sv[0]);
444                    close(srv[i].sv[1]);
445                    break;
446                }
447             }
448             if (WIFEXITED(status)) {
449                 LOG(log_info, logtype_cnid, "cnid_dbd pid %i exited with exit code %i", 
450                     pid, WEXITSTATUS(status));
451             }
452             else if (WIFSIGNALED(status)) {
453                 LOG(log_info, logtype_cnid, "cnid_dbd pid %i exited with signal %i", 
454                     pid, WTERMSIG(status));
455             }
456             /* FIXME should */
457             
458         }
459         if ((rqstfd = usockfd_check(srvfd, 10000000)) <= 0)
460             continue;
461         /* TODO: Check out read errors, broken pipe etc. in libatalk. Is
462            SIGIPE ignored there? Answer: Ignored for dsi, but not for asp ... */
463         ret = read(rqstfd, &len, sizeof(int));
464         if (!ret) {
465             /* already close */
466             goto loop_end;
467         }
468         else if (ret < 0) {
469             LOG(log_error, logtype_cnid, "error read: %s", strerror(errno));
470             goto loop_end;
471         }
472         else if (ret != sizeof(int)) {
473             LOG(log_error, logtype_cnid, "short read: got %d", ret);
474             goto loop_end;
475         }
476         /*
477          *  checks for buffer overruns. The client libatalk side does it too 
478          *  before handing the dir path over but who trusts clients?
479          */
480         if (!len || len +DBHOMELEN +2 > MAXPATHLEN) {
481             LOG(log_error, logtype_cnid, "wrong len parameter: %d", len);
482             goto loop_end;
483         }
484         if (read(rqstfd, dbdir, len) != len) {
485             LOG(log_error, logtype_cnid, "error/short read (dir): %s", strerror(errno));
486             goto loop_end;
487         }
488         dbdir[len] = '\0';
489         
490         if (set_dbdir(dbdir, len) < 0) {
491             goto loop_end;
492         }
493         
494         if ((dbp = db_param_read(dbdir)) == NULL) {
495             LOG(log_error, logtype_cnid, "Error reading config file");
496             goto loop_end;
497         }
498         maybe_start_dbd(dbdpn, dbdir, dbp->usock_file);
499
500     loop_end:
501         close(rqstfd);
502     }
503 }