Update portability notes.
[darkstat] / conv.c
1 /* darkstat 3
2 * copyright (c) 2001-2014 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 "conv.h"
20
21 #include <sys/wait.h>
22 #include <assert.h>
23 #include <ctype.h>
24 #include "err.h"
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <grp.h>
28 #include <limits.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
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 privdrop(const char *chroot_dir, const char *privdrop_user) {
293 struct passwd *pw;
294
295 errno = 0;
296 pw = getpwnam(privdrop_user);
297
298 if (pw == NULL) {
299 if (errno == 0)
300 errx(1, "getpwnam(\"%s\") failed: no such user", privdrop_user);
301 else
302 err(1, "getpwnam(\"%s\") failed", privdrop_user);
303 }
304 if (chroot_dir == NULL) {
305 verbosef("no --chroot dir specified, darkstat will not chroot()");
306 } else {
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 {
315 gid_t list[1];
316 list[0] = pw->pw_gid;
317 if (setgroups(1, list) == -1)
318 err(1, "setgroups");
319 }
320 if (setgid(pw->pw_gid) == -1)
321 err(1, "setgid");
322 if (setuid(pw->pw_uid) == -1)
323 err(1, "setuid");
324 verbosef("set uid/gid to %d/%d", (int)pw->pw_uid, (int)pw->pw_gid);
325 }
326
327 /* Make the specified file descriptor non-blocking. */
328 void
329 fd_set_nonblock(const int fd)
330 {
331 int flags;
332
333 if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
334 err(1, "fcntl(fd %d) to get flags", fd);
335 flags |= O_NONBLOCK;
336 if (fcntl(fd, F_SETFL, flags) == -1)
337 err(1, "fcntl(fd %d) to set O_NONBLOCK", fd);
338 assert( (fcntl(fd, F_GETFL, 0) & O_NONBLOCK ) == O_NONBLOCK );
339 }
340
341 /* Make the specified file descriptor blocking. */
342 void
343 fd_set_block(const int fd)
344 {
345 int flags;
346
347 if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
348 err(1, "fcntl(fd %d) to get flags", fd);
349 flags &= ~O_NONBLOCK;
350 if (fcntl(fd, F_SETFL, flags) == -1)
351 err(1, "fcntl(fd %d) to unset O_NONBLOCK", fd);
352 assert( (fcntl(fd, F_GETFL, 0) & O_NONBLOCK ) == 0 );
353 }
354
355 /* vim:set ts=3 sw=3 tw=78 expandtab: */