Implement str_to_addr() and unit test.
[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, int *num_chunks)
140 {
141 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 read(lifeline[0], tmp, sizeof(tmp));
248 verbosef("parent done reading, calling waitpid");
249 w = waitpid(f, &status, WNOHANG);
250 verbosef("waitpid ret %d, status is %d", w, status);
251 if (w == -1)
252 err(1, "waitpid");
253 else if (w == 0)
254 /* child is running happily */
255 exit(EXIT_SUCCESS);
256 else
257 /* child init failed, pass on its exit status */
258 exit(WEXITSTATUS(status));
259 }
260 /* else we are the child: continue initializing */
261 }
262
263 void
264 daemonize_finish(void)
265 {
266 if (fd_null == -1)
267 return; /* didn't daemonize_start(), i.e. we're not daemonizing */
268
269 if (setsid() == -1)
270 err(1, "setsid");
271 if (close(lifeline[0]) == -1)
272 warn("close read end of lifeline in child");
273 if (close(lifeline[1]) == -1)
274 warn("couldn't cut the lifeline");
275
276 /* close all our std fds */
277 if (dup2(fd_null, STDIN_FILENO) == -1)
278 warn("dup2(stdin)");
279 if (dup2(fd_null, STDOUT_FILENO) == -1)
280 warn("dup2(stdout)");
281 if (dup2(fd_null, STDERR_FILENO) == -1)
282 warn("dup2(stderr)");
283 if (fd_null > 2)
284 close(fd_null);
285 }
286
287 /*
288 * For security, chroot (optionally) and drop privileges.
289 * Pass a NULL chroot_dir to disable chroot() behaviour.
290 */
291 void
292 privdrop(const char *chroot_dir, const char *privdrop_user)
293 {
294 struct passwd *pw;
295
296 errno = 0;
297 pw = getpwnam(privdrop_user);
298
299 if (pw == NULL) {
300 if (errno == 0)
301 errx(1, "getpwnam(\"%s\") failed: no such user", privdrop_user);
302 else
303 err(1, "getpwnam(\"%s\") failed", privdrop_user);
304 }
305 if (chroot_dir != NULL) {
306 tzset(); /* read /etc/localtime before we chroot */
307 if (chdir(chroot_dir) == -1)
308 err(1, "chdir(\"%s\") failed", chroot_dir);
309 if (chroot(chroot_dir) == -1)
310 err(1, "chroot(\"%s\") failed", chroot_dir);
311 verbosef("chrooted into: %s", chroot_dir);
312 }
313 if (setgid(pw->pw_gid) == -1)
314 err(1, "setgid");
315 if (setuid(pw->pw_uid) == -1)
316 err(1, "setuid");
317 verbosef("set uid/gid to %d/%d", (int)pw->pw_uid, (int)pw->pw_gid);
318 }
319
320 /* Make the specified file descriptor non-blocking. */
321 void
322 fd_set_nonblock(const int fd)
323 {
324 int flags;
325
326 if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
327 err(1, "fcntl(fd %d) to get flags", fd);
328 flags |= O_NONBLOCK;
329 if (fcntl(fd, F_SETFL, flags) == -1)
330 err(1, "fcntl(fd %d) to set O_NONBLOCK", fd);
331 assert( (fcntl(fd, F_GETFL, 0) & O_NONBLOCK ) == O_NONBLOCK );
332 }
333
334 /* Make the specified file descriptor blocking. */
335 void
336 fd_set_block(const int fd)
337 {
338 int flags;
339
340 if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
341 err(1, "fcntl(fd %d) to get flags", fd);
342 flags &= ~O_NONBLOCK;
343 if (fcntl(fd, F_SETFL, flags) == -1)
344 err(1, "fcntl(fd %d) to unset O_NONBLOCK", fd);
345 assert( (fcntl(fd, F_GETFL, 0) & O_NONBLOCK ) == 0 );
346 }
347
348 /* strlcpy() and strlcat() are derived from:
349 *
350 * $OpenBSD: strlcpy.c,v 1.4
351 * $FreeBSD: src/lib/libc/string/strlcpy.c,v 1.8
352 *
353 * $OpenBSD: strlcat.c,v 1.2
354 * $FreeBSD: src/lib/libc/string/strlcat.c,v 1.10
355 *
356 * under the following license:
357 *
358 * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
359 * All rights reserved.
360 *
361 * Redistribution and use in source and binary forms, with or without
362 * modification, are permitted provided that the following conditions
363 * are met:
364 * 1. Redistributions of source code must retain the above copyright
365 * notice, this list of conditions and the following disclaimer.
366 * 2. Redistributions in binary form must reproduce the above copyright
367 * notice, this list of conditions and the following disclaimer in the
368 * documentation and/or other materials provided with the distribution.
369 * 3. The name of the author may not be used to endorse or promote products
370 * derived from this software without specific prior written permission.
371 *
372 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
373 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
374 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
375 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
376 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
377 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
378 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
379 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
380 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
381 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
382 */
383
384 #ifndef HAVE_STRLCPY
385 /*
386 * Copy src to string dst of size siz. At most siz-1 characters
387 * will be copied. Always NUL terminates (unless siz == 0).
388 * Returns strlen(src); if retval >= siz, truncation occurred.
389 */
390 size_t strlcpy(dst, src, siz)
391 char *dst;
392 const char *src;
393 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(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(dst, src, siz)
429 char *dst;
430 const char *src;
431 size_t siz;
432 {
433 char *d = dst;
434 const char *s = src;
435 size_t n = siz;
436 size_t dlen;
437
438 /* Find the end of dst and adjust bytes left but don't go past end */
439 while (n-- != 0 && *d != '\0')
440 d++;
441 dlen = d - dst;
442 n = siz - dlen;
443
444 if (n == 0)
445 return(dlen + strlen(s));
446 while (*s != '\0') {
447 if (n != 1) {
448 *d++ = *s;
449 n--;
450 }
451 s++;
452 }
453 *d = '\0';
454
455 return(dlen + (s - src)); /* count does not include NUL */
456 }
457 #endif
458
459 #ifndef HAVE_STRTONUM
460 /*
461 * Convert an ASCII string to a decimal numerical value. An acceptable
462 * range is specified, and an optional error message string.
463 *
464 * Implementation built from the manual page description of OpenBSD 4.6.
465 */
466 long long
467 strtonum(const char *nptr, long long minval, long long maxval,
468 const char **errstr)
469 {
470 long long val;
471 char *p;
472
473 if ((nptr == NULL) || (*nptr == '\0') || (minval > maxval)) {
474 if (errstr)
475 *errstr = "invalid";
476 errno = EINVAL;
477 return 0;
478 }
479
480 errno = 0;
481 val = strtoll(nptr, &p, 10);
482
483 if (*p != '\0') {
484 if (errstr)
485 *errstr = "invalid";
486 errno = EINVAL;
487 return 0;
488 }
489
490 if ((val == LLONG_MIN) || (val < minval)) {
491 if (errstr)
492 *errstr = "too small";
493 errno = ERANGE;
494 return 0;
495 }
496 if ((val == LLONG_MAX) || (val > maxval)) {
497 if (errstr)
498 *errstr = "too large";
499 errno = ERANGE;
500 return 0;
501 }
502
503 /* Correct conversion. */
504 if (errstr)
505 *errstr = NULL;
506 return val;
507 }
508 #endif /* !HAVE_STRTONUM */
509
510 /* vim:set ts=3 sw=3 tw=78 expandtab: */