Include config.h explicitly.
[darkstat] / 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: */