1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 *
6 * Copyright 1997 - July 2008 CWI, August 2008 - 2019 MonetDB B.V.
7 */
8
9/**
10 * utils
11 * Fabian Groffen
12 * Shared utility functions between merovingian and monetdb
13 */
14
15/* NOTE: for this file to work correctly, the random number generator
16 * must have been seeded (srand) with something like the current time */
17
18#include "monetdb_config.h"
19#include "utils.h"
20#include <unistd.h> /* unlink */
21#include <string.h> /* memcpy */
22#include <strings.h> /* strcasecmp */
23#include <sys/types.h>
24#include <sys/stat.h>
25#include <fcntl.h>
26#include <time.h>
27#include <ctype.h>
28#ifdef HAVE_SYS_TIME_H
29#include <sys/time.h>
30#endif
31#ifdef HAVE_OPENSSL
32#include <openssl/rand.h> /* RAND_bytes */
33#else
34#ifdef HAVE_COMMONCRYPTO
35#include <CommonCrypto/CommonCrypto.h>
36#include <CommonCrypto/CommonRandom.h>
37#endif
38#endif
39
40#ifndef O_CLOEXEC
41#define O_CLOEXEC 0
42#endif
43
44/**
45 * Parses the given file stream matching the keys from list. If a match
46 * is found, the value is set in list->value. Values are malloced.
47 */
48void
49readConfFile(confkeyval *list, FILE *cnf) {
50 char buf[1024];
51 confkeyval *t;
52 size_t len;
53 char *err;
54
55 while (fgets(buf, 1024, cnf) != NULL) {
56 /* eliminate fgets' newline */
57 buf[strlen(buf) - 1] = '\0';
58 for (t = list; t->key != NULL; t++) {
59 len = strlen(t->key);
60 if (*buf && strncmp(buf, t->key, len) == 0 && buf[len] == '=') {
61 if ((err = setConfVal(t, buf + len + 1)) != NULL)
62 free(err); /* ignore, just fall back to default */
63 }
64 }
65 }
66}
67
68/**
69 * Parses the given file stream matching the and writes all values to the list.
70 */
71void
72readConfFileFull(confkeyval *list, FILE *cnf) {
73 char buf[1024];
74 char *key, *val;
75 char *separator = "=";
76 char *err;
77 confkeyval *t = list;
78 int cnt = 0;
79
80 /* iterate until the end of the array */
81 while (list->key != NULL) {
82 /* If we already have PROPLENGTH entries, we cannot add any more. Do
83 * read the file because it might specify a different value for an
84 * existing property.
85 *
86 * TODO: This is an arbitrary limitation and should either be justified
87 * sufficiently or removed.
88 */
89 if (cnt >= PROPLENGTH - 1) {
90 break;
91 }
92 list++;
93 cnt++;
94 }
95 /* read the file a line at a time */
96 while (fgets(buf, sizeof(buf), cnf) != NULL) {
97 if (strlen(buf) > 1 && buf[0] != '#') {
98 /* tokenize */
99 key = strtok(buf, separator);
100 val = strtok(NULL, separator);
101 /* strip trailing newline */
102 val = strtok(val, "\n");
103 if ((err = setConfValForKey(t, key, val)) != NULL) {
104 if (strstr(err, "is not recognized") != NULL) {
105 /* If we already have PROPLENGTH entries in the list, ignore
106 * every ad hoc property, but continue reading the file
107 * because a different value might be specified later in the
108 * file for one of the properties we have already in the list.
109 */
110 if (cnt >= PROPLENGTH - 1) {
111 free(err);
112 continue;
113 }
114 list->key = strdup(key);
115 list->val = strdup(val);
116 list->ival = 0;
117 list->type = STR;
118 list++;
119 cnt++;
120 }
121 /* else: ignore the property */
122 free(err);
123 }
124 }
125 }
126}
127
128/**
129 * Frees the values allocated by readConfFile().
130 */
131inline void
132freeConfFile(confkeyval *list) {
133 if (list == NULL)
134 return;
135 while (list->key != NULL) {
136 if (list->val != NULL) {
137 free(list->val);
138 list->val = NULL;
139 }
140 list++;
141 }
142}
143
144/**
145 * Returns a pointer to the key-value that has a matching key with the
146 * given key, or NULL if no key was found.
147 */
148inline confkeyval *
149findConfKey(confkeyval *list, const char *key) {
150 while (list->key != NULL) {
151 if (strcmp(list->key, key) == 0)
152 return(list);
153 list++;
154 }
155 return(NULL);
156}
157
158/**
159 * Returns a pointer to the value for the given key, or NULL if not
160 * found (or set to NULL)
161 */
162inline char *
163getConfVal(confkeyval *list, const char *key) {
164 while (list->key != NULL) {
165 if (strcmp(list->key, key) == 0)
166 return(list->val);
167 list++;
168 }
169 return(NULL);
170}
171
172/**
173 * Returns the int-representation of the value for the given key, or
174 * 0 if not found.
175 */
176inline int
177getConfNum(confkeyval *list, const char *key) {
178 while (list->key != NULL) {
179 if (strcmp(list->key, key) == 0)
180 return(list->ival);
181 list++;
182 }
183 return(0);
184}
185
186/**
187 * Sets the value in the given confkeyval struct to val ensuring it is
188 * of the desired type. In case of type BOOL, val is converted to "yes"
189 * or "no", based on val. If the type does not match, this function
190 * returns a malloced diagnostic message, or if everything is
191 * successful, NULL. If val is NULL, this function always returns
192 * successful and unsets the value for the given key. Upon an error,
193 * the original value for the key is left untouched.
194 */
195char *
196setConfVal(confkeyval *ckv, const char *val) {
197 int ival = 0;
198
199 /* handle the unset directly */
200 if (val == NULL) {
201 if (ckv->val != NULL) {
202 free(ckv->val);
203 ckv->val = NULL;
204 ckv->ival = 0;
205 }
206 return(NULL);
207 }
208
209 /* check the input */
210 switch (ckv->type) {
211 case INVALID: {
212 char buf[256];
213 snprintf(buf, sizeof(buf),
214 "key '%s' is unitialised (invalid value), internal error",
215 ckv->key);
216 return(strdup(buf));
217 }
218 case INT: {
219 const char *p = val;
220 while (isdigit((unsigned char) *p))
221 p++;
222 if (*p != '\0') {
223 char buf[256];
224 snprintf(buf, sizeof(buf),
225 "key '%s' requires an integer-type value, got: %s",
226 ckv->key, val);
227 return(strdup(buf));
228 }
229 ival = atoi(val);
230 }; break;
231 case BOOLEAN: {
232 if (strcasecmp(val, "true") == 0 ||
233 strcasecmp(val, "yes") == 0 ||
234 strcmp(val, "1") == 0)
235 {
236 val = "yes";
237 ival = 1;
238 } else if (strcasecmp(val, "false") == 0 ||
239 strcasecmp(val, "no") == 0 ||
240 strcmp(val, "0") == 0)
241 {
242 val = "no";
243 ival = 0;
244 } else {
245 char buf[256];
246 snprintf(buf, sizeof(buf),
247 "key '%s' requires a boolean-type value, got: %s",
248 ckv->key, val);
249 return(strdup(buf));
250 }
251 }; break;
252 case MURI: {
253 if (strncmp(val, "mapi:monetdb://",
254 sizeof("mapi:monetdb://") -1) != 0)
255 {
256 char buf[256];
257 snprintf(buf, sizeof(buf),
258 "key '%s' requires a mapi:monetdb:// URI value, got: %s",
259 ckv->key, val);
260 return(strdup(buf));
261 }
262 /* TODO: check full URL? */
263 }; break;
264 case STR:
265 case OTHER:
266 /* leave as is, not much to check */
267 break;
268 }
269 if (ckv->val != NULL)
270 free(ckv->val);
271 ckv->val = strdup(val);
272 ckv->ival = ival;
273
274 return(NULL);
275}
276
277char *
278setConfValForKey(confkeyval *list, const char *key, const char *val) {
279 char buf[256];
280
281 while (list->key != NULL) {
282 if (strcmp(list->key, key) == 0) {
283 return setConfVal(list, val);
284 }
285 list++;
286 }
287 /* XXX: Do NOT change this error message or readConfFileFull will stop
288 * working as expected.
289 */
290 snprintf(buf, sizeof(buf), "key '%s' is not recognized, internal error", key);
291 return(strdup(buf));
292}
293
294/**
295 * Fills the array pointed to by buf with a human representation of t.
296 * The argument longness represents the number of units to print
297 * starting from the biggest unit that has a non-zero value for t.
298 */
299inline void
300secondsToString(char *buf, time_t t, int longness)
301{
302 time_t p;
303 size_t i = 0;
304
305 p = 1 * 60 * 60 * 24 * 7 * 52;
306 if (t > p) {
307 i += sprintf(buf + i, "%dy", (int)(t / p));
308 t -= (t / p) * p;
309 if (--longness == 0)
310 return;
311 buf[i++] = ' ';
312 }
313 p /= 52;
314 if (t > p) {
315 i += sprintf(buf + i, "%dw", (int)(t / p));
316 t -= (t / p) * p;
317 if (--longness == 0)
318 return;
319 buf[i++] = ' ';
320 }
321 p /= 7;
322 if (t > p) {
323 i += sprintf(buf + i, "%dd", (int)(t / p));
324 t -= (t / p) * p;
325 if (--longness == 0)
326 return;
327 buf[i++] = ' ';
328 }
329 p /= 24;
330 if (t > p) {
331 i += sprintf(buf + i, "%dh", (int)(t / p));
332 t -= (t / p) * p;
333 if (--longness == 0)
334 return;
335 buf[i++] = ' ';
336 }
337 p /= 60;
338 if (t > p) {
339 i += sprintf(buf + i, "%dm", (int)(t / p));
340 t -= (t / p) * p;
341 if (--longness == 0)
342 return;
343 buf[i++] = ' ';
344 }
345
346 /* t must be < 60 */
347 if (--longness == 0 || !(i > 0 && t == 0)) {
348 sprintf(buf + i, "%ds", (int)(t));
349 } else {
350 buf[--i] = '\0';
351 }
352}
353
354/**
355 * Fills the array pointed to by ret, with the string from in,
356 * abbreviating it when it is longer than width chars long.
357 * The array pointed to by ret must be at least of size width + 1.
358 */
359inline void
360abbreviateString(char *ret, const char *in, size_t width)
361{
362 size_t len;
363 size_t off;
364
365 if ((len = strlen(in)) > width) {
366 /* position abbreviation dots in the middle (Mac style, iso
367 * Windows style) */
368 memcpy(ret, in, (width / 2) - 2);
369 memcpy(ret + (width / 2) - 2, "...", 3);
370 off = len - (width - ((width / 2) - 2) - 3);
371 memcpy(ret + (width / 2) + 1, in + off, (len - off) + 1);
372 } else {
373 sprintf(ret, "%s", in);
374 }
375}
376
377static char seedChars[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
378 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
379 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
380 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
381 '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
382
383/**
384 * Fills the array pointed to by buf of size len with a random salt.
385 * Padds the remaining bytes in buf with null-bytes.
386 */
387void
388generateSalt(char *buf, unsigned int len)
389{
390 unsigned int c;
391 unsigned int size;
392 unsigned int fill;
393 unsigned int min;
394
395#ifdef HAVE_OPENSSL
396 if (RAND_bytes((unsigned char *) &size, (int) sizeof(size)) < 0)
397#else
398#ifdef HAVE_COMMONCRYPTO
399 if (CCRandomGenerateBytes(&size, sizeof(size)) != kCCSuccess)
400#endif
401#endif
402#ifndef STATIC_CODE_ANALYSIS
403 size = (unsigned int)rand();
404#else
405 size = 0;
406#endif
407 fill = len * 0.75;
408 min = len * 0.42;
409 size = (size % (fill - min)) + min;
410#ifdef HAVE_OPENSSL
411 if (RAND_bytes((unsigned char *) buf, (int) size) >= 0) {
412 for (c = 0; c < size; c++)
413 buf[c] = seedChars[((unsigned char *) buf)[c] % 62];
414 } else
415#else
416#ifdef HAVE_COMMONCRYPTO
417 if (CCRandomGenerateBytes(buf, size) >= 0) {
418 for (c = 0; c < size; c++)
419 buf[c] = seedChars[((unsigned char *) buf)[c] % 62];
420 } else
421#endif
422#endif
423 for (c = 0; c < size; c++) {
424#ifndef STATIC_CODE_ANALYSIS
425 buf[c] = seedChars[rand() % 62];
426#else
427 buf[c] = seedChars[0];
428#endif
429 }
430 for ( ; c < len; c++)
431 buf[c] = '\0';
432}
433
434/**
435 * Creates a file path read/writable for the user only containing a
436 * random passphrase.
437 */
438char *
439generatePassphraseFile(const char *path)
440{
441 int fd;
442 FILE *f;
443 char buf[48];
444 unsigned int len = sizeof(buf);
445
446 /* delete such that we are sure we recreate the file with restricted
447 * permissions */
448 remove(path);
449 if ((fd = open(path, O_CREAT | O_WRONLY | O_CLOEXEC, S_IRUSR | S_IWUSR)) == -1) {
450 char err[512];
451 snprintf(err, sizeof(err), "unable to open '%s': %s",
452 path, strerror(errno));
453 return(strdup(err));
454 }
455
456 generateSalt(buf, len);
457 if ((f = fdopen(fd, "w")) == NULL) {
458 char err[512];
459 snprintf(err, sizeof(err), "unable to open '%s': %s",
460 path, strerror(errno));
461 close(fd);
462 return(strdup(err));
463 }
464 if (fwrite(buf, 1, len, f) < len) {
465 char err[512];
466 snprintf(err, sizeof(err), "cannot write secret: %s",
467 strerror(errno));
468 fclose(f);
469 return(strdup(err));
470 }
471 fclose(f);
472 return(NULL);
473}
474
475void
476sleep_ms(size_t ms)
477{
478 struct timeval tv;
479
480 tv.tv_sec = ms / 1000;
481 tv.tv_usec = 1000 * (ms % 1000);
482 (void) select(0, NULL, NULL, NULL, &tv);
483}
484
485/* vim:set ts=4 sw=4 noexpandtab: */
486