diff --git a/NEWS.adoc b/NEWS.adoc index b618b57d3c..ad32b08d8c 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -338,6 +338,9 @@ several `FSD` notifications into one executed action. [PR #3097] * Introduced a `@NUT_UPSSTATS_TEMPLATE@` command which the HTML template files now MUST start with (safety check that we are reading a template). [issue #3252, PR #3249] + * (Experimental) Custom templates other than `upsstats{,-single}.html` can + now be specified as CGI parameters, if locally permitted via `hosts.conf`. + [issue #2524, PR #3304] - `upssched` tool updates: * Previously in PR #2896 (NUT releases v2.8.3 and v2.8.4) the `UPSNAME` and diff --git a/clients/cgilib.c b/clients/cgilib.c index 36bc0395b8..044432c1b0 100644 --- a/clients/cgilib.c +++ b/clients/cgilib.c @@ -87,8 +87,15 @@ void extractcgiargs(void) while (ptr) { varname = ptr; eq = strchr(varname, '='); - if (!eq) { - ptr = strchr(varname, '&'); + amp = strchr(varname, '&'); + if (!eq + || (eq && amp && amp < eq) + ) { + /* Last token is a flag (without assignment in sight), + * OR we've got a flag token in the middle of a query + * string, followed by another key=value pair later on. + */ + ptr = amp; if (ptr) *ptr++ = '\0'; @@ -99,6 +106,8 @@ void extractcgiargs(void) continue; } + /* The nearest point of interest is a key=value pair, + * maybe followed by another amp and flag or assignment... */ *eq = '\0'; value = eq + 1; amp = strchr(value, '&'); @@ -111,6 +120,8 @@ void extractcgiargs(void) cleanvar = unescape(varname); cleanval = unescape(value); + upsdebugx(3, "%s: parsearg('%s', '%s')
", + __func__, NUT_STRARG(cleanvar), NUT_STRARG(cleanval)); parsearg(cleanvar, cleanval); free(cleanvar); free(cleanval); diff --git a/clients/upsimage.c b/clients/upsimage.c index 1d99fdac15..18d7cf882b 100644 --- a/clients/upsimage.c +++ b/clients/upsimage.c @@ -20,6 +20,7 @@ Copyrights: (C) 1998 Russell Kroll (C) 2002 Simon Rozman + (C) 2020-2026 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -619,14 +620,14 @@ int main(int argc, char **argv) double var = 0; #ifdef WIN32 - /* Required ritual before calling any socket functions */ - static WSADATA WSAdata; - static int WSA_Started = 0; - if (!WSA_Started) { - WSAStartup(2, &WSAdata); - atexit((void(*)(void))WSACleanup); - WSA_Started = 1; - } + /* Required ritual before calling any socket functions */ + static WSADATA WSAdata; + static int WSA_Started = 0; + if (!WSA_Started) { + WSAStartup(2, &WSAdata); + atexit((void(*)(void))WSACleanup); + WSA_Started = 1; + } /* Avoid binary output conversions, e.g. * mangling what looks like CRLF on WIN32 */ @@ -646,6 +647,23 @@ int main(int argc, char **argv) nut_debug_level = i; } +#ifdef NUT_CGI_DEBUG_UPSIMAGE +# if (NUT_CGI_DEBUG_UPSIMAGE - 0 < 1) +# undef NUT_CGI_DEBUG_UPSIMAGE +# define NUT_CGI_DEBUG_UPSIMAGE 6 +# endif + /* Un-comment via make flags when developer-troubleshooting: */ + nut_debug_level = NUT_CGI_DEBUG_UPSIMAGE; +#endif + + if (nut_debug_level > 0) { + cgilogbit_set(); + printf("Content-type: text/html\n"); + printf("Pragma: no-cache\n"); + printf("\n"); + printf("

NUT CGI Debugging enabled, level: %d

\n\n", nut_debug_level); + } + extractcgiargs(); upscli_init_default_connect_timeout(NULL, NULL, UPSCLI_DEFAULT_CONNECT_TIMEOUT); diff --git a/clients/upsset.c b/clients/upsset.c index 898acd4dba..3074bf63ef 100644 --- a/clients/upsset.c +++ b/clients/upsset.c @@ -1,6 +1,7 @@ /* upsset - CGI program to manage read/write variables Copyright (C) 1999 Russell Kroll + Copyright (C) 2020-2026 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1116,14 +1117,14 @@ int main(int argc, char **argv) int i; #ifdef WIN32 - /* Required ritual before calling any socket functions */ - static WSADATA WSAdata; - static int WSA_Started = 0; - if (!WSA_Started) { - WSAStartup(2, &WSAdata); - atexit((void(*)(void))WSACleanup); - WSA_Started = 1; - } + /* Required ritual before calling any socket functions */ + static WSADATA WSAdata; + static int WSA_Started = 0; + if (!WSA_Started) { + WSAStartup(2, &WSAdata); + atexit((void(*)(void))WSACleanup); + WSA_Started = 1; + } /* Avoid binary output conversions, e.g. * mangling what looks like CRLF on WIN32 */ @@ -1136,7 +1137,9 @@ int main(int argc, char **argv) NUT_UNUSED_VARIABLE(argv); username = password = function = monups = NULL; - printf("Content-type: text/html\n\n"); + printf("Content-type: text/html\n"); + printf("Pragma: no-cache\n"); + printf("\n"); /* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc * and NUT methods called from it. This line aims to just initialize @@ -1148,6 +1151,20 @@ int main(int argc, char **argv) nut_debug_level = i; } +#ifdef NUT_CGI_DEBUG_UPSSET +# if (NUT_CGI_DEBUG_UPSSET - 0 < 1) +# undef NUT_CGI_DEBUG_UPSSET +# define NUT_CGI_DEBUG_UPSSET 6 +# endif + /* Un-comment via make flags when developer-troubleshooting: */ + nut_debug_level = NUT_CGI_DEBUG_UPSSET; +#endif + + if (nut_debug_level > 0) { + cgilogbit_set(); + printf("

NUT CGI Debugging enabled, level: %d

\n\n", nut_debug_level); + } + /* see if the magic string is present in the config file */ check_conf(); diff --git a/clients/upsstats.c b/clients/upsstats.c index 5a0bf47521..e1d310b192 100644 --- a/clients/upsstats.c +++ b/clients/upsstats.c @@ -2,6 +2,7 @@ Copyright (C) 1998 Russell Kroll Copyright (C) 2005 Arnaud Quette + Copyright (C) 2020-2026 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -62,13 +63,18 @@ static char *monhostdesc = NULL; static uint16_t port; static char *upsname, *hostname; -static char *upsimgpath="upsimage.cgi" EXEEXT, *upsstatpath="upsstats.cgi" EXEEXT; +static char *upsimgpath = "upsimage.cgi" EXEEXT, *upsstatpath = "upsstats.cgi" EXEEXT, + *template_single = NULL, *template_list = NULL; static UPSCONN_t ups; static FILE *tf; static long forofs = 0; -static ulist_t *ulhead = NULL, *currups = NULL; +static ulist_t *ulhead = NULL, *currups = NULL, + /* hijack the linked-list structure to store + * just filenames (as "sys") so far */ + *allowed_template_single_lhead = NULL, + *allowed_template_list_lhead = NULL; static int skip_clause = 0, skip_block = 0; @@ -99,6 +105,18 @@ void parsearg(char *var, char *value) output_json = 1; } + if (!strcmp(var, "template_single")) { + /* Error-checking in display_template(), when we have all options in place */ + free(template_single); + template_single = xstrdup(value); + } + + if (!strcmp(var, "template_list")) { + /* Error-checking in display_template(), when we have all options in place */ + free(template_list); + template_list = xstrdup(value); + } + upsdebug_call_finished0(); } @@ -526,6 +544,14 @@ static void do_hostlink(void) printf("sys); + if (template_single && strcmp(template_single, "upsstats-single.html")) { + printf("&template_single=%s", template_single); + } + + if (template_list && strcmp(template_list, "upsstats.html")) { + printf("&template_list=%s", template_list); + } + if (refreshdelay > 0) { printf("&refresh=%d", refreshdelay); } @@ -543,8 +569,22 @@ static void do_treelink_json(const char *text) return; } - printf("%s", - upsstatpath, currups->sys, + printf("sys); + + if (template_single && strcmp(template_single, "upsstats-single.html")) { + printf("&template_single=%s", template_single); + } + + if (template_list && strcmp(template_list, "upsstats.html")) { + printf("&template_list=%s", template_list); + } + + if (refreshdelay > 0) { + printf("&refresh=%d", refreshdelay); + } + + printf("\">%s", ((text && *text) ? text : "JSON")); upsdebug_call_finished0(); @@ -559,8 +599,22 @@ static void do_treelink(const char *text) return; } - printf("%s", - upsstatpath, currups->sys, + printf("sys); + + if (template_single && strcmp(template_single, "upsstats-single.html")) { + printf("&template_single=%s", template_single); + } + + if (template_list && strcmp(template_list, "upsstats.html")) { + printf("&template_list=%s", template_list); + } + + if (refreshdelay > 0) { + printf("&refresh=%d", refreshdelay); + } + + printf("\">%s", ((text && *text) ? text : "All data")); upsdebug_call_finished0(); @@ -1087,29 +1141,85 @@ static void parse_line(const char *buf) upsdebug_call_finished0(); } -static void display_template(const char *tfn) +/* type = 1 for upsstats-single.html (or custom copy), 2 for upsstats.html (list) */ +static void display_template(const char *tfn, int type) { char fn[NUT_PATH_MAX + 1], buf[LARGEBUF]; + ulist_t *tmp = NULL; upsdebug_call_starting_for_str1(tfn); + if (!tfn || !*tfn || strstr(tfn, "/") || strstr(tfn, "\\")) { + /* We only allow pre-configured templates in one managed location, with ".htm" in the name */ + errno = EPERM; + fprintf(stderr, "upsstats: Can't open %s: %s: asked to look not exactly in the managed location
\n", tfn, strerror(errno)); + + printf("Error: can't open template file (%s): Not authorized
\n", tfn); + + upsdebug_call_finished1(": subdir in template"); + exit(EXIT_FAILURE); + } + + if (!strstr(tfn, ".htm")) { + /* We only allow pre-configured templates with ".htm" in the name */ + errno = EPERM; + fprintf(stderr, "upsstats: Can't open %s: %s: asked to look at not a *.htm* file
\n", tfn, strerror(errno)); + + printf("Error: can't open template file (%s): Not authorized
\n", tfn); + + upsdebug_call_finished1(": not a *.htm* file"); + exit(EXIT_FAILURE); + } + + if (type == 1) { + /* Check if [custom] single template is allowed + * (built-in/legacy default starts the list) */ + tmp = allowed_template_single_lhead; + while (tmp) { + if (!strcmp(tmp->sys, tfn)) + break; + tmp = (ulist_t *)tmp->next; + } + } else + if (type == 2) { + /* Check if [custom] list template is allowed + * (built-in/legacy default starts the list) */ + tmp = allowed_template_list_lhead; + while (tmp) { + if (!strcmp(tmp->sys, tfn)) + break; + tmp = (ulist_t *)tmp->next; + } + } + + if (!tmp) { + /* We only allow pre-configured templates permitted via hosts.conf */ + errno = EPERM; + fprintf(stderr, "upsstats: Can't open %s: %s: Not authorized: template not permitted via hosts.conf
\n", tfn, strerror(errno)); + + printf("Error: can't open template file (%s): Not authorized
\n", tfn); + + upsdebug_call_finished1(": template not permitted via hosts.conf"); + exit(EXIT_FAILURE); + } + snprintf(fn, sizeof(fn), "%s/%s", confpath(), tfn); tf = fopen(fn, "rb"); if (!tf) { - fprintf(stderr, "upsstats: Can't open %s: %s\n", fn, strerror(errno)); + fprintf(stderr, "upsstats: Can't open %s: %s
\n", fn, strerror(errno)); - printf("Error: can't open template file (%s)\n", tfn); + printf("Error: can't open template file (%s)
\n", tfn); upsdebug_call_finished1(": no template"); exit(EXIT_FAILURE); } if (!fgets(buf, sizeof(buf), tf)) { - fprintf(stderr, "upsstats: template file %s seems to be empty (fgets failed): %s\n", fn, strerror(errno)); + fprintf(stderr, "upsstats: template file %s seems to be empty (fgets failed): %s
\n", fn, strerror(errno)); - printf("Error: template file %s seems to be empty\n", tfn); + printf("Error: template file %s seems to be empty
\n", tfn); upsdebug_call_finished1(": empty template"); exit(EXIT_FAILURE); @@ -1119,9 +1229,9 @@ static void display_template(const char *tfn) if (!strncmp(buf, "@NUT_UPSSTATS_TEMPLATE", 22)) { parse_line(buf); } else { - fprintf(stderr, "upsstats: template file %s does not start with NUT_UPSSTATS_TEMPLATE command\n", fn); + fprintf(stderr, "upsstats: template file %s does not start with NUT_UPSSTATS_TEMPLATE command
\n", fn); - printf("Error: template file %s does not start with NUT_UPSSTATS_TEMPLATE command\n", tfn); + printf("Error: template file %s does not start with NUT_UPSSTATS_TEMPLATE command
\n", tfn); upsdebug_call_finished1(": not a valid template"); exit(EXIT_FAILURE); @@ -1231,13 +1341,69 @@ static void add_ups(char *sys, char *desc) ulhead = tmp; } +static void add_allowed_template_list(char *tfn) +{ + ulist_t *tmp, *last; + + if (!tfn || !*tfn) + return; + + tmp = last = allowed_template_list_lhead; + + while (tmp) { + if (!strcmp(tmp->sys, tfn)) + return; + last = tmp; + tmp = (ulist_t *)tmp->next; + } + + tmp = (ulist_t *)xmalloc(sizeof(ulist_t)); + + tmp->sys = xstrdup(tfn); + tmp->desc = NULL; + tmp->next = NULL; + + if (last) + last->next = tmp; + else + allowed_template_list_lhead = tmp; +} + +static void add_allowed_template_single(char *tfn) +{ + ulist_t *tmp, *last; + + if (!tfn || !*tfn) + return; + + tmp = last = allowed_template_single_lhead; + + while (tmp) { + if (!strcmp(tmp->sys, tfn)) + return; + last = tmp; + tmp = (ulist_t *)tmp->next; + } + + tmp = (ulist_t *)xmalloc(sizeof(ulist_t)); + + tmp->sys = xstrdup(tfn); + tmp->desc = NULL; + tmp->next = NULL; + + if (last) + last->next = tmp; + else + allowed_template_single_lhead = tmp; +} + /* called for fatal errors in parseconf like malloc failures */ static void upsstats_hosts_err(const char *errmsg) { upslogx(LOG_ERR, "Fatal error in parseconf(hosts.conf): %s", errmsg); } -static void load_hosts_conf(void) +static void load_hosts_conf(int handle_MONITOR) { char fn[NUT_PATH_MAX + 1]; PCONF_CTX_t ctx; @@ -1275,18 +1441,29 @@ static void load_hosts_conf(void) continue; } + if (ctx.numargs < 2) + continue; + + /* CUSTOM_TEMPLATE_LIST */ + if (!strcmp(ctx.arglist[0], "CUSTOM_TEMPLATE_LIST")) + add_allowed_template_list(ctx.arglist[1]); + + /* CUSTOM_TEMPLATE_SINGLE */ + if (!strcmp(ctx.arglist[0], "CUSTOM_TEMPLATE_SINGLE")) + add_allowed_template_single(ctx.arglist[1]); + if (ctx.numargs < 3) continue; /* MONITOR */ - if (!strcmp(ctx.arglist[0], "MONITOR")) + if (handle_MONITOR && !strcmp(ctx.arglist[0], "MONITOR")) add_ups(ctx.arglist[1], ctx.arglist[2]); } pconf_finish(&ctx); - if (!ulhead) { + if (!ulhead && handle_MONITOR) { /* Don't print HTML here if we are in JSON mode. * The JSON function will handle the error. */ @@ -1326,7 +1503,7 @@ static void display_single(void) if (treemode) display_tree(1); else - display_template("upsstats-single.html"); + display_template(template_single, 1); upscli_disconnect(&ups); upsdebug_call_finished0(); @@ -1368,7 +1545,7 @@ static void display_json(void) add_ups(monhost, monhostdesc); currups = ulhead; } else { - load_hosts_conf(); /* This populates ulhead */ + load_hosts_conf(1); /* This populates ulhead */ currups = ulhead; } @@ -1489,14 +1666,14 @@ int main(int argc, char **argv) int i; #ifdef WIN32 - /* Required ritual before calling any socket functions */ - static WSADATA WSAdata; - static int WSA_Started = 0; - if (!WSA_Started) { - WSAStartup(2, &WSAdata); - atexit((void(*)(void))WSACleanup); - WSA_Started = 1; - } + /* Required ritual before calling any socket functions */ + static WSADATA WSAdata; + static int WSA_Started = 0; + if (!WSA_Started) { + WSAStartup(2, &WSAdata); + atexit((void(*)(void))WSACleanup); + WSA_Started = 1; + } /* Avoid binary output conversions, e.g. * mangling what looks like CRLF on WIN32 */ @@ -1516,6 +1693,28 @@ int main(int argc, char **argv) nut_debug_level = i; } + +#ifdef NUT_CGI_DEBUG_UPSSTATS +# if (NUT_CGI_DEBUG_UPSSTATS - 0 < 1) +# undef NUT_CGI_DEBUG_UPSSTATS +# define NUT_CGI_DEBUG_UPSSTATS 6 +# endif + /* Un-comment via make flags when developer-troubleshooting: */ + nut_debug_level = NUT_CGI_DEBUG_UPSSTATS; +#endif + + if (nut_debug_level > 0) { + cgilogbit_set(); + printf("Content-type: text/html\n"); + printf("Pragma: no-cache\n"); + printf("\n"); + printf("

NUT CGI Debugging enabled, level: %d

\n\n", nut_debug_level); + } + + /* Built-in defaults */ + template_single = xstrdup("upsstats-single.html"); + template_list = xstrdup("upsstats.html"); + extractcgiargs(); upscli_init_default_connect_timeout(NULL, NULL, UPSCLI_DEFAULT_CONNECT_TIMEOUT); @@ -1541,6 +1740,8 @@ int main(int argc, char **argv) } free(upsname); free(hostname); + free(template_single); + free(template_list); exit(EXIT_SUCCESS); } @@ -1551,14 +1752,18 @@ int main(int argc, char **argv) printf("Pragma: no-cache\n"); printf("\n"); - /* if a host is specified, use upsstats-single.html instead */ + /* if a host is specified, use upsstats-single.html instead + * of listing whatever we know about with upsstats.html */ + add_allowed_template_single("upsstats-single.html"); + add_allowed_template_list("upsstats.html"); if (monhost) { + load_hosts_conf(0); display_single(); } else { /* default: multimon replacement mode */ - load_hosts_conf(); + load_hosts_conf(1); currups = ulhead; - display_template("upsstats.html"); + display_template(template_list, 2); } /* Clean up memory */ @@ -1573,6 +1778,25 @@ int main(int argc, char **argv) free(ulhead); ulhead = currups; } + free(template_single); + free(template_list); + + /* Free storage of allowed template names (reusing same kind of structure as UPSes) */ + while (allowed_template_single_lhead) { + currups = (ulist_t *)allowed_template_single_lhead->next; + free(allowed_template_single_lhead->sys); + free(allowed_template_single_lhead->desc); + free(allowed_template_single_lhead); + allowed_template_single_lhead = currups; + } + + while (allowed_template_list_lhead) { + currups = (ulist_t *)allowed_template_list_lhead->next; + free(allowed_template_list_lhead->sys); + free(allowed_template_list_lhead->desc); + free(allowed_template_list_lhead); + allowed_template_list_lhead = currups; + } return 0; } diff --git a/common/common.c b/common/common.c index 50803b25f4..832871ae77 100644 --- a/common/common.c +++ b/common/common.c @@ -588,6 +588,16 @@ int syslog_is_disabled(void) return value; } +/* enable writing upslog_with_errno() and upslogx() type messages to + * the stdout instead of stderr, and end them with HTML
tag, + * to help troubleshoot NUT CGI programs specifically */ +void cgilogbit_set(void) +{ + xbit_set(&upslog_flags, UPSLOG_STDOUT); + xbit_set(&upslog_flags, UPSLOG_CGI_BR); + xbit_clear(&upslog_flags, UPSLOG_STDERR); +} + /* enable writing upslog_with_errno() and upslogx() type messages to the syslog */ void syslogbit_set(void) @@ -3927,7 +3937,7 @@ static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) upslog_start = now; } - if (xbit_test(upslog_flags, UPSLOG_STDERR)) { + if (xbit_test(upslog_flags, UPSLOG_STDERR) || xbit_test(upslog_flags, UPSLOG_STDOUT)) { if (nut_debug_level > 0) { struct timeval now; @@ -3940,15 +3950,42 @@ static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) /* Print all in one shot, to better avoid * mixed lines in parallel threads */ - fprintf(stderr, "%4.0f.%06ld\t%s\n", - difftime(now.tv_sec, upslog_start.tv_sec), - (long)(now.tv_usec - upslog_start.tv_usec), - buf); + if (xbit_test(upslog_flags, UPSLOG_STDERR)) { +#ifdef WIN32 + fflush(stderr); +#endif /* WIN32 */ + fprintf(stderr, "%s%4.0f.%06ld\t%s%s\n", + xbit_test(upslog_flags, UPSLOG_CGI_BR) ? "
" : "",
+					difftime(now.tv_sec, upslog_start.tv_sec),
+					(long)(now.tv_usec - upslog_start.tv_usec),
+					buf,
+					xbit_test(upslog_flags, UPSLOG_CGI_BR) ? "
" : "" + ); + } + + if (xbit_test(upslog_flags, UPSLOG_STDOUT)) { +#ifdef WIN32 + fflush(stdout); +#endif /* WIN32 */ + fprintf(stdout, "%s%4.0f.%06ld\t%s%s\n", + xbit_test(upslog_flags, UPSLOG_CGI_BR) ? "
" : "",
+					difftime(now.tv_sec, upslog_start.tv_sec),
+					(long)(now.tv_usec - upslog_start.tv_usec),
+					buf,
+					xbit_test(upslog_flags, UPSLOG_CGI_BR) ? "
" : "" + ); + } } else { - fprintf(stderr, "%s\n", buf); + if (xbit_test(upslog_flags, UPSLOG_STDERR)) + fprintf(stderr, "%s\n", buf); + if (xbit_test(upslog_flags, UPSLOG_STDOUT)) + fprintf(stdout, "%s\n", buf); } #ifdef WIN32 - fflush(stderr); + if (xbit_test(upslog_flags, UPSLOG_STDERR)) + fflush(stderr); + if (xbit_test(upslog_flags, UPSLOG_STDOUT)) + fflush(stdout); #endif /* WIN32 */ } if (xbit_test(upslog_flags, UPSLOG_SYSLOG)) diff --git a/conf/hosts.conf.sample b/conf/hosts.conf.sample index 37e561e78b..ef9fb8b525 100644 --- a/conf/hosts.conf.sample +++ b/conf/hosts.conf.sample @@ -9,8 +9,21 @@ # ----------------------------------------------------------------------- # # upsstats will use the list of MONITOR entries when displaying the -# default template (upsstats.html). The "FOREACHUPS" directive in the -# template will use this file to find systems running upsd. +# list template (default upsstats.html). The "FOREACHUPS" directive +# in the template will use this file to find systems running upsd. +# +# upsstats allows to use custom HTML template files for some of its +# outputs (JSON and "treemode" mark-ups are currently hard-coded in +# the binary), which you can specify in the query string part of the +# URI (in your `index.html` and/or added cells of `header.html`) as e.g. +# .../cgi-bin/upsstats.cgi?template_single=upsstats-custom-single.html&template_list=upsstats-custom-list.html +# Specific file names must be permitted below with `CUSTOM_TEMPLATE_LIST` +# or `CUSTOM_TEMPLATE_SINGLE` directive, as applicable; they must contain +# the `.htm` substring, and be located directly in the NUT configuration +# directory (same as default templates). If custom templates are used +# in the original request URI, they will be automatically suffixed to +# generated links (primarily HOSTLINK, but also just in case added to +# TREELINK and TREELINK_JSON). # # upsstats and upsimage also use this file to determine if a host may be # monitored. This keeps evil people from using your system to annoy @@ -30,3 +43,19 @@ # MONITOR myups@localhost "Local UPS" # MONITOR su2200@10.64.1.1 "Finance department" # MONITOR matrix@shs-server.example.edu "Sierra High School data room #1" + +# ----------------------------------------------------------------------- +# +# Allowed custom template file (adapted copy of upsstats.html) for listing +# your devices, which must be located in the same configuration directory +# and contain `.htm` in the file name. +# +# CUSTOM_TEMPLATE_LIST + +# ----------------------------------------------------------------------- +# +# Allowed custom template file (adapted copy of upsstats-single.html) for +# listing details about a single device, which must be located in the same +# configuration directory and contain `.htm` in the file name. +# +# CUSTOM_TEMPLATE_SINGLE diff --git a/docs/man/hosts.conf.txt b/docs/man/hosts.conf.txt index 940329b871..9f57e657d4 100644 --- a/docs/man/hosts.conf.txt +++ b/docs/man/hosts.conf.txt @@ -40,6 +40,26 @@ The description must be one element, so if it has spaces, then it must be wrapped with quotes as shown above. The default hostname is "localhost". +*CUSTOM_TEMPLATE_LIST* 'filename':: +*CUSTOM_TEMPLATE_SINGLE* 'filename':: + +linkman:upsstats[8] allows to use custom HTML template files for some of +its outputs (JSON and "treemode" mark-ups are currently hard-coded in +the binary), which you can specify in the query string part of the +URI (in your `index.html` and/or added cells of `header.html`) as e.g. +------ +.../cgi-bin/upsstats.cgi?template_single=custom-s.htm&template_list=custom-l.htm +------ ++ +Specific file names for a specific role must be permitted with these +directives (can be repeated). File names must contain the `.htm` sub-string +and must be located directly in the NUT configuration directory (same as +default templates). If custom templates are used in the original request +URI, they will be automatically suffixed to generated links (primarily +`HOSTLINK`, but also just in case added to `TREELINK` and `TREELINK_JSON`, +see linkman:upsstats.html[5] for more details). + + SEE ALSO -------- diff --git a/docs/man/upsstats.cgi.txt b/docs/man/upsstats.cgi.txt index b83fca8c13..b30a37a03e 100644 --- a/docs/man/upsstats.cgi.txt +++ b/docs/man/upsstats.cgi.txt @@ -48,9 +48,24 @@ The web page that is displayed is actually a template containing commands to `upsstats` which are replaced by status information. The default file used for the overview of devices is `upsstats.html`. +An alternate template may be provided by `&template_list=...` CGI +query option; relevant file must be allowed via `CUSTOM_TEMPLATE_LIST` +option in linkman:hosts.conf[5]. When monitoring a single UPS, the file displayed is `upsstats-single.html`. +An alternate template may be provided by `&template_single=...` CGI +query option; relevant file must be allowed via `CUSTOM_TEMPLATE_SINGLE` +option in linkman:hosts.conf[5]. + +Alternate templates must be structured similarly to default ones, +located in the same directory, and contain `.htm` in the file name. +You can specify in the query string part of the URI (for example, +in your local `index.html` and/or added cells of `header.html`) as e.g. + +------ +.../cgi-bin/upsstats.cgi?template_single=custom-s.html&template_list=custom-l.html +------ The format of these files, including the possible commands, is documented in linkman:upsstats.html[5]. @@ -83,7 +98,7 @@ parameter is also provided: In both modes, each UPS object includes: * *host*: The UPS identifier (e.g., "myups@localhost") -* *desc*: The host description from ``hosts.conf`` +* *desc*: The host description from `hosts.conf` * *status_raw*: The raw status string (e.g., "OL") * *status_parsed*: An array of human-readable status strings (e.g., ["Online"]) diff --git a/include/common.h b/include/common.h index 39987916ca..85bb4e8291 100644 --- a/include/common.h +++ b/include/common.h @@ -446,6 +446,11 @@ int sendsignalfnaliases(const char *pidfn, const char * sig, const char **progna * caller should strdup() a copy to retain beyond the lifetime of "file" */ const char *xbasename(const char *file); +/* enable writing upslog_with_errno() and upslogx() type messages to + * the stdout instead of stderr, and end them with HTML
tag, + * to help troubleshoot NUT CGI programs specifically */ +void cgilogbit_set(void); + /* enable writing upslog_with_errno() and upslogx() type messages to the syslog */ void syslogbit_set(void); @@ -755,6 +760,12 @@ extern int optind; #define UPSLOG_STDERR_ON_FATAL 0x0004 #define UPSLOG_SYSLOG_ON_FATAL 0x0008 +/* Special cases, primarily for NUT CGI programs to dump logs + * in a way better usable when troubleshooting with a browser: + */ +#define UPSLOG_STDOUT 0x0010 +#define UPSLOG_CGI_BR 0x0020 + #ifndef HAVE_SETEUID # define seteuid(x) setresuid(-1,x,-1) /* Works for HP-UX 10.20 */ # define setegid(x) setresgid(-1,x,-1) /* Works for HP-UX 10.20 */