/* * Copyright (C) 2015 Jan-Piet Mens and OwnTracks * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * JAN-PIET MENS OR OWNTRACKS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include "util.h" #include "misc.h" #include #include #include #include #include #include #include #include #ifndef LINESIZE # define LINESIZE 8192 #endif int is_directory(char *path) { struct stat sb; if (stat(path, &sb) != 0) return (0); return (S_ISDIR(sb.st_mode)); } const char *isotime(time_t t) { static char buf[] = "YYYY-MM-DDTHH:MM:SSZ"; strftime(buf, sizeof(buf), "%FT%TZ", gmtime(&t)); return(buf); } char *slurp_file(char *filename, int fold_newlines) { FILE *fp; char *buf, *bp; off_t len; int ch; if ((fp = fopen(filename, "rb")) == NULL) return (NULL); if (fseeko(fp, 0, SEEK_END) != 0) { fclose(fp); return (NULL); } len = ftello(fp); fseeko(fp, 0, SEEK_SET); if ((bp = buf = malloc(len + 1)) == NULL) { fclose(fp); return (NULL); } while ((ch = fgetc(fp)) != EOF) { if (ch == '\n') { if (!fold_newlines) *bp++ = ch; } else *bp++ = ch; } *bp = 0; fclose(fp); return (buf); } /* Copy the node into obj */ int json_copy_element_to_object(JsonNode *obj, char *key, JsonNode *node) { if (obj->tag != JSON_OBJECT) return (FALSE); if (node->tag == JSON_STRING) json_append_member(obj, key, json_mkstring(node->string_)); else if (node->tag == JSON_NUMBER) json_append_member(obj, key, json_mknumber(node->number_)); else if (node->tag == JSON_BOOL) json_append_member(obj, key, json_mkbool(node->bool_)); else if (node->tag == JSON_NULL) json_append_member(obj, key, json_mknull()); return (TRUE); } int json_copy_to_object(JsonNode * obj, JsonNode * object_or_array, int clobber) { JsonNode *node; if (obj->tag != JSON_OBJECT && obj->tag != JSON_ARRAY) return (FALSE); json_foreach(node, object_or_array) { if (!clobber & (json_find_member(obj, node->key) != NULL)) continue; /* Don't clobber existing keys */ if (obj->tag == JSON_OBJECT) { if (node->tag == JSON_STRING) json_append_member(obj, node->key, json_mkstring(node->string_)); else if (node->tag == JSON_NUMBER) json_append_member(obj, node->key, json_mknumber(node->number_)); else if (node->tag == JSON_BOOL) json_append_member(obj, node->key, json_mkbool(node->bool_)); else if (node->tag == JSON_NULL) json_append_member(obj, node->key, json_mknull()); else if (node->tag == JSON_ARRAY) { JsonNode *array = json_mkarray(); json_copy_to_object(array, node, clobber); json_append_member(obj, node->key, array); } else if (node->tag == JSON_OBJECT) { JsonNode *newobj = json_mkobject(); json_copy_to_object(newobj, node, clobber); json_append_member(obj, node->key, newobj); } else printf("PANIC: unhandled JSON type %d\n", node->tag); } else if (obj->tag == JSON_ARRAY) { if (node->tag == JSON_STRING) json_append_element(obj, json_mkstring(node->string_)); if (node->tag == JSON_NUMBER) json_append_element(obj, json_mknumber(node->number_)); if (node->tag == JSON_BOOL) json_append_element(obj, json_mkbool(node->bool_)); if (node->tag == JSON_NULL) json_append_element(obj, json_mknull()); } } return (TRUE); } /* * Open filename for reading; slurp in the whole file and attempt * to decode JSON from it into the JSON object at `obj'. TRUE on success, FALSE on failure. */ int json_copy_from_file(JsonNode *obj, char *filename) { char *js_string; JsonNode *node; if ((js_string = slurp_file(filename, TRUE)) == NULL) { return (FALSE); } if ((node = json_decode(js_string)) == NULL) { fprintf(stderr, "json_copy_from_file can't decode JSON from %s\n", filename); return (FALSE); } json_copy_to_object(obj, node, FALSE); json_delete(node); return (TRUE); } #define strprefix(s, pfx) (strncmp((s), (pfx), strlen(pfx)) == 0) #define MAXPARTS 40 /* * Split the string at `s', separated by characters in `sep' * into individual strings, in array `parts'. The caller must * free `parts'. * Returns -1 on error, or the number of parts. */ int splitter(char *s, char *sep, char **parts) { char *token, *p, *ds = strdup(s); int nt = 0; if (!ds) return (-1); for (token = strtok(ds, sep); token && *token && nt < (MAXPARTS - 1); token = strtok(NULL, sep)) { if ((p = strdup(token)) == NULL) return (-1); parts[nt++] = p; } parts[nt] = NULL; free(ds); return (nt); } /* * Split a string separated by characters in `sep' into a JSON * array and return that or NULL on error. */ JsonNode *json_splitter(char *s, char *sep) { char *token, *ds = strdup(s); JsonNode *array = json_mkarray(); if (!ds || !array) return (NULL); for (token = strtok(ds, sep); token && *token; token = strtok(NULL, sep)) { json_append_element(array, json_mkstring(token)); } free(ds); return (array); } /* Return the numeric LOG_ value for a syslog facilty name. */ int syslog_facility_code(char *facility) { struct _log { char *name; int val; }; static struct _log codes[] = { {"kern", LOG_KERN}, {"user", LOG_USER}, {"mail", LOG_MAIL}, {"daemon", LOG_DAEMON}, {"auth", LOG_AUTH}, {"syslog", LOG_SYSLOG}, {"lpr", LOG_LPR}, {"news", LOG_NEWS}, {"uucp", LOG_UUCP}, {"local0", LOG_LOCAL0}, {"local1", LOG_LOCAL1}, {"local2", LOG_LOCAL2}, {"local3", LOG_LOCAL3}, {"local4", LOG_LOCAL4}, {"local5", LOG_LOCAL5}, {"local6", LOG_LOCAL6}, {"local7", LOG_LOCAL7}, {NULL, -1} }, *lp; for (lp = codes; lp->val != -1; lp++) { if (!strcasecmp(facility, lp->name)) return (lp->val); } return (LOG_LOCAL0); } void olog(int level, char *fmt, ...) { va_list ap; static UT_string *u = NULL; va_start(ap, fmt); utstring_renew(u); utstring_printf_va(u, fmt, ap); // fprintf(stderr, "+++++ [%s]\n", utstring_body(u)); syslog(level, "%s", utstring_body(u)); va_end(ap); } const char *yyyymm(time_t t) { static char buf[] = "YYYY-MM"; strftime(buf, sizeof(buf), "%Y-%m", gmtime(&t)); return(buf); } /* * Read the open file fp (which must have file poiter at EOF) line by line * backwards. * (http://stackoverflow.com/questions/14834267/) */ static char *tac_gets(char *buf, int n, FILE * fp) { long fpos; int cpos; int first = 1; if (n <= 1 || (fpos = ftell(fp)) == -1 || fpos == 0) return (NULL); cpos = n - 1; buf[cpos] = '\0'; while (1) { int c; if (fseek(fp, --fpos, SEEK_SET) != 0 || (c = fgetc(fp)) == EOF) return (NULL); if (c == '\n' && first == 0) /* accept at most one '\n' */ break; first = 0; if (c != '\r') {/* ignore DOS/Windows '\r' */ unsigned char ch = c; if (cpos == 0) { memmove(buf + 1, buf, n - 2); ++cpos; } memcpy(buf + --cpos, &ch, 1); } if (fpos == 0) { fseek(fp, 0, SEEK_SET); break; } } memmove(buf, buf + cpos, n - cpos); return (buf); } /* * Open filename and read lines from it, invoking func() on each line. Func * is passed the line and an arbitrary argument pointer. * If filename is "-", read stdin. */ int cat(char *filename, int (*func)(char *, void *), void *param) { FILE *fp; char buf[LINESIZE], *bp; int rc = 0, doclose = FALSE; if (strcmp(filename, "-") != 0) { if ((fp = fopen(filename, "r")) == NULL) { fprintf(stderr, "failed to open file \'%s\'\n", filename); return (-1); } doclose = TRUE; } else { fp = stdin; } while (fgets(buf, sizeof(buf), fp) != NULL) { if ((bp = strchr(buf, '\n')) != NULL) *bp = 0; rc = func(buf, param); if (rc == -1) break; } if (doclose) fclose(fp); return (rc); } /* * Open file and read at most `lines' lines from it in reverse, invoking * func() on each line. The user-supplied func() is passed the line and * an argument. If func returns 1, the line is considered "printed"; if * 0 is returned it is ignored, and if -2 is returned, tac stops reading * the file and returns. */ int tac(char *filename, long lines, int (*func)(char *, void *), void *param) { FILE *fp; long file_len; char buf[LINESIZE], *bp; int rc; if ((fp = fopen(filename, "r")) == NULL) { fprintf(stderr, "failed to open file \'%s\'\n", filename); return (-1); } fseek(fp, 0, SEEK_END); file_len = ftell(fp); if (file_len > 0) { while (tac_gets(buf, sizeof(buf), fp) != NULL) { if ((bp = strchr(buf, '\n')) != NULL) *bp = 0; rc = func(buf, param); if (rc == 1) { if (--lines <= 0) break; } else if (rc == -1) break; } } fclose(fp); return (0); } static void ut_lower(UT_string *us) { char *p; for (p = utstring_body(us); p && *p; p++) { if (!isalnum(*p) || isspace(*p)) *p = '-'; else if (isupper(*p)) *p = tolower(*p); } } static void ut_clean(UT_string *us) { char *p; for (p = utstring_body(us); p && *p; p++) { if (isspace(*p)) *p = '-'; } } /* Return an open append file pointer to storage for user/device, creating directories on the fly. If device is NULL, omit it. */ FILE *pathn(char *mode, char *prefix, UT_string *user, UT_string *device, char *suffix) { static UT_string *path = NULL; time_t now; utstring_renew(path); ut_lower(user); if (device) { ut_lower(device); utstring_printf(path, "%s/%s/%s/%s", STORAGEDIR, prefix, utstring_body(user), utstring_body(device)); } else { utstring_printf(path, "%s/%s/%s", STORAGEDIR, prefix, utstring_body(user)); } ut_clean(path); if (mkpath(utstring_body(path)) < 0) { perror(utstring_body(path)); return (NULL); } #if 0 if (device) { utstring_printf(path, "/%s-%s.%s", utstring_body(user), utstring_body(device), suffix); } else { utstring_printf(path, "/%s.%s", utstring_body(user), suffix); } #endif if (strcmp(prefix, "rec") == 0) { time(&now); utstring_printf(path, "/%s.%s", yyyymm(now), suffix); } else { utstring_printf(path, "/%s.%s", utstring_body(user), suffix); } ut_clean(path); return (fopen(utstring_body(path), mode)); } int safewrite(char *filename, char *buf) { char *tmpfile = malloc(strlen(filename) + 3); mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; int fd; if (tmpfile == NULL) return (-1); sprintf(tmpfile, "%s~", filename); if (unlink(tmpfile) == -1) { if (errno != ENOENT) { fprintf(stderr, "Failed to remove %s (errno=%d)\n", tmpfile, errno); free(tmpfile); return (-1); } } if ((fd = open(tmpfile, O_RDWR|O_CREAT|O_TRUNC, mode)) == -1) { fprintf(stderr, "Failed to create %s (errno=%d)\n", tmpfile, errno); free(tmpfile); return (-1); } if (write(fd, buf, strlen(buf)) != strlen(buf)) { fprintf(stderr, "Failed to write to %s (errno=%d)\n", tmpfile, errno); free(tmpfile); close(fd); return (-1); } /* Ensure NL-terminated */ if (buf[strlen(buf) - 1] != '\n') { write(fd, "\n", 1); } close(fd); if ((rename(tmpfile, filename)) == -1) { fprintf(stderr, "Failed to rename %s to %s (errno=%d)\n", tmpfile, filename, errno); } free(tmpfile); return (0); } static int _precision = GHASHPREC; void geohash_setprec(int precision) { _precision = precision; } int geohash_prec(void) { return (_precision); }