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