Update ChangeLog for 3.0.717.
[darkstat] / conv.c
1 /* darkstat 3
2 * copyright (c) 2001-2011 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 <pwd.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32 #include <unistd.h>
33 #include <limits.h>
34
35 #define PATH_DEVNULL "/dev/null"
36
37 /* malloc() that exits on failure. */
38 void *
39 xmalloc(const size_t size)
40 {
41 void *ptr = malloc(size);
42
43 if (ptr == NULL)
44 errx(1, "malloc(): out of memory");
45 return (ptr);
46 }
47
48 /* calloc() that exits on failure. */
49 void *
50 xcalloc(const size_t num, const size_t size)
51 {
52 void *ptr = calloc(num, size);
53
54 if (ptr == NULL)
55 errx(1, "calloc(): out of memory");
56 return (ptr);
57 }
58
59 /* realloc() that exits on failure. */
60 void *
61 xrealloc(void *original, const size_t size)
62 {
63 void *ptr = realloc(original, size);
64
65 if (ptr == NULL)
66 errx(1, "realloc(): out of memory");
67 return (ptr);
68 }
69
70 /* strdup() that exits on failure. */
71 char *
72 xstrdup(const char *s)
73 {
74 char *tmp = strdup(s);
75
76 if (tmp == NULL)
77 errx(1, "strdup(): out of memory");
78 return (tmp);
79 }
80
81 /* ---------------------------------------------------------------------------
82 * Split string out of src with range [left:right-1]
83 */
84 char *
85 split_string(const char *src, const size_t left, const size_t right)
86 {
87 char *dest;
88 assert(left <= right);
89 assert(left < strlen(src)); /* [left means must be smaller */
90 assert(right <= strlen(src)); /* right) means can be equal or smaller */
91
92 dest = xmalloc(right - left + 1);
93 memcpy(dest, src+left, right-left);
94 dest[right-left] = '\0';
95 return (dest);
96 }
97
98 /* ---------------------------------------------------------------------------
99 * Uppercasify all characters in a string of given length.
100 */
101 void
102 strntoupper(char *str, const size_t length)
103 {
104 size_t i;
105
106 for (i=0; i<length; i++)
107 str[i] = toupper(str[i]);
108 }
109
110 /* ---------------------------------------------------------------------------
111 * Returns non-zero if haystack starts with needle.
112 */
113 int
114 str_starts_with(const char *haystack, const char *needle)
115 {
116 int i = 0;
117
118 while (needle[i] != '\0') {
119 if ((haystack[i] == '\0') || (haystack[i] != needle[i]))
120 return (0);
121 i++;
122 }
123 return (1);
124 }
125
126 /* split - splits a string by a delimiter character into an array of
127 * string chunks.
128 *
129 * The chunks and the array are dynamically allocated using xmalloc() so
130 * it will errx() if it runs out of memory.
131 *
132 * int num_chunks;
133 * char **chunks = split('.', "..one...two....", &num_chunks);
134 *
135 * num_chunks = 2, chunks = { "one", "two", NULL }
136 */
137 char **
138 split(const char delimiter, const char *str, unsigned int *num_chunks)
139 {
140 unsigned int num = 0;
141 char **chunks = NULL;
142 size_t left, right = 0;
143
144 #define PUSH(c) do { num++; chunks = (char**) xrealloc(chunks, \
145 sizeof(*chunks) * num); chunks[num-1] = c; } while(0)
146
147 for(;;) {
148 /* find first non-delimiter */
149 for (left = right; str[left] == delimiter; left++)
150 ;
151
152 if (str[left] == '\0')
153 break; /* ran out of string */
154
155 /* find first delimiter or end of string */
156 for (right=left+1;
157 str[right] != delimiter && str[right] != '\0';
158 right++)
159 ;
160
161 /* split chunk out */
162 PUSH( split_string(str, left, right) );
163
164 if (str[right] == '\0')
165 break; /* ran out of string */
166 else
167 right++;
168 }
169
170 /* return */
171 PUSH(NULL);
172 if (num_chunks != NULL)
173 *num_chunks = num-1; /* NULL doesn't count */
174 return (chunks);
175 #undef PUSH
176 }
177
178 /* Given an HTTP query string and a key to search for, return the value
179 * associated with it, or NULL if there is no such key or qs is NULL.
180 * The returned string needs to be freed.
181 *
182 * e.g.:
183 * qs = "sort=in&start=20";
184 * qs_get(sq, "sort") returns "in"
185 * qs_get(sq, "end") returns NULL
186 */
187 char *
188 qs_get(const char *qs, const char *key)
189 {
190 size_t pos, qslen, keylen;
191
192 if (qs == NULL) return NULL;
193
194 qslen = strlen(qs);
195 keylen = strlen(key);
196 pos = 0;
197 while (pos < qslen) {
198 if (!(pos + keylen + 1 < qslen))
199 /* not enough room for "key" + "=" */
200 return NULL;
201 else {
202 if (str_starts_with(qs+pos, key) && qs[pos+keylen] == '=') {
203 /* found key= */
204 size_t start, end;
205
206 start = pos + keylen + 1;
207 for (end=start; end<qslen && qs[end] != '&'; end++)
208 ;
209 return split_string(qs, start, end);
210 } else {
211 /* didn't find key, skip to next & */
212 do { pos++; } while ((pos < qslen) && (qs[pos] != '&'));
213 pos++; /* skip the ampersand */
214 }
215 }
216 }
217 return NULL; /* not found */
218 }
219
220 static int lifeline[2] = { -1, -1 };
221 static int fd_null = -1;
222
223 void
224 daemonize_start(void)
225 {
226 pid_t f, w;
227
228 if (pipe(lifeline) == -1)
229 err(1, "pipe(lifeline)");
230
231 fd_null = open(PATH_DEVNULL, O_RDWR, 0);
232 if (fd_null == -1)
233 err(1, "open(" PATH_DEVNULL ")");
234
235 f = fork();
236 if (f == -1)
237 err(1, "fork");
238 else if (f != 0) {
239 /* parent: wait for child */
240 char tmp[1];
241 int status;
242
243 verbosef("parent waiting");
244 if (close(lifeline[1]) == -1)
245 warn("close lifeline in parent");
246 if (read(lifeline[0], tmp, sizeof(tmp)) != 0) /* expecting EOF */
247 err(1, "lifeline read() failed");
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 }