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