web_api_v1.c 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #include "web_api_v1.h"
  3. char *api_secret;
  4. static struct {
  5. const char *name;
  6. uint32_t hash;
  7. RRDR_OPTIONS value;
  8. } api_v1_data_options[] = {
  9. { "nonzero" , 0 , RRDR_OPTION_NONZERO}
  10. , {"flip" , 0 , RRDR_OPTION_REVERSED}
  11. , {"reversed" , 0 , RRDR_OPTION_REVERSED}
  12. , {"reverse" , 0 , RRDR_OPTION_REVERSED}
  13. , {"jsonwrap" , 0 , RRDR_OPTION_JSON_WRAP}
  14. , {"min2max" , 0 , RRDR_OPTION_MIN2MAX}
  15. , {"ms" , 0 , RRDR_OPTION_MILLISECONDS}
  16. , {"milliseconds" , 0 , RRDR_OPTION_MILLISECONDS}
  17. , {"abs" , 0 , RRDR_OPTION_ABSOLUTE}
  18. , {"absolute" , 0 , RRDR_OPTION_ABSOLUTE}
  19. , {"absolute_sum" , 0 , RRDR_OPTION_ABSOLUTE}
  20. , {"absolute-sum" , 0 , RRDR_OPTION_ABSOLUTE}
  21. , {"display_absolute", 0 , RRDR_OPTION_DISPLAY_ABS}
  22. , {"display-absolute", 0 , RRDR_OPTION_DISPLAY_ABS}
  23. , {"seconds" , 0 , RRDR_OPTION_SECONDS}
  24. , {"null2zero" , 0 , RRDR_OPTION_NULL2ZERO}
  25. , {"objectrows" , 0 , RRDR_OPTION_OBJECTSROWS}
  26. , {"google_json" , 0 , RRDR_OPTION_GOOGLE_JSON}
  27. , {"google-json" , 0 , RRDR_OPTION_GOOGLE_JSON}
  28. , {"percentage" , 0 , RRDR_OPTION_PERCENTAGE}
  29. , {"unaligned" , 0 , RRDR_OPTION_NOT_ALIGNED}
  30. , {"match_ids" , 0 , RRDR_OPTION_MATCH_IDS}
  31. , {"match-ids" , 0 , RRDR_OPTION_MATCH_IDS}
  32. , {"match_names" , 0 , RRDR_OPTION_MATCH_NAMES}
  33. , {"match-names" , 0 , RRDR_OPTION_MATCH_NAMES}
  34. , {"showcustomvars" , 0 , RRDR_OPTION_CUSTOM_VARS}
  35. , { NULL, 0, 0}
  36. };
  37. static struct {
  38. const char *name;
  39. uint32_t hash;
  40. uint32_t value;
  41. } api_v1_data_formats[] = {
  42. { DATASOURCE_FORMAT_DATATABLE_JSON , 0 , DATASOURCE_DATATABLE_JSON}
  43. , {DATASOURCE_FORMAT_DATATABLE_JSONP, 0 , DATASOURCE_DATATABLE_JSONP}
  44. , {DATASOURCE_FORMAT_JSON , 0 , DATASOURCE_JSON}
  45. , {DATASOURCE_FORMAT_JSONP , 0 , DATASOURCE_JSONP}
  46. , {DATASOURCE_FORMAT_SSV , 0 , DATASOURCE_SSV}
  47. , {DATASOURCE_FORMAT_CSV , 0 , DATASOURCE_CSV}
  48. , {DATASOURCE_FORMAT_TSV , 0 , DATASOURCE_TSV}
  49. , {"tsv-excel" , 0 , DATASOURCE_TSV}
  50. , {DATASOURCE_FORMAT_HTML , 0 , DATASOURCE_HTML}
  51. , {DATASOURCE_FORMAT_JS_ARRAY , 0 , DATASOURCE_JS_ARRAY}
  52. , {DATASOURCE_FORMAT_SSV_COMMA , 0 , DATASOURCE_SSV_COMMA}
  53. , {DATASOURCE_FORMAT_CSV_JSON_ARRAY , 0 , DATASOURCE_CSV_JSON_ARRAY}
  54. , {DATASOURCE_FORMAT_CSV_MARKDOWN , 0 , DATASOURCE_CSV_MARKDOWN}
  55. , { NULL, 0, 0}
  56. };
  57. static struct {
  58. const char *name;
  59. uint32_t hash;
  60. uint32_t value;
  61. } api_v1_data_google_formats[] = {
  62. // this is not error - when google requests json, it expects javascript
  63. // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source#responseformat
  64. { "json" , 0 , DATASOURCE_DATATABLE_JSONP}
  65. , {"html" , 0 , DATASOURCE_HTML}
  66. , {"csv" , 0 , DATASOURCE_CSV}
  67. , {"tsv-excel", 0 , DATASOURCE_TSV}
  68. , { NULL, 0, 0}
  69. };
  70. void web_client_api_v1_init(void) {
  71. int i;
  72. for(i = 0; api_v1_data_options[i].name ; i++)
  73. api_v1_data_options[i].hash = simple_hash(api_v1_data_options[i].name);
  74. for(i = 0; api_v1_data_formats[i].name ; i++)
  75. api_v1_data_formats[i].hash = simple_hash(api_v1_data_formats[i].name);
  76. for(i = 0; api_v1_data_google_formats[i].name ; i++)
  77. api_v1_data_google_formats[i].hash = simple_hash(api_v1_data_google_formats[i].name);
  78. web_client_api_v1_init_grouping();
  79. uuid_t uuid;
  80. // generate
  81. uuid_generate(uuid);
  82. // unparse (to string)
  83. char uuid_str[37];
  84. uuid_unparse_lower(uuid, uuid_str);
  85. }
  86. char *get_mgmt_api_key(void) {
  87. char filename[FILENAME_MAX + 1];
  88. snprintfz(filename, FILENAME_MAX, "%s/netdata.api.key", netdata_configured_varlib_dir);
  89. char *api_key_filename=config_get(CONFIG_SECTION_REGISTRY, "netdata management api key file", filename);
  90. static char guid[GUID_LEN + 1] = "";
  91. if(likely(guid[0]))
  92. return guid;
  93. // read it from disk
  94. int fd = open(api_key_filename, O_RDONLY);
  95. if(fd != -1) {
  96. char buf[GUID_LEN + 1];
  97. if(read(fd, buf, GUID_LEN) != GUID_LEN)
  98. error("Failed to read management API key from '%s'", api_key_filename);
  99. else {
  100. buf[GUID_LEN] = '\0';
  101. if(regenerate_guid(buf, guid) == -1) {
  102. error("Failed to validate management API key '%s' from '%s'.",
  103. buf, api_key_filename);
  104. guid[0] = '\0';
  105. }
  106. }
  107. close(fd);
  108. }
  109. // generate a new one?
  110. if(!guid[0]) {
  111. uuid_t uuid;
  112. uuid_generate_time(uuid);
  113. uuid_unparse_lower(uuid, guid);
  114. guid[GUID_LEN] = '\0';
  115. // save it
  116. fd = open(api_key_filename, O_WRONLY|O_CREAT|O_TRUNC, 444);
  117. if(fd == -1)
  118. fatal("Cannot create unique management API key file '%s'. Please fix this.", api_key_filename);
  119. if(write(fd, guid, GUID_LEN) != GUID_LEN)
  120. fatal("Cannot write the unique management API key file '%s'. Please fix this.", api_key_filename);
  121. close(fd);
  122. }
  123. return guid;
  124. }
  125. void web_client_api_v1_management_init(void) {
  126. api_secret = get_mgmt_api_key();
  127. }
  128. inline uint32_t web_client_api_request_v1_data_options(char *o) {
  129. uint32_t ret = 0x00000000;
  130. char *tok;
  131. while(o && *o && (tok = mystrsep(&o, ", |"))) {
  132. if(!*tok) continue;
  133. uint32_t hash = simple_hash(tok);
  134. int i;
  135. for(i = 0; api_v1_data_options[i].name ; i++) {
  136. if (unlikely(hash == api_v1_data_options[i].hash && !strcmp(tok, api_v1_data_options[i].name))) {
  137. ret |= api_v1_data_options[i].value;
  138. break;
  139. }
  140. }
  141. }
  142. return ret;
  143. }
  144. inline uint32_t web_client_api_request_v1_data_format(char *name) {
  145. uint32_t hash = simple_hash(name);
  146. int i;
  147. for(i = 0; api_v1_data_formats[i].name ; i++) {
  148. if (unlikely(hash == api_v1_data_formats[i].hash && !strcmp(name, api_v1_data_formats[i].name))) {
  149. return api_v1_data_formats[i].value;
  150. }
  151. }
  152. return DATASOURCE_JSON;
  153. }
  154. inline uint32_t web_client_api_request_v1_data_google_format(char *name) {
  155. uint32_t hash = simple_hash(name);
  156. int i;
  157. for(i = 0; api_v1_data_google_formats[i].name ; i++) {
  158. if (unlikely(hash == api_v1_data_google_formats[i].hash && !strcmp(name, api_v1_data_google_formats[i].name))) {
  159. return api_v1_data_google_formats[i].value;
  160. }
  161. }
  162. return DATASOURCE_JSON;
  163. }
  164. int web_client_api_request_v1_alarms_select (char *url) {
  165. int all = 0;
  166. while(url) {
  167. char *value = mystrsep(&url, "&");
  168. if (!value || !*value) continue;
  169. if(!strcmp(value, "all")) all = 1;
  170. else if(!strcmp(value, "active")) all = 0;
  171. }
  172. return all;
  173. }
  174. inline int web_client_api_request_v1_alarms(RRDHOST *host, struct web_client *w, char *url) {
  175. int all = web_client_api_request_v1_alarms_select(url);
  176. buffer_flush(w->response.data);
  177. w->response.data->contenttype = CT_APPLICATION_JSON;
  178. health_alarms2json(host, w->response.data, all);
  179. buffer_no_cacheable(w->response.data);
  180. return HTTP_RESP_OK;
  181. }
  182. inline int web_client_api_request_v1_alarms_values(RRDHOST *host, struct web_client *w, char *url) {
  183. int all = web_client_api_request_v1_alarms_select(url);
  184. buffer_flush(w->response.data);
  185. w->response.data->contenttype = CT_APPLICATION_JSON;
  186. health_alarms_values2json(host, w->response.data, all);
  187. buffer_no_cacheable(w->response.data);
  188. return HTTP_RESP_OK;
  189. }
  190. inline int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_client *w, char *url) {
  191. RRDCALC_STATUS status = RRDCALC_STATUS_RAISED;
  192. BUFFER *contexts = NULL;
  193. buffer_flush(w->response.data);
  194. buffer_sprintf(w->response.data, "[");
  195. while(url) {
  196. char *value = mystrsep(&url, "&");
  197. if(!value || !*value) continue;
  198. char *name = mystrsep(&value, "=");
  199. if(!name || !*name) continue;
  200. if(!value || !*value) continue;
  201. debug(D_WEB_CLIENT, "%llu: API v1 alarm_count query param '%s' with value '%s'", w->id, name, value);
  202. char* p = value;
  203. if(!strcmp(name, "status")) {
  204. while ((*p = toupper(*p))) p++;
  205. if (!strcmp("CRITICAL", value)) status = RRDCALC_STATUS_CRITICAL;
  206. else if (!strcmp("WARNING", value)) status = RRDCALC_STATUS_WARNING;
  207. else if (!strcmp("UNINITIALIZED", value)) status = RRDCALC_STATUS_UNINITIALIZED;
  208. else if (!strcmp("UNDEFINED", value)) status = RRDCALC_STATUS_UNDEFINED;
  209. else if (!strcmp("REMOVED", value)) status = RRDCALC_STATUS_REMOVED;
  210. else if (!strcmp("CLEAR", value)) status = RRDCALC_STATUS_CLEAR;
  211. }
  212. else if(!strcmp(name, "context") || !strcmp(name, "ctx")) {
  213. if(!contexts) contexts = buffer_create(255);
  214. buffer_strcat(contexts, "|");
  215. buffer_strcat(contexts, value);
  216. }
  217. }
  218. health_aggregate_alarms(host, w->response.data, contexts, status);
  219. buffer_sprintf(w->response.data, "]\n");
  220. w->response.data->contenttype = CT_APPLICATION_JSON;
  221. buffer_no_cacheable(w->response.data);
  222. buffer_free(contexts);
  223. return 200;
  224. }
  225. inline int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url) {
  226. uint32_t after = 0;
  227. while(url) {
  228. char *value = mystrsep(&url, "&");
  229. if (!value || !*value) continue;
  230. char *name = mystrsep(&value, "=");
  231. if(!name || !*name) continue;
  232. if(!value || !*value) continue;
  233. if(!strcmp(name, "after")) after = (uint32_t)strtoul(value, NULL, 0);
  234. }
  235. buffer_flush(w->response.data);
  236. w->response.data->contenttype = CT_APPLICATION_JSON;
  237. health_alarm_log2json(host, w->response.data, after);
  238. return HTTP_RESP_OK;
  239. }
  240. inline int web_client_api_request_single_chart(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf)) {
  241. int ret = HTTP_RESP_BAD_REQUEST;
  242. char *chart = NULL;
  243. buffer_flush(w->response.data);
  244. while(url) {
  245. char *value = mystrsep(&url, "&");
  246. if(!value || !*value) continue;
  247. char *name = mystrsep(&value, "=");
  248. if(!name || !*name) continue;
  249. if(!value || !*value) continue;
  250. // name and value are now the parameters
  251. // they are not null and not empty
  252. if(!strcmp(name, "chart")) chart = value;
  253. //else {
  254. /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name);
  255. // goto cleanup;
  256. //}
  257. }
  258. if(!chart || !*chart) {
  259. buffer_sprintf(w->response.data, "No chart id is given at the request.");
  260. goto cleanup;
  261. }
  262. RRDSET *st = rrdset_find(host, chart);
  263. if(!st) st = rrdset_find_byname(host, chart);
  264. if(!st) {
  265. buffer_strcat(w->response.data, "Chart is not found: ");
  266. buffer_strcat_htmlescape(w->response.data, chart);
  267. ret = HTTP_RESP_NOT_FOUND;
  268. goto cleanup;
  269. }
  270. w->response.data->contenttype = CT_APPLICATION_JSON;
  271. st->last_accessed_time = now_realtime_sec();
  272. callback(st, w->response.data);
  273. return HTTP_RESP_OK;
  274. cleanup:
  275. return ret;
  276. }
  277. inline int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url) {
  278. return web_client_api_request_single_chart(host, w, url, health_api_v1_chart_variables2json);
  279. }
  280. inline int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, char *url) {
  281. (void)url;
  282. buffer_flush(w->response.data);
  283. w->response.data->contenttype = CT_APPLICATION_JSON;
  284. charts2json(host, w->response.data, 0, 0);
  285. return HTTP_RESP_OK;
  286. }
  287. inline int web_client_api_request_v1_archivedcharts(RRDHOST *host, struct web_client *w, char *url) {
  288. (void)url;
  289. buffer_flush(w->response.data);
  290. w->response.data->contenttype = CT_APPLICATION_JSON;
  291. charts2json(host, w->response.data, 0, 1);
  292. return HTTP_RESP_OK;
  293. }
  294. inline int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, char *url) {
  295. return web_client_api_request_single_chart(host, w, url, rrd_stats_api_v1_chart);
  296. }
  297. void fix_google_param(char *s) {
  298. if(unlikely(!s)) return;
  299. for( ; *s ;s++) {
  300. if(!isalnum(*s) && *s != '.' && *s != '_' && *s != '-')
  301. *s = '_';
  302. }
  303. }
  304. // returns the HTTP code
  305. inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url) {
  306. debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
  307. int ret = HTTP_RESP_BAD_REQUEST;
  308. BUFFER *dimensions = NULL;
  309. buffer_flush(w->response.data);
  310. char *google_version = "0.6",
  311. *google_reqId = "0",
  312. *google_sig = "0",
  313. *google_out = "json",
  314. *responseHandler = NULL,
  315. *outFileName = NULL;
  316. time_t last_timestamp_in_data = 0, google_timestamp = 0;
  317. char *chart = NULL
  318. , *before_str = NULL
  319. , *after_str = NULL
  320. , *group_time_str = NULL
  321. , *points_str = NULL
  322. , *context = NULL;
  323. int group = RRDR_GROUPING_AVERAGE;
  324. uint32_t format = DATASOURCE_JSON;
  325. uint32_t options = 0x00000000;
  326. while(url) {
  327. char *value = mystrsep(&url, "&");
  328. if(!value || !*value) continue;
  329. char *name = mystrsep(&value, "=");
  330. if(!name || !*name) continue;
  331. if(!value || !*value) continue;
  332. debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value);
  333. // name and value are now the parameters
  334. // they are not null and not empty
  335. if(!strcmp(name, "context")) context = value;
  336. else if(!strcmp(name, "chart")) chart = value;
  337. else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) {
  338. if(!dimensions) dimensions = buffer_create(100);
  339. buffer_strcat(dimensions, "|");
  340. buffer_strcat(dimensions, value);
  341. }
  342. else if(!strcmp(name, "after")) after_str = value;
  343. else if(!strcmp(name, "before")) before_str = value;
  344. else if(!strcmp(name, "points")) points_str = value;
  345. else if(!strcmp(name, "gtime")) group_time_str = value;
  346. else if(!strcmp(name, "group")) {
  347. group = web_client_api_request_v1_data_group(value, RRDR_GROUPING_AVERAGE);
  348. }
  349. else if(!strcmp(name, "format")) {
  350. format = web_client_api_request_v1_data_format(value);
  351. }
  352. else if(!strcmp(name, "options")) {
  353. options |= web_client_api_request_v1_data_options(value);
  354. }
  355. else if(!strcmp(name, "callback")) {
  356. responseHandler = value;
  357. }
  358. else if(!strcmp(name, "filename")) {
  359. outFileName = value;
  360. }
  361. else if(!strcmp(name, "tqx")) {
  362. // parse Google Visualization API options
  363. // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source
  364. char *tqx_name, *tqx_value;
  365. while(value) {
  366. tqx_value = mystrsep(&value, ";");
  367. if(!tqx_value || !*tqx_value) continue;
  368. tqx_name = mystrsep(&tqx_value, ":");
  369. if(!tqx_name || !*tqx_name) continue;
  370. if(!tqx_value || !*tqx_value) continue;
  371. if(!strcmp(tqx_name, "version"))
  372. google_version = tqx_value;
  373. else if(!strcmp(tqx_name, "reqId"))
  374. google_reqId = tqx_value;
  375. else if(!strcmp(tqx_name, "sig")) {
  376. google_sig = tqx_value;
  377. google_timestamp = strtoul(google_sig, NULL, 0);
  378. }
  379. else if(!strcmp(tqx_name, "out")) {
  380. google_out = tqx_value;
  381. format = web_client_api_request_v1_data_google_format(google_out);
  382. }
  383. else if(!strcmp(tqx_name, "responseHandler"))
  384. responseHandler = tqx_value;
  385. else if(!strcmp(tqx_name, "outFileName"))
  386. outFileName = tqx_value;
  387. }
  388. }
  389. }
  390. // validate the google parameters given
  391. fix_google_param(google_out);
  392. fix_google_param(google_sig);
  393. fix_google_param(google_reqId);
  394. fix_google_param(google_version);
  395. fix_google_param(responseHandler);
  396. fix_google_param(outFileName);
  397. RRDSET *st = NULL;
  398. if((!chart || !*chart) && (!context)) {
  399. buffer_sprintf(w->response.data, "No chart id is given at the request.");
  400. goto cleanup;
  401. }
  402. struct context_param *context_param_list = NULL;
  403. if (context) {
  404. RRDSET *st1;
  405. uint32_t context_hash = simple_hash(context);
  406. rrdhost_rdlock(localhost);
  407. rrdset_foreach_read(st1, localhost) {
  408. if (st1->hash_context == context_hash && !strcmp(st1->context, context))
  409. build_context_param_list(&context_param_list, st1);
  410. }
  411. rrdhost_unlock(localhost);
  412. if (likely(context_param_list && context_param_list->rd)) // Just set the first one
  413. st = context_param_list->rd->rrdset;
  414. }
  415. else {
  416. st = rrdset_find(host, chart);
  417. if (!st)
  418. st = rrdset_find_byname(host, chart);
  419. if (likely(st))
  420. st->last_accessed_time = now_realtime_sec();
  421. }
  422. if (!st && !context_param_list) {
  423. if (context) {
  424. buffer_strcat(w->response.data, "Context is not found: ");
  425. buffer_strcat_htmlescape(w->response.data, context);
  426. }
  427. else {
  428. buffer_strcat(w->response.data, "Chart is not found: ");
  429. buffer_strcat_htmlescape(w->response.data, chart);
  430. }
  431. ret = HTTP_RESP_NOT_FOUND;
  432. goto cleanup;
  433. }
  434. long long before = (before_str && *before_str)?str2l(before_str):0;
  435. long long after = (after_str && *after_str) ?str2l(after_str):-600;
  436. int points = (points_str && *points_str)?str2i(points_str):0;
  437. long group_time = (group_time_str && *group_time_str)?str2l(group_time_str):0;
  438. debug(D_WEB_CLIENT, "%llu: API command 'data' for chart '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', format '%u', options '0x%08x'"
  439. , w->id
  440. , chart
  441. , (dimensions)?buffer_tostring(dimensions):""
  442. , after
  443. , before
  444. , points
  445. , group
  446. , format
  447. , options
  448. );
  449. if(outFileName && *outFileName) {
  450. buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName);
  451. debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName);
  452. }
  453. if(format == DATASOURCE_DATATABLE_JSONP) {
  454. if(responseHandler == NULL)
  455. responseHandler = "google.visualization.Query.setResponse";
  456. debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
  457. w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName
  458. );
  459. buffer_sprintf(w->response.data,
  460. "%s({version:'%s',reqId:'%s',status:'ok',sig:'%ld',table:",
  461. responseHandler, google_version, google_reqId, st->last_updated.tv_sec);
  462. }
  463. else if(format == DATASOURCE_JSONP) {
  464. if(responseHandler == NULL)
  465. responseHandler = "callback";
  466. buffer_strcat(w->response.data, responseHandler);
  467. buffer_strcat(w->response.data, "(");
  468. }
  469. ret = rrdset2anything_api_v1(st, w->response.data, dimensions, format, points, after, before, group, group_time
  470. , options, &last_timestamp_in_data, context_param_list);
  471. free_context_param_list(&context_param_list);
  472. if(format == DATASOURCE_DATATABLE_JSONP) {
  473. if(google_timestamp < last_timestamp_in_data)
  474. buffer_strcat(w->response.data, "});");
  475. else {
  476. // the client already has the latest data
  477. buffer_flush(w->response.data);
  478. buffer_sprintf(w->response.data,
  479. "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
  480. responseHandler, google_version, google_reqId);
  481. }
  482. }
  483. else if(format == DATASOURCE_JSONP)
  484. buffer_strcat(w->response.data, ");");
  485. cleanup:
  486. buffer_free(dimensions);
  487. return ret;
  488. }
  489. // Pings a netdata server:
  490. // /api/v1/registry?action=hello
  491. //
  492. // Access to a netdata registry:
  493. // /api/v1/registry?action=access&machine=${machine_guid}&name=${hostname}&url=${url}
  494. //
  495. // Delete from a netdata registry:
  496. // /api/v1/registry?action=delete&machine=${machine_guid}&name=${hostname}&url=${url}&delete_url=${delete_url}
  497. //
  498. // Search for the URLs of a machine:
  499. // /api/v1/registry?action=search&machine=${machine_guid}&name=${hostname}&url=${url}&for=${machine_guid}
  500. //
  501. // Impersonate:
  502. // /api/v1/registry?action=switch&machine=${machine_guid}&name=${hostname}&url=${url}&to=${new_person_guid}
  503. inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *w, char *url) {
  504. static uint32_t hash_action = 0, hash_access = 0, hash_hello = 0, hash_delete = 0, hash_search = 0,
  505. hash_switch = 0, hash_machine = 0, hash_url = 0, hash_name = 0, hash_delete_url = 0, hash_for = 0,
  506. hash_to = 0 /*, hash_redirects = 0 */;
  507. if(unlikely(!hash_action)) {
  508. hash_action = simple_hash("action");
  509. hash_access = simple_hash("access");
  510. hash_hello = simple_hash("hello");
  511. hash_delete = simple_hash("delete");
  512. hash_search = simple_hash("search");
  513. hash_switch = simple_hash("switch");
  514. hash_machine = simple_hash("machine");
  515. hash_url = simple_hash("url");
  516. hash_name = simple_hash("name");
  517. hash_delete_url = simple_hash("delete_url");
  518. hash_for = simple_hash("for");
  519. hash_to = simple_hash("to");
  520. /*
  521. hash_redirects = simple_hash("redirects");
  522. */
  523. }
  524. char person_guid[GUID_LEN + 1] = "";
  525. debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url);
  526. // TODO
  527. // The browser may send multiple cookies with our id
  528. char *cookie = strstr(w->response.data->buffer, NETDATA_REGISTRY_COOKIE_NAME "=");
  529. if(cookie)
  530. strncpyz(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME)], 36);
  531. char action = '\0';
  532. char *machine_guid = NULL,
  533. *machine_url = NULL,
  534. *url_name = NULL,
  535. *search_machine_guid = NULL,
  536. *delete_url = NULL,
  537. *to_person_guid = NULL;
  538. /*
  539. int redirects = 0;
  540. */
  541. while(url) {
  542. char *value = mystrsep(&url, "&");
  543. if (!value || !*value) continue;
  544. char *name = mystrsep(&value, "=");
  545. if (!name || !*name) continue;
  546. if (!value || !*value) continue;
  547. debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value);
  548. uint32_t hash = simple_hash(name);
  549. if(hash == hash_action && !strcmp(name, "action")) {
  550. uint32_t vhash = simple_hash(value);
  551. if(vhash == hash_access && !strcmp(value, "access")) action = 'A';
  552. else if(vhash == hash_hello && !strcmp(value, "hello")) action = 'H';
  553. else if(vhash == hash_delete && !strcmp(value, "delete")) action = 'D';
  554. else if(vhash == hash_search && !strcmp(value, "search")) action = 'S';
  555. else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W';
  556. #ifdef NETDATA_INTERNAL_CHECKS
  557. else error("unknown registry action '%s'", value);
  558. #endif /* NETDATA_INTERNAL_CHECKS */
  559. }
  560. /*
  561. else if(hash == hash_redirects && !strcmp(name, "redirects"))
  562. redirects = atoi(value);
  563. */
  564. else if(hash == hash_machine && !strcmp(name, "machine"))
  565. machine_guid = value;
  566. else if(hash == hash_url && !strcmp(name, "url"))
  567. machine_url = value;
  568. else if(action == 'A') {
  569. if(hash == hash_name && !strcmp(name, "name"))
  570. url_name = value;
  571. }
  572. else if(action == 'D') {
  573. if(hash == hash_delete_url && !strcmp(name, "delete_url"))
  574. delete_url = value;
  575. }
  576. else if(action == 'S') {
  577. if(hash == hash_for && !strcmp(name, "for"))
  578. search_machine_guid = value;
  579. }
  580. else if(action == 'W') {
  581. if(hash == hash_to && !strcmp(name, "to"))
  582. to_person_guid = value;
  583. }
  584. #ifdef NETDATA_INTERNAL_CHECKS
  585. else error("unused registry URL parameter '%s' with value '%s'", name, value);
  586. #endif /* NETDATA_INTERNAL_CHECKS */
  587. }
  588. if(unlikely(respect_web_browser_do_not_track_policy && web_client_has_donottrack(w))) {
  589. buffer_flush(w->response.data);
  590. buffer_sprintf(w->response.data, "Your web browser is sending 'DNT: 1' (Do Not Track). The registry requires persistent cookies on your browser to work.");
  591. return HTTP_RESP_BAD_REQUEST;
  592. }
  593. if(unlikely(action == 'H')) {
  594. // HELLO request, dashboard ACL
  595. if(unlikely(!web_client_can_access_dashboard(w)))
  596. return web_client_permission_denied(w);
  597. }
  598. else {
  599. // everything else, registry ACL
  600. if(unlikely(!web_client_can_access_registry(w)))
  601. return web_client_permission_denied(w);
  602. }
  603. switch(action) {
  604. case 'A':
  605. if(unlikely(!machine_guid || !machine_url || !url_name)) {
  606. error("Invalid registry request - access requires these parameters: machine ('%s'), url ('%s'), name ('%s')", machine_guid ? machine_guid : "UNSET", machine_url ? machine_url : "UNSET", url_name ? url_name : "UNSET");
  607. buffer_flush(w->response.data);
  608. buffer_strcat(w->response.data, "Invalid registry Access request.");
  609. return HTTP_RESP_BAD_REQUEST;
  610. }
  611. web_client_enable_tracking_required(w);
  612. return registry_request_access_json(host, w, person_guid, machine_guid, machine_url, url_name, now_realtime_sec());
  613. case 'D':
  614. if(unlikely(!machine_guid || !machine_url || !delete_url)) {
  615. error("Invalid registry request - delete requires these parameters: machine ('%s'), url ('%s'), delete_url ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", delete_url?delete_url:"UNSET");
  616. buffer_flush(w->response.data);
  617. buffer_strcat(w->response.data, "Invalid registry Delete request.");
  618. return HTTP_RESP_BAD_REQUEST;
  619. }
  620. web_client_enable_tracking_required(w);
  621. return registry_request_delete_json(host, w, person_guid, machine_guid, machine_url, delete_url, now_realtime_sec());
  622. case 'S':
  623. if(unlikely(!machine_guid || !machine_url || !search_machine_guid)) {
  624. error("Invalid registry request - search requires these parameters: machine ('%s'), url ('%s'), for ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", search_machine_guid?search_machine_guid:"UNSET");
  625. buffer_flush(w->response.data);
  626. buffer_strcat(w->response.data, "Invalid registry Search request.");
  627. return HTTP_RESP_BAD_REQUEST;
  628. }
  629. web_client_enable_tracking_required(w);
  630. return registry_request_search_json(host, w, person_guid, machine_guid, machine_url, search_machine_guid, now_realtime_sec());
  631. case 'W':
  632. if(unlikely(!machine_guid || !machine_url || !to_person_guid)) {
  633. error("Invalid registry request - switching identity requires these parameters: machine ('%s'), url ('%s'), to ('%s')", machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", to_person_guid?to_person_guid:"UNSET");
  634. buffer_flush(w->response.data);
  635. buffer_strcat(w->response.data, "Invalid registry Switch request.");
  636. return HTTP_RESP_BAD_REQUEST;
  637. }
  638. web_client_enable_tracking_required(w);
  639. return registry_request_switch_json(host, w, person_guid, machine_guid, machine_url, to_person_guid, now_realtime_sec());
  640. case 'H':
  641. return registry_request_hello_json(host, w);
  642. default:
  643. buffer_flush(w->response.data);
  644. buffer_strcat(w->response.data, "Invalid registry request - you need to set an action: hello, access, delete, search");
  645. return HTTP_RESP_BAD_REQUEST;
  646. }
  647. }
  648. static inline void web_client_api_request_v1_info_summary_alarm_statuses(RRDHOST *host, BUFFER *wb) {
  649. int alarm_normal = 0, alarm_warn = 0, alarm_crit = 0;
  650. RRDCALC *rc;
  651. rrdhost_rdlock(host);
  652. for(rc = host->alarms; rc ; rc = rc->next) {
  653. if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec))
  654. continue;
  655. switch(rc->status) {
  656. case RRDCALC_STATUS_WARNING:
  657. alarm_warn++;
  658. break;
  659. case RRDCALC_STATUS_CRITICAL:
  660. alarm_crit++;
  661. break;
  662. default:
  663. alarm_normal++;
  664. }
  665. }
  666. rrdhost_unlock(host);
  667. buffer_sprintf(wb, "\t\t\"normal\": %d,\n", alarm_normal);
  668. buffer_sprintf(wb, "\t\t\"warning\": %d,\n", alarm_warn);
  669. buffer_sprintf(wb, "\t\t\"critical\": %d\n", alarm_crit);
  670. }
  671. static inline void web_client_api_request_v1_info_mirrored_hosts(BUFFER *wb) {
  672. RRDHOST *host;
  673. int count = 0;
  674. buffer_strcat(wb, "\t\"mirrored_hosts\": [\n");
  675. rrd_rdlock();
  676. rrdhost_foreach_read(host) {
  677. if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED))
  678. continue;
  679. if (count > 0)
  680. buffer_strcat(wb, ",\n");
  681. buffer_sprintf(wb, "\t\t\"%s\"", host->hostname);
  682. count++;
  683. }
  684. buffer_strcat(wb, "\n\t],\n\t\"mirrored_hosts_status\": [\n");
  685. count = 0;
  686. rrdhost_foreach_read(host)
  687. {
  688. if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED))
  689. continue;
  690. if (count > 0)
  691. buffer_strcat(wb, ",\n");
  692. netdata_mutex_lock(&host->receiver_lock);
  693. buffer_sprintf(
  694. wb, "\t\t{ \"guid\": \"%s\", \"reachable\": %s, \"claim_id\": ", host->machine_guid,
  695. (host->receiver || host == localhost) ? "true" : "false");
  696. netdata_mutex_unlock(&host->receiver_lock);
  697. netdata_mutex_lock(&host->claimed_id_lock);
  698. if (host->claimed_id)
  699. buffer_sprintf(wb, "\"%s\" }", host->claimed_id);
  700. else
  701. buffer_strcat(wb, "null }");
  702. netdata_mutex_unlock(&host->claimed_id_lock);
  703. count++;
  704. }
  705. rrd_unlock();
  706. buffer_strcat(wb, "\n\t],\n");
  707. }
  708. inline void host_labels2json(RRDHOST *host, BUFFER *wb, size_t indentation) {
  709. char tabs[11];
  710. if (indentation > 10)
  711. indentation = 10;
  712. tabs[0] = '\0';
  713. while (indentation) {
  714. strcat(tabs, "\t");
  715. indentation--;
  716. }
  717. int count = 0;
  718. rrdhost_rdlock(host);
  719. netdata_rwlock_rdlock(&host->labels_rwlock);
  720. for (struct label *label = host->labels; label; label = label->next) {
  721. if(count > 0) buffer_strcat(wb, ",\n");
  722. buffer_strcat(wb, tabs);
  723. char value[CONFIG_MAX_VALUE * 2 + 1];
  724. sanitize_json_string(value, label->value, CONFIG_MAX_VALUE * 2);
  725. buffer_sprintf(wb, "\"%s\": \"%s\"", label->key, value);
  726. count++;
  727. }
  728. buffer_strcat(wb, "\n");
  729. netdata_rwlock_unlock(&host->labels_rwlock);
  730. rrdhost_unlock(host);
  731. }
  732. extern int aclk_connected;
  733. inline int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb)
  734. {
  735. buffer_strcat(wb, "{\n");
  736. buffer_sprintf(wb, "\t\"version\": \"%s\",\n", host->program_version);
  737. buffer_sprintf(wb, "\t\"uid\": \"%s\",\n", host->machine_guid);
  738. web_client_api_request_v1_info_mirrored_hosts(wb);
  739. buffer_strcat(wb, "\t\"alarms\": {\n");
  740. web_client_api_request_v1_info_summary_alarm_statuses(host, wb);
  741. buffer_strcat(wb, "\t},\n");
  742. buffer_sprintf(wb, "\t\"os_name\": \"%s\",\n", (host->system_info->host_os_name) ? host->system_info->host_os_name : "");
  743. buffer_sprintf(wb, "\t\"os_id\": \"%s\",\n", (host->system_info->host_os_id) ? host->system_info->host_os_id : "");
  744. buffer_sprintf(wb, "\t\"os_id_like\": \"%s\",\n", (host->system_info->host_os_id_like) ? host->system_info->host_os_id_like : "");
  745. buffer_sprintf(wb, "\t\"os_version\": \"%s\",\n", (host->system_info->host_os_version) ? host->system_info->host_os_version : "");
  746. buffer_sprintf(wb, "\t\"os_version_id\": \"%s\",\n", (host->system_info->host_os_version_id) ? host->system_info->host_os_version_id : "");
  747. buffer_sprintf(wb, "\t\"os_detection\": \"%s\",\n", (host->system_info->host_os_detection) ? host->system_info->host_os_detection : "");
  748. buffer_sprintf(wb, "\t\"cores_total\": \"%s\",\n", (host->system_info->host_cores) ? host->system_info->host_cores : "");
  749. buffer_sprintf(wb, "\t\"total_disk_space\": \"%s\",\n", (host->system_info->host_disk_space) ? host->system_info->host_disk_space : "");
  750. buffer_sprintf(wb, "\t\"cpu_freq\": \"%s\",\n", (host->system_info->host_cpu_freq) ? host->system_info->host_cpu_freq : "");
  751. buffer_sprintf(wb, "\t\"ram_total\": \"%s\",\n", (host->system_info->host_ram_total) ? host->system_info->host_ram_total : "");
  752. if (host->system_info->container_os_name)
  753. buffer_sprintf(wb, "\t\"container_os_name\": \"%s\",\n", host->system_info->container_os_name);
  754. if (host->system_info->container_os_id)
  755. buffer_sprintf(wb, "\t\"container_os_id\": \"%s\",\n", host->system_info->container_os_id);
  756. if (host->system_info->container_os_id_like)
  757. buffer_sprintf(wb, "\t\"container_os_id_like\": \"%s\",\n", host->system_info->container_os_id_like);
  758. if (host->system_info->container_os_version)
  759. buffer_sprintf(wb, "\t\"container_os_version\": \"%s\",\n", host->system_info->container_os_version);
  760. if (host->system_info->container_os_version_id)
  761. buffer_sprintf(wb, "\t\"container_os_version_id\": \"%s\",\n", host->system_info->container_os_version_id);
  762. if (host->system_info->container_os_detection)
  763. buffer_sprintf(wb, "\t\"container_os_detection\": \"%s\",\n", host->system_info->container_os_detection);
  764. buffer_sprintf(wb, "\t\"kernel_name\": \"%s\",\n", (host->system_info->kernel_name) ? host->system_info->kernel_name : "");
  765. buffer_sprintf(wb, "\t\"kernel_version\": \"%s\",\n", (host->system_info->kernel_version) ? host->system_info->kernel_version : "");
  766. buffer_sprintf(wb, "\t\"architecture\": \"%s\",\n", (host->system_info->architecture) ? host->system_info->architecture : "");
  767. buffer_sprintf(wb, "\t\"virtualization\": \"%s\",\n", (host->system_info->virtualization) ? host->system_info->virtualization : "");
  768. buffer_sprintf(wb, "\t\"virt_detection\": \"%s\",\n", (host->system_info->virt_detection) ? host->system_info->virt_detection : "");
  769. buffer_sprintf(wb, "\t\"container\": \"%s\",\n", (host->system_info->container) ? host->system_info->container : "");
  770. buffer_sprintf(wb, "\t\"container_detection\": \"%s\",\n", (host->system_info->container_detection) ? host->system_info->container_detection : "");
  771. buffer_strcat(wb, "\t\"host_labels\": {\n");
  772. host_labels2json(host, wb, 2);
  773. buffer_strcat(wb, "\t},\n");
  774. buffer_strcat(wb, "\t\"collectors\": [");
  775. chartcollectors2json(host, wb);
  776. buffer_strcat(wb, "\n\t],\n");
  777. #ifdef DISABLE_CLOUD
  778. buffer_strcat(wb, "\t\"cloud-enabled\": false,\n");
  779. #else
  780. buffer_sprintf(wb, "\t\"cloud-enabled\": %s,\n",
  781. appconfig_get_boolean(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", 1) ? "true" : "false");
  782. #endif
  783. #ifdef ENABLE_ACLK
  784. buffer_strcat(wb, "\t\"cloud-available\": true,\n");
  785. #else
  786. buffer_strcat(wb, "\t\"cloud-available\": false,\n");
  787. #endif
  788. char *agent_id = is_agent_claimed();
  789. if (agent_id == NULL)
  790. buffer_strcat(wb, "\t\"agent-claimed\": false,\n");
  791. else {
  792. buffer_strcat(wb, "\t\"agent-claimed\": true,\n");
  793. freez(agent_id);
  794. }
  795. #ifdef ENABLE_ACLK
  796. if (aclk_connected)
  797. buffer_strcat(wb, "\t\"aclk-available\": true\n");
  798. else
  799. #endif
  800. buffer_strcat(wb, "\t\"aclk-available\": false\n"); // Intentionally valid with/without #ifdef above
  801. buffer_strcat(wb, "}");
  802. return 0;
  803. }
  804. inline int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url) {
  805. (void)url;
  806. if (!netdata_ready) return HTTP_RESP_BACKEND_FETCH_FAILED;
  807. BUFFER *wb = w->response.data;
  808. buffer_flush(wb);
  809. wb->contenttype = CT_APPLICATION_JSON;
  810. web_client_api_request_v1_info_fill_buffer(host, wb);
  811. buffer_no_cacheable(wb);
  812. return HTTP_RESP_OK;
  813. }
  814. static struct api_command {
  815. const char *command;
  816. uint32_t hash;
  817. WEB_CLIENT_ACL acl;
  818. int (*callback)(RRDHOST *host, struct web_client *w, char *url);
  819. } api_commands[] = {
  820. { "info", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_info },
  821. { "data", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_data },
  822. { "chart", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_chart },
  823. { "charts", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_charts },
  824. { "archivedcharts", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_archivedcharts },
  825. // registry checks the ACL by itself, so we allow everything
  826. { "registry", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v1_registry },
  827. // badges can be fetched with both dashboard and badge permissions
  828. { "badge.svg", 0, WEB_CLIENT_ACL_DASHBOARD|WEB_CLIENT_ACL_BADGE, web_client_api_request_v1_badge },
  829. { "alarms", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarms },
  830. { "alarms_values", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarms_values },
  831. { "alarm_log", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarm_log },
  832. { "alarm_variables", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarm_variables },
  833. { "alarm_count", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarm_count },
  834. { "allmetrics", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_allmetrics },
  835. { "manage/health", 0, WEB_CLIENT_ACL_MGMT, web_client_api_request_v1_mgmt_health },
  836. // terminator
  837. { NULL, 0, WEB_CLIENT_ACL_NONE, NULL },
  838. };
  839. inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url) {
  840. static int initialized = 0;
  841. int i;
  842. if(unlikely(initialized == 0)) {
  843. initialized = 1;
  844. for(i = 0; api_commands[i].command ; i++)
  845. api_commands[i].hash = simple_hash(api_commands[i].command);
  846. }
  847. // get the command
  848. if(url) {
  849. debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, url);
  850. uint32_t hash = simple_hash(url);
  851. for(i = 0; api_commands[i].command ;i++) {
  852. if(unlikely(hash == api_commands[i].hash && !strcmp(url, api_commands[i].command))) {
  853. if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NOCHECK) && !(w->acl & api_commands[i].acl))
  854. return web_client_permission_denied(w);
  855. //return api_commands[i].callback(host, w, url);
  856. return api_commands[i].callback(host, w, (w->decoded_query_string + 1));
  857. }
  858. }
  859. buffer_flush(w->response.data);
  860. buffer_strcat(w->response.data, "Unsupported v1 API command: ");
  861. buffer_strcat_htmlescape(w->response.data, url);
  862. return HTTP_RESP_NOT_FOUND;
  863. }
  864. else {
  865. buffer_flush(w->response.data);
  866. buffer_sprintf(w->response.data, "Which API v1 command?");
  867. return HTTP_RESP_BAD_REQUEST;
  868. }
  869. }