]> arthur.barton.de Git - netatalk.git/blob - libatalk/util/unix.c
Add advanced option "chmod request" controlling ACLs
[netatalk.git] / libatalk / util / unix.c
1 /*
2   Copyright (c) 2010 Frank Lahm <franklahm@gmail.com>
3
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation; either version 2 of the License, or
7   (at your option) any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13 */
14
15 /*!
16  * @file
17  * Netatalk utility functions
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif /* HAVE_CONFIG_H */
23
24 #include <unistd.h>
25 #include <stdint.h>
26 #include <errno.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <dirent.h>
33 #include <sys/time.h>
34 #include <time.h>
35 #include <sys/wait.h>
36 #include <libgen.h>
37
38 #include <atalk/adouble.h>
39 #include <atalk/ea.h>
40 #include <atalk/afp.h>
41 #include <atalk/logger.h>
42 #include <atalk/vfs.h>
43 #include <atalk/util.h>
44 #include <atalk/unix.h>
45 #include <atalk/compat.h>
46 #include <atalk/errchk.h>
47 #include <atalk/acl.h>
48
49 /* close all FDs >= a specified value */
50 static void closeall(int fd)
51 {
52     int fdlimit = sysconf(_SC_OPEN_MAX);
53
54     while (fd < fdlimit)
55         close(fd++);
56 }
57
58 /*!
59  * Run command in a child and wait for it to finish
60  */
61 int run_cmd(const char *cmd, char **cmd_argv)
62 {
63     EC_INIT;
64     pid_t pid, wpid;
65     sigset_t sigs, oldsigs;
66         int status = 0;
67
68     sigfillset(&sigs);
69     pthread_sigmask(SIG_SETMASK, &sigs, &oldsigs);
70
71     if ((pid = fork()) < 0) {
72         LOG(log_error, logtype_default, "run_cmd: fork: %s", strerror(errno));
73         return -1;
74     }
75
76     if (pid == 0) {
77         /* child */
78         closeall(3);
79         execvp("mv", cmd_argv);
80     }
81
82     /* parent */
83         while ((wpid = waitpid(pid, &status, 0)) < 0) {
84             if (errno == EINTR)
85             continue;
86             break;
87         }
88         if (wpid != pid) {
89             LOG(log_error, logtype_default, "waitpid(%d): %s", (int)pid, strerror(errno));
90         EC_FAIL;
91         }
92
93     if (WIFEXITED(status))
94         status = WEXITSTATUS(status);
95     else if (WIFSIGNALED(status))
96         status = WTERMSIG(status);
97
98     LOG(log_note, logtype_default, "run_cmd(\"%s\"): status: %d", cmd, status);
99
100 EC_CLEANUP:
101     if (status != 0)
102         ret = status;
103     pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
104     EC_EXIT;
105 }
106
107 /*!
108  * Daemonize
109  *
110  * Fork, exit parent, setsid(), optionally chdir("/"), optionally close all fds
111  *
112  * returns -1 on failure, but you can't do much except exit in that case
113  * since we may already have forked
114  */
115 int daemonize(int nochdir, int noclose)
116 {
117     switch (fork()) {
118     case 0:
119         break;
120     case -1:
121         return -1;
122     default:
123         _exit(0);
124     }
125
126     if (setsid() < 0)
127         return -1;
128
129     switch (fork()) {
130     case 0: 
131         break;
132     case -1:
133         return -1;
134     default:
135         _exit(0);
136     }
137
138     if (!nochdir)
139         chdir("/");
140
141     if (!noclose) {
142         closeall(0);
143         open("/dev/null",O_RDWR);
144         dup(0);
145         dup(0);
146     }
147
148     return 0;
149 }
150
151 static uid_t saved_uid = -1;
152
153 /*
154  * seteuid(0) and back, if either fails and panic != 0 we PANIC
155  */
156 void become_root(void)
157 {
158     if (getuid() == 0) {
159         saved_uid = geteuid();
160         if (seteuid(0) != 0)
161             AFP_PANIC("Can't seteuid(0)");
162     }
163 }
164
165 void unbecome_root(void)
166 {
167     if (getuid() == 0) {
168         if (saved_uid == -1 || seteuid(saved_uid) < 0)
169             AFP_PANIC("Can't seteuid back");
170         saved_uid = -1;
171     }
172 }
173
174 /*!
175  * @brief get cwd in static buffer
176  *
177  * @returns pointer to path or pointer to error messages on error
178  */
179 const char *getcwdpath(void)
180 {
181     static char cwd[MAXPATHLEN + 1];
182     char *p;
183
184     if ((p = getcwd(cwd, MAXPATHLEN)) != NULL)
185         return p;
186     else
187         return strerror(errno);
188 }
189
190 /*!
191  * @brief Request absolute path
192  *
193  * @returns Absolute filesystem path to object
194  */
195 const char *fullpathname(const char *name)
196 {
197     static char wd[MAXPATHLEN + 1];
198
199     if (name[0] == '/')
200         return name;
201
202     if (getcwd(wd , MAXPATHLEN)) {
203         strlcat(wd, "/", MAXPATHLEN);
204         strlcat(wd, name, MAXPATHLEN);
205     } else {
206         strlcpy(wd, name, MAXPATHLEN);
207     }
208
209     return wd;
210 }
211
212 /*!
213  * Takes a buffer with a path, strips slashs, returns basename
214  *
215  * @param p (rw) path
216  *        path may be
217  *          "[/][dir/[...]]file"
218  *        or
219  *          "[/][dir/[...]]dir/[/]"
220  *        Result is "file" or "dir" 
221  *
222  * @returns pointer to basename in path buffer, buffer is possibly modified
223  */
224 char *stripped_slashes_basename(char *p)
225 {
226     int i = strlen(p) - 1;
227     while (i > 0 && p[i] == '/')
228         p[i--] = 0;
229     return (strrchr(p, '/') ? strrchr(p, '/') + 1 : p);
230 }
231
232 /*********************************************************************************
233  * chdir(), chmod(), chown(), stat() wrappers taking an additional option.
234  * Currently the only used options are O_NOFOLLOW, used to switch between symlink
235  * behaviour, and O_NETATALK_ACL for ochmod() indicating chmod_acl() shall be
236  * called which does special ACL handling depending on the filesytem
237  *********************************************************************************/
238
239 int ostat(const char *path, struct stat *buf, int options)
240 {
241     if (options & O_NOFOLLOW)
242         return lstat(path, buf);
243     else
244         return stat(path, buf);
245 }
246
247 int ochown(const char *path, uid_t owner, gid_t group, int options)
248 {
249     if (options & O_NOFOLLOW)
250         return lchown(path, owner, group);
251     else
252         return chown(path, owner, group);
253 }
254
255 /*!
256  * chmod() wrapper for symlink and ACL handling
257  *
258  * @param path       (r) path
259  * @param mode       (r) requested mode
260  * @param sb         (r) stat() of path or NULL
261  * @param option     (r) O_NOFOLLOW | O_NETATALK_ACL
262  *
263  * Options description:
264  * O_NOFOLLOW: don't chmod() symlinks, do nothing, return 0
265  * O_NETATALK_ACL: call chmod_acl() instead of chmod()
266  * O_IGNORE: ignore chmod() request, directly return 0
267  */
268 int ochmod(char *path, mode_t mode, const struct stat *st, int options)
269 {
270     struct stat sb;
271
272     if (options & O_IGNORE)
273         return 0;
274
275     if (!st) {
276         if (lstat(path, &sb) != 0)
277             return -1;
278         st = &sb;
279     }
280
281     if (options & O_NOFOLLOW)
282         if (S_ISLNK(st->st_mode))
283             return 0;
284
285     if (options & O_NETATALK_ACL) {
286         return chmod_acl(path, mode);
287     } else {
288         return chmod(path, mode);
289     }
290 }
291
292 /* 
293  * @brief ostat/fsstatat multiplexer
294  *
295  * ostatat mulitplexes ostat and fstatat. If we dont HAVE_ATFUNCS, dirfd is ignored.
296  *
297  * @param dirfd   (r) Only used if HAVE_ATFUNCS, ignored else, -1 gives AT_FDCWD
298  * @param path    (r) pathname
299  * @param st      (rw) pointer to struct stat
300  */
301 int ostatat(int dirfd, const char *path, struct stat *st, int options)
302 {
303 #ifdef HAVE_ATFUNCS
304     if (dirfd == -1)
305         dirfd = AT_FDCWD;
306     return fstatat(dirfd, path, st, (options & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0);
307 #else
308     return ostat(path, st, options);
309 #endif            
310
311     /* DEADC0DE */
312     return -1;
313 }
314
315 /*!
316  * @brief symlink safe chdir replacement
317  *
318  * Only chdirs to dir if it doesn't contain symlinks or if symlink checking
319  * is disabled
320  *
321  * @returns 1 if a path element is a symlink, 0 otherwise, -1 on syserror
322  */
323 int ochdir(const char *dir, int options)
324 {
325     char buf[MAXPATHLEN+1];
326     char cwd[MAXPATHLEN+1];
327     char *test;
328     int  i;
329
330     if (!(options & O_NOFOLLOW))
331         return chdir(dir);
332
333     /*
334      dir is a canonical path (without "../" "./" "//" )
335      but may end with a / 
336     */
337     *cwd = 0;
338     if (*dir != '/') {
339         if (getcwd(cwd, MAXPATHLEN) == NULL)
340             return -1;
341     }
342     if (chdir(dir) != 0)
343         return -1;
344
345     /* 
346      * Cases:
347      * chdir request   | realpath result | ret
348      * (after getwcwd) |                 |
349      * =======================================
350      * /a/b/.          | /a/b            | 0
351      * /a/b/.          | /c              | 1
352      * /a/b/.          | /c/d/e/f        | 1
353      */
354     if (getcwd(buf, MAXPATHLEN) == NULL)
355         return 1;
356
357     i = 0;
358     if (*cwd) {
359         /* relative path requested, 
360          * Same directory?
361         */
362         for (; cwd[i]; i++) {
363             if (buf[i] != cwd[i])
364                 return 1;
365         }
366         if (buf[i]) {
367             if (buf[i] != '/')
368                 return 1;
369             i++;
370         }                    
371     }
372
373     test = &buf[i];    
374     for (i = 0; test[i]; i++) {
375         if (test[i] != dir[i]) {
376             return 1;
377         }
378     }
379     /* trailing '/' ? */
380     if (!dir[i])
381         return 0;
382
383     if (dir[i] != '/')
384         return 1;
385
386     i++;
387     if (dir[i])
388         return 1;
389
390     return 0;
391 }
392
393 /*!
394  * Store n random bytes an buf
395  */
396 void randombytes(void *buf, int n)
397 {
398     char *p = (char *)buf;
399     int fd, i;
400     struct timeval tv;
401
402     if ((fd = open("/dev/urandom", O_RDONLY)) != -1) {
403         /* generate from /dev/urandom */
404         if (read(fd, buf, n) != n) {
405             close(fd);
406             fd = -1;
407         } else {
408             close(fd);
409             /* fd now != -1, so srandom wont be called below */
410         }
411     }
412
413     if (fd == -1) {
414         gettimeofday(&tv, NULL);
415         srandom((unsigned int)tv.tv_usec);
416         for (i=0 ; i < n ; i++)
417             p[i] = random() & 0xFF;
418     }
419
420     return;
421 }
422
423 int gmem(gid_t gid, int ngroups, gid_t *groups)
424 {
425     int         i;
426
427     for ( i = 0; i < ngroups; i++ ) {
428         if ( groups[ i ] == gid ) {
429             return( 1 );
430         }
431     }
432     return( 0 );
433 }
434
435 /*
436  * realpath() replacement that always allocates storage for returned path
437  */
438 char *realpath_safe(const char *path)
439 {
440     char *resolved_path;
441
442 #ifdef REALPATH_TAKES_NULL
443     if ((resolved_path = realpath(path, NULL)) == NULL) {
444         LOG(log_debug, logtype_afpd, "realpath() cannot resolve path \"%s\"", path);
445         return NULL;
446     }
447     return resolved_path;
448 #else
449     if ((resolved_path = malloc(MAXPATHLEN+1)) == NULL)
450         return NULL;
451     if (realpath(path, resolved_path) == NULL) {
452         free(resolved_path);
453         LOG(log_debug, logtype_afpd, "realpath() cannot resolve path \"%s\"", path);
454         return NULL;
455     }
456     /* Safe some memory */
457     char *tmp;
458     if ((tmp = strdup(resolved_path)) == NULL) {
459         free(resolved_path);
460         return NULL;
461     }
462     free(resolved_path);
463     resolved_path = tmp;
464     return resolved_path;
465 #endif
466 }
467
468 /**
469  * Returns pointer to static buffer with basename of path
470  **/
471 const char *basename_safe(const char *path)
472 {
473     static char buf[MAXPATHLEN+1];
474     strlcpy(buf, path, MAXPATHLEN);
475     return basename(buf);
476 }
477
478 /**
479  * extended strtok allows the quoted strings
480  * modified strtok.c in glibc 2.0.6
481  **/
482 char *strtok_quote(char *s, const char *delim)
483 {
484     static char *olds = NULL;
485     char *token;
486
487     if (s == NULL)
488         s = olds;
489
490     /* Scan leading delimiters.  */
491     s += strspn (s, delim);
492     if (*s == '\0')
493         return NULL;
494
495     /* Find the end of the token.  */
496     token = s;
497
498     if (token[0] == '\"') {
499         token++;
500         s = strpbrk (token, "\"");
501     } else {
502         s = strpbrk (token, delim);
503     }
504
505     if (s == NULL) {
506         /* This token finishes the string.  */
507         olds = strchr (token, '\0');
508     } else {
509         /* Terminate the token and make OLDS point past it.  */
510         *s = '\0';
511         olds = s + 1;
512     }
513     return token;
514 }
515
516 int set_groups(AFPObj *obj, struct passwd *pwd)
517 {
518     if (initgroups(pwd->pw_name, pwd->pw_gid) < 0)
519         LOG(log_error, logtype_afpd, "initgroups(%s, %d): %s", pwd->pw_name, pwd->pw_gid, strerror(errno));
520
521     if ((obj->ngroups = getgroups(0, NULL)) < 0) {
522         LOG(log_error, logtype_afpd, "login: %s getgroups: %s", pwd->pw_name, strerror(errno));
523         return -1;
524     }
525
526     if (obj->groups)
527         free(obj->groups);
528     if (NULL == (obj->groups = calloc(obj->ngroups, sizeof(gid_t))) ) {
529         LOG(log_error, logtype_afpd, "login: %s calloc: %d", obj->ngroups);
530         return -1;
531     }
532
533     if ((obj->ngroups = getgroups(obj->ngroups, obj->groups)) < 0 ) {
534         LOG(log_error, logtype_afpd, "login: %s getgroups: %s", pwd->pw_name, strerror(errno));
535         return -1;
536     }
537
538     return 0;
539 }
540
541 #define GROUPSTR_BUFSIZE 1024
542 const char *print_groups(int ngroups, gid_t *groups)
543 {
544     static char groupsstr[GROUPSTR_BUFSIZE];
545     int i;
546     char *s = groupsstr;
547
548     if (ngroups == 0)
549         return "-";
550
551     for (i = 0; (i < ngroups) && (s < &groupsstr[GROUPSTR_BUFSIZE]); i++) {
552         s += snprintf(s, &groupsstr[GROUPSTR_BUFSIZE] - s, " %u", groups[i]);
553     }
554
555     return groupsstr;
556 }