]> arthur.barton.de Git - netatalk.git/blob - libatalk/util/unix.c
Configurable symlink behaviour
[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
36 #include <atalk/adouble.h>
37 #include <atalk/ea.h>
38 #include <atalk/afp.h>
39 #include <atalk/logger.h>
40 #include <atalk/vfs.h>
41 #include <atalk/util.h>
42 #include <atalk/unix.h>
43
44 /* close all FDs >= a specified value */
45 static void closeall(int fd)
46 {
47     int fdlimit = sysconf(_SC_OPEN_MAX);
48
49     while (fd < fdlimit)
50         close(fd++);
51 }
52
53 /*!
54  * Daemonize
55  *
56  * Fork, exit parent, setsid(), optionally chdir("/"), optionally close all fds
57  *
58  * returns -1 on failure, but you can't do much except exit in that case
59  * since we may already have forked
60  */
61 int daemonize(int nochdir, int noclose)
62 {
63     switch (fork()) {
64     case 0:
65         break;
66     case -1:
67         return -1;
68     default:
69         _exit(0);
70     }
71
72     if (setsid() < 0)
73         return -1;
74
75     switch (fork()) {
76     case 0: 
77         break;
78     case -1:
79         return -1;
80     default:
81         _exit(0);
82     }
83
84     if (!nochdir)
85         chdir("/");
86
87     if (!noclose) {
88         closeall(0);
89         open("/dev/null",O_RDWR);
90         dup(0);
91         dup(0);
92     }
93
94     return 0;
95 }
96
97 static uid_t saved_uid = -1;
98
99 void become_root(void)
100 {
101     saved_uid = geteuid();
102     if (seteuid(0) != 0)
103         AFP_PANIC("Can't seteuid(0)");
104 }
105
106 void unbecome_root(void)
107 {
108     if (saved_uid == -1 || seteuid(saved_uid) < 0)
109         AFP_PANIC("Can't seteuid back");
110     saved_uid = -1;
111 }
112
113 /*!
114  * @brief get cwd in static buffer
115  *
116  * @returns pointer to path or pointer to error messages on error
117  */
118 const char *getcwdpath(void)
119 {
120     static char cwd[MAXPATHLEN + 1];
121     char *p;
122
123     if ((p = getcwd(cwd, MAXPATHLEN)) != NULL)
124         return p;
125     else
126         return strerror(errno);
127 }
128
129 /*!
130  * @brief Request absolute path
131  *
132  * @returns Absolute filesystem path to object
133  */
134 const char *fullpathname(const char *name)
135 {
136     static char wd[MAXPATHLEN + 1];
137
138     if (name[0] == '/')
139         return name;
140
141     if (getcwd(wd , MAXPATHLEN)) {
142         strlcat(wd, "/", MAXPATHLEN);
143         strlcat(wd, name, MAXPATHLEN);
144     } else {
145         strlcpy(wd, name, MAXPATHLEN);
146     }
147
148     return wd;
149 }
150
151 /*!
152  * Takes a buffer with a path, strips slashs, returns basename
153  *
154  * @param p (rw) path
155  *        path may be
156  *          "[/][dir/[...]]file"
157  *        or
158  *          "[/][dir/[...]]dir/[/]"
159  *        Result is "file" or "dir" 
160  *
161  * @returns pointer to basename in path buffer, buffer is possibly modified
162  */
163 char *stripped_slashes_basename(char *p)
164 {
165     int i = strlen(p) - 1;
166     while (i > 0 && p[i] == '/')
167         p[i--] = 0;
168     return (strrchr(p, '/') ? strrchr(p, '/') + 1 : p);
169 }
170
171 /*********************************************************************************
172  * chdir(), chmod(), chown(), stat() wrappers taking an additional option.
173  * Currently the only used options are O_NOFOLLOW, used to switch between symlink
174  * behaviour, and O_NETATALK_ACL for ochmod() indicating chmod_acl() shall be
175  * called which does special ACL handling depending on the filesytem
176  *********************************************************************************/
177
178 int ostat(const char *path, struct stat *buf, int options)
179 {
180     if (options & O_NOFOLLOW)
181         return lstat(path, buf);
182     else
183         return stat(path, buf);
184 }
185
186 int ochown(const char *path, uid_t owner, gid_t group, int options)
187 {
188     if (options & O_NOFOLLOW)
189         return lchown(path, owner, group);
190     else
191         return chown(path, owner, group);
192 }
193
194 /*!
195  * chmod() wrapper for symlink and ACL handling
196  *
197  * @param path       (r) path
198  * @param mode       (r) requested mode
199  * @param sb         (r) stat() of path or NULL
200  * @param option     (r) O_NOFOLLOW | O_NETATALK_ACL
201  *
202  * Options description:
203  * O_NOFOLLOW: don't chmod() symlinks, do nothing, return 0
204  * O_NETATALK_ACL: call chmod_acl() instead of chmod()
205  */
206 int ochmod(const char *path, mode_t mode, const struct stat *st, int options)
207 {
208     struct stat sb;
209
210     if (!st) {
211         if (lstat(path, &sb) != 0)
212             return -1;
213         st = &sb;
214     }
215
216     if (options & O_NOFOLLOW)
217         if (S_ISLNK(st->st_mode))
218             return 0;
219
220     if (options & O_NETATALK_ACL) {
221         return chmod_acl(path, mode);
222     } else {
223         return chmod(path, mode);
224     }
225 }
226
227 /* 
228  * @brief ostat/fsstatat multiplexer
229  *
230  * ostatat mulitplexes ostat and fstatat. If we dont HAVE_ATFUNCS, dirfd is ignored.
231  *
232  * @param dirfd   (r) Only used if HAVE_ATFUNCS, ignored else, -1 gives AT_FDCWD
233  * @param path    (r) pathname
234  * @param st      (rw) pointer to struct stat
235  */
236 int ostatat(int dirfd, const char *path, struct stat *st, int options)
237 {
238 #ifdef HAVE_ATFUNCS
239     if (dirfd == -1)
240         dirfd = AT_FDCWD;
241     return fstatat(dirfd, path, st, (options & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0);
242 #else
243     return ostat(path, st, options);
244 #endif            
245
246     /* DEADC0DE */
247     return -1;
248 }
249
250 /*!
251  * @brief symlink safe chdir replacement
252  *
253  * Only chdirs to dir if it doesn't contain symlinks or if symlink checking
254  * is disabled
255  *
256  * @returns 1 if a path element is a symlink, 0 otherwise, -1 on syserror
257  */
258 int ochdir(const char *dir, int options)
259 {
260     char buf[MAXPATHLEN+1];
261     char cwd[MAXPATHLEN+1];
262     char *test;
263     int  i;
264
265     if (!(options & O_NOFOLLOW))
266         return chdir(dir);
267
268     /*
269      dir is a canonical path (without "../" "./" "//" )
270      but may end with a / 
271     */
272     *cwd = 0;
273     if (*dir != '/') {
274         if (getcwd(cwd, MAXPATHLEN) == NULL)
275             return -1;
276     }
277     if (chdir(dir) != 0)
278         return -1;
279
280     /* 
281      * Cases:
282      * chdir request   | realpath result | ret
283      * (after getwcwd) |                 |
284      * =======================================
285      * /a/b/.          | /a/b            | 0
286      * /a/b/.          | /c              | 1
287      * /a/b/.          | /c/d/e/f        | 1
288      */
289     if (getcwd(buf, MAXPATHLEN) == NULL)
290         return 1;
291
292     i = 0;
293     if (*cwd) {
294         /* relative path requested, 
295          * Same directory?
296         */
297         for (; cwd[i]; i++) {
298             if (buf[i] != cwd[i])
299                 return 1;
300         }
301         if (buf[i]) {
302             if (buf[i] != '/')
303                 return 1;
304             i++;
305         }                    
306     }
307
308     test = &buf[i];    
309     for (i = 0; test[i]; i++) {
310         if (test[i] != dir[i]) {
311             return 1;
312         }
313     }
314     /* trailing '/' ? */
315     if (!dir[i])
316         return 0;
317
318     if (dir[i] != '/')
319         return 1;
320
321     i++;
322     if (dir[i])
323         return 1;
324
325     return 0;
326 }
327
328 /*!
329  * Store n random bytes an buf
330  */
331 void randombytes(void *buf, int n)
332 {
333     char *p = (char *)buf;
334     int fd, i;
335     struct timeval tv;
336
337     if ((fd = open("/dev/urandom", O_RDONLY)) != -1) {
338         /* generate from /dev/urandom */
339         if (read(fd, buf, n) != n) {
340             close(fd);
341             fd = -1;
342         } else {
343             close(fd);
344             /* fd now != -1, so srandom wont be called below */
345         }
346     }
347
348     if (fd == -1) {
349         gettimeofday(&tv, NULL);
350         srandom((unsigned int)tv.tv_usec);
351         for (i=0 ; i < n ; i++)
352             p[i] = random() & 0xFF;
353     }
354
355     return;
356 }