1
/* upsstats - cgi program to generate the main ups info page
3
Copyright (C) 1998 Russell Kroll <rkroll@exploits.org>
4
Copyright (C) 2005 Arnaud Quette <http://arnaud.quette.free.fr/contact.html>
6
This program is free software; you can redistribute it and/or modify
7
it under the terms of the GNU General Public License as published by
8
the Free Software Foundation; either version 2 of the License, or
9
(at your option) any later version.
11
This program is distributed in the hope that it will be useful,
12
but WITHOUT ANY WARRANTY; without even the implied warranty of
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
GNU General Public License for more details.
16
You should have received a copy of the GNU General Public License
17
along with this program; if not, write to the Free Software
18
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
#include "upsclient.h"
25
#include "parseconf.h"
28
#include "upsimagearg.h"
30
#define MAX_CGI_STRLEN 128
31
#define MAX_PARSE_ARGS 16
33
static char *monhost = NULL;
34
static int use_celsius = 1, refreshdelay = -1, treemode = 0;
36
/* from cgilib's checkhost() */
37
static char *monhostdesc = NULL;
40
static char *upsname, *hostname;
41
static char *upsimgpath="upsimage.cgi", *upsstatpath="upsstats.cgi";
45
static long forofs = 0;
47
static ulist_t *ulhead = NULL, *currups = NULL;
49
static int skip_clause = 0, skip_block = 0;
51
void parsearg(char *var, char *value)
53
/* avoid bogus junk from evil people */
54
if ((strlen(var) > MAX_CGI_STRLEN) || (strlen(value) > MAX_CGI_STRLEN))
57
if (!strcmp(var, "host")) {
59
monhost = xstrdup(value);
63
if (!strcmp(var, "refresh"))
64
refreshdelay = (int) strtol(value, (char **) NULL, 10);
66
if (!strcmp(var, "treemode")) {
67
/* FIXME: Validate that treemode is allowed */
72
static void report_error(void)
74
if (upscli_upserror(&ups) == UPSCLI_ERR_VARNOTSUPP)
75
printf("Not supported\n");
77
printf("[error: %s]\n", upscli_strerror(&ups));
80
/* make sure we're actually connected to upsd */
81
static int check_ups_fd(int do_report)
83
if (upscli_fd(&ups) == -1) {
90
/* also check for insanity in currups */
94
printf("No UPS specified for monitoring\n");
103
static int get_var(const char *var, char *buf, size_t buflen, int verbose)
106
unsigned int numq, numa;
107
const char *query[4];
110
if (!check_ups_fd(1))
115
printf("[No UPS name specified]\n");
126
ret = upscli_get(&ups, numq, query, &numa, &answer);
136
printf("[Invalid response]\n");
141
snprintf(buf, buflen, "%s", answer[3]);
145
static void parse_var(const char *var)
147
char answer[SMALLBUF];
149
if (!get_var(var, answer, sizeof(answer), 1))
152
printf("%s", answer);
155
static void do_status(void)
158
char status[SMALLBUF], *ptr, *last = NULL;
160
if (!get_var("ups.status", status, sizeof(status), 1)) {
164
for (ptr = strtok_r(status, " \n", &last); ptr != NULL; ptr = strtok_r(NULL, " \n", &last)) {
166
/* expand from table in status.h */
167
for (i = 0; stattab[i].name != NULL; i++) {
169
if (!strcasecmp(ptr, stattab[i].name)) {
170
printf("%s<br>", stattab[i].desc);
176
static void do_runtime(void)
178
int total, hours, minutes, seconds;
179
char runtime[SMALLBUF];
181
if (!get_var("battery.runtime", runtime, sizeof(runtime), 1))
184
total = (int) strtol(runtime, (char **) NULL, 10);
186
hours = total / 3600;
187
minutes = (total - (hours * 3600)) / 60;
188
seconds = total % 60;
190
printf("%02d:%02d:%02d", hours, minutes, seconds);
194
static int do_date(const char *buf)
196
char datebuf[SMALLBUF];
200
if (strftime(datebuf, sizeof(datebuf), buf, localtime(&tod))) {
201
printf("%s", datebuf);
208
static int get_img_val(const char *var, const char *desc, const char *imgargs)
210
char answer[SMALLBUF];
212
if (!get_var(var, answer, sizeof(answer), 1))
215
printf("<IMG SRC=\"%s?host=%s&display=%s",
216
upsimgpath, currups->sys, var);
218
if ((imgargs) && (strlen(imgargs) > 0))
219
printf("&%s", imgargs);
221
printf("\" ALT=\"%s: %s\">", desc, answer);
226
/* see if <arg> is valid - table from upsimagearg.h */
227
static void check_imgarg(char *arg, char *out, size_t outlen)
232
ep = strchr(arg, '=');
239
/* if it's allowed, append it so it can become part of the URL */
240
for (i = 0; imgarg[i].name != NULL; i++) {
241
if (!strcmp(imgarg[i].name, arg)) {
243
if (strlen(out) == 0)
244
snprintf(out, outlen, "%s=%s", arg, ep);
246
snprintfcat(out, outlen, "&%s=%s", arg, ep);
252
/* split out the var=val commands from the IMG line */
253
static void split_imgarg(char *in, char *out, size_t outlen)
262
sp = strchr(ptr, ' ');
264
/* split by spaces, then check each one (can't use parseconf...) */
267
check_imgarg(ptr, out, outlen);
270
sp = strchr(ptr, ' ');
273
check_imgarg(ptr, out, outlen);
276
/* IMG <type> [<var>=<val] [<var>=<val>] ... */
277
static int do_img(char *buf)
279
char *type, *ptr, imgargs[SMALLBUF];
281
memset(imgargs, '\0', sizeof(imgargs));
285
ptr = strchr(buf, ' ');
289
split_imgarg(ptr, imgargs, sizeof(imgargs));
292
/* only allow known types through */
294
if (!strcmp(type, "input.voltage")
295
|| !strcmp(type, "input.L1-N.voltage")
296
|| !strcmp(type, "input.L2-N.voltage")
297
|| !strcmp(type, "input.L3-N.voltage")
298
|| !strcmp(type, "input.L1-L2.voltage")
299
|| !strcmp(type, "input.L2-L3.voltage")
300
|| !strcmp(type, "input.L3-L1.voltage")) {
301
return get_img_val(type, "Input voltage", imgargs);
304
if (!strcmp(type, "battery.voltage"))
305
return get_img_val(type, "Battery voltage", imgargs);
307
if (!strcmp(type, "battery.charge"))
308
return get_img_val(type, "Battery charge", imgargs);
310
if (!strcmp(type, "output.voltage")
311
|| !strcmp(type, "output.L1-N.voltage")
312
|| !strcmp(type, "output.L2-N.voltage")
313
|| !strcmp(type, "output.L3-N.voltage")
314
|| !strcmp(type, "output.L1-L2.voltage")
315
|| !strcmp(type, "output.L2-L3.voltage")
316
|| !strcmp(type, "output.L3-L1.voltage")) {
317
return get_img_val(type, "Output voltage", imgargs);
320
if (!strcmp(type, "ups.load")
321
|| !strcmp(type, "output.L1.power.percent")
322
|| !strcmp(type, "output.L2.power.percent")
323
|| !strcmp(type, "output.L3.power.percent")
324
|| !strcmp(type, "output.L1.realpower.percent")
325
|| !strcmp(type, "output.L2.realpower.percent")
326
|| !strcmp(type, "output.L3.realpower.percent")) {
327
return get_img_val(type, "UPS load", imgargs);
330
if (!strcmp(type, "input.frequency"))
331
return get_img_val(type, "Input frequency", imgargs);
333
if (!strcmp(type, "output.frequency"))
334
return get_img_val(type, "Output frequency", imgargs);
336
if (!strcmp(type, "ups.temperature"))
337
return get_img_val(type, "UPS temperature", imgargs);
339
if (!strcmp(type, "ambient.temperature"))
340
return get_img_val(type, "Ambient temperature", imgargs);
342
if (!strcmp(type, "ambient.humidity"))
343
return get_img_val(type, "Ambient humidity", imgargs);
348
static void ups_connect(void)
350
static ulist_t *lastups = NULL;
351
char *newups, *newhost;
354
/* try to minimize reconnects */
357
/* don't reconnect if these are both the same UPS */
358
if (!strcmp(lastups->sys, currups->sys)) {
363
/* see if it's just on the same host */
364
newups = newhost = NULL;
366
if (upscli_splitname(currups->sys, &newups, &newhost,
368
printf("Unusable UPS definition [%s]\n", currups->sys);
369
fprintf(stderr, "Unusable UPS definition [%s]\n",
374
if ((!strcmp(newhost, hostname)) && (port == newport)) {
383
/* not the same upsd, so disconnect */
388
upscli_disconnect(&ups);
393
if (upscli_splitname(currups->sys, &upsname, &hostname, &port) != 0) {
394
printf("Unusable UPS definition [%s]\n", currups->sys);
395
fprintf(stderr, "Unusable UPS definition [%s]\n", currups->sys);
399
if (upscli_connect(&ups, hostname, port, 0) < 0)
400
fprintf(stderr, "UPS [%s]: can't connect to server: %s\n", currups->sys, upscli_strerror(&ups));
405
static void do_hostlink(void)
411
printf("<a href=\"%s?host=%s", upsstatpath, currups->sys);
413
if (refreshdelay > 0) {
414
printf("&refresh=%d", refreshdelay);
417
printf("\">%s</a>", currups->desc);
420
static void do_treelink(void)
426
printf("<a href=\"%s?host=%s&treemode\">All data</a>", upsstatpath, currups->sys);
429
/* see if the UPS supports this variable - skip to the next ENDIF if not */
430
/* if val is not null, value returned by var must be equal to val to match */
431
static void do_ifsupp(const char *var, const char *val)
433
char dummy[SMALLBUF];
435
/* if not connected, act like it's not supported and skip the rest */
436
if (!check_ups_fd(0)) {
441
if (!get_var(var, dummy, sizeof(dummy), 0)) {
450
if(strcmp(dummy, val)) {
456
static int breakargs(char *s, char **aargs)
463
for(p=s; *p && i<(MAX_PARSE_ARGS-1); p++) {
464
if(aargs[i] == NULL) {
474
/* Check how many valid args we got */
475
for(i=0; aargs[i]; i++);
480
static void do_ifeq(const char *s)
483
char *aa[MAX_PARSE_ARGS];
488
nargs = breakargs(var, aa);
490
printf("upsstats: IFEQ: Argument error!\n");
494
do_ifsupp(aa[0], aa[1]);
497
/* IFBETWEEN var1 var2 var3. Skip if var3 not between var1
499
static void do_ifbetween(const char *s)
502
char *aa[MAX_PARSE_ARGS];
510
nargs = breakargs(var, aa);
512
printf("upsstats: IFBETWEEN: Argument error!\n");
516
if (!check_ups_fd(0)) {
520
if (!get_var(aa[0], tmp, sizeof(tmp), 0)) {
523
v1 = strtol(tmp, &isvalid, 10);
528
if (!get_var(aa[1], tmp, sizeof(tmp), 0)) {
531
v2 = strtol(tmp, &isvalid, 10);
536
if (!get_var(aa[2], tmp, sizeof(tmp), 0)) {
539
v3 = strtol(tmp, &isvalid, 10);
544
if(v1 > v3 || v2 < v3) {
550
static void do_upsstatpath(const char *s) {
553
upsstatpath = strdup(s);
557
static void do_upsimgpath(const char *s) {
560
upsimgpath = strdup(s);
564
static void do_temp(const char *var)
566
char tempc[SMALLBUF];
569
if (!get_var(var, tempc, sizeof(tempc), 1))
577
tempf = (strtod(tempc, (char **) NULL) * 1.8) + 32;
578
printf("%.1f", tempf);
581
static void do_degrees(void)
591
/* plug in the right color string (like #FF0000) for the UPS status */
592
static void do_statuscolor(void)
595
char stat[SMALLBUF], *sp, *ptr;
597
if (!check_ups_fd(0)) {
599
/* can't print the warning here - give a red error condition */
604
if (!get_var("ups.status", stat, sizeof(stat), 0)) {
605
/* status not available - give yellow as a warning */
614
ptr = strchr(sp, ' ');
618
/* expand from table in status.h */
619
for (i = 0; stattab[i].name != NULL; i++)
620
if (!strcmp(stattab[i].name, sp))
621
if (stattab[i].severity > severity)
622
severity = stattab[i].severity;
628
case 0: printf("#00FF00"); break; /* green : OK */
629
case 1: printf("#FFFF00"); break; /* yellow : warning */
631
default: printf("#FF0000"); break; /* red : error */
635
static int do_command(char *cmd)
637
/* ending an if block? */
638
if (!strcmp(cmd, "ENDIF")) {
644
/* Skipping a block means skip until ENDIF, so... */
649
/* Toggle state when we run across ELSE */
650
if (!strcmp(cmd, "ELSE")) {
659
/* don't do any commands if skipping a section */
660
if (skip_clause == 1) {
664
if (!strncmp(cmd, "VAR ", 4)) {
669
if (!strcmp(cmd, "HOST")) {
670
printf("%s", currups->sys);
674
if (!strcmp(cmd, "HOSTDESC")) {
675
printf("%s", currups->desc);
679
if (!strcmp(cmd, "RUNTIME")) {
684
if (!strcmp(cmd, "STATUS")) {
689
if (!strcmp(cmd, "STATUSCOLOR")) {
694
if (!strcmp(cmd, "TEMPF")) {
699
if (!strcmp(cmd, "TEMPC")) {
704
if (!strncmp(cmd, "DATE ", 5)) {
705
return do_date(&cmd[5]);
708
if (!strncmp(cmd, "IMG ", 4)) {
709
return do_img(&cmd[4]);
712
if (!strcmp(cmd, "VERSION")) {
713
printf("%s", UPS_VERSION);
717
if (!strcmp(cmd, "REFRESH")) {
718
if (refreshdelay > 0) {
719
printf("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"%d\">", refreshdelay);
724
if (!strcmp(cmd, "FOREACHUPS")) {
732
if (!strcmp(cmd, "ENDFOR")) {
734
/* if not in a for, ignore this */
739
currups = currups->next;
742
fseek(tf, forofs, SEEK_SET);
749
if (!strcmp(cmd, "HOSTLINK")) {
754
if (!strcmp(cmd, "TREELINK")) {
759
if (!strncmp(cmd, "IFSUPP ", 7)) {
760
do_ifsupp(&cmd[7], NULL);
764
if (!strcmp(cmd, "UPSTEMP")) {
765
do_temp("ups.temperature");
769
if (!strcmp(cmd, "BATTTEMP")) {
770
do_temp("battery.temperature");
774
if (!strcmp(cmd, "AMBTEMP")) {
775
do_temp("ambient.temperature");
779
if (!strcmp(cmd, "DEGREES")) {
784
if (!strncmp(cmd, "IFEQ ", 5)) {
789
if (!strncmp(cmd, "IFBETWEEN ", 10)) {
790
do_ifbetween(&cmd[10]);
794
if (!strncmp(cmd, "UPSSTATSPATH ", 13)) {
795
do_upsstatpath(&cmd[13]);
799
if (!strncmp(cmd, "UPSIMAGEPATH ", 13)) {
800
do_upsimgpath(&cmd[13]);
807
static void parse_line(const char *buf)
810
int i, len, do_cmd = 0;
812
for (i = 0; buf[i]; i += len) {
814
len = strcspn(&buf[i], "@");
824
i++; /* skip over the '@' character */
829
snprintf(cmd, sizeof(cmd), "%.*s", len, &buf[i]);
833
if (skip_clause || skip_block) {
839
printf("%.*s", len, &buf[i]);
843
static void display_template(const char *tfn)
845
char fn[SMALLBUF], buf[LARGEBUF];
847
snprintf(fn, sizeof(fn), "%s/%s", confpath(), tfn);
852
fprintf(stderr, "upsstats: Can't open %s: %s\n", fn, strerror(errno));
854
printf("Error: can't open template file (%s)\n", tfn);
859
while (fgets(buf, sizeof(buf), tf)) {
866
static void display_tree(int verbose)
868
unsigned int numq, numa;
869
const char *query[4];
874
printf("[No UPS name specified]\n");
882
if (upscli_list_start(&ups, numq, query) < 0) {
888
printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"\n");
889
printf(" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n");
891
printf("<HEAD><TITLE>upsstat: data tree of %s</TITLE></HEAD>\n", currups->desc);
893
printf("<BODY BGCOLOR=\"#FFFFFF\" TEXT=\"#000000\" LINK=\"#0000EE\" VLINK=\"#551A8B\">\n");
895
printf("<TABLE BGCOLOR=\"#50A0A0\" ALIGN=\"CENTER\">\n");
896
printf("<TR><TD>\n");
898
printf("<TABLE CELLPADDING=\"5\" CELLSPACING=\"0\" ALIGN=\"CENTER\" WIDTH=\"100%%\">\n");
900
/* include the description from checkhost() if present */
901
printf("<TR><TH COLSPAN=3 BGCOLOR=\"#50A0A0\">\n");
902
printf("<FONT SIZE=\"+2\">%s</FONT>\n", currups->desc);
903
printf("</TH></TR>\n");
905
printf("<TR><TH COLSPAN=3 BGCOLOR=\"#60B0B0\"></TH></TR>\n");
907
while (upscli_list_next(&ups, numq, query, &numa, &answer) == 1) {
909
/* VAR <upsname> <varname> <val> */
912
printf("[Invalid response]\n");
917
printf("<TR BGCOLOR=\"#60B0B0\" ALIGN=\"LEFT\">\n");
919
printf("<TD>%s</TD>\n", answer[2]);
920
printf("<TD>:</TD>\n");
921
printf("<TD>%s<br></TD>\n", answer[3]);
926
printf("</TABLE>\n");
927
printf("</TD></TR></TABLE>\n");
929
/* FIXME (AQ): add a save button (?), and a checkbt for showing var.desc */
930
printf("</BODY></HTML>\n");
933
static void add_ups(char *sys, char *desc)
944
tmp = xmalloc(sizeof(ulist_t));
946
tmp->sys = xstrdup(sys);
947
tmp->desc = xstrdup(desc);
956
/* called for fatal errors in parseconf like malloc failures */
957
static void upsstats_hosts_err(const char *errmsg)
959
upslogx(LOG_ERR, "Fatal error in parseconf(hosts.conf): %s", errmsg);
962
static void load_hosts_conf(void)
967
snprintf(fn, sizeof(fn), "%s/hosts.conf", CONFPATH);
969
pconf_init(&ctx, upsstats_hosts_err);
971
if (!pconf_file_begin(&ctx, fn)) {
974
printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"\n");
975
printf(" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n");
976
printf("<HTML><HEAD>\n");
977
printf("<TITLE>Error: can't open hosts.conf</TITLE>\n");
978
printf("</HEAD><BODY>\n");
979
printf("Error: can't open hosts.conf\n");
980
printf("</BODY></HTML>\n");
982
/* leave something for the admin */
983
fprintf(stderr, "upsstats: %s\n", ctx.errmsg);
987
while (pconf_file_next(&ctx)) {
988
if (pconf_parse_error(&ctx)) {
989
upslogx(LOG_ERR, "Parse error: %s:%d: %s",
990
fn, ctx.linenum, ctx.errmsg);
997
/* MONITOR <host> <desc> */
998
if (!strcmp(ctx.arglist[0], "MONITOR"))
999
add_ups(ctx.arglist[1], ctx.arglist[2]);
1006
printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"\n");
1007
printf(" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n");
1008
printf("<HTML><HEAD>\n");
1009
printf("<TITLE>Error: no hosts to monitor</TITLE>\n");
1010
printf("</HEAD><BODY>\n");
1011
printf("Error: no hosts to monitor (check <CODE>hosts.conf</CODE>)\n");
1012
printf("</BODY></HTML>\n");
1014
/* leave something for the admin */
1015
fprintf(stderr, "upsstats: no hosts to monitor\n");
1020
static void display_single(void)
1022
if (!checkhost(monhost, &monhostdesc)) {
1023
printf("Access to that host [%s] is not authorized.\n",
1028
add_ups(monhost, monhostdesc);
1033
/* switch between data tree view and standard single view */
1037
display_template("upsstats-single.html");
1039
upscli_disconnect(&ups);
1042
int main(int argc, char **argv)
1046
printf("Content-type: text/html\n");
1047
printf("Pragma: no-cache\n");
1050
/* if a host is specified, use upsstats-single.html instead */
1056
/* default: multimon replacement mode */
1062
display_template("upsstats.html");
1064
upscli_disconnect(&ups);