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