Be explicit that per-host pages are directories.
[darkstat.git] / conv.c
1 /* darkstat 3
2  * copyright (c) 2001-2007 Emil Mikulic.
3  *
4  * conv.c: convenience functions.
5  *
6  * Permission to use, copy, modify, and distribute this file for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18
19 #include "darkstat.h"
20 #include "config.h"
21 #include "conv.h"
22
23 #include <sys/wait.h>
24 #include <assert.h>
25 #include <ctype.h>
26 #include "err.h"
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <pwd.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
34 #include <unistd.h>
35 #include <limits.h>
36
37 #define PATH_DEVNULL "/dev/null"
38
39 /* malloc() that exits on failure. */
40 void *
41 xmalloc(const size_t size)
42 {
43    void *ptr = malloc(size);
44
45    if (ptr == NULL)
46       errx(1, "malloc(): out of memory");
47    return (ptr);
48 }
49
50 /* calloc() that exits on failure. */
51 void *
52 xcalloc(const size_t num, const size_t size)
53 {
54    void *ptr = calloc(num, size);
55
56    if (ptr == NULL)
57       errx(1, "calloc(): out of memory");
58    return (ptr);
59 }
60
61 /* realloc() that exits on failure. */
62 void *
63 xrealloc(void *original, const size_t size)
64 {
65     void *ptr = realloc(original, size);
66
67     if (ptr == NULL)
68       errx(1, "realloc(): out of memory");
69     return (ptr);
70 }
71
72 /* strdup() that exits on failure. */
73 char *
74 xstrdup(const char *s)
75 {
76    char *tmp = strdup(s);
77
78    if (tmp == NULL)
79       errx(1, "strdup(): out of memory");
80    return (tmp);
81 }
82
83 /* ---------------------------------------------------------------------------
84  * Split string out of src with range [left:right-1]
85  */
86 char *
87 split_string(const char *src, const size_t left, const size_t right)
88 {
89     char *dest;
90     assert(left <= right);
91     assert(left < strlen(src));   /* [left means must be smaller */
92     assert(right <= strlen(src)); /* right) means can be equal or smaller */
93
94     dest = xmalloc(right - left + 1);
95     memcpy(dest, src+left, right-left);
96     dest[right-left] = '\0';
97     return (dest);
98 }
99
100 /* ---------------------------------------------------------------------------
101  * Uppercasify all characters in a string of given length.
102  */
103 void
104 strntoupper(char *str, const size_t length)
105 {
106     size_t i;
107
108     for (i=0; i<length; i++)
109         str[i] = toupper(str[i]);
110 }
111
112 /* ---------------------------------------------------------------------------
113  * Returns non-zero if haystack starts with needle.
114  */
115 int
116 str_starts_with(const char *haystack, const char *needle)
117 {
118    int i = 0;
119
120    while (needle[i] != '\0') {
121       if ((haystack[i] == '\0') || (haystack[i] != needle[i]))
122          return (0);
123       i++;
124    }
125    return (1);
126 }
127
128 /* split - splits a string by a delimiter character into an array of
129  * string chunks.
130  *
131  * The chunks and the array are dynamically allocated using xmalloc() so
132  * it will errx() if it runs out of memory.
133  *
134  *    int num_chunks;
135  *    char **chunks = split('.', "..one...two....", &num_chunks);
136  *
137  *    num_chunks = 2, chunks = { "one", "two", NULL }
138  */
139 char **
140 split(const char delimiter, const char *str, unsigned int *num_chunks)
141 {
142    unsigned int num = 0;
143    char **chunks = NULL;
144    size_t left, right = 0;
145
146    #define PUSH(c) do { num++;  chunks = (char**) xrealloc(chunks, \
147       sizeof(*chunks) * num);  chunks[num-1] = c; } while(0)
148
149    for(;;) {
150       /* find first non-delimiter */
151       for (left = right; str[left] == delimiter; left++)
152             ;
153
154       if (str[left] == '\0')
155          break; /* ran out of string */
156
157       /* find first delimiter or end of string */
158       for (right=left+1;
159          str[right] != delimiter && str[right] != '\0';
160          right++)
161             ;
162
163       /* split chunk out */
164       PUSH( split_string(str, left, right) );
165
166       if (str[right] == '\0')
167          break; /* ran out of string */
168       else
169          right++;
170    }
171
172    /* return */
173    PUSH(NULL);
174    if (num_chunks != NULL)
175       *num_chunks = num-1; /* NULL doesn't count */
176    return (chunks);
177    #undef PUSH
178 }
179
180 /* Given an HTTP query string and a key to search for, return the value
181  * associated with it, or NULL if there is no such key or qs is NULL.
182  * The returned string needs to be freed.
183  *
184  * e.g.:
185  * qs = "sort=in&start=20";
186  * qs_get(sq, "sort") returns "in"
187  * qs_get(sq, "end") returns NULL
188  */
189 char *
190 qs_get(const char *qs, const char *key)
191 {
192    size_t pos, qslen, keylen;
193
194    if (qs == NULL) return NULL;
195
196    qslen = strlen(qs);
197    keylen = strlen(key);
198    pos = 0;
199    while (pos < qslen) {
200       if (!(pos + keylen + 1 < qslen))
201          /* not enough room for "key" + "=" */
202          return NULL;
203       else {
204          if (str_starts_with(qs+pos, key) && qs[pos+keylen] == '=') {
205             /* found key= */
206             size_t start, end;
207
208             start = pos + keylen + 1;
209             for (end=start; end<qslen && qs[end] != '&'; end++)
210                ;
211             return split_string(qs, start, end);
212          } else {
213             /* didn't find key, skip to next & */
214             do { pos++; } while ((pos < qslen) && (qs[pos] != '&'));
215             pos++; /* skip the ampersand */
216          }
217       }
218    }
219    return NULL; /* not found */
220 }
221
222 static int lifeline[2] = { -1, -1 };
223 static int fd_null = -1;
224
225 void
226 daemonize_start(void)
227 {
228    pid_t f, w;
229
230    if (pipe(lifeline) == -1)
231       err(1, "pipe(lifeline)");
232
233    fd_null = open(PATH_DEVNULL, O_RDWR, 0);
234    if (fd_null == -1)
235       err(1, "open(" PATH_DEVNULL ")");
236
237    f = fork();
238    if (f == -1)
239       err(1, "fork");
240    else if (f != 0) {
241       /* parent: wait for child */
242       char tmp[1];
243       int status;
244
245       verbosef("parent waiting");
246       if (close(lifeline[1]) == -1)
247          warn("close lifeline in parent");
248       if (read(lifeline[0], tmp, sizeof(tmp)) != 0) /* expecting EOF */
249          err(1, "lifeline read() failed");
250       verbosef("parent done reading, calling waitpid");
251       w = waitpid(f, &status, WNOHANG);
252       verbosef("waitpid ret %d, status is %d", w, status);
253       if (w == -1)
254          err(1, "waitpid");
255       else if (w == 0)
256          /* child is running happily */
257          exit(EXIT_SUCCESS);
258       else
259          /* child init failed, pass on its exit status */
260          exit(WEXITSTATUS(status));
261    }
262    /* else we are the child: continue initializing */
263 }
264
265 void
266 daemonize_finish(void)
267 {
268    if (fd_null == -1)
269       return; /* didn't daemonize_start(), i.e. we're not daemonizing */
270
271    if (setsid() == -1)
272       err(1, "setsid");
273    if (close(lifeline[0]) == -1)
274       warn("close read end of lifeline in child");
275    if (close(lifeline[1]) == -1)
276       warn("couldn't cut the lifeline");
277
278    /* close all our std fds */
279    if (dup2(fd_null, STDIN_FILENO) == -1)
280       warn("dup2(stdin)");
281    if (dup2(fd_null, STDOUT_FILENO) == -1)
282       warn("dup2(stdout)");
283    if (dup2(fd_null, STDERR_FILENO) == -1)
284       warn("dup2(stderr)");
285    if (fd_null > 2)
286       close(fd_null);
287 }
288
289 /*
290  * For security, chroot (optionally) and drop privileges.
291  * Pass a NULL chroot_dir to disable chroot() behaviour.
292  */
293 void
294 privdrop(const char *chroot_dir, const char *privdrop_user)
295 {
296    struct passwd *pw;
297
298    errno = 0;
299    pw = getpwnam(privdrop_user);
300
301    if (pw == NULL) {
302       if (errno == 0)
303          errx(1, "getpwnam(\"%s\") failed: no such user", privdrop_user);
304       else
305          err(1, "getpwnam(\"%s\") failed", privdrop_user);
306    }
307    if (chroot_dir != NULL) {
308       tzset(); /* read /etc/localtime before we chroot */
309       if (chdir(chroot_dir) == -1)
310          err(1, "chdir(\"%s\") failed", chroot_dir);
311       if (chroot(chroot_dir) == -1)
312          err(1, "chroot(\"%s\") failed", chroot_dir);
313       verbosef("chrooted into: %s", chroot_dir);
314    }
315    if (setgid(pw->pw_gid) == -1)
316       err(1, "setgid");
317    if (setuid(pw->pw_uid) == -1)
318       err(1, "setuid");
319    verbosef("set uid/gid to %d/%d", (int)pw->pw_uid, (int)pw->pw_gid);
320 }
321
322 /* Make the specified file descriptor non-blocking. */
323 void
324 fd_set_nonblock(const int fd)
325 {
326    int flags;
327
328    if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
329       err(1, "fcntl(fd %d) to get flags", fd);
330    flags |= O_NONBLOCK;
331    if (fcntl(fd, F_SETFL, flags) == -1)
332       err(1, "fcntl(fd %d) to set O_NONBLOCK", fd);
333    assert( (fcntl(fd, F_GETFL, 0) & O_NONBLOCK ) == O_NONBLOCK );
334 }
335
336 /* Make the specified file descriptor blocking. */
337 void
338 fd_set_block(const int fd)
339 {
340    int flags;
341
342    if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
343       err(1, "fcntl(fd %d) to get flags", fd);
344    flags &= ~O_NONBLOCK;
345    if (fcntl(fd, F_SETFL, flags) == -1)
346       err(1, "fcntl(fd %d) to unset O_NONBLOCK", fd);
347    assert( (fcntl(fd, F_GETFL, 0) & O_NONBLOCK ) == 0 );
348 }
349
350 /* strlcpy() and strlcat() are derived from:
351  *
352  * $OpenBSD: strlcpy.c,v 1.4
353  * $FreeBSD: src/lib/libc/string/strlcpy.c,v 1.8
354  *
355  * $OpenBSD: strlcat.c,v 1.2
356  * $FreeBSD: src/lib/libc/string/strlcat.c,v 1.10
357  *
358  * under the following license:
359  *
360  * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
361  * All rights reserved.
362  *
363  * Redistribution and use in source and binary forms, with or without
364  * modification, are permitted provided that the following conditions
365  * are met:
366  * 1. Redistributions of source code must retain the above copyright
367  *    notice, this list of conditions and the following disclaimer.
368  * 2. Redistributions in binary form must reproduce the above copyright
369  *    notice, this list of conditions and the following disclaimer in the
370  *    documentation and/or other materials provided with the distribution.
371  * 3. The name of the author may not be used to endorse or promote products
372  *    derived from this software without specific prior written permission.
373  *
374  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
375  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
376  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
377  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
378  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
379  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
380  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
381  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
382  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
383  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
384  */
385
386 #ifndef HAVE_STRLCPY
387 /*
388  * Copy src to string dst of size siz.  At most siz-1 characters
389  * will be copied.  Always NUL terminates (unless siz == 0).
390  * Returns strlen(src); if retval >= siz, truncation occurred.
391  */
392 size_t
393 strlcpy(char * restrict dst, const char * restrict src, const size_t siz)
394 {
395         char *d = dst;
396         const char *s = src;
397         size_t n = siz;
398
399         /* Copy as many bytes as will fit */
400         if (n != 0 && --n != 0) {
401                 do {
402                         if ((*d++ = *s++) == 0)
403                                 break;
404                 } while (--n != 0);
405         }
406
407         /* Not enough room in dst, add NUL and traverse rest of src */
408         if (n == 0) {
409                 if (siz != 0)
410                         *d = '\0';              /* NUL-terminate dst */
411                 while (*s++)
412                         ;
413         }
414
415         return (size_t)(s - src - 1); /* count does not include NUL */
416 }
417 #endif
418
419 #ifndef HAVE_STRLCAT
420 /*
421  * Appends src to string dst of size siz (unlike strncat, siz is the
422  * full size of dst, not space left).  At most siz-1 characters
423  * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
424  * Returns strlen(src) + MIN(siz, strlen(initial dst)).
425  * If retval >= siz, truncation occurred.
426  */
427 size_t
428 strlcat(char * restrict dst, const char * restrict src, const size_t siz)
429 {
430         char *d = dst;
431         const char *s = src;
432         size_t n = siz;
433         size_t dlen;
434
435         /* Find the end of dst and adjust bytes left but don't go past end */
436         while (n-- != 0 && *d != '\0')
437                 d++;
438         dlen = (size_t)(d - dst);
439         n = siz - dlen;
440
441         if (n == 0)
442                 return(dlen + strlen(s));
443         while (*s != '\0') {
444                 if (n != 1) {
445                         *d++ = *s;
446                         n--;
447                 }
448                 s++;
449         }
450         *d = '\0';
451
452         return (dlen + (size_t)(s - src)); /* count does not include NUL */
453 }
454 #endif
455
456 #ifndef HAVE_STRTONUM
457 /*
458  * Convert an ASCII string to a decimal numerical value. An acceptable
459  * range is specified, and an optional error message string.
460  *
461  * Implementation built from the manual page description of OpenBSD 4.6.
462  */
463 long long
464 strtonum(const char *nptr, long long minval, long long maxval,
465          const char **errstr)
466 {
467    long long val;
468    char *p;
469
470    if ((nptr == NULL) || (*nptr == '\0') || (minval > maxval)) {
471       if (errstr)
472          *errstr = "invalid";
473       errno = EINVAL;
474       return 0;
475    }
476
477    errno = 0;
478    val = strtoll(nptr, &p, 10);
479
480    if (*p != '\0') {
481       if (errstr)
482          *errstr = "invalid";
483       errno = EINVAL;
484       return 0;
485    }
486
487    if ((val == LLONG_MIN) || (val < minval)) {
488       if (errstr)
489          *errstr = "too small";
490       errno = ERANGE;
491       return 0;
492    }
493    if ((val == LLONG_MAX) || (val > maxval)) {
494       if (errstr)
495          *errstr = "too large";
496       errno = ERANGE;
497       return 0;
498    }
499
500    /* Correct conversion.  */
501    if (errstr)
502       *errstr = NULL;
503    return val;
504 }
505 #endif /* !HAVE_STRTONUM */
506
507 /* vim:set ts=3 sw=3 tw=78 expandtab: */