Skip to content

Commit 57f62f6

Browse files
authored
Merge branch 'master' into sysusers
2 parents d15a70c + d4b89ab commit 57f62f6

29 files changed

+1057
-257
lines changed

NEWS.adoc

Lines changed: 118 additions & 110 deletions
Large diffs are not rendered by default.

UPGRADING.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ Changes from 2.8.4 to 2.8.5
5050
installed (and possibly customized) with the `*.html.sample` files delivered
5151
by the new build. [PR #3180]
5252
53+
- Introduced a `@NUT_UPSSTATS_TEMPLATE@` command which the NUT CGI template
54+
files now MUST start with (safety check that we are reading a template).
55+
While the delivered `upsstats*.html.sample` files would include the change,
56+
ultimate `upsstats*.html` templates deployed for end-users MUST be updated.
57+
[issue #3252, PR #3249]
58+
5359
- Dropped the `compile` script from Git sources. It originates from automake
5460
and is added to work area (if missing) during `autogen.sh` rituals anyway
5561
(as `make` says, `'automake --add-missing' can install 'compile'` when you

appveyor.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@ build_script:
106106
set MSYSTEM=MINGW64
107107
REM Note: currently we save job time and do not install asciidoc/a2x
108108
REM # --with-docs="man=auto html-single=auto html-chunked=no pdf=no"
109+
REM The resulting configuration defaults to --prefix=/mingw{32,64}
110+
REM as appropriate for the platform, which ends up as a subdirectory
111+
REM under DESTDIR when we `make install` later, and in our 7z archive.
112+
REM The builds are relocatable however, so end-users can extract to
113+
REM e.g. "C:\Program Files\NUT" and forfeit "mingwXX" part, probably,
114+
REM but this can complicate automatic relative directory resolution
115+
REM to find "nearby" program or configuration files (see common.c
116+
REM for current implementation). Hard-coded fallback strings may
117+
REM end up getting used in those cases.
109118
C:\msys64\usr\bin\bash -lc 'date -u; PATH="/mingw64/bin:$PATH" CI_SKIP_CHECK=true CANBUILD_WITH_LIBMODBUS_USB=yes ./ci_build.sh --with-docs=no'
110119
111120

clients/cgilib.c

Lines changed: 97 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -116,42 +116,120 @@ void extractcgiargs(void)
116116

117117
void extractpostargs(void)
118118
{
119-
char buf[SMALLBUF], *ptr, *cleanval;
120-
int ch;
119+
char buf[SMALLBUF], *ptr, *cleanval, *server_software = NULL;
120+
int ch, content_length = -1, bytes_seen = 0;
121+
size_t buflen;
122+
123+
/* First, see if there's anything waiting...
124+
* the server may not close STDIN properly
125+
* or somehow delay opening/populating it. */
126+
#ifndef WIN32
127+
int selret;
128+
fd_set fds;
129+
struct timeval tv;
130+
131+
FD_ZERO(&fds);
132+
FD_SET(STDIN_FILENO, &fds);
133+
tv.tv_sec = 0;
134+
tv.tv_usec = 250000; /* wait for up to 250ms for a POST query to come */
135+
136+
selret = select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
137+
if (selret <= 0) {
138+
#else
139+
HANDLE hSTDIN = GetStdHandle(STD_INPUT_HANDLE);
140+
DWORD selret = WaitForSingleObject(hSTDIN, 250);
141+
if (selret != WAIT_OBJECT_0) { /* or == WAIT_TIMEOUT ? */
142+
#endif /* WIN32 */
143+
upsdebug_with_errno(1, "%s: no stdin is waiting (%" PRIiMAX ")<br/>", __func__, (intmax_t)selret);
144+
return;
145+
}
121146

122-
ch = fgetc(stdin);
123147
buf[0] = '\0';
124148

125-
while (ch != EOF) {
126-
if (ch == '&') {
149+
/* Does the web server tell us how much it sent
150+
* (and might keep the channel open... indefinitely)? */
151+
ptr = getenv("CONTENT_LENGTH");
152+
if (ptr) {
153+
content_length = atoi(ptr);
154+
}
155+
156+
ptr = getenv("SERVER_SOFTWARE");
157+
if (ptr) {
158+
server_software = ptr;
159+
} else {
160+
server_software = "";
161+
}
162+
163+
if (content_length > 0 && strstr(server_software, "IIS")) {
164+
/* Our POSTs end with a newline, and that one never arrives
165+
* (reads hang), possibly buffered output from IIS?
166+
* Our own setmode() in e.g. upsset.c does not help.
167+
* So upsset.c ends each FORM with do_hidden_sentinel()
168+
* to sacrifice a few bytes we would not use.
169+
*/
170+
upsdebugx(3, "%s: truncating expected content length on IIS<br/>", __func__);
171+
content_length--;
172+
}
173+
upsdebugx(3, "%s: starting to read %d POSTed bytes on server '%s'<br/>", __func__, content_length, server_software);
174+
175+
ch = fgetc(stdin);
176+
upsdebugx(6, "%s: got char: '%c' (%d, 0x%02X)<br/>", __func__, ch, ch, (unsigned int)ch);
177+
178+
if (ch == EOF) {
179+
bytes_seen++;
180+
upsdebugx(3, "%s: got immediate EOF in stdin<br/>", __func__);
181+
} else while(1) {
182+
bytes_seen++;
183+
if (ch == '&' || ch == EOF || (content_length >= 0 && bytes_seen >= content_length)) {
184+
buflen = strlen(buf);
185+
upsdebugx(1, "%s: collected a chunk of %" PRIuSIZE " bytes on stdin: %s<br/>",
186+
__func__, buflen, buf);
127187
ptr = strchr(buf, '=');
128-
if (!ptr)
188+
if (!ptr) {
189+
upsdebugx(3, "%s: parsearg('%s', '')<br/>", __func__, buf);
129190
parsearg(buf, "");
130-
else {
191+
} else {
131192
*ptr++ = '\0';
132193
cleanval = unescape(ptr);
194+
upsdebugx(3, "%s: parsearg('%s', '%s')<br/>", __func__, buf, cleanval);
133195
parsearg(buf, cleanval);
134196
free(cleanval);
135197
}
136198
buf[0] = '\0';
199+
200+
if (ch == EOF || (content_length >= 0 && bytes_seen >= content_length))
201+
break; /* end the loop */
137202
}
138203
else
139204
snprintfcat(buf, sizeof(buf), "%c", ch);
140205

206+
#ifndef WIN32
207+
/* Must re-init every time when looping (array is changed by select method) */
208+
FD_ZERO(&fds);
209+
FD_SET(STDIN_FILENO, &fds);
210+
tv.tv_sec = 0;
211+
tv.tv_usec = 250000; /* wait for up to 250ms for a POST response */
212+
213+
selret = select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
214+
if (selret <= 0) {
215+
#else
216+
selret = WaitForSingleObject(hSTDIN, 250);
217+
if (selret != WAIT_OBJECT_0) { /* or == WAIT_TIMEOUT ? */
218+
#endif
219+
/* We do not always get EOF, so assume the input stream stopped */
220+
upsdebug_with_errno(1, "%s: timed out waiting for an stdin byte (%" PRIiMAX ")<br/>", __func__, (intmax_t)selret);
221+
break;
222+
}
223+
224+
fflush(stderr);
141225
ch = fgetc(stdin);
142-
}
226+
upsdebugx(6, "%s: got char: '%c' (%d, 0x%02X)<br/>", __func__, ch, ch, (unsigned int)ch);
227+
if (ch == EOF)
228+
upsdebugx(3, "%s: got proper stdin EOF<br/>", __func__);
229+
upsdebugx(6, "%s: processed %d bytes with %d expected incoming content length on server '%s'<br/>", __func__, bytes_seen, content_length, server_software);
230+
} /* end of infinite loop */
143231

144-
if (strlen(buf) != 0) {
145-
ptr = strchr(buf, '=');
146-
if (!ptr)
147-
parsearg(buf, "");
148-
else {
149-
*ptr++ = '\0';
150-
cleanval = unescape(ptr);
151-
parsearg(buf, cleanval);
152-
free(cleanval);
153-
}
154-
}
232+
upsdebugx(3, "%s: processed %d bytes with %d incoming content length<br/>", __func__, bytes_seen, content_length);
155233
}
156234

157235
/* called for fatal errors in parseconf like malloc failures */

clients/upsimage.c

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,12 +614,38 @@ static int get_var(const char *var, char *buf, size_t buflen)
614614

615615
int main(int argc, char **argv)
616616
{
617-
char str[SMALLBUF];
617+
char str[SMALLBUF], *s;
618618
int i, min, nom, max;
619619
double var = 0;
620+
621+
#ifdef WIN32
622+
/* Required ritual before calling any socket functions */
623+
static WSADATA WSAdata;
624+
static int WSA_Started = 0;
625+
if (!WSA_Started) {
626+
WSAStartup(2, &WSAdata);
627+
atexit((void(*)(void))WSACleanup);
628+
WSA_Started = 1;
629+
}
630+
631+
/* Avoid binary output conversions, e.g.
632+
* mangling what looks like CRLF on WIN32 */
633+
setmode(STDOUT_FILENO, O_BINARY);
634+
#endif
635+
620636
NUT_UNUSED_VARIABLE(argc);
621637
NUT_UNUSED_VARIABLE(argv);
622638

639+
/* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc
640+
* and NUT methods called from it. This line aims to just initialize
641+
* the subsystem, and set initial timestamp. Debugging the client is
642+
* primarily of use to developers, so is not exposed via `-D` args.
643+
*/
644+
s = getenv("NUT_DEBUG_LEVEL");
645+
if (s && str_to_int(s, &i, 10) && i > 0) {
646+
nut_debug_level = i;
647+
}
648+
623649
extractcgiargs();
624650

625651
upscli_init_default_connect_timeout(NULL, NULL, UPSCLI_DEFAULT_CONNECT_TIMEOUT);

clients/upsset.c

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ static void do_hidden(const char *next)
170170
next);
171171
}
172172

173+
static void do_hidden_sentinel(void)
174+
{
175+
/* MS IIS tends to not close CGI STDIN and not serve the last byte(s)
176+
* but just hangs at fgets(), so we truncate the inputs in cgilib,
177+
* and add a dummy entry here that we can afford to lose in the end */
178+
printf("<INPUT TYPE=\"HIDDEN\" NAME=\"zzz\" VALUE=\"sentinel\">\n");
179+
}
180+
173181
/* generate SELECT chooser from hosts.conf entries */
174182
static void upslist_arg(size_t numargs, char **arg)
175183
{
@@ -202,7 +210,7 @@ static void do_pickups(const char *currfunc)
202210

203211
snprintf(hostfn, sizeof(hostfn), "%s/hosts.conf", confpath());
204212

205-
printf("<FORM METHOD=\"POST\" ACTION=\"upsset.cgi\">\n");
213+
printf("<FORM METHOD=\"POST\" ACTION=\"upsset.cgi" EXEEXT "\">\n");
206214

207215
printf("Select UPS and function:\n<BR>\n");
208216

@@ -258,6 +266,8 @@ static void do_pickups(const char *currfunc)
258266
do_hidden(NULL);
259267

260268
printf("<INPUT TYPE=\"SUBMIT\" VALUE=\"View\">\n");
269+
270+
do_hidden_sentinel();
261271
printf("</FORM>\n");
262272
}
263273

@@ -315,7 +325,7 @@ static void loginscreen(void)
315325
static void loginscreen(void)
316326
{
317327
do_header("Login");
318-
printf("<FORM METHOD=\"POST\" ACTION=\"upsset.cgi\">\n");
328+
printf("<FORM METHOD=\"POST\" ACTION=\"upsset.cgi" EXEEXT "\">\n");
319329
start_table();
320330

321331
printf("<TR BGCOLOR=\"#60B0B0\">\n");
@@ -332,6 +342,7 @@ static void loginscreen(void)
332342
printf("<INPUT TYPE=\"HIDDEN\" NAME=\"function\" VALUE=\"pickups\">\n");
333343
printf("<INPUT TYPE=\"SUBMIT\" VALUE=\"Login\">\n");
334344
printf("<INPUT TYPE=\"RESET\" VALUE=\"Reset fields\">\n");
345+
do_hidden_sentinel();
335346
printf("</TD></TR></TABLE>\n");
336347
printf("</FORM>\n");
337348
printf("</TD></TR></TABLE>\n");
@@ -445,7 +456,7 @@ static void showcmds(void)
445456
"This UPS doesn't support any instant commands.");
446457

447458
do_header("Instant commands");
448-
printf("<FORM ACTION=\"upsset.cgi\" METHOD=\"POST\">\n");
459+
printf("<FORM METHOD=\"POST\" ACTION=\"upsset.cgi" EXEEXT "\">\n");
449460
start_table();
450461

451462
/* include the description from checkhost() if present */
@@ -483,6 +494,7 @@ static void showcmds(void)
483494
printf("<INPUT TYPE=\"HIDDEN\" NAME=\"monups\" VALUE=\"%s\">\n", monups);
484495
printf("<INPUT TYPE=\"SUBMIT\" VALUE=\"Issue command\">\n");
485496
printf("<INPUT TYPE=\"RESET\" VALUE=\"Reset\">\n");
497+
do_hidden_sentinel();
486498
printf("</TD></TR>\n");
487499
printf("</TABLE>\n");
488500
printf("</FORM>\n");
@@ -873,7 +885,7 @@ static void showsettings(void)
873885
}
874886

875887
do_header("Current settings");
876-
printf("<FORM ACTION=\"upsset.cgi\" METHOD=\"POST\">\n");
888+
printf("<FORM METHOD=\"POST\" ACTION=\"upsset.cgi" EXEEXT "\">\n");
877889
start_table();
878890

879891
/* include the description from checkhost() if present */
@@ -905,6 +917,7 @@ static void showsettings(void)
905917
printf("<INPUT TYPE=\"HIDDEN\" NAME=\"monups\" VALUE=\"%s\">\n", monups);
906918
printf("<INPUT TYPE=\"SUBMIT\" VALUE=\"Save changes\">\n");
907919
printf("<INPUT TYPE=\"RESET\" VALUE=\"Reset\">\n");
920+
do_hidden_sentinel();
908921
printf("</TD></TR>\n");
909922
printf("</TABLE>\n");
910923
printf("</FORM>\n");
@@ -1043,6 +1056,7 @@ static void check_conf(void)
10431056
PCONF_CTX_t ctx;
10441057

10451058
snprintf(fn, sizeof(fn), "%s/upsset.conf", confpath());
1059+
upsdebugx(1, "%s: considering configuration file %s", __func__, fn);
10461060

10471061
pconf_init(&ctx, upsset_conf_err);
10481062

@@ -1092,30 +1106,50 @@ static void check_conf(void)
10921106

10931107
int main(int argc, char **argv)
10941108
{
1109+
char *s;
1110+
int i;
1111+
1112+
#ifdef WIN32
1113+
/* Required ritual before calling any socket functions */
1114+
static WSADATA WSAdata;
1115+
static int WSA_Started = 0;
1116+
if (!WSA_Started) {
1117+
WSAStartup(2, &WSAdata);
1118+
atexit((void(*)(void))WSACleanup);
1119+
WSA_Started = 1;
1120+
}
1121+
1122+
/* Avoid binary output conversions, e.g.
1123+
* mangling what looks like CRLF on WIN32 */
1124+
setmode(STDOUT_FILENO, O_BINARY);
1125+
/* Also do not break what we receive from HTTP POST queries */
1126+
setmode(STDIN_FILENO, O_BINARY);
1127+
#endif
1128+
10951129
NUT_UNUSED_VARIABLE(argc);
10961130
NUT_UNUSED_VARIABLE(argv);
10971131
username = password = function = monups = NULL;
10981132

10991133
printf("Content-type: text/html\n\n");
11001134

1135+
/* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc
1136+
* and NUT methods called from it. This line aims to just initialize
1137+
* the subsystem, and set initial timestamp. Debugging the client is
1138+
* primarily of use to developers, so is not exposed via `-D` args.
1139+
*/
1140+
s = getenv("NUT_DEBUG_LEVEL");
1141+
if (s && str_to_int(s, &i, 10) && i > 0) {
1142+
nut_debug_level = i;
1143+
}
1144+
11011145
/* see if the magic string is present in the config file */
11021146
check_conf();
11031147

11041148
upscli_init_default_connect_timeout(NULL, NULL, UPSCLI_DEFAULT_CONNECT_TIMEOUT);
11051149

1106-
/* see if there's anything waiting .. the server my not close STDIN properly */
1107-
if (1) {
1108-
fd_set fds;
1109-
struct timeval tv;
1150+
extractpostargs();
11101151

1111-
FD_ZERO(&fds);
1112-
FD_SET(STDIN_FILENO, &fds);
1113-
tv.tv_sec = 0;
1114-
tv.tv_usec = 250000; /* wait for up to 250ms for a POST response */
1115-
1116-
if ((select(STDIN_FILENO+1, &fds, 0, 0, &tv)) > 0)
1117-
extractpostargs();
1118-
}
1152+
/* Nothing POSTed (or parsed correctly)? */
11191153
if ((!username) || (!password) || (!function))
11201154
loginscreen();
11211155

0 commit comments

Comments
 (0)