UPS: apcupsd clean sources
[tomato.git] / release / src / router / apcupsd / src / drivers / pcnet / pcnet.c
blob88a9cce7c94b8f9b8b887f620cbc3d1cdf545a43
1 /*
2 * pcnet.c
4 * Driver for PowerChute Network Shutdown protocol.
5 */
7 /*
8 * Copyright (C) 2006 Adam Kropelin
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of version 2 of the GNU General
12 * Public License as published by the Free Software Foundation.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
19 * You should have received a copy of the GNU General Public
20 * License along with this program; if not, write to the Free
21 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
22 * MA 02111-1307, USA.
25 #include "apc.h"
26 #include "md5.h"
27 #include <sys/socket.h>
28 #include <netinet/in.h>
30 /* UPS broadcasts status packets to UDP port 3052 */
31 #define PCNET_DEFAULT_PORT 3052
34 * Number of seconds with no data before we declare COMMLOST.
35 * UPS should report in every 25 seconds. We allow 2 missing
36 * reports plus a fudge factor.
38 #define COMMLOST_TIMEOUT 55
40 /* Win32 needs a special close for sockets */
41 #ifdef HAVE_MINGW
42 #define close(fd) closesocket(fd)
43 #endif
45 typedef struct {
46 char device[MAXSTRING]; /* Copy of ups->device */
47 char *ipaddr; /* IP address of UPS */
48 char *user; /* Username */
49 char *pass; /* Pass phrase */
50 bool auth; /* Authenticate? */
51 unsigned long uptime; /* UPS uptime counter */
52 unsigned long reboots; /* UPS reboot counter */
53 time_t datatime; /* Last time we got valid data */
54 } PCNET_DATA;
56 /* Convert UPS response to enum and string */
57 static SelfTestResult decode_testresult(const char* str)
60 * Responses are:
61 * "OK" - good battery,
62 * "BT" - failed due to insufficient capacity,
63 * "NG" - failed due to overload,
64 * "NO" - no results available (no test performed in last 5 minutes)
66 if (str[0] == 'O' && str[1] == 'K')
67 return TEST_PASSED;
68 else if (str[0] == 'B' && str[1] == 'T')
69 return TEST_FAILCAP;
70 else if (str[0] == 'N' && str[1] == 'G')
71 return TEST_FAILLOAD;
73 return TEST_NONE;
76 /* Convert UPS response to enum and string */
77 static LastXferCause decode_lastxfer(const char *str)
79 Dmsg1(80, "Transfer reason: %c\n", *str);
81 switch (*str) {
82 case 'N':
83 return XFER_NA;
84 case 'R':
85 return XFER_RIPPLE;
86 case 'H':
87 return XFER_OVERVOLT;
88 case 'L':
89 return XFER_UNDERVOLT;
90 case 'T':
91 return XFER_NOTCHSPIKE;
92 case 'O':
93 return XFER_NONE;
94 case 'K':
95 return XFER_FORCED;
96 case 'S':
97 return XFER_SELFTEST;
98 default:
99 return XFER_UNKNOWN;
103 static bool pcnet_process_data(UPSINFO* ups, const char *key, const char *value)
105 unsigned long cmd;
106 int ci;
107 bool ret;
109 /* Make sure we have a value */
110 if (*value == '\0')
111 return false;
113 /* Detect remote shutdown command */
114 if (strcmp(key, "SD") == 0)
116 cmd = strtoul(value, NULL, 10);
117 switch (cmd)
119 case 0:
120 Dmsg0(80, "SD: The UPS is NOT shutting down\n");
121 ups->clear_shut_remote();
122 break;
124 case 1:
125 Dmsg0(80, "SD: The UPS is shutting down\n");
126 ups->set_shut_remote();
127 break;
129 default:
130 Dmsg1(80, "Unrecognized SD value %s!\n", value);
131 break;
134 return true;
137 /* Key must be 2 hex digits */
138 if (!isxdigit(key[0]) || !isxdigit(key[1]))
139 return false;
141 /* Convert command to CI */
142 cmd = strtoul(key, NULL, 16);
143 for (ci=0; ci<CI_MAXCI; ci++)
144 if (ups->UPS_Cmd[ci] == cmd)
145 break;
147 /* No match? */
148 if (ci == CI_MAXCI)
149 return false;
151 /* Mark this CI as available */
152 ups->UPS_Cap[ci] = true;
154 /* Handle the data */
155 ret = true;
156 switch (ci) {
158 * VOLATILE DATA
160 case CI_STATUS:
161 Dmsg1(80, "Got CI_STATUS: %s\n", value);
162 ups->Status &= ~0xFF; /* clear APC byte */
163 ups->Status |= strtoul(value, NULL, 16) & 0xFF; /* set APC byte */
164 break;
165 case CI_LQUAL:
166 Dmsg1(80, "Got CI_LQUAL: %s\n", value);
167 astrncpy(ups->linequal, value, sizeof(ups->linequal));
168 break;
169 case CI_WHY_BATT:
170 Dmsg1(80, "Got CI_WHY_BATT: %s\n", value);
171 ups->lastxfer = decode_lastxfer(value);
172 break;
173 case CI_ST_STAT:
174 Dmsg1(80, "Got CI_ST_STAT: %s\n", value);
175 ups->testresult = decode_testresult(value);
176 break;
177 case CI_VLINE:
178 Dmsg1(80, "Got CI_VLINE: %s\n", value);
179 ups->LineVoltage = atof(value);
180 break;
181 case CI_VMIN:
182 Dmsg1(80, "Got CI_VMIN: %s\n", value);
183 ups->LineMin = atof(value);
184 break;
185 case CI_VMAX:
186 Dmsg1(80, "Got CI_VMAX: %s\n", value);
187 ups->LineMax = atof(value);
188 break;
189 case CI_VOUT:
190 Dmsg1(80, "Got CI_VOUT: %s\n", value);
191 ups->OutputVoltage = atof(value);
192 break;
193 case CI_BATTLEV:
194 Dmsg1(80, "Got CI_BATTLEV: %s\n", value);
195 ups->BattChg = atof(value);
196 break;
197 case CI_VBATT:
198 Dmsg1(80, "Got CI_VBATT: %s\n", value);
199 ups->BattVoltage = atof(value);
200 break;
201 case CI_LOAD:
202 Dmsg1(80, "Got CI_LOAD: %s\n", value);
203 ups->UPSLoad = atof(value);
204 break;
205 case CI_FREQ:
206 Dmsg1(80, "Got CI_FREQ: %s\n", value);
207 ups->LineFreq = atof(value);
208 break;
209 case CI_RUNTIM:
210 Dmsg1(80, "Got CI_RUNTIM: %s\n", value);
211 ups->TimeLeft = atof(value);
212 break;
213 case CI_ITEMP:
214 Dmsg1(80, "Got CI_ITEMP: %s\n", value);
215 ups->UPSTemp = atof(value);
216 break;
217 case CI_DIPSW:
218 Dmsg1(80, "Got CI_DIPSW: %s\n", value);
219 ups->dipsw = strtoul(value, NULL, 16);
220 break;
221 case CI_REG1:
222 Dmsg1(80, "Got CI_REG1: %s\n", value);
223 ups->reg1 = strtoul(value, NULL, 16);
224 break;
225 case CI_REG2:
226 ups->reg2 = strtoul(value, NULL, 16);
227 ups->set_battpresent(!(ups->reg2 & 0x20));
228 break;
229 case CI_REG3:
230 Dmsg1(80, "Got CI_REG3: %s\n", value);
231 ups->reg3 = strtoul(value, NULL, 16);
232 break;
233 case CI_HUMID:
234 Dmsg1(80, "Got CI_HUMID: %s\n", value);
235 ups->humidity = atof(value);
236 break;
237 case CI_ATEMP:
238 Dmsg1(80, "Got CI_ATEMP: %s\n", value);
239 ups->ambtemp = atof(value);
240 break;
241 case CI_ST_TIME:
242 Dmsg1(80, "Got CI_ST_TIME: %s\n", value);
243 ups->LastSTTime = atof(value);
244 break;
247 * STATIC DATA
249 case CI_SENS:
250 Dmsg1(80, "Got CI_SENS: %s\n", value);
251 astrncpy(ups->sensitivity, value, sizeof(ups->sensitivity));
252 break;
253 case CI_DWAKE:
254 Dmsg1(80, "Got CI_DWAKE: %s\n", value);
255 ups->dwake = (int)atof(value);
256 break;
257 case CI_DSHUTD:
258 Dmsg1(80, "Got CI_DSHUTD: %s\n", value);
259 ups->dshutd = (int)atof(value);
260 break;
261 case CI_LTRANS:
262 Dmsg1(80, "Got CI_LTRANS: %s\n", value);
263 ups->lotrans = (int)atof(value);
264 break;
265 case CI_HTRANS:
266 Dmsg1(80, "Got CI_HTRANS: %s\n", value);
267 ups->hitrans = (int)atof(value);
268 break;
269 case CI_RETPCT:
270 Dmsg1(80, "Got CI_RETPCT: %s\n", value);
271 ups->rtnpct = (int)atof(value);
272 break;
273 case CI_DALARM:
274 Dmsg1(80, "Got CI_DALARM: %s\n", value);
275 astrncpy(ups->beepstate, value, sizeof(ups->beepstate));
276 break;
277 case CI_DLBATT:
278 Dmsg1(80, "Got CI_DLBATT: %s\n", value);
279 ups->dlowbatt = (int)atof(value);
280 break;
281 case CI_IDEN:
282 Dmsg1(80, "Got CI_IDEN: %s\n", value);
283 if (ups->upsname[0] == 0)
284 astrncpy(ups->upsname, value, sizeof(ups->upsname));
285 break;
286 case CI_STESTI:
287 Dmsg1(80, "Got CI_STESTI: %s\n", value);
288 astrncpy(ups->selftest, value, sizeof(ups->selftest));
289 break;
290 case CI_MANDAT:
291 Dmsg1(80, "Got CI_MANDAT: %s\n", value);
292 astrncpy(ups->birth, value, sizeof(ups->birth));
293 break;
294 case CI_SERNO:
295 Dmsg1(80, "Got CI_SERNO: %s\n", value);
296 astrncpy(ups->serial, value, sizeof(ups->serial));
297 break;
298 case CI_BATTDAT:
299 Dmsg1(80, "Got CI_BATTDAT: %s\n", value);
300 astrncpy(ups->battdat, value, sizeof(ups->battdat));
301 break;
302 case CI_NOMOUTV:
303 Dmsg1(80, "Got CI_NOMOUTV: %s\n", value);
304 ups->NomOutputVoltage = (int)atof(value);
305 break;
306 case CI_NOMBATTV:
307 Dmsg1(80, "Got CI_NOMBATTV: %s\n", value);
308 ups->nombattv = atof(value);
309 break;
310 case CI_REVNO:
311 Dmsg1(80, "Got CI_REVNO: %s\n", value);
312 astrncpy(ups->firmrev, value, sizeof(ups->firmrev));
313 break;
314 case CI_EXTBATTS:
315 Dmsg1(80, "Got CI_EXTBATTS: %s\n", value);
316 ups->extbatts = (int)atof(value);
317 break;
318 case CI_BADBATTS:
319 Dmsg1(80, "Got CI_BADBATTS: %s\n", value);
320 ups->badbatts = (int)atof(value);
321 break;
322 case CI_UPSMODEL:
323 Dmsg1(80, "Got CI_UPSMODEL: %s\n", value);
324 astrncpy(ups->upsmodel, value, sizeof(ups->upsmodel));
325 break;
326 case CI_EPROM:
327 Dmsg1(80, "Got CI_EPROM: %s\n", value);
328 astrncpy(ups->eprom, value, sizeof(ups->eprom));
329 break;
330 default:
331 Dmsg1(100, "Unknown CI (%d)\n", ci);
332 ret = false;
333 break;
336 return ret;
339 static char *digest2ascii(md5_byte_t *digest)
341 static char ascii[33];
342 char *ptr;
343 int idx;
345 /* Convert binary digest to ascii */
346 ptr = ascii;
347 for (idx=0; idx<16; idx++) {
348 sprintf(ptr, "%02x", (unsigned char)digest[idx]);
349 ptr += 2;
352 return ascii;
355 struct pair {
356 const char* key;
357 const char* value;
360 #define MAX_PAIRS 256
362 static const char *lookup_key(const char *key, struct pair table[])
364 int idx;
365 const char *ret = NULL;
367 for (idx=0; table[idx].key; idx++) {
368 if (strcmp(key, table[idx].key) == 0) {
369 ret = table[idx].value;
370 break;
374 return ret;
377 static struct pair *auth_and_map_packet(UPSINFO* ups, char *buf, int len)
379 PCNET_DATA *my_data = (PCNET_DATA *)ups->driver_internal_data;
380 char *key, *end, *ptr, *value;
381 const char *val, *hash=NULL;
382 static struct pair pairs[MAX_PAIRS+1];
383 md5_state_t ms;
384 md5_byte_t digest[16];
385 unsigned int idx;
386 unsigned long uptime, reboots;
388 /* If there's no MD= field, drop the packet */
389 if ((ptr = strstr(buf, "MD=")) == NULL || ptr == buf)
390 return NULL;
392 if (my_data->auth) {
393 /* Calculate the MD5 of the packet before messing with it */
394 md5_init(&ms);
395 md5_append(&ms, (md5_byte_t*)buf, ptr-buf);
396 md5_append(&ms, (md5_byte_t*)my_data->user, strlen(my_data->user));
397 md5_append(&ms, (md5_byte_t*)my_data->pass, strlen(my_data->pass));
398 md5_finish(&ms, digest);
400 /* Convert binary digest to ascii */
401 hash = digest2ascii(digest);
404 /* Build a table of pointers to key/value pairs */
405 memset(pairs, 0, sizeof(pairs));
406 ptr = buf;
407 idx = 0;
408 while (*ptr && idx < MAX_PAIRS) {
409 /* Find the beginning of the line */
410 while (isspace(*ptr))
411 ptr++;
412 key = ptr;
414 /* Find the end of the line */
415 while (*ptr && *ptr != '\r' && *ptr != '\n')
416 ptr++;
417 end = ptr;
418 if (*ptr != '\0')
419 ptr++;
421 /* Remove trailing whitespace */
422 do {
423 *end-- = '\0';
424 } while (end >= key && isspace(*end));
426 Dmsg1(300, "process_packet: line='%s'\n", key);
428 /* Split the string */
429 if ((value = strchr(key, '=')) == NULL)
430 continue;
431 *value++ = '\0';
433 Dmsg2(300, "process_packet: key='%s' value='%s'\n",
434 key, value);
436 /* Save key/value in table */
437 pairs[idx].key = key;
438 pairs[idx].value = value;
439 idx++;
442 if (my_data->auth) {
443 /* Check calculated hash vs received */
444 Dmsg1(200, "process_packet: calculated=%s\n", hash);
445 val = lookup_key("MD", pairs);
446 if (!val || strcmp(hash, val)) {
447 Dmsg0(200, "process_packet: message hash failed\n");
448 return NULL;
450 Dmsg1(200, "process_packet: message hash passed\n", val);
452 /* Check management card IP address */
453 val = lookup_key("PC", pairs);
454 if (!val) {
455 Dmsg0(200, "process_packet: Missing PC field\n");
456 return NULL;
458 Dmsg1(200, "process_packet: Expected IP=%s\n", my_data->ipaddr);
459 Dmsg1(200, "process_packet: Received IP=%s\n", val);
460 if (strcmp(val, my_data->ipaddr)) {
461 Dmsg2(200, "process_packet: IP address mismatch\n",
462 my_data->ipaddr, val);
463 return NULL;
468 * Check that uptime and/or reboots have advanced. If not,
469 * this packet could be out of order, or an attacker may
470 * be trying to replay an old packet.
472 val = lookup_key("SR", pairs);
473 if (!val) {
474 Dmsg0(200, "process_packet: Missing SR field\n");
475 return NULL;
477 reboots = strtoul(val, NULL, 16);
479 val = lookup_key("SU", pairs);
480 if (!val) {
481 Dmsg0(200, "process_packet: Missing SU field\n");
482 return NULL;
484 uptime = strtoul(val, NULL, 16);
486 Dmsg1(200, "process_packet: Our reboots=%d\n", my_data->reboots);
487 Dmsg1(200, "process_packet: UPS reboots=%d\n", reboots);
488 Dmsg1(200, "process_packet: Our uptime=%d\n", my_data->uptime);
489 Dmsg1(200, "process_packet: UPS uptime=%d\n", uptime);
491 if ((reboots == my_data->reboots && uptime <= my_data->uptime) ||
492 (reboots < my_data->reboots)) {
493 Dmsg0(200, "process_packet: Packet is out of order or replayed\n");
494 return NULL;
497 my_data->reboots = reboots;
498 my_data->uptime = uptime;
499 return pairs;
503 * Read UPS events. I.e. state changes.
505 int pcnet_ups_check_state(UPSINFO *ups)
507 PCNET_DATA *my_data = (PCNET_DATA *)ups->driver_internal_data;
508 struct timeval tv, now, exit;
509 fd_set rfds;
510 bool done = false;
511 struct sockaddr_in from;
512 socklen_t fromlen;
513 int retval;
514 char buf[4096];
515 struct pair *map;
516 int idx;
518 /* Figure out when we need to exit by */
519 gettimeofday(&exit, NULL);
520 exit.tv_sec += ups->wait_time;
522 while (!done) {
524 /* Figure out how long until we have to exit */
525 gettimeofday(&now, NULL);
527 if (now.tv_sec > exit.tv_sec ||
528 (now.tv_sec == exit.tv_sec &&
529 now.tv_usec >= exit.tv_usec)) {
530 /* Done already? How time flies... */
531 break;
534 tv.tv_sec = exit.tv_sec - now.tv_sec;
535 tv.tv_usec = exit.tv_usec - now.tv_usec;
536 if (tv.tv_usec < 0) {
537 tv.tv_sec--; /* Normalize */
538 tv.tv_usec += 1000000;
541 Dmsg2(100, "Waiting for %d.%d\n", tv.tv_sec, tv.tv_usec);
542 FD_ZERO(&rfds);
543 FD_SET(ups->fd, &rfds);
545 retval = select(ups->fd + 1, &rfds, NULL, NULL, &tv);
547 if (retval == 0) {
548 /* No chars available in TIMER seconds. */
549 break;
550 } else if (retval == -1) {
551 if (errno == EINTR || errno == EAGAIN) /* assume SIGCHLD */
552 continue;
553 Dmsg1(200, "select error: ERR=%s\n", strerror(errno));
554 return 0;
557 do {
558 fromlen = sizeof(from);
559 retval = recvfrom(ups->fd, buf, sizeof(buf)-1, 0, (struct sockaddr*)&from, &fromlen);
560 } while (retval == -1 && (errno == EAGAIN || errno == EINTR));
562 if (retval < 0) { /* error */
563 Dmsg1(200, "recvfrom error: ERR=%s\n", strerror(errno));
564 // usb_link_check(ups); /* notify that link is down, wait */
565 break;
568 Dmsg4(200, "Packet from: %d.%d.%d.%d\n",
569 (ntohl(from.sin_addr.s_addr) >> 24) & 0xff,
570 (ntohl(from.sin_addr.s_addr) >> 16) & 0xff,
571 (ntohl(from.sin_addr.s_addr) >> 8) & 0xff,
572 ntohl(from.sin_addr.s_addr) & 0xff);
574 /* Ensure the packet is nul-terminated */
575 buf[retval] = '\0';
577 hex_dump(300, buf, retval);
579 map = auth_and_map_packet(ups, buf, retval);
580 if (map == NULL)
581 continue;
583 write_lock(ups);
585 for (idx=0; map[idx].key; idx++)
586 done |= pcnet_process_data(ups, map[idx].key, map[idx].value);
588 write_unlock(ups);
591 /* If we successfully received a data packet, update timer. */
592 if (done) {
593 time(&my_data->datatime);
594 Dmsg1(100, "Valid data at time_t=%d\n", my_data->datatime);
597 return done;
600 int pcnet_ups_open(UPSINFO *ups)
602 struct sockaddr_in addr;
603 PCNET_DATA *my_data = (PCNET_DATA *)ups->driver_internal_data;
604 char *ptr;
606 write_lock(ups);
608 if (my_data == NULL) {
609 my_data = (PCNET_DATA *)malloc(sizeof(*my_data));
610 memset(my_data, 0, sizeof(*my_data));
611 ups->driver_internal_data = my_data;
614 unsigned short port = PCNET_DEFAULT_PORT;
615 if (ups->device[0] != '\0') {
616 my_data->auth = true;
618 astrncpy(my_data->device, ups->device, sizeof(my_data->device));
619 ptr = my_data->device;
621 my_data->ipaddr = ptr;
622 ptr = strchr(ptr, ':');
623 if (ptr == NULL)
624 Error_abort0("Malformed DEVICE [ip:user:pass]\n");
625 *ptr++ = '\0';
627 my_data->user = ptr;
628 ptr = strchr(ptr, ':');
629 if (ptr == NULL)
630 Error_abort0("Malformed DEVICE [ip:user:pass]\n");
631 *ptr++ = '\0';
633 my_data->pass = ptr;
634 if (*ptr == '\0')
635 Error_abort0("Malformed DEVICE [ip:user:pass]\n");
637 // Last segment is optional port number
638 ptr = strchr(ptr, ':');
639 if (ptr)
641 *ptr++ = '\0';
642 port = atoi(ptr);
643 if (port == 0)
644 port = PCNET_DEFAULT_PORT;
648 ups->fd = socket(PF_INET, SOCK_DGRAM, 0);
649 if (ups->fd == -1)
650 Error_abort1("Cannot create socket (%d)\n", errno);
652 int enable = 1;
653 setsockopt(ups->fd, SOL_SOCKET, SO_BROADCAST, (const char*)&enable, sizeof(enable));
655 memset(&addr, 0, sizeof(addr));
656 addr.sin_family = AF_INET;
657 addr.sin_port = htons(port);
658 addr.sin_addr.s_addr = INADDR_ANY;
659 if (bind(ups->fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
660 close(ups->fd);
661 Error_abort1("Cannot bind socket (%d)\n", errno);
664 /* Reset datatime to now */
665 time(&my_data->datatime);
667 write_unlock(ups);
668 return 1;
671 int pcnet_ups_setup(UPSINFO *ups)
673 /* Seems that there is nothing to do. */
674 return 1;
677 int pcnet_ups_close(UPSINFO *ups)
679 write_lock(ups);
681 close(ups->fd);
682 ups->fd = -1;
684 write_unlock(ups);
685 return 1;
689 * Setup capabilities structure for UPS
691 int pcnet_ups_get_capabilities(UPSINFO *ups)
694 * Unfortunately, we don't know capabilities until we
695 * receive the first broadcast status message.
697 return 1;
701 * Read UPS info that remains unchanged -- e.g. transfer
702 * voltages, shutdown delay, ...
704 * This routine is called once when apcupsd is starting
706 int pcnet_ups_read_static_data(UPSINFO *ups)
708 /* All our data gathering is done in pcnet_ups_check_state() */
709 return 1;
713 * Read UPS info that changes -- e.g. Voltage, temperature, ...
715 * This routine is called once every N seconds to get
716 * a current idea of what the UPS is doing.
718 int pcnet_ups_read_volatile_data(UPSINFO *ups)
720 PCNET_DATA *my_data = (PCNET_DATA *)ups->driver_internal_data;
721 time_t now, diff;
724 * All our data gathering is done in pcnet_ups_check_state().
725 * But we do use this function to check our commlost state.
728 time(&now);
729 diff = now - my_data->datatime;
731 if (ups->is_commlost()) {
732 if (diff < COMMLOST_TIMEOUT) {
733 generate_event(ups, CMDCOMMOK);
734 ups->clear_commlost();
736 } else {
737 if (diff >= COMMLOST_TIMEOUT) {
738 generate_event(ups, CMDCOMMFAILURE);
739 ups->set_commlost();
743 return 1;
746 int pcnet_ups_kill_power(UPSINFO *ups)
748 PCNET_DATA *my_data = (PCNET_DATA *)ups->driver_internal_data;
749 struct sockaddr_in addr;
750 char data[1024];
751 int s, len=0, temp=0;
752 char *start;
753 const char *cs, *hash;
754 struct pair *map;
755 md5_state_t ms;
756 md5_byte_t digest[16];
758 /* We cannot perform a killpower without authentication data */
759 if (!my_data->auth) {
760 Error_abort0("Cannot perform killpower without authentication "
761 "data. Please set ip:user:pass for DEVICE in "
762 "apcupsd.conf.\n");
765 /* Open a TCP stream to the UPS */
766 s = socket(PF_INET, SOCK_STREAM, 0);
767 if (s == -1) {
768 Dmsg1(100, "pcnet_ups_kill_power: Unable to open socket: %s\n",
769 strerror(errno));
770 return 0;
773 memset(&addr, 0, sizeof(addr));
774 addr.sin_family = AF_INET;
775 addr.sin_port = htons(80);
776 inet_pton(AF_INET, my_data->ipaddr, &addr.sin_addr.s_addr);
778 if (connect(s, (sockaddr*)&addr, sizeof(addr))) {
779 Dmsg3(100, "pcnet_ups_kill_power: Unable to connect to %s:%d: %s\n",
780 my_data->ipaddr, 80, strerror(errno));
781 close(s);
782 return 0;
785 /* Send a simple HTTP request for "/macontrol.htm". */
786 asnprintf(data, sizeof(data),
787 "GET /macontrol.htm HTTP/1.1\r\n"
788 "Host: %s\r\n"
789 "\r\n",
790 my_data->ipaddr);
792 Dmsg1(200, "Request:\n---\n%s---\n", data);
794 if (send(s, data, strlen(data), 0) != (int)strlen(data)) {
795 Dmsg1(100, "pcnet_ups_kill_power: send failed: %s\n", strerror(errno));
796 close(s);
797 return 0;
801 * Clear buffer and read data until we find the 0-length
802 * chunk. We know that AP9617 uses chunked encoding, so we
803 * can count on the 0-length chunk at the end.
805 memset(data, 0, sizeof(data));
806 do {
807 len += temp;
808 temp = recv(s, data+len, sizeof(data)-len, 0);
809 } while(temp > 0 && strstr(data, "\r\n0\r\n") == NULL);
811 Dmsg1(200, "Response:\n---\n%s---\n", data);
813 if (temp < 0) {
814 Dmsg1(100, "pcnet_ups_kill_power: recv failed: %s\n", strerror(errno));
815 close(s);
816 return 0;
820 * Find "<html>" since that's where the real authenticated
821 * data begins. Everything before that is headers.
823 start = strstr(data, "<html>");
824 if (start == NULL) {
825 Dmsg0(100, "pcnet_ups_kill_power: Malformed data\n");
826 close(s);
827 return 0;
831 * Authenticate and map the packet contents. This will
832 * extract all key/value pairs and ensure the packet
833 * authentication hash is valid.
835 map = auth_and_map_packet(ups, start, strlen(start));
836 if (map == NULL) {
837 close(s);
838 return 0;
841 /* Check that we got a challenge string. */
842 cs = lookup_key("CS", map);
843 if (cs == NULL) {
844 Dmsg0(200, "pcnet_ups_kill_power: Missing CS field\n");
845 close(s);
846 return 0;
850 * Now construct the hash of the packet we're about to
851 * send using the challenge string from the packet we
852 * just received, plus our username and passphrase.
854 md5_init(&ms);
855 md5_append(&ms, (md5_byte_t*)"macontrol1_control_shutdown_1=1,", 32);
856 md5_append(&ms, (md5_byte_t*)cs, strlen(cs));
857 md5_append(&ms, (md5_byte_t*)my_data->user, strlen(my_data->user));
858 md5_append(&ms, (md5_byte_t*)my_data->pass, strlen(my_data->pass));
859 md5_finish(&ms, digest);
860 hash = digest2ascii(digest);
862 /* Send the shutdown request */
863 asnprintf(data, sizeof(data),
864 "POST /Forms/macontrol1 HTTP/1.1\r\n"
865 "Host: %s\r\n"
866 "Content-Type: application/x-www-form-urlencoded\r\n"
867 "Content-Length: 72\r\n"
868 "\r\n"
869 "macontrol1%%5fcontrol%%5fshutdown%%5f1=1%%2C%s",
870 my_data->ipaddr, hash);
872 Dmsg2(200, "Request: (strlen=%d)\n---\n%s---\n", strlen(data), data);
874 if (send(s, data, strlen(data), 0) != (int)strlen(data)) {
875 Dmsg1(100, "pcnet_ups_kill_power: send failed: %s\n", strerror(errno));
876 close(s);
877 return 0;
880 /* That's it, we're done. */
881 close(s);
883 return 1;
886 int pcnet_ups_program_eeprom(UPSINFO *ups, int command, const char *data)
888 /* Unsupported */
889 return 0;
892 int pcnet_ups_entry_point(UPSINFO *ups, int command, void *data)
894 int temp;
896 switch (command) {
897 case DEVICE_CMD_CHECK_SELFTEST:
898 Dmsg0(80, "Checking self test.\n");
899 if (ups->UPS_Cap[CI_WHY_BATT] && ups->lastxfer == XFER_SELFTEST) {
901 * set Self Test start time
903 ups->SelfTest = time(NULL);
904 Dmsg1(80, "Self Test time: %s", ctime(&ups->SelfTest));
906 break;
908 case DEVICE_CMD_GET_SELFTEST_MSG:
910 * This is a bit kludgy. The selftest result isn't available from
911 * the UPS for about 10 seconds after the selftest completes. So we
912 * invoke pcnet_ups_check_state() with a 12 second timeout,
913 * expecting that it should get a status report before then.
916 /* Save current ups->wait_time and set it to 12 seconds */
917 temp = ups->wait_time;
918 ups->wait_time = 12;
920 /* Let check_status wait for the result */
921 write_unlock(ups);
922 pcnet_ups_check_state(ups);
923 write_lock(ups);
925 /* Restore ups->wait_time */
926 ups->wait_time = temp;
927 break;
929 default:
930 return FAILURE;
933 return SUCCESS;