1 /* upscmd - simple "client" to test instant commands via upsd
2 
3    Copyright (C)
4      2000  Russell Kroll <rkroll@exploits.org>
5      2019  EATON (author: Arnaud Quette <ArnaudQuette@eaton.com>)
6 
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21 
22 #include "common.h"
23 #include "nut_platform.h"
24 
25 #include <pwd.h>
26 #include <netdb.h>
27 #include <sys/types.h>
28 #include <netinet/in.h>
29 #include <sys/socket.h>
30 
31 #include "nut_stdint.h"
32 #include "upsclient.h"
33 
34 static char			*upsname = NULL, *hostname = NULL;
35 static UPSCONN_t	*ups = NULL;
36 static int			tracking_enabled = 0;
37 static unsigned int	timeout = DEFAULT_TRACKING_TIMEOUT;
38 
39 struct list_t {
40 	char	*name;
41 	struct list_t	*next;
42 };
43 
usage(const char * prog)44 static void usage(const char *prog)
45 {
46 	printf("Network UPS Tools upscmd %s\n\n", UPS_VERSION);
47 	printf("usage: %s [-h]\n", prog);
48 	printf("       %s [-l <ups>]\n", prog);
49 	printf("       %s [-u <username>] [-p <password>] [-w] [-t <timeout>] <ups> <command> [<value>]\n\n", prog);
50 	printf("Administration program to initiate instant commands on UPS hardware.\n");
51 	printf("\n");
52 	printf("  -h		display this help text\n");
53 	printf("  -l <ups>	show available commands on UPS <ups>\n");
54 	printf("  -u <username>	set username for command authentication\n");
55 	printf("  -p <password>	set password for command authentication\n");
56 	printf("  -w            wait for the completion of command by the driver\n");
57 	printf("                and return its actual result from the device\n");
58 	printf("  -t <timeout>	set a timeout when using -w (in seconds, default: %u)\n", DEFAULT_TRACKING_TIMEOUT);
59 	printf("\n");
60 	printf("  <ups>		UPS identifier - <upsname>[@<hostname>[:<port>]]\n");
61 	printf("  <command>	Valid instant command - test.panel.start, etc.\n");
62 	printf("  [<value>]	Additional data for command - number of seconds, etc.\n");
63 }
64 
print_cmd(char * cmdname)65 static void print_cmd(char *cmdname)
66 {
67 	int		ret;
68 	size_t	numq, numa;
69 	const char	*query[4];
70 	char		**answer;
71 
72 	query[0] = "CMDDESC";
73 	query[1] = upsname;
74 	query[2] = cmdname;
75 	numq = 3;
76 
77 	ret = upscli_get(ups, numq, query, &numa, &answer);
78 
79 	if ((ret < 0) || (numa < numq)) {
80 		printf("%s\n", cmdname);
81 		return;
82 	}
83 
84 	/* CMDDESC <upsname> <cmdname> <desc> */
85 	printf("%s - %s\n", cmdname, answer[3]);
86 }
87 
listcmds(void)88 static void listcmds(void)
89 {
90 	int		ret;
91 	size_t	numq, numa;
92 	const char	*query[4];
93 	char		**answer;
94 	struct list_t	*lhead = NULL, *llast = NULL, *ltmp, *lnext;
95 
96 	query[0] = "CMD";
97 	query[1] = upsname;
98 	numq = 2;
99 
100 	ret = upscli_list_start(ups, numq, query);
101 
102 	if (ret < 0) {
103 
104 		/* old upsd = no way to continue */
105 		if (upscli_upserror(ups) == UPSCLI_ERR_UNKCOMMAND) {
106 			fatalx(EXIT_FAILURE, "Error: upsd is too old to support this query");
107 		}
108 
109 		fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups));
110 	}
111 
112 	while (upscli_list_next(ups, numq, query, &numa, &answer) == 1) {
113 
114 		/* CMD <upsname> <cmdname> */
115 		if (numa < 3) {
116 			fatalx(EXIT_FAILURE, "Error: insufficient data (got %zu args, need at least 3)", numa);
117 		}
118 
119 		/* we must first read the entire list of commands,
120 		   before we can start reading the descriptions */
121 
122 		ltmp = xcalloc(1, sizeof(*ltmp));
123 		ltmp->name = xstrdup(answer[2]);
124 
125 		if (llast) {
126 			llast->next = ltmp;
127 		} else {
128 			lhead = ltmp;
129 		}
130 
131 		llast = ltmp;
132 	}
133 
134 	/* walk the list and try to get descriptions, freeing as we go */
135 	printf("Instant commands supported on UPS [%s]:\n\n", upsname);
136 
137 	for (ltmp = lhead; ltmp; ltmp = lnext) {
138 		lnext = ltmp->next;
139 
140 		print_cmd(ltmp->name);
141 
142 		free(ltmp->name);
143 		free(ltmp);
144 	}
145 }
146 
147 #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC) )
148 # pragma GCC diagnostic push
149 #endif
150 #if (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC)
151 # pragma GCC diagnostic ignored "-Wtype-limits"
152 #endif
153 #if (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC)
154 # pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare"
155 #endif
do_cmd(char ** argv,const int argc)156 static void do_cmd(char **argv, const int argc)
157 {
158 	int		cmd_complete = 0;
159 	char	buf[SMALLBUF];
160 	char	tracking_id[UUID4_LEN];
161 	time_t	start, now;
162 
163 	if (argc > 1) {
164 		snprintf(buf, sizeof(buf), "INSTCMD %s %s %s\n", upsname, argv[0], argv[1]);
165 	} else {
166 		snprintf(buf, sizeof(buf), "INSTCMD %s %s\n", upsname, argv[0]);
167 	}
168 
169 	if (upscli_sendline(ups, buf, strlen(buf)) < 0) {
170 		fatalx(EXIT_FAILURE, "Can't send instant command: %s", upscli_strerror(ups));
171 	}
172 
173 	if (upscli_readline(ups, buf, sizeof(buf)) < 0) {
174 		fatalx(EXIT_FAILURE, "Instant command failed: %s", upscli_strerror(ups));
175 	}
176 
177 	/* verify answer */
178 	if (strncmp(buf, "OK", 2) != 0) {
179 		fatalx(EXIT_FAILURE, "Unexpected response from upsd: %s", buf);
180 	}
181 
182 	/* check for status tracking id */
183 	if (
184 		!tracking_enabled ||
185 		/* sanity check on the size: "OK TRACKING " + UUID4_LEN */
186 		strlen(buf) != (UUID4_LEN - 1 + strlen("OK TRACKING "))
187 	) {
188 		/* reply as usual */
189 		fprintf(stderr, "%s\n", buf);
190 		return;
191 	}
192 
193 #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION
194 #pragma GCC diagnostic push
195 #endif
196 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION
197 #pragma GCC diagnostic ignored "-Wformat-truncation"
198 #endif
199 	/* From the check above, we know that we have exactly UUID4_LEN chars
200 	 * (aka sizeof(tracking_id)) in the buf after "OK TRACKING " prefix.
201 	 */
202 	assert (UUID4_LEN == snprintf(tracking_id, sizeof(tracking_id), "%s", buf + strlen("OK TRACKING ")));
203 #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION
204 #pragma GCC diagnostic pop
205 #endif
206 	time(&start);
207 
208 	/* send status tracking request, looping if status is PENDING */
209 	while (!cmd_complete) {
210 
211 		/* check for timeout */
212 		time(&now);
213 		if (difftime(now, start) >= timeout)
214 			fatalx(EXIT_FAILURE, "Can't receive status tracking information: timeout");
215 
216 		snprintf(buf, sizeof(buf), "GET TRACKING %s\n", tracking_id);
217 
218 		if (upscli_sendline(ups, buf, strlen(buf)) < 0)
219 			fatalx(EXIT_FAILURE, "Can't send status tracking request: %s", upscli_strerror(ups));
220 
221 #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) )
222 /* Note for gating macros above: unsuffixed HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP
223  * means support of contexts both inside and outside function body, so the push
224  * above and pop below (outside this finction) are not used.
225  */
226 # pragma GCC diagnostic push
227 #endif
228 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS
229 /* Note that the individual warning pragmas for use inside function bodies
230  * are named without a _INSIDEFUNC suffix, for simplicity and legacy reasons
231  */
232 # pragma GCC diagnostic ignored "-Wtype-limits"
233 #endif
234 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE
235 # pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare"
236 #endif
237 		/* and get status tracking reply */
238 		assert(timeout < LONG_MAX);
239 #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) )
240 # pragma GCC diagnostic pop
241 #endif
242 
243 		if (upscli_readline_timeout(ups, buf, sizeof(buf), (long)timeout) < 0)
244 			fatalx(EXIT_FAILURE, "Can't receive status tracking information: %s", upscli_strerror(ups));
245 
246 		if (strncmp(buf, "PENDING", 7))
247 			cmd_complete = 1;
248 		else
249 			/* wait a second before retrying */
250 			sleep(1);
251 	}
252 
253 	fprintf(stderr, "%s\n", buf);
254 }
255 #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC) )
256 # pragma GCC diagnostic pop
257 #endif
258 
clean_exit(void)259 static void clean_exit(void)
260 {
261 	if (ups) {
262 		upscli_disconnect(ups);
263 	}
264 
265 	free(upsname);
266 	free(hostname);
267 	free(ups);
268 }
269 
main(int argc,char ** argv)270 int main(int argc, char **argv)
271 {
272 	int	i, port;
273 	ssize_t	ret;
274 	int	have_un = 0, have_pw = 0, cmdlist = 0;
275 	char	buf[SMALLBUF * 2], username[SMALLBUF], password[SMALLBUF];
276 	const char	*prog = xbasename(argv[0]);
277 
278 	while ((i = getopt(argc, argv, "+lhu:p:t:wV")) != -1) {
279 
280 		switch (i)
281 		{
282 		case 'l':
283 			cmdlist = 1;
284 			break;
285 
286 		case 'u':
287 			snprintf(username, sizeof(username), "%s", optarg);
288 			have_un = 1;
289 			break;
290 
291 		case 'p':
292 			snprintf(password, sizeof(password), "%s", optarg);
293 			have_pw = 1;
294 			break;
295 
296 		case 't':
297 			if (!str_to_uint(optarg, &timeout, 10))
298 				fatal_with_errno(EXIT_FAILURE, "Could not convert the provided value for timeout ('-t' option) to unsigned int");
299 			break;
300 
301 		case 'w':
302 			tracking_enabled = 1;
303 			break;
304 
305 		case 'V':
306 			fatalx(EXIT_SUCCESS, "Network UPS Tools upscmd %s", UPS_VERSION);
307 #ifndef HAVE___ATTRIBUTE__NORETURN
308 			exit(EXIT_SUCCESS);	/* Should not get here in practice, but compiler is afraid we can fall through */
309 #endif
310 
311 		case 'h':
312 		default:
313 			usage(prog);
314 			exit(EXIT_SUCCESS);
315 		}
316 	}
317 
318 	argc -= optind;
319 	argv += optind;
320 
321 	if (argc < 1) {
322 		usage(prog);
323 		exit(EXIT_SUCCESS);
324 	}
325 
326 	/* be a good little client that cleans up after itself */
327 	atexit(clean_exit);
328 
329 	if (upscli_splitname(argv[0], &upsname, &hostname, &port) != 0) {
330 		fatalx(EXIT_FAILURE, "Error: invalid UPS definition.  Required format: upsname[@hostname[:port]]");
331 	}
332 
333 	ups = xcalloc(1, sizeof(*ups));
334 
335 	if (upscli_connect(ups, hostname, port, 0) < 0) {
336 		fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups));
337 	}
338 
339 	if (cmdlist) {
340 		listcmds();
341 		exit(EXIT_SUCCESS);
342 	}
343 
344 	if (argc < 2) {
345 		usage(prog);
346 		exit(EXIT_SUCCESS);
347 	}
348 
349 	/* also fallback for old command names */
350 	if (!strchr(argv[1], '.')) {
351 		fatalx(EXIT_FAILURE, "Error: old command names are not supported");
352 	}
353 
354 	if (!have_un) {
355 		struct passwd	*pw;
356 
357 		memset(username, '\0', sizeof(username));
358 		pw = getpwuid(getuid());
359 
360 		if (pw) {
361 			printf("Username (%s): ", pw->pw_name);
362 		} else {
363 			printf("Username: ");
364 		}
365 
366 		if (!fgets(username, sizeof(username), stdin)) {
367 			fatalx(EXIT_FAILURE, "Error reading from stdin!");
368 		}
369 
370 		/* deal with that pesky newline */
371 		if (strlen(username) > 1) {
372 			username[strlen(username) - 1] = '\0';
373 		} else {
374 			if (!pw) {
375 				fatalx(EXIT_FAILURE, "No username available - even tried getpwuid");
376 			}
377 
378 			snprintf(username, sizeof(username), "%s", pw->pw_name);
379 		}
380 	}
381 
382 	/* getpass leaks slightly - use -p when testing in valgrind */
383 	if (!have_pw) {
384 		/* using getpass or getpass_r might not be a
385 		   good idea here (marked obsolete in POSIX) */
386 		char	*pwtmp = GETPASS("Password: ");
387 
388 		if (!pwtmp) {
389 			fatalx(EXIT_FAILURE, "getpass failed: %s", strerror(errno));
390 		}
391 
392 		snprintf(password, sizeof(password), "%s", pwtmp);
393 	}
394 
395 	snprintf(buf, sizeof(buf), "USERNAME %s\n", username);
396 
397 	if (upscli_sendline(ups, buf, strlen(buf)) < 0) {
398 		fatalx(EXIT_FAILURE, "Can't set username: %s", upscli_strerror(ups));
399 	}
400 
401 	ret = upscli_readline(ups, buf, sizeof(buf));
402 
403 	if (ret < 0) {
404 		if (upscli_upserror(ups) != UPSCLI_ERR_UNKCOMMAND) {
405 			fatalx(EXIT_FAILURE, "Set username failed: %s", upscli_strerror(ups));
406 		}
407 
408 		fatalx(EXIT_FAILURE,
409 			"Set username failed due to an unknown command.\n"
410 			"You probably need to upgrade upsd.");
411 	}
412 
413 	snprintf(buf, sizeof(buf), "PASSWORD %s\n", password);
414 
415 	if (upscli_sendline(ups, buf, strlen(buf)) < 0) {
416 		fatalx(EXIT_FAILURE, "Can't set password: %s", upscli_strerror(ups));
417 	}
418 
419 	if (upscli_readline(ups, buf, sizeof(buf)) < 0) {
420 		fatalx(EXIT_FAILURE, "Set password failed: %s", upscli_strerror(ups));
421 	}
422 
423 	/* enable status tracking ID */
424 	if (tracking_enabled) {
425 
426 		snprintf(buf, sizeof(buf), "SET TRACKING ON\n");
427 
428 		if (upscli_sendline(ups, buf, strlen(buf)) < 0) {
429 			fatalx(EXIT_FAILURE, "Can't enable command status tracking: %s", upscli_strerror(ups));
430 		}
431 
432 		if (upscli_readline(ups, buf, sizeof(buf)) < 0) {
433 			fatalx(EXIT_FAILURE, "Enabling command status tracking failed: %s", upscli_strerror(ups));
434 		}
435 
436 		/* Verify the result */
437 		if (strncmp(buf, "OK", 2) != 0) {
438 			fatalx(EXIT_FAILURE, "Enabling command status tracking failed. upsd answered: %s", buf);
439 		}
440 	}
441 
442 	do_cmd(&argv[1], argc - 1);
443 
444 	exit(EXIT_SUCCESS);
445 }
446 
447 
448 /* Formal do_upsconf_args implementation to satisfy linker on AIX */
449 #if (defined NUT_PLATFORM_AIX)
do_upsconf_args(char * upsname,char * var,char * val)450 void do_upsconf_args(char *upsname, char *var, char *val) {
451         fatalx(EXIT_FAILURE, "INTERNAL ERROR: formal do_upsconf_args called");
452 }
453 #endif  /* end of #if (defined NUT_PLATFORM_AIX) */
454