/* upsset - CGI program to manage read/write variables Copyright (C) 1999 Russell Kroll 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 the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common.h" #include #include #include #include #include "nut_stdint.h" #include "upsclient.h" #include "cgilib.h" #include "parseconf.h" struct list_t { char *name; struct list_t *next; }; /* see the stock upsset.conf for the whole rant on what this is */ #define MAGIC_ENABLE_STRING "I_HAVE_SECURED_MY_CGI_DIRECTORY" #define HARD_UPSVAR_LIMIT_NUM 64 #define HARD_UPSVAR_LIMIT_LEN 256 static char *monups, *username, *password, *function, *upscommand; /* set once the MAGIC_ENABLE_STRING is found in the upsset.conf */ static int magic_string_set = 0; static uint16_t port; static char *upsname, *hostname; static UPSCONN_t ups; typedef struct { char *var; char *value; void *next; } uvtype_t; static uvtype_t *firstuv = NULL; void parsearg(char *var, char *value) { char *ptr; uvtype_t *last, *tmp = NULL; static int upsvc = 0; /* store variables from a SET command for the later commit */ if (!strncmp(var, "UPSVAR_", 7)) { /* if someone bombs us with variables, stop at some point */ if (upsvc > HARD_UPSVAR_LIMIT_NUM) return; /* same idea: throw out anything that's much too long */ if (strlen(value) > HARD_UPSVAR_LIMIT_LEN) return; ptr = strchr(var, '_'); if (!ptr) /* sanity check */ return; ptr++; tmp = last = firstuv; while (tmp) { last = tmp; tmp = tmp->next; } tmp = xmalloc(sizeof(uvtype_t)); tmp->var = xstrdup(ptr); tmp->value = xstrdup(value); tmp->next = NULL; if (last) last->next = tmp; else firstuv = tmp; upsvc++; return; } if (!strcmp(var, "username")) { free(username); username = xstrdup(value); } if (!strcmp(var, "password")) { free(password); password = xstrdup(value); } if (!strcmp(var, "function")) { free(function); function = xstrdup(value); } if (!strcmp(var, "monups")) { free(monups); monups = xstrdup(value); } if (!strcmp(var, "upscommand")) { free(upscommand); upscommand = xstrdup(value); } } static void do_header(const char *title) { printf("\n"); printf("\n"); printf("upsset: %s\n", title); printf("\n"); printf("\n"); printf("
\n"); } static void start_table(void) { printf("\n"); printf("\n"); } /* propagate login details across pages - no cookies here! */ static void do_hidden(const char *next) { printf("\n", username); printf("\n", password); if (next) printf("\n", next); } /* generate SELECT chooser from hosts.conf entries */ static void upslist_arg(size_t numargs, char **arg) { if (numargs < 3) return; /* MONITOR */ if (!strcmp(arg[0], "MONITOR")) { printf("\n", arg[2]); } } /* called for fatal errors in parseconf like malloc failures */ static void upsset_hosts_err(const char *errmsg) { upslogx(LOG_ERR, "Fatal error in parseconf(hosts.conf): %s", errmsg); } /* this defaults to wherever we are now, ups and function-wise */ static void do_pickups(const char *currfunc) { char hostfn[SMALLBUF]; PCONF_CTX_t ctx; snprintf(hostfn, sizeof(hostfn), "%s/hosts.conf", confpath()); printf("
\n"); printf("Select UPS and function:\n
\n"); pconf_init(&ctx, upsset_hosts_err); if (!pconf_file_begin(&ctx, hostfn)) { pconf_finish(&ctx); printf("Error: hosts.conf unavailable\n"); printf("\n"); /* stderr is for the admin - should wind up in error.log */ fprintf(stderr, "upsset: %s\n", ctx.errmsg); return; } printf("\n"); printf("\n"); do_hidden(NULL); printf("\n"); printf("\n"); } static void error_page(const char *next, const char *title, const char *fmt, ...) __attribute__((noreturn)); static void error_page(const char *next, const char *title, const char *fmt, ...) { char msg[SMALLBUF]; va_list ap; va_start(ap, fmt); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif vsnprintf(msg, sizeof(msg), fmt, ap); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif va_end(ap); do_header(title); start_table(); printf("
\n"); printf("\n"); printf("
\n"); printf("Network UPS Tools upsset %s\n", UPS_VERSION); printf("
\n"); printf("Error: %s\n", msg); printf("
\n"); do_pickups(next); printf("
\n"); printf("
\n"); printf("\n"); upscli_disconnect(&ups); exit(EXIT_SUCCESS); } static void loginscreen(void) __attribute__((noreturn)); static void loginscreen(void) { do_header("Login"); printf("
\n"); start_table(); printf("\n"); printf("Username\n"); printf("\n"); printf("\n"); printf("\n"); printf("Password\n"); printf("\n"); printf("\n"); printf("\n"); printf("\n"); printf("\n"); printf("\n"); printf("\n"); printf("
\n"); printf("\n"); printf("\n"); upscli_disconnect(&ups); exit(EXIT_SUCCESS); } /* try to connect to upsd - generate an error page if it fails */ static void upsd_connect(void) { if (upscli_splitname(monups, &upsname, &hostname, &port) != 0) { error_page("showsettings", "UPS name is unusable", "Unable to split UPS name [%s]", monups); /* NOTREACHED */ } if (upscli_connect(&ups, hostname, port, 0) < 0) { error_page("showsettings", "Connect failure", "Unable to connect to %s: %s", monups, upscli_strerror(&ups)); /* NOTREACHED */ } } static void print_cmd(const char *cmd) { int ret; size_t numq, numa; char **answer; const char *query[4]; query[0] = "CMDDESC"; query[1] = upsname; query[2] = cmd; numq = 3; ret = upscli_get(&ups, numq, query, &numa, &answer); if ((ret < 0) || (numa < numq)) return; /* CMDDESC */ printf("\n", cmd, answer[3]); } /* generate a list of instant commands */ static void showcmds(void) { int ret; size_t numq, numa; const char *query[2]; char **answer; struct list_t *lhead, *llast, *ltmp, *lnext; char *desc; if (!checkhost(monups, &desc)) error_page("showsettings", "Access denied", "Access to that host is not authorized"); upsd_connect(); llast = lhead = NULL; query[0] = "CMD"; query[1] = upsname; numq = 2; ret = upscli_list_start(&ups, numq, query); if (ret < 0) { fprintf(stderr, "LIST CMD %s failed: %s\n", upsname, upscli_strerror(&ups)); error_page("showcmds", "Server protocol error", "LIST CMD command failed"); /* NOTREACHED */ } ret = upscli_list_next(&ups, numq, query, &numa, &answer); while (ret == 1) { /* CMD upsname cmdname */ if (numa < 3) { fprintf(stderr, "Error: insufficient data " "(got %zu args, need at least 3)\n", numa); return; } ltmp = xmalloc(sizeof(struct list_t)); ltmp->name = xstrdup(answer[2]); ltmp->next = NULL; if (llast) llast->next = ltmp; else lhead = ltmp; llast = ltmp; ret = upscli_list_next(&ups, numq, query, &numa, &answer); } if (!lhead) error_page("showcmds", "No instant commands supported", "This UPS doesn't support any instant commands."); do_header("Instant commands"); printf("
\n"); start_table(); /* include the description from checkhost() if present */ if (desc) printf("%s\n", desc); printf("\n"); printf("Instant commands\n"); printf("\n"); printf("\n"); printf("\n"); printf("\n"); printf("\n"); do_hidden("docmd"); printf("\n", monups); printf("\n"); printf("\n"); printf("\n"); printf("\n"); printf("
\n"); printf("\n"); do_pickups("showcmds"); printf("\n"); printf("\n"); printf("\n"); upscli_disconnect(&ups); exit(EXIT_SUCCESS); } /* handle setting authentication data in the server */ static void send_auth(const char *next) { char buf[SMALLBUF]; snprintf(buf, sizeof(buf), "USERNAME %s\n", username); if (upscli_sendline(&ups, buf, strlen(buf)) < 0) { fprintf(stderr, "Can't set username: %s\n", upscli_strerror(&ups)); error_page(next, "Can't set username", "Set username failed: %s", upscli_strerror(&ups)); } if (upscli_readline(&ups, buf, sizeof(buf)) < 0) { /* test for old upsd that doesn't do USERNAME */ if (upscli_upserror(&ups) == UPSCLI_ERR_UNKCOMMAND) { error_page(next, "Protocol mismatch", "upsd version too old - USERNAME not supported"); } error_page(next, "Can't set user name", "Set user name failed: %s", upscli_strerror(&ups)); } snprintf(buf, sizeof(buf), "PASSWORD %s\n", password); if (upscli_sendline(&ups, buf, strlen(buf)) < 0) error_page(next, "Can't set password", "Password set failed: %s", upscli_strerror(&ups)); if (upscli_readline(&ups, buf, sizeof(buf)) < 0) error_page(next, "Can't set password", "Password set failed: %s", upscli_strerror(&ups)); } static void docmd(void) __attribute__((noreturn)); static void docmd(void) { char buf[SMALLBUF], *desc; if (!checkhost(monups, &desc)) error_page("showsettings", "Access denied", "Access to that host is not authorized"); /* the user is messing with us */ if (!upscommand) error_page("showcmds", "Form error", "No instant command selected"); /* (l)user took the default blank option */ if (strlen(upscommand) == 0) error_page("showcmds", "Form error", "No instant command selected"); upsd_connect(); send_auth("showcmds"); snprintf(buf, sizeof(buf), "INSTCMD %s %s\n", upsname, upscommand); if (upscli_sendline(&ups, buf, strlen(buf)) < 0) { do_header("Error while issuing command"); start_table(); printf("Error sending command: %s\n", upscli_strerror(&ups)); printf("\n"); do_pickups("showcmds"); printf("\n"); printf("\n"); printf("\n"); printf("\n"); upscli_disconnect(&ups); exit(EXIT_SUCCESS); } if (upscli_readline(&ups, buf, sizeof(buf)) < 0) { do_header("Error while reading command response"); start_table(); printf("Error reading command response: %s\n", upscli_strerror(&ups)); printf("\n"); do_pickups("showcmds"); printf("\n"); printf("\n"); printf("\n"); printf("\n"); upscli_disconnect(&ups); exit(EXIT_SUCCESS); } do_header("Issuing command"); start_table(); printf("
\n");
	printf("Sending command: %s\n", upscommand);
	printf("Response: %s\n", buf);
	printf("
\n"); printf("\n"); do_pickups("showcmds"); printf("\n"); printf("\n"); printf("\n"); printf("\n"); upscli_disconnect(&ups); exit(EXIT_SUCCESS); } static const char *get_data(const char *type, const char *varname) { int ret; size_t numq, numa; char **answer; const char *query[4]; query[0] = type; query[1] = upsname; query[2] = varname; numq = 3; ret = upscli_get(&ups, numq, query, &numa, &answer); if ((ret < 0) || (numa < numq)) return NULL; /* */ return answer[3]; } static void do_string(const char *varname, int maxlen) { const char *val; val = get_data("VAR", varname); if (!val) { printf("Unavailable\n"); fprintf(stderr, "do_string: can't get current value of %s\n", varname); return; } printf("\n", varname, val, maxlen); } static void do_enum(const char *varname) { int ret; size_t numq, numa; char **answer, *val; const char *query[4], *tmp; /* get current value */ tmp = get_data("VAR", varname); if (!tmp) { printf("Unavailable\n"); fprintf(stderr, "do_enum: can't get current value of %s\n", varname); return; } /* tmp is a pointer into answer - have to save it somewhere else */ val = xstrdup(tmp); query[0] = "ENUM"; query[1] = upsname; query[2] = varname; numq = 3; ret = upscli_list_start(&ups, numq, query); if (ret < 0) { printf("Unavailable\n"); fprintf(stderr, "Error doing ENUM %s %s: %s\n", upsname, varname, upscli_strerror(&ups)); free(val); return; } ret = upscli_list_next(&ups, numq, query, &numa, &answer); printf("\n"); } static void do_type(const char *varname) { int ret; size_t i, numq, numa; char **answer; const char *query[4]; query[0] = "TYPE"; query[1] = upsname; query[2] = varname; numq = 3; ret = upscli_get(&ups, numq, query, &numa, &answer); if ((ret < 0) || (numa < numq)) { printf("Unknown type\n"); return; } /* TYPE ... */ for (i = 3; i < numa; i++) { if (!strcasecmp(answer[i], "ENUM")) { do_enum(varname); return; } if (!strncasecmp(answer[i], "STRING:", 7)) { char *ptr, len; /* split out the : data */ ptr = strchr(answer[i], ':'); *ptr++ = '\0'; long l = strtol(ptr, (char **) NULL, 10); assert(l <= 127); /* FIXME: Loophole about longer numbers? Why are we limited to char at all here? */ len = (char)l; do_string(varname, len); return; } /* ignore this one */ if (!strcasecmp(answer[i], "RW")) continue; printf("Unrecognized\n"); } } static void print_rw(const char *arg_upsname, const char *varname) { const char *tmp; printf("\n", arg_upsname); printf("\n"); printf(""); tmp = get_data("DESC", varname); if ((tmp) && (strcmp(tmp, "Unavailable") != 0)) printf("%s", tmp); else printf("%s", varname); printf("\n"); printf("\n"); do_type(varname); printf("\n"); printf("\n"); } static void showsettings(void) __attribute__((noreturn)); static void showsettings(void) { int ret; size_t numq, numa; const char *query[2]; char **answer, *desc = NULL; struct list_t *lhead, *llast, *ltmp, *lnext; if (!checkhost(monups, &desc)) error_page("showsettings", "Access denied", "Access to that host is not authorized"); upsd_connect(); query[0] = "RW"; query[1] = upsname; numq = 2; ret = upscli_list_start(&ups, numq, query); if (ret < 0) { fprintf(stderr, "LIST RW %s failed: %s\n", upsname, upscli_strerror(&ups)); error_page("showsettings", "Server protocol error", "LIST RW command failed"); /* NOTREACHED */ } llast = lhead = NULL; ret = upscli_list_next(&ups, numq, query, &numa, &answer); while (ret == 1) { /* sock this entry away for later */ ltmp = xmalloc(sizeof(struct list_t)); ltmp->name = xstrdup(answer[2]); ltmp->next = NULL; if (llast) llast->next = ltmp; else lhead = ltmp; llast = ltmp; ret = upscli_list_next(&ups, numq, query, &numa, &answer); } do_header("Current settings"); printf("
\n"); start_table(); /* include the description from checkhost() if present */ if (desc) printf("%s\n", desc); printf("\n"); printf("Setting\n"); printf("Value\n"); /* use the list to get descriptions and types */ ltmp = lhead; while (ltmp) { lnext = ltmp->next; print_rw(upsname, ltmp->name); free(ltmp->name); free(ltmp); ltmp = lnext; } printf("\n"); printf("\n"); do_hidden("savesettings"); printf("\n", monups); printf("\n"); printf("\n"); printf("\n"); printf("\n"); printf("
\n"); printf("\n"); do_pickups("showsettings"); printf("\n"); printf("\n"); printf("\n"); upscli_disconnect(&ups); exit(EXIT_SUCCESS); } static int setvar(const char *var, const char *val) { char buf[SMALLBUF], enc[SMALLBUF]; const char *tmp; /* get old value */ tmp = get_data("VAR", var); if (!tmp) { printf("Can't get old value for %s, aborting SET\n", var); return 0; } /* don't send a SET if it hasn't chnaged */ if (!strcmp(tmp, val)) return 0; printf("set %s to %s (was %s)\n", var, val, tmp); snprintf(buf, sizeof(buf), "SET VAR %s %s \"%s\"\n", upsname, var, pconf_encode(val, enc, sizeof(enc))); if (upscli_sendline(&ups, buf, strlen(buf)) < 0) { printf("Error: SET failed: %s\n", upscli_strerror(&ups)); return 0; } if (upscli_readline(&ups, buf, sizeof(buf)) < 0) { printf("Error: SET failed: %s\n", upscli_strerror(&ups)); return 0; } if (strncmp(buf, "OK", 2) != 0) { printf("Unexpected response: %s\n", buf); return 0; } printf("OK\n"); return 1; } /* turn a form submission of settings into SET commands for upsd */ static void savesettings(void) __attribute__((noreturn)); static void savesettings(void) { int changed = 0; char *desc; uvtype_t *upsvar; if (!checkhost(monups, &desc)) error_page("showsettings", "Access denied", "Access to that host is not authorized"); upsd_connect(); upsvar = firstuv; send_auth("showsettings"); do_header("Saving settings"); start_table(); printf("
\n");

	while (upsvar) {
		changed += setvar(upsvar->var, upsvar->value);
		upsvar = upsvar->next;
	}

	if (changed == 0)
		printf("No settings changed.\n");
	else
		printf("Updated %d setting%s.\n",
			changed, changed == 1 ? "" : "s");

	printf("
\n"); printf("\n"); do_pickups("showsettings"); printf("\n"); printf("\n"); printf("\n"); printf("\n"); upscli_disconnect(&ups); exit(EXIT_SUCCESS); } static void initial_pickups(void) __attribute__((noreturn)); static void initial_pickups(void) { do_header("Select a UPS"); start_table(); printf("\n"); do_pickups(""); printf("\n"); printf("\n"); printf("\n"); printf("\n"); upscli_disconnect(&ups); exit(EXIT_SUCCESS); } static void upsset_conf_err(const char *errmsg) { upslogx(LOG_ERR, "Fatal error in parseconf(upsset.conf): %s", errmsg); } /* see if the user has confirmed their cgi directory's secure state */ static void check_conf(void) { char fn[SMALLBUF]; PCONF_CTX_t ctx; snprintf(fn, sizeof(fn), "%s/upsset.conf", confpath()); pconf_init(&ctx, upsset_conf_err); if (!pconf_file_begin(&ctx, fn)) { pconf_finish(&ctx); printf("
\n");
		printf("Error: Can't open upsset.conf to verify security settings.\n");
		printf("Refusing to start until this is fixed.\n");
		printf("
\n"); /* leave something in the httpd log for the admin */ fprintf(stderr, "upsset.conf does not exist to permit execution\n"); exit(EXIT_FAILURE); } while (pconf_file_next(&ctx)) { if (pconf_parse_error(&ctx)) { upslogx(LOG_ERR, "Parse error: %s:%d: %s", fn, ctx.linenum, ctx.errmsg); continue; } if (ctx.numargs < 1) continue; if (!strcmp(ctx.arglist[0], MAGIC_ENABLE_STRING)) magic_string_set = 1; } pconf_finish(&ctx); /* if we've been enabled, jump out of here and go to work */ if (magic_string_set == 1) return; printf("
\n");
	printf("Error: Secure mode has not been enabled in upsset.conf.\n");
	printf("Refusing to start until this is fixed.\n");
	printf("
\n"); /* leave something in the httpd log for the admin */ fprintf(stderr, "upsset.conf does not permit execution\n"); exit(EXIT_FAILURE); } int main(int argc, char **argv) { NUT_UNUSED_VARIABLE(argc); NUT_UNUSED_VARIABLE(argv); username = password = function = monups = NULL; printf("Content-type: text/html\n\n"); /* see if the magic string is present in the config file */ check_conf(); /* see if there's anything waiting .. the server my not close STDIN properly */ if (1) { fd_set fds; struct timeval tv; FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); tv.tv_sec = 0; tv.tv_usec = 250000; /* wait for up to 250ms for a POST response */ if ((select(STDIN_FILENO+1, &fds, 0, 0, &tv)) > 0) extractpostargs(); } if ((!username) || (!password) || (!function)) loginscreen(); if ((!strcmp(function, "pickups")) || (!monups)) initial_pickups(); if (!strcmp(function, "showsettings")) showsettings(); if (!strcmp(function, "savesettings")) savesettings(); #if 0 /* FUTURE */ if (!strcmp(function, "showstatus")) showstatus(); #endif if (!strcmp(function, "showcmds")) showcmds(); if (!strcmp(function, "docmd")) docmd(); printf("Error: Unhandled function name [%s]\n", function); return 0; }