/************************************************************************** * * JSONUTILS.C - Utilities for Nagios CGIs for returning JSON-formatted * object data * * Copyright (c) 2013 Nagios Enterprises, LLC * Last Modified: 04-13-2013 * * License: * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *************************************************************************/ #include "../include/config.h" #include "../include/common.h" #include "../include/objects.h" #include "../include/statusdata.h" #include "../include/comments.h" #include "../include/cgiutils.h" #include "../include/getcgi.h" #include "../include/cgiauth.h" #include "../include/jsonutils.h" /* Multiplier to increment the buffer in json_escape_string() to avoid frequent repeated reallocations */ #define BUF_REALLOC_MULTIPLIER 16 const char *result_types[] = { "Success", "Unable to Allocate Memory", "Unable to Open File for Reading", "Option Invalid", "Option Missing", "Option Value Missing", "Option Value Invalid", "Option Ignored" }; const string_value_mapping svm_format_options[] = { { "whitespace", JSON_FORMAT_WHITESPACE, "Pad with whitespace to increase readability" }, { "enumerate", JSON_FORMAT_ENUMERATE, "Use textual representations of enumerated values rather than " "raw numeric values" }, { "bitmask", JSON_FORMAT_BITMASK, "Use textual representations of bitmask values rather than " "raw numeric values" }, { "duration", JSON_FORMAT_DURATION, "Use textual representations (xd xh xm xs) of duration values rather " "than raw number of seconds" }, #if 0 { "datetime", JSON_FORMAT_DATETIME, "Format date/time values according to the supplied strftime format " "or '%%Y-%%m-%%d %%H:%%M:%%S' if no format specified" }, { "date", JSON_FORMAT_DATE, "Format dates according to the supplied strftime format or " "default Javascript format (number of ms since the beginning of the " "Unix epoch) if no format specified" }, { "time", JSON_FORMAT_TIME, "Format times according the supplied strftime format or " "'%%H:%%M:%%S' in for format specified" }, #endif { NULL, -1, NULL }, }; const string_value_mapping query_statuses[] = { { "alpha", QUERY_STATUS_ALPHA, "Alpha" }, { "beta", QUERY_STATUS_BETA, "Beta" }, { "released", QUERY_STATUS_RELEASED, "Released" }, { "deprecated", QUERY_STATUS_DEPRECATED, "Deprecated" }, { NULL, -1, NULL }, }; const string_value_mapping svm_host_statuses[] = { #ifdef JSON_NAGIOS_4X { "up", SD_HOST_UP, "HOST_UP" }, { "down", SD_HOST_DOWN, "HOST_DOWN" }, { "unreachable", SD_HOST_UNREACHABLE, "HOST_UNREACHABLE" }, #else { "up", HOST_UP, "HOST_UP" }, { "down", HOST_DOWN, "HOST_DOWN" }, { "unreachable", HOST_UNREACHABLE, "HOST_UNREACHABLE" }, #endif { "pending", HOST_PENDING, "HOST_PENDING" }, { NULL, -1, NULL }, }; /* Hard-coded values used because the HOST_UP/DOWN/UNREACHABLE macros are host status (and include PENDING), not host state */ const string_value_mapping svm_host_states[] = { { "up", 0, "HOST_UP" }, { "down", 1, "HOST_DOWN" }, { "unreachable", 2, "HOST_UNREACHABLE" }, { NULL, -1, NULL }, }; const string_value_mapping svm_service_statuses[] = { { "ok", SERVICE_OK, "SERVICE_OK" }, { "warning", SERVICE_WARNING, "SERVICE_WARNING" }, { "critical", SERVICE_CRITICAL, "SERVICE_CRITICAL" }, { "unknown", SERVICE_UNKNOWN, "SERVICE_UNKNOWN" }, { "pending", SERVICE_PENDING, "SERVICE_PENDING" }, { NULL, -1, NULL }, }; /* Hard-coded values used because the SERVICE_OK/WARNING/CRITICAL/UNKNOWN macros are service status (and include PENDING), not service state */ const string_value_mapping svm_service_states[] = { { "ok", 0, "SERVICE_OK" }, { "warning", 1, "SERVICE_WARNING" }, { "critical", 2, "SERVICE_CRITICAL" }, { "unknown", 3, "SERVICE_UNKNOWN" }, { NULL, -1, NULL }, }; const string_value_mapping svm_check_options[] = { { "force_execution", CHECK_OPTION_FORCE_EXECUTION, "FORCE_EXECUTION" }, { "freshness_check", CHECK_OPTION_FRESHNESS_CHECK, "FRESHNESS_CHECK" }, { "orphan_check", CHECK_OPTION_ORPHAN_CHECK, "ORPHAN_CHECK" }, { NULL, -1, NULL }, }; const string_value_mapping svm_host_check_types[] = { { "active", HOST_CHECK_ACTIVE, "ACTIVE" }, { "passive", HOST_CHECK_PASSIVE, "PASSIVE" }, { NULL, -1, NULL }, }; const string_value_mapping svm_service_check_types[] = { { "active", SERVICE_CHECK_ACTIVE, "ACTIVE" }, { "passive", SERVICE_CHECK_PASSIVE, "PASSIVE" }, { NULL, -1, NULL }, }; const string_value_mapping svm_state_types[] = { { "soft", SOFT_STATE, "SOFT" }, { "hard", HARD_STATE, "HARD" }, { NULL, -1, NULL }, }; const string_value_mapping svm_acknowledgement_types[] = { { "none", ACKNOWLEDGEMENT_NONE, "NONE" }, { "normal", ACKNOWLEDGEMENT_NORMAL, "NORMAL" }, { "sticky", ACKNOWLEDGEMENT_STICKY, "STICKY" }, { NULL, -1, NULL }, }; const string_value_mapping svm_comment_types[] = { { "host", HOST_COMMENT, "Host Comment" }, { "service", SERVICE_COMMENT, "Service Comment" }, { NULL, -1, NULL }, }; const string_value_mapping svm_comment_entry_types[] = { { "user", USER_COMMENT, "User Comment" }, { "downtime", DOWNTIME_COMMENT, "Downtime Comment" }, { "flapping", FLAPPING_COMMENT, "Flapping Comment" }, { "acknowledgement", ACKNOWLEDGEMENT_COMMENT, "Acknowledgement Comment" }, { NULL, -1, NULL }, }; const string_value_mapping svm_downtime_types[] = { { "service", SERVICE_DOWNTIME, "Service Downtime" }, { "host", HOST_DOWNTIME, "Host Downtime" }, { "any", ANY_DOWNTIME, "Any Downtime" }, { NULL, -1, NULL }, }; #ifdef JSON_NAGIOS_4X const string_value_mapping svm_option_types[] = { { "up", OPT_UP, "Up" }, { "down", OPT_DOWN, "Down" }, { "unreachable", OPT_UNREACHABLE, "Unreachable" }, { "ok", OPT_OK, "OK" }, { "unknown", OPT_UNKNOWN, "Unknown" }, { "warning", OPT_WARNING, "Warning" }, { "critical", OPT_CRITICAL, "Critical" }, { "recovery", OPT_RECOVERY, "Recovery" }, { "pending", OPT_PENDING, "Pending" }, { "flapping", OPT_FLAPPING, "Flapping" }, { "downtime", OPT_DOWNTIME, "Downtime" }, { NULL, -1, NULL }, }; #endif const string_value_mapping parent_host_extras[] = { { "none", 0, "Hosts that are directly reachable by the Nagios Core host" }, { NULL, -1, NULL }, }; const string_value_mapping child_host_extras[] = { { "none", 0, "Hosts that have no child hosts" }, { NULL, -1, NULL }, }; const string_value_mapping parent_service_extras[] = { { "none", 0, "Services that have no parent services" }, { NULL, -1, NULL }, }; const string_value_mapping child_service_extras[] = { { "none", 0, "Services that have no child services" }, { NULL, -1, NULL }, }; const char *dayofweek[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; const char *month[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; static const json_escape_pair string_escape_pairs[] = { { L"\\", L"\\\\" }, { L"\x01", L"\\u0001" }, { L"\x02", L"\\u0002" }, { L"\x03", L"\\u0003" }, { L"\x04", L"\\u0004" }, { L"\x05", L"\\u0004" }, { L"\x06", L"\\u0006" }, { L"\a", L"\\a" }, { L"\b", L"\\b" }, { L"\t", L"\\t" }, { L"\n", L"\\n" }, { L"\v", L"\\v" }, { L"\f", L"\\f" }, { L"\r", L"\\r" }, { L"\x0e", L"\\u000e" }, { L"\x0f", L"\\u000f" }, { L"\x10", L"\\u0010" }, { L"\x11", L"\\u0011" }, { L"\x12", L"\\u0012" }, { L"\x13", L"\\u0013" }, { L"\x14", L"\\u0014" }, { L"\x15", L"\\u0015" }, { L"\x16", L"\\u0016" }, { L"\x17", L"\\u0017" }, { L"\x18", L"\\u0018" }, { L"\x19", L"\\u0019" }, { L"\x1a", L"\\u001a" }, { L"\x1b", L"\\u001b" }, { L"\x1c", L"\\u001c" }, { L"\x1d", L"\\u001d" }, { L"\x1e", L"\\u001e" }, { L"\x1f", L"\\u001f" }, { L"\"", L"\\\"" }, }; static const json_escape string_escapes = { (sizeof(string_escape_pairs) / sizeof(string_escape_pairs[0])), string_escape_pairs }; const json_escape_pair percent_escape_pairs[] = { { L"%", L"%%" }, }; const json_escape percent_escapes = { (sizeof(percent_escape_pairs) / sizeof(percent_escape_pairs[0])), percent_escape_pairs }; extern char main_config_file[MAX_FILENAME_LENGTH]; extern time_t program_start; static json_object_member * json_object_add_member(json_object *); json_object *json_new_object(void) { json_object *new; new = calloc(1, sizeof(json_object)); return new; } void json_free_object(json_object *obj, int free_children) { int x; json_object_member **mpp; if(1 == free_children) { for(x = 0, mpp = obj->members; x < obj->member_count; x++, mpp++) { json_free_member(*mpp, free_children); } } free(obj->members); free(obj); } json_array *json_new_array(void) { return (json_array *)json_new_object(); } void json_free_member(json_object_member *mp, int free_children) { if(NULL != mp->key) free(mp->key); switch(mp->type) { case JSON_TYPE_OBJECT: case JSON_TYPE_ARRAY: if(NULL != mp->value.object) { json_free_object(mp->value.object, free_children); } break; case JSON_TYPE_STRING: if(NULL != mp->value.string) { free(mp->value.string); } break; case JSON_TYPE_INTEGER: case JSON_TYPE_REAL: case JSON_TYPE_TIME_T: case JSON_TYPE_BOOLEAN: break; default: break; } free(mp); } /* Adds a member to a JSON object and returns a pointer to the new member. Returns NULL on failure. */ static json_object_member * json_object_add_member(json_object *obj) { if(0 == obj->member_count) { obj->members = calloc(1, sizeof(json_object_member *)); if(NULL == obj->members) { obj->member_count = 0; return NULL; } } else { obj->members = realloc(obj->members, ((obj->member_count + 1) * sizeof(json_object_member *))); if(NULL == obj->members) { obj->member_count = 0; return NULL; } } obj->members[ obj->member_count] = calloc(1, sizeof(json_object_member)); if(NULL == obj->members[ obj->member_count]) { return NULL; } obj->member_count++; return obj->members[ obj->member_count - 1]; } void json_object_append_object(json_object *obj, char *key, json_object *value) { json_object_member *mp; if(NULL == obj) return; if(NULL == value) return; if((mp = json_object_add_member(obj)) == NULL) { return; } mp->type = JSON_TYPE_OBJECT; if(NULL != key) { mp->key = strdup(key); if(NULL == mp->key) { obj->member_count--; return; } } mp->value.object = value; } void json_array_append_object(json_object *obj, json_object *value) { json_object_append_object(obj, NULL, value); } void json_object_append_array(json_object *obj, char *key, json_array *value) { json_object_member *mp; if(NULL == obj) return; if(NULL == value) return; if((mp = json_object_add_member(obj)) == NULL) { return; } mp->type = JSON_TYPE_ARRAY; if(NULL != key) { mp->key = strdup(key); if(NULL == mp->key) { obj->member_count--; return; } } mp->value.object = value; } void json_array_append_array(json_array *obj, json_array *value) { json_object_append_array((json_object *)obj, NULL, value); } void json_object_append_integer(json_object *obj, char *key, int value) { json_object_member *mp; if(NULL == obj) return; if((mp = json_object_add_member(obj)) == NULL) { return; } mp->type = JSON_TYPE_INTEGER; if(NULL != key) { mp->key = strdup(key); if(NULL == mp->key) { obj->member_count--; return; } } mp->value.integer = value; } void json_array_append_integer(json_object *obj, int value) { json_object_append_integer(obj, NULL, value); } void json_object_append_real(json_object *obj, char *key, double value) { json_object_member *mp; if(NULL == obj) return; if((mp = json_object_add_member(obj)) == NULL) { return; } mp->type = JSON_TYPE_REAL; if(NULL != key) { mp->key = strdup(key); if(NULL == mp->key) { obj->member_count--; return; } } mp->value.real = value; } void json_array_append_real(json_array *obj, double value) { json_object_append_real(obj, NULL, value); } void json_object_append_time(json_object *obj, char *key, unsigned long value) { unsigned hours; unsigned minutes; unsigned seconds; hours = (unsigned)(value / 3600); value -= hours * 3600; minutes = (unsigned)(value / 60); value -= minutes * 60; seconds = value; json_object_append_string(obj, key, NULL, "%02u:%02u:%02u", hours, minutes, seconds); } void json_array_append_time(json_array *obj, unsigned long value) { json_object_append_time(obj, NULL, value); } void json_object_append_time_t(json_object *obj, char *key, time_t value) { json_object_member *mp; if(NULL == obj) return; if((mp = json_object_add_member(obj)) == NULL) { return; } mp->type = JSON_TYPE_TIME_T; if(NULL != key) { mp->key = strdup(key); if(NULL == mp->key) { obj->member_count--; return; } } mp->value.time = value; } void json_set_time_t(json_object_member *mp, time_t value) { if(NULL == mp) return; mp->value.time = value; } void json_object_append_string(json_object *obj, char *key, const json_escape *format_escapes, char *format, ...) { json_object_member *mp; va_list a_list; int result; char *escaped_format; char *buf; if(NULL == obj) return; if((mp = json_object_add_member(obj)) == NULL) { return; } mp->type = JSON_TYPE_STRING; if(NULL != key) { mp->key = strdup(key); if(NULL == mp->key) { obj->member_count--; return; } } if((NULL != format_escapes) && (NULL != format)) { escaped_format = json_escape_string(format, format_escapes); } else { escaped_format = format; } if(NULL != escaped_format) { va_start(a_list, format); result = vasprintf(&buf, escaped_format, a_list); va_end(a_list); if(result >= 0) { mp->value.string = buf; } } if((NULL != format_escapes) && (NULL != escaped_format)) { /* free only if format_escapes were passed and the escaping succeeded */ free(escaped_format); } } void json_array_append_string(json_object *obj, const json_escape *format_escapes, char *format, ...) { va_list a_list; int result; char *buf; va_start( a_list, format); result = vasprintf(&buf, format, a_list); va_end( a_list); if(result >= 0) { json_object_append_string(obj, NULL, format_escapes, buf); } } void json_object_append_boolean(json_object *obj, char *key, int value) { json_object_member *mp; if(NULL == obj) return; if((mp = json_object_add_member(obj)) == NULL) { return; } mp->type = JSON_TYPE_BOOLEAN; if(NULL != key) { mp->key = strdup(key); if(NULL == mp->key) { obj->member_count--; return; } } mp->value.boolean = value; } void json_array_append_boolean(json_object *obj, int value) { json_object_append_boolean(obj, NULL, value); } void json_object_append_duration(json_object *obj, char *key, unsigned long value) { json_object_member *mp; if(NULL == obj) return; if((mp = json_object_add_member(obj)) == NULL) { return; } mp->type = JSON_TYPE_DURATION; if(NULL != key) { mp->key = strdup(key); if(NULL == mp->key) { obj->member_count--; return; } } mp->value.unsigned_integer = value; } void json_array_append_duration(json_object *obj, unsigned long value) { json_object_append_duration(obj, NULL, value); } /* Fetch an object member based on the path. The path is a dot-separated list of nodes. Nodes may be either a key or a zero-based array index. For example to return the query_time key in the result object, the path would be "result.query_time". To find the 2nd host host in the list of hosts for a hostlist query, the path would be "data.hostlist.1" */ json_object_member *json_get_object_member(json_object *root, char *path) { char *dot; char node[1024]; int x; json_object_member **mpp; /* Parse the path to get the first node */ dot = strchr(path, '.'); if(NULL == dot) { /* single node path */ strcpy(node, path); } else { strncpy(node, path, (dot - path)); node[dot - path] = '\0'; } /* Loop over the members of the passed root looking for the node name */ for(x = 0, mpp = root->members; x < root->member_count; x++, mpp++) { if(!strcmp((*mpp)->key, node)) { if(NULL == dot) { /* return this node */ return *mpp; } else { switch((*mpp)->type) { case JSON_TYPE_OBJECT: return json_get_object_member((*mpp)->value.object, dot + 1); break; case JSON_TYPE_ARRAY: return json_get_array_member((*mpp)->value.object, dot + 1); break; default: /* It should never happen that we want the child of a childless node */ return NULL; break; } } } } return NULL; } json_object_member *json_get_array_member(json_object *root, char *path) { char *dot; char node[1024]; int index; json_object_member *mp; /* Parse the path to get the first node */ dot = strchr(path, '.'); if(NULL == dot) { /* single node path */ strcpy(node, path); } else { strncpy(node, path, (dot - path)); node[dot - path] = '\0'; } index = (int)strtol(node, NULL, 10); /* Verify that we have a reasonable index */ if(index < 0 || index >= root->member_count) { return NULL; } /* Find the requested member and deal with it appropriately */ mp = root->members[ index]; if(NULL == dot) { /* return this node */ return mp; } else { switch(mp->type) { case JSON_TYPE_OBJECT: return json_get_object_member(mp->value.object, dot + 1); break; case JSON_TYPE_ARRAY: return json_get_array_member(mp->value.object, dot + 1); break; default: /* It should never happen that we want the child of a childless node */ return NULL; break; } } return NULL; } void json_object_print(json_object *obj, int padding, int whitespace, char *strftime_format, unsigned format_options) { int x; json_object_member **mpp; //indentf(padding, whitespace, "{%s", (whitespace ? "\n" : "")); printf( "{%s", (whitespace ? "\n" : "")); padding++; for(x = 0, mpp = obj->members; x < obj->member_count; x++, mpp++) { json_member_print(*mpp, padding, whitespace, strftime_format, format_options); if(x != obj->member_count - 1) printf(","); if(whitespace) printf("\n"); } padding--; indentf(padding, whitespace, "}"); } void json_array_print(json_array *obj, int padding, int whitespace, char *strftime_format, unsigned format_options) { int x; json_object_member **mpp; printf( "[%s", (whitespace ? "\n" : "")); padding++; for(x = 0, mpp = obj->members; x < obj->member_count; x++, mpp++) { json_member_print(*mpp, padding, whitespace, strftime_format, format_options); if(x != obj->member_count - 1) printf(","); if(whitespace) printf("\n"); } padding--; indentf(padding, whitespace, "]"); } void json_member_print(json_object_member *mp, int padding, int whitespace, char *strftime_format, unsigned format_options) { char *buf = NULL; switch(mp->type) { case JSON_TYPE_OBJECT: if(NULL != mp->key) { buf = json_escape_string(mp->key, &string_escapes); indentf(padding, whitespace, "\"%s\": ", buf); if(NULL != buf) free(buf); } else { indentf(padding, whitespace, ""); } json_object_print(mp->value.object, padding, whitespace, strftime_format, format_options); break; case JSON_TYPE_ARRAY: if(NULL != mp->key) { buf = json_escape_string(mp->key, &string_escapes); indentf(padding, whitespace, "\"%s\": ", buf); if(NULL != buf) free(buf); } else { indentf(padding, whitespace, ""); } json_array_print(mp->value.object, padding, whitespace, strftime_format, format_options); break; case JSON_TYPE_INTEGER: json_int(padding, whitespace, mp->key, mp->value.integer); break; case JSON_TYPE_REAL: json_float(padding, whitespace, mp->key, mp->value.real); break; case JSON_TYPE_TIME_T: json_time_t(padding, whitespace, mp->key, mp->value.time, strftime_format); break; case JSON_TYPE_STRING: json_string(padding, whitespace, mp->key, mp->value.string); break; case JSON_TYPE_BOOLEAN: json_boolean(padding, whitespace, mp->key, mp->value.boolean); break; case JSON_TYPE_DURATION: json_duration(padding, whitespace, mp->key, mp->value.unsigned_integer, format_options & JSON_FORMAT_DURATION); break; default: break; } } void indentf(int padding, int whitespace, char *format, ...) { va_list a_list; int padvar; if( whitespace > 0) { for(padvar = 0; padvar < padding; padvar++) printf( " "); } va_start( a_list, format); vprintf(format, a_list); va_end( a_list); } json_object * json_result(time_t query_time, char *cgi, char *query, int query_status, time_t last_data_update, authdata *authinfo, int type, char *message, ...) { json_object *json_result; va_list a_list; char *buf; json_result = json_new_object(); json_object_append_time_t(json_result, "query_time", query_time); json_object_append_string(json_result, "cgi", &percent_escapes, cgi); if(NULL != authinfo) { json_object_append_string(json_result, "user", &percent_escapes, authinfo->username); } if(NULL != query) { json_object_append_string(json_result, "query", &percent_escapes, query); json_object_append_string(json_result, "query_status", &percent_escapes, svm_get_string_from_value(query_status, query_statuses)); } json_object_append_time_t(json_result, "program_start", program_start); if(last_data_update != (time_t)-1) { json_object_append_time_t(json_result, "last_data_update", last_data_update); } json_object_append_integer(json_result, "type_code", type); json_object_append_string(json_result, "type_text", &percent_escapes, (char *)result_types[ type]); va_start( a_list, message); if(vasprintf(&buf, message, a_list) == -1) { buf = NULL; } va_end( a_list); json_object_append_string(json_result, "message", &percent_escapes, buf); if(NULL != buf) free(buf); return json_result; } json_object *json_help(option_help *help) { json_object *json_data = json_new_object(); json_object *json_options = json_new_object(); json_object *json_option; json_array *json_required; json_array *json_optional; json_object *json_validvalues; json_object *json_validvalue; int x; char ** stpp; string_value_mapping *svmp; while(NULL != help->name) { json_option = json_new_object(); json_object_append_string(json_option, "label", &percent_escapes, (char *)help->label); json_object_append_string(json_option, "type", &percent_escapes, (char *)help->type); json_required = json_new_array(); for(x = 0, stpp = (char **)help->required; (( x < sizeof( help->required) / sizeof( help->required[ 0])) && ( NULL != *stpp)); x++, stpp++) { json_array_append_string(json_required, &percent_escapes, *stpp); } json_object_append_array(json_option, "required", json_required); json_optional = json_new_array(); for(x = 0, stpp = (char **)help->optional; (( x < sizeof( help->optional) / sizeof( help->optional[ 0])) && ( NULL != *stpp)); x++, stpp++) { json_array_append_string(json_optional, &percent_escapes, *stpp); } json_object_append_array(json_option, "optional", json_optional); json_object_append_string(json_option, "depends_on", &percent_escapes, (char *)help->depends_on); json_object_append_string(json_option, "description", &percent_escapes, (char *)help->description); if( NULL != help->valid_values) { json_validvalues = json_new_object(); for(svmp = (string_value_mapping *)help->valid_values; NULL != svmp->string; svmp++) { if( NULL != svmp->description) { json_validvalue = json_new_object(); json_object_append_string(json_validvalue, "description", &percent_escapes, svmp->description); json_object_append_object(json_validvalues, svmp->string, json_validvalue); } else { json_array_append_string(json_validvalues, &percent_escapes, svmp->string); } } json_object_append_object(json_option, "valid_values", json_validvalues); } json_object_append_object(json_options, (char *)help->name, json_option); help++; } json_object_append_object(json_data, "options", json_options); return json_data; } int passes_start_and_count_limits(int start, int max, int current, int counted) { int result = FALSE; if(start > 0) { /* The user requested we start at a specific index */ if(current >= start) { if(max > 0) { /* The user requested a limit on the number of items returned */ if(counted < max) { result = TRUE; } } else { /* The user did not request a limit on the number of items returned */ result = TRUE; } } } else { /* The user did not request we start at a specific index */ if(max > 0) { /* The user requested a limit on the number of items returned */ if(counted < max) { result = TRUE; } } else { /* The user did not request a limit on the number of items returned */ result = TRUE; } } return result; } void json_string(int padding, int whitespace, char *key, char *value) { char *keybuf = NULL; char *valbuf = NULL; valbuf = json_escape_string(value, &string_escapes); if( NULL == key) { indentf(padding, whitespace, "\"%s\"", (( NULL == valbuf) ? "" : valbuf)); } else { keybuf = json_escape_string(key, &string_escapes); indentf(padding, whitespace, "\"%s\":%s\"%s\"", keybuf, (( whitespace> 0) ? " " : ""), (( NULL == valbuf) ? "" : valbuf)); } if(NULL != keybuf) free(keybuf); if(NULL != valbuf) free(valbuf); } void json_boolean(int padding, int whitespace, char *key, int value) { char *keybuf = NULL; if( NULL == key) { indentf(padding, whitespace, "%s", (( 0 == value) ? "false" : "true")); } else { keybuf = json_escape_string(key, &string_escapes); indentf(padding, whitespace, "\"%s\":%s%s", keybuf, (( whitespace > 0) ? " " : ""), (( 0 == value) ? "false" : "true")); } if(NULL != keybuf) free(keybuf); } void json_int(int padding, int whitespace, char *key, int value) { char *keybuf = NULL; if( NULL == key) { indentf(padding, whitespace, "%d", value); } else { keybuf = json_escape_string(key, &string_escapes); indentf(padding, whitespace, "\"%s\":%s%d", keybuf, (( whitespace > 0) ? " " : ""), value); } if(NULL != keybuf) free(keybuf); } void json_unsigned(int padding, int whitespace, char *key, unsigned long long value) { char *keybuf = NULL; if( NULL == key) { indentf(padding, whitespace, "%llu", value); } else { keybuf = json_escape_string(key, &string_escapes); indentf(padding, whitespace, "\"%s\":%s%llu", keybuf, (( whitespace > 0) ? " " : ""), value); } if(NULL != keybuf) free(keybuf); } void json_float(int padding, int whitespace, char *key, double value) { char *keybuf = NULL; if( NULL == key) { indentf(padding, whitespace, "%.2f", value); } else { keybuf = json_escape_string(key, &string_escapes); indentf(padding, whitespace, "\"%s\":%s%.2f", keybuf, (( whitespace > 0) ? " " : ""), value); } if(NULL != keybuf) free(keybuf); } void json_time(int padding, int whitespace, char *key, unsigned long value) { char *keybuf = NULL; unsigned hours; unsigned minutes; unsigned seconds; hours = (unsigned)(value / 3600); value -= hours * 3600; minutes = (unsigned)(value / 60); value -= minutes * 60; seconds = value; if( NULL == key) { indentf(padding, whitespace, "\"%02u:%02u:%02u\"", hours, minutes, seconds); } else { keybuf = json_escape_string(key, &string_escapes); indentf(padding, whitespace, "\"%s\":%s\"%02u:%02u:%02u\"", keybuf, (( whitespace > 0) ? " " : ""), hours, minutes, seconds); } if(NULL != keybuf) free(keybuf); } void json_time_t(int padding, int whitespace, char *key, time_t value, char *format) { char *keybuf = NULL; char buf[1024]; struct tm *tmp_tm; if(NULL == format) { snprintf(buf, sizeof(buf)-1, "%llu%s", (unsigned long long)value, ((unsigned long long)value > 0 ? "000" : "")); } else { tmp_tm = localtime(&value); buf[ 0] = '"'; strftime(buf+1, sizeof(buf)-3, format, tmp_tm); strcat(buf, "\""); } if(NULL == key) { indentf(padding, whitespace, "%s", buf); } else { keybuf = json_escape_string(key, &string_escapes); indentf(padding, whitespace, "\"%s\":%s%s", keybuf, (( whitespace > 0) ? " " : ""), buf); } if(NULL != keybuf) free(keybuf); } void json_duration(int padding, int whitespace, char *key, unsigned long value, int format_duration) { char *keybuf = NULL; char buf[1024]; int days = 0; int hours = 0; int minutes = 0; int seconds = 0; if(0 == format_duration) { snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long)value); } else { days = (unsigned)(value / 86400); value -= days * 86400; hours = (unsigned)(value / 3600); value -= hours * 3600; minutes = (unsigned)(value / 60); value -= minutes * 60; seconds = value; snprintf(buf, sizeof(buf)-1, "%ud %uh %um %us", days, hours, minutes, seconds); } if( NULL == key) { indentf(padding, whitespace, "%s", buf); } else { keybuf = json_escape_string(key, &string_escapes); indentf(padding, whitespace, "\"%s\":%s%s%s%s", keybuf, (( whitespace > 0) ? " " : ""), (format_duration ? "\"" : ""), buf, (format_duration ? "\"" : "")); } if(NULL != keybuf) free(keybuf); } void json_enumeration(json_object *json_parent, unsigned format_options, char *key, int value, const string_value_mapping *map) { string_value_mapping *svmp; if(format_options & JSON_FORMAT_ENUMERATE) { for(svmp = (string_value_mapping *)map; NULL != svmp->string; svmp++) { if( value == svmp->value) { json_object_append_string(json_parent, key, &percent_escapes, svmp->string); break; } } if( NULL == svmp->string) { json_object_append_string(json_parent, key, NULL, "Unknown value %d", svmp->value); } } else { json_object_append_integer(json_parent, key, value); } } void json_bitmask(json_object *json_parent, unsigned format_options, char *key, int value, const string_value_mapping *map) { json_array *json_bitmask_array; string_value_mapping *svmp; if(format_options & JSON_FORMAT_BITMASK) { json_bitmask_array = json_new_array(); for(svmp = (string_value_mapping *)map; NULL != svmp->string; svmp++) { if( value & svmp->value) { json_array_append_string(json_bitmask_array, &percent_escapes, svmp->string); } } json_object_append_array(json_parent, key, json_bitmask_array); } else { json_object_append_integer(json_parent, key, value); } } int parse_bitmask_cgivar(char *cgi, char *query, int query_status, json_object *json_parent, time_t query_time, authdata *authinfo, char *key, char *value, const string_value_mapping *svm, unsigned *var) { int result = RESULT_SUCCESS; char *option; char *saveptr; string_value_mapping *svmp; if(value == NULL) { json_object_append_object(json_parent, "result", json_result(query_time, cgi, query, query_status, (time_t)-1, authinfo, RESULT_OPTION_VALUE_MISSING, "No value specified for %s option.", key)); return RESULT_OPTION_VALUE_MISSING; } option = strtok_r(value, " ", &saveptr); while(NULL != option) { for(svmp = (string_value_mapping *)svm; NULL != svmp->string; svmp++) { if( !strcmp( svmp->string, option)) { *var |= svmp->value; break; } } if( NULL == svmp->string) { json_object_append_object(json_parent, "result", json_result(query_time, cgi, query, query_status, (time_t)-1, authinfo, RESULT_OPTION_VALUE_INVALID, "The %s option value '%s' is invalid.", key, option)); result = RESULT_OPTION_VALUE_INVALID; break; } option = strtok_r(NULL, " ", &saveptr); } return result; } int parse_enumeration_cgivar(char *cgi, char *query, int query_status, json_object *json_parent, time_t query_time, authdata *authinfo, char *key, char *value, const string_value_mapping *svm, int *var) { string_value_mapping *svmp; if(value == NULL) { json_object_append_object(json_parent, "result", json_result(query_time, cgi, query, query_status, (time_t)-1, authinfo, RESULT_OPTION_VALUE_MISSING, "No value specified for %s option.", key)); return RESULT_OPTION_VALUE_MISSING; } for(svmp = (string_value_mapping *)svm; NULL != svmp->string; svmp++) { if( !strcmp( svmp->string, value)) { *var = svmp->value; break; } } if( NULL == svmp->string) { json_object_append_object(json_parent, "result", json_result(query_time, cgi, query, query_status, (time_t)-1, authinfo, RESULT_OPTION_VALUE_INVALID, "The %s option value '%s' is invalid.", key, value)); return RESULT_OPTION_VALUE_INVALID; } return RESULT_SUCCESS; } int parse_string_cgivar(char *cgi, char *query, int query_status, json_object *json_parent, time_t query_time, authdata *authinfo, char *key, char *value, char **var) { if(value == NULL) { json_object_append_object(json_parent, "result", json_result(query_time, cgi, query, query_status, (time_t)-1, authinfo, RESULT_OPTION_VALUE_MISSING, "No value specified for %s option.", key)); return RESULT_OPTION_VALUE_MISSING; } if(NULL == (*var = strdup( value))) { json_object_append_object(json_parent, "result", json_result(query_time, cgi, query, query_status, (time_t)-1, authinfo, RESULT_MEMORY_ALLOCATION_ERROR, "Unable to allocate memory for %s option.", key)); return RESULT_MEMORY_ALLOCATION_ERROR; } return RESULT_SUCCESS; } int parse_time_cgivar(char *cgi, char *query, int query_status, json_object *json_parent, time_t query_time, authdata *authinfo, char *key, char *value, time_t *var) { long long templl; if(value == NULL) { json_object_append_object(json_parent, "result", json_result(query_time, cgi, query, query_status, (time_t)-1, authinfo, RESULT_OPTION_VALUE_MISSING, "No value specified for %s option.", key)); return RESULT_OPTION_VALUE_MISSING; } if('+' == value[0]) { templl = strtoll(&(value[1]), NULL, 10); *var = (time_t)((long long)query_time + templl); } else if('-' == value[0]) { templl = strtoll(&(value[1]), NULL, 10); *var = (time_t)((long long)query_time - templl); } else { templl = strtoll(value, NULL, 10); *var = (time_t)templl; } return RESULT_SUCCESS; } int parse_boolean_cgivar(char *cgi, char *query, int query_status, json_object *json_parent, time_t query_time, authdata *authinfo, char *key, char *value, int *var) { if(value == NULL) { json_object_append_object(json_parent, "result", json_result(query_time, cgi, query, query_status, (time_t)-1, authinfo, RESULT_OPTION_VALUE_MISSING, "No value specified for %s option.", key)); return ERROR; } if(!strcmp(value, "true")) { *var = 1; } else if(!strcmp(value, "false")) { *var = 0; } else { json_object_append_object(json_parent, "result", json_result(query_time, cgi, query, query_status, (time_t)-1, authinfo, RESULT_OPTION_VALUE_INVALID, "Value for %s option must be 'true' or 'false'.", key)); return RESULT_OPTION_VALUE_INVALID; } return RESULT_SUCCESS; } int parse_int_cgivar(char *cgi, char *query, int query_status, json_object *json_parent, time_t query_time, authdata *authinfo, char *key, char *value, int *var) { if(value == NULL) { json_object_append_object(json_parent, "result", json_result(query_time, cgi, query, query_status, (time_t)-1, authinfo, RESULT_OPTION_VALUE_MISSING, "No value specified for %s option.", key)); return RESULT_OPTION_VALUE_MISSING; } *var = atoi(value); return RESULT_SUCCESS; } int get_query_status(const int statuses[][2], int query) { int x; for(x = 0; -1 != statuses[x][0]; x++) { if(statuses[x][0] == query) return statuses[x][1]; } return -1; } char *svm_get_string_from_value(int value, const string_value_mapping *svm) { string_value_mapping *svmp; for(svmp = (string_value_mapping *)svm; NULL != svmp->string; svmp++) { if(svmp->value == value) return svmp->string; } return NULL; } char *svm_get_description_from_value(int value, const string_value_mapping *svm) { string_value_mapping *svmp; for(svmp = (string_value_mapping *)svm; NULL != svmp->string; svmp++) { if(svmp->value == value) return svmp->description; } return NULL; } /* Thanks to Jerry Coffin for posting the basis of this function on Stack Overflow */ time_t compile_time(const char *date, const char *time) { char buf[5]; int year; int month; int day; int hour; int minute; int second; struct tm t; const char *months = "JanFebMarAprMayJunJulAugSepOctNovDec"; sscanf(date, "%s %d %d", buf, &day, &year); sscanf(time, "%d:%d:%d", &hour, &minute, &second); month = (strstr(months, buf) - months) / 3; t.tm_year = year - 1900; t.tm_mon = month; t.tm_mday = day; t.tm_hour = hour; t.tm_min = minute; t.tm_sec = second; t.tm_isdst = -1; return mktime(&t); } /* Escape a string based on the values in the escapes parameter */ char *json_escape_string(const char *src, const json_escape *escapes) { wchar_t *wdest; /* wide character version of the output string */ size_t wdest_size; /* number of available wchars in wdest */ size_t wdest_len; /* number of wchars in wdest */ int x; json_escape_pair *escp; /* pointer to current escape pair */ size_t from_len; size_t to_len; wchar_t *fromp; /* pointer to a found "from" string */ long offset; /* offset from beginning of wdest to a "from" string */ size_t wchars; /* number of wide characters to move */ size_t dest_len; /* length of output string "dest" */ char *dest; /* buffer containing the escaped version of src */ /* Make sure we're passed valid parameters */ if((NULL == src) || (NULL == escapes)) { return NULL; } /* Make a wide string copy of src */ wdest_len = mbstowcs(NULL, src, 0); if(wdest_len <= 0) return NULL; if((wdest = calloc(wdest_len + 1, sizeof(wchar_t))) == NULL) { return NULL; } if(mbstowcs(wdest, src, wdest_len) != wdest_len) { free(wdest); return NULL; } wdest_size = wdest_len; /* Process each escape pair */ for(x = 0, escp = (json_escape_pair *)escapes->pairs; x < escapes->count; x++, escp++) { from_len = wcslen(escp->from); to_len = wcslen(escp->to); fromp = wdest; while((fromp = wcsstr(fromp, escp->from)) != NULL) { offset = fromp - wdest; if(from_len < to_len) { if((wdest_size - wdest_len) < (to_len - from_len)) { /* If more room is needed, realloc and update variables */ wdest_size += (to_len - from_len) * BUF_REALLOC_MULTIPLIER; wdest = realloc(wdest, (wdest_size + 1) * sizeof(wchar_t)); if(NULL == wdest) return NULL; fromp = wdest + offset; } wchars = wdest_len - offset - from_len + 1; wmemmove(fromp + to_len, fromp + from_len, wchars); wcsncpy(fromp, escp->to, to_len); wdest_len += (to_len - from_len); fromp += to_len; } else { wchars = wdest_len - offset - to_len; memmove(fromp + to_len, fromp + from_len, wchars * sizeof(wchar_t)); wcsncpy(fromp, escp->to, to_len); fromp += (from_len - to_len); wdest_len -= (from_len - to_len); } } } /* Covert the wide string back to a multibyte string */ dest_len = wcstombs(NULL, wdest, 0); if(0 == dest_len) return NULL; if((dest = calloc(dest_len + 1, sizeof(char))) == NULL) { return NULL; } if(wcstombs(dest, wdest, dest_len) != dest_len) { free(dest); return NULL; } return dest; }