/* upsd.c - watches ups state files and answers queries Copyright (C) 1999 Russell Kroll This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "upsd.h" #include "upstype.h" #include "conf.h" #include "netcmds.h" #include "upsconf.h" #include #include #include #ifdef HAVE_SSL #include #include #endif #include "user.h" #include "access.h" #include "ctype.h" #include "stype.h" #include "ssl.h" #include "sstate.h" #include "desc.h" #include "neterr.h" /* externally-visible settings and pointers */ upstype_t *firstups = NULL; /* default 15 seconds before data is marked stale */ int maxage = 15; /* preloaded to STATEPATH in main, can be overridden via upsd.conf */ char *statepath = NULL; /* preloaded to DATADIR in main, can be overridden via upsd.conf */ char *datapath = NULL; /* everything else */ static ctype_t *firstclient = NULL; /* default is to listen on all local interfaces */ static stype_t *firstaddr = NULL; #ifdef HAVE_IPV6 static int opt_af = AF_UNSPEC; #endif /* signal handlers */ static struct sigaction sa; static sigset_t nut_upsd_sigmask; /* pid file */ static char pidfn[SMALLBUF]; /* set by signal handlers */ static int reload_flag = 0, exit_flag = 0; #ifdef HAVE_IPV6 static const char *inet_ntopW (struct sockaddr_storage *s) { static char str[40]; switch (s->ss_family) { case AF_INET: return inet_ntop (AF_INET, &(((struct sockaddr_in *)s)->sin_addr), str, 16); case AF_INET6: return inet_ntop (AF_INET6, &(((struct sockaddr_in6 *)s)->sin6_addr), str, 40); default: errno = EAFNOSUPPORT; return NULL; } } #endif /* return a pointer to the named ups if possible */ upstype_t *get_ups_ptr(const char *name) { upstype_t *tmp; if (!name) { return NULL; } for (tmp = firstups; tmp != NULL; tmp = tmp->next) { if (!strcasecmp(tmp->name, name)) { return tmp; } } return NULL; } /* mark the data stale if this is new, otherwise cleanup any remaining junk */ static void ups_data_stale(upstype_t *ups) { /* don't complain again if it's already known to be stale */ if (ups->stale == 1) { return; } ups->stale = 1; upslogx(LOG_NOTICE, "Data for UPS [%s] is stale - check driver", ups->name); } /* mark the data ok if this is new, otherwise do nothing */ static void ups_data_ok(upstype_t *ups) { if (ups->stale == 0) { return; } upslogx(LOG_NOTICE, "UPS [%s] data is no longer stale", ups->name); ups->stale = 0; } /* make sure this UPS is connected and has fresh data */ static void check_ups(upstype_t *ups) { /* sanity checks */ if ((!ups) || (!ups->fn)) { return; } /* see if we need to (re)connect to the socket */ if (ups->sock_fd == -1) { ups->sock_fd = sstate_connect(ups); } /* throw some warnings if it's not feeding us data any more */ if (sstate_dead(ups, maxage)) { ups_data_stale(ups); } else { ups_data_ok(ups); } } /* add another listening address */ void listen_add(const char *addr, const char *port) { stype_t *stmp, *last; /* don't change listening addresses on reload */ if (reload_flag) { return; } stmp = last = firstaddr; /* find end of linked list */ while (stmp != NULL) { last = stmp; stmp = stmp->next; } /* grab some memory and add the info */ stmp = xmalloc(sizeof(stype_t)); stmp->addr = xstrdup(addr); stmp->port = xstrdup(port); stmp->sock_fd = -1; stmp->next = NULL; if (last == NULL) { firstaddr = stmp; } else { last->next = stmp; } upsdebugx(3, "listen_add: added %s:%s", stmp->addr, stmp->port); } /* create a listening socket for tcp connections */ static void setuptcp(stype_t *serv) { #ifndef HAVE_IPV6 struct hostent *host; struct sockaddr_in server; int res, one = 1; host = gethostbyname(serv->addr); if (host == NULL) { struct in_addr listenaddr; if (!inet_aton(serv->addr, &listenaddr)) { fatal_with_errno(EXIT_FAILURE, "inet_aton"); } host = gethostbyaddr(&listenaddr, sizeof(listenaddr), AF_INET); if (host == NULL) { fatal_with_errno(EXIT_FAILURE, "gethostbyaddr"); } } if ((serv->sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fatal_with_errno(EXIT_FAILURE, "socket"); } res = setsockopt(serv->sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *) &one, sizeof(one)); if (res != 0) { fatal_with_errno(EXIT_FAILURE, "setsockopt(SO_REUSEADDR)"); } memset(&server, '\0', sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(atoi(serv->port)); memcpy(&server.sin_addr, host->h_addr, host->h_length); if (bind(serv->sock_fd, (struct sockaddr *) &server, sizeof(server)) == -1) { fatal_with_errno(EXIT_FAILURE, "Can't bind TCP port %s", serv->port); } if ((res = fcntl(serv->sock_fd, F_GETFL, 0)) == -1) { fatal_with_errno(EXIT_FAILURE, "fcntl(get)"); } if (fcntl(serv->sock_fd, F_SETFL, res | O_NDELAY) == -1) { fatal_with_errno(EXIT_FAILURE, "fcntl(set)"); } if (listen(serv->sock_fd, 16)) { fatal_with_errno(EXIT_FAILURE, "listen"); } #else struct addrinfo hints, *res, *ai; int v = 0, one = 1; upsdebugx(3, "setuptcp: try to bind to %s port %s", serv->addr, serv->port); memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_PASSIVE; hints.ai_family = opt_af; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; if ((v = getaddrinfo(serv->addr, serv->port, &hints, &res)) != 0) { if (v == EAI_SYSTEM) { fatal_with_errno(EXIT_FAILURE, "getaddrinfo"); } fatalx(EXIT_FAILURE, "getaddrinfo: %s", gai_strerror(v)); } for (ai = res; ai != NULL; ai = ai->ai_next) { int sock_fd; if ((sock_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) { upsdebug_with_errno(3, "setuptcp: socket"); continue; } if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one)) != 0) { fatal_with_errno(EXIT_FAILURE, "setuptcp: setsockopt"); } if (bind(sock_fd, ai->ai_addr, ai->ai_addrlen) < 0) { upsdebug_with_errno(3, "setuptcp: bind"); close(sock_fd); continue; } if ((v = fcntl(sock_fd, F_GETFL, 0)) == -1) { fatal_with_errno(EXIT_FAILURE, "setuptcp: fcntl(get)"); } if (fcntl(sock_fd, F_SETFL, v | O_NDELAY) == -1) { fatal_with_errno(EXIT_FAILURE, "setuptcp: fcntl(set)"); } if (listen(sock_fd, 16) < 0) { upsdebug_with_errno(3, "setuptcp: listen"); close(sock_fd); continue; } serv->sock_fd = sock_fd; break; } freeaddrinfo(res); #endif /* don't fail silently */ if (serv->sock_fd < 0) { fatalx(EXIT_FAILURE, "not listening on %s port %s", serv->addr, serv->port); } else { upslogx(LOG_INFO, "listening on %s port %s", serv->addr, serv->port); } return; } /* decrement the login counter for this ups */ static void declogins(const char *upsname) { upstype_t *ups; ups = get_ups_ptr(upsname); if (ups == NULL) { upslogx(LOG_INFO, "Tried to decrement invalid ups name (%s)", upsname); return; } ups->numlogins--; if (ups->numlogins < 0) { upslogx(LOG_ERR, "Programming error: UPS [%s] has numlogins=%d", ups->name, ups->numlogins); } } /* disconnect a client connection and free all related memory */ static void delclient(ctype_t *dclient) { ctype_t *tmp, *last; if (dclient == NULL) { return; } last = NULL; tmp = firstclient; while (tmp != NULL) { if (tmp == dclient) { /* found it */ shutdown(dclient->fd, 2); close(dclient->fd); free(tmp->addr); if (tmp->loginups != NULL) { declogins(tmp->loginups); free(tmp->loginups); } free(tmp->password); #ifdef HAVE_SSL if (tmp->ssl) { SSL_free(tmp->ssl); } #endif pconf_finish(&tmp->ctx); if (last == NULL) { /* deleting first entry */ firstclient = tmp->next; } else { last->next = tmp->next; } free(tmp); return; } last = tmp; tmp = tmp->next; } /* not found?! */ upslogx(LOG_WARNING, "Tried to delete client struct that doesn't exist!"); return; } /* send the buffer of length to host */ int sendback(ctype_t *client, const char *fmt, ...) { int res, len; char ans[NUT_NET_ANSWER_MAX+1]; va_list ap; if (!client) { return 0; } if (client->delete) { return 0; } va_start(ap, fmt); vsnprintf(ans, sizeof(ans), fmt, ap); va_end(ap); len = strlen(ans); if (client->ssl) { res = ssl_write(client, ans, len); } else { res = write(client->fd, ans, len); } upsdebugx(2, "write: [destfd=%d] [len=%d] [%s]", client->fd, len, rtrim(ans, '\n')); if (len != res) { upslog_with_errno(LOG_NOTICE, "write() failed for %s", client->addr); client->delete = 1; return 0; /* failed */ } return 1; /* OK */ } /* just a simple wrapper for now */ int send_err(ctype_t *client, const char *errtype) { if (!client) { return -1; } upsdebugx(4, "Sending error [%s] to client %s", errtype, client->addr); return sendback(client, "ERR %s\n", errtype); } /* disconnect anyone logged into this UPS */ void kick_login_clients(const char *upsname) { ctype_t *tmp, *next; tmp = firstclient; while (tmp) { next = tmp->next; /* if it's not logged in, don't check it */ if (!tmp->loginups) { tmp = next; continue; } if (!strcmp(tmp->loginups, upsname)) { upslogx(LOG_INFO, "Kicking client %s (was on UPS [%s])\n", tmp->addr, upsname); delclient(tmp); } tmp = next; } } /* make sure a UPS is sane - connected, with fresh data */ int ups_available(const upstype_t *ups, ctype_t *client) { if (ups->sock_fd == -1) { send_err(client, NUT_ERR_DRIVER_NOT_CONNECTED); return 0; } if (ups->stale) { send_err(client, NUT_ERR_DATA_STALE); return 0; } /* must be OK */ return 1; } /* check flags and access for an incoming command from the network */ static void check_command(int cmdnum, ctype_t *client, int numarg, const char **arg) { if (netcmds[cmdnum].flags & FLAG_USER) { if (!client->username) { send_err(client, NUT_ERR_USERNAME_REQUIRED); return; } if (!client->password) { send_err(client, NUT_ERR_PASSWORD_REQUIRED); return; } } /* looks good - call the command */ netcmds[cmdnum].func(client, numarg - 1, &arg[1]); } /* parse requests from the network */ static void parse_net(ctype_t *client) { int i; /* see if this client is still allowed to talk to us */ if (!access_check(&client->sock)) { send_err(client, NUT_ERR_ACCESS_DENIED); client->delete = 1; return; } /* paranoia */ client->rq[client->rqpos] = '\0'; /* by this point we should always have a usable line */ if (pconf_line(&client->ctx, client->rq) != 1) { /* shouldn't happen */ upslogx(LOG_WARNING, "pconf_line couldn't handle the input"); send_err(client, NUT_ERR_UNKNOWN_COMMAND); return; } if (pconf_parse_error(&client->ctx)) { upsdebugx(4, "parse error on net read"); send_err(client, NUT_ERR_UNKNOWN_COMMAND); return; } /* shouldn't happen */ if (client->ctx.numargs < 1) { send_err(client, NUT_ERR_UNKNOWN_COMMAND); return; } for (i = 0; netcmds[i].name != NULL; i++) { if (!strcasecmp(netcmds[i].name, client->ctx.arglist[0])) { check_command(i, client, client->ctx.numargs, (const char **) client->ctx.arglist); return; } } /* fallthrough = not matched by any entry in netcmds */ send_err(client, NUT_ERR_UNKNOWN_COMMAND); } /* scan the list of UPSes for sanity */ static void check_every_ups(void) { upstype_t *ups; ups = firstups; while (ups != NULL) { check_ups(ups); ups = ups->next; } } /* answer incoming tcp connections */ static void answertcp(stype_t *serv) #ifndef HAVE_IPV6 { struct sockaddr_in csock; #else { struct sockaddr_storage csock; #endif int acc; ctype_t *tmp, *last; socklen_t clen; clen = sizeof(csock); acc = accept(serv->sock_fd, (struct sockaddr *) &csock, &clen); if (acc < 0) { return; } if (!access_check(&csock)) { upslogx(LOG_NOTICE, "Rejecting TCP connection from %s", #ifndef HAVE_IPV6 inet_ntoa(csock.sin_addr)); #else inet_ntopW(&csock)); #endif shutdown(acc, shutdown_how); close(acc); return; } last = tmp = firstclient; while (tmp != NULL) { last = tmp; tmp = tmp->next; } tmp = xmalloc(sizeof(ctype_t)); tmp->fd = acc; tmp->delete = 0; #ifndef HAVE_IPV6 tmp->addr = xstrdup(inet_ntoa(csock.sin_addr)); memcpy(&tmp->sock, &csock, sizeof(struct sockaddr_in)); #else tmp->addr = xstrdup(inet_ntopW(&csock)); memcpy(&tmp->sock, &csock, sizeof(struct sockaddr_storage)); #endif tmp->rqpos = 0; memset(tmp->rq, '\0', sizeof(tmp->rq)); pconf_init(&tmp->ctx, NULL); tmp->loginups = NULL; /* for upsmon */ tmp->username = NULL; tmp->password = NULL; tmp->ssl = NULL; tmp->ssl_connected = 0; tmp->next = NULL; if (last == NULL) { firstclient = tmp; } else { last->next = tmp; } upslogx(LOG_DEBUG, "Connection from %s", tmp->addr); } /* read tcp messages and handle them */ static void readtcp(ctype_t *client) { char buf[SMALLBUF]; int i, ret; memset(buf, '\0', sizeof(buf)); if (client->ssl) { ret = ssl_read(client, buf, sizeof(buf)); } else { ret = read(client->fd, buf, sizeof(buf)); } if (ret < 1) { upslogx(LOG_INFO, "Host %s disconnected (read failure)", client->addr); delclient(client); return; } /* if an overflow will happen, then clear the queue */ if ((ret + client->rqpos) >= sizeof(client->rq)) { memset(client->rq, '\0', sizeof(client->rq)); client->rqpos = 0; } /* fragment handling code */ for (i = 0; i < ret; i++) { /* add to the receive queue one by one */ client->rq[client->rqpos++] = buf[i]; /* parse on linefeed ("blah blah\n") */ if (buf[i] == 10) { /* LF */ parse_net(client); /* bail out if the connection closed in parse_net */ if (client->delete) { delclient(client); return; } /* reset queue */ client->rqpos = 0; memset(client->rq, '\0', sizeof(client->rq)); } } return; } void server_load(void) { stype_t *serv; /* default behaviour if no LISTEN addres has been specified */ if (firstaddr == NULL) { listen_add("0.0.0.0", string_const(PORT)); } for (serv = firstaddr; serv != NULL; serv = serv->next) { setuptcp(serv); } } void server_free(void) { stype_t *stmp, *snext; /* cleanup server fds */ stmp = firstaddr; while (stmp) { snext = stmp->next; if (stmp->sock_fd != -1) close(stmp->sock_fd); free(stmp->addr); free(stmp->port); free(stmp); stmp = snext; } firstaddr = NULL; } static void upsd_cleanup(void) { ctype_t *tmpcli, *tmpnext; upstype_t *ups, *unext; /* cleanup client fds */ tmpcli = firstclient; while (tmpcli) { tmpnext = tmpcli->next; delclient(tmpcli); tmpcli = tmpnext; } if (strcmp(pidfn, "") != 0) { unlink(pidfn); } ups = firstups; while (ups) { unext = ups->next; if (ups->sock_fd != -1) { close(ups->sock_fd); } sstate_infofree(ups); sstate_cmdfree(ups); pconf_finish(&ups->sock_ctx); free(ups->fn); free(ups->name); free(ups->desc); free(ups); ups = unext; } /* dump everything */ acl_free(); access_free(); user_flush(); desc_free(); server_free(); free(statepath); free(datapath); free(certfile); } /* service requests and check on new data */ static void mainloop(void) { fd_set rfds; struct timeval tv; int res, maxfd = -1; ctype_t *tmpcli, *tmpnext; upstype_t *utmp, *unext; stype_t *stmp, *snext; if (reload_flag) { conf_reload(); reload_flag = 0; } tv.tv_sec = 2; tv.tv_usec = 0; FD_ZERO(&rfds); /* scan through servers and add to FD_SET */ for (stmp = firstaddr; stmp != NULL; stmp = stmp->next) { if (stmp->sock_fd != -1) { FD_SET(stmp->sock_fd, &rfds); if (stmp->sock_fd > maxfd) { maxfd = stmp->sock_fd; } } } /* scan through clients and add to FD_SET */ for (tmpcli = firstclient; tmpcli != NULL; tmpcli = tmpcli->next) { FD_SET(tmpcli->fd, &rfds); if (tmpcli->fd > maxfd) { maxfd = tmpcli->fd; } } /* also add new driver sockets */ for (utmp = firstups; utmp != NULL; utmp = utmp->next) { if (utmp->sock_fd != -1) { FD_SET(utmp->sock_fd, &rfds); if (utmp->sock_fd > maxfd) { maxfd = utmp->sock_fd; } } } res = select(maxfd + 1, &rfds, NULL, NULL, &tv); if (res > 0) { /* scan servers for activity */ stmp = firstaddr; while (stmp) { snext = stmp->next; if (stmp->sock_fd != -1) { if (FD_ISSET(stmp->sock_fd, &rfds)) { answertcp(stmp); } } stmp = snext; } /* scan clients for activity */ tmpcli = firstclient; while (tmpcli != NULL) { /* preserve for later since delclient may run */ tmpnext = tmpcli->next; if (FD_ISSET(tmpcli->fd, &rfds)) { readtcp(tmpcli); } tmpcli = tmpnext; } /* now scan ups sockets for activity */ utmp = firstups; while (utmp) { unext = utmp->next; if (utmp->sock_fd != -1) { if (FD_ISSET(utmp->sock_fd, &rfds)) { sstate_sock_read(utmp); } } utmp = unext; } } check_every_ups(); } static void help(const char *progname) { printf("Network server for UPS data.\n\n"); printf("usage: %s [OPTIONS]\n", progname); printf("\n"); printf(" -c send via signal to background process\n"); printf(" commands:\n"); printf(" - reload: reread configuration files\n"); printf(" - stop: stop process and exit\n"); printf(" -D raise debugging level\n"); printf(" -f stay in the foreground for testing\n"); printf(" -h display this help\n"); printf(" -r chroots to \n"); printf(" -u switch to (if started as root)\n"); printf(" -V display the version of this software\n"); #ifdef HAVE_IPV6 printf(" -4 IPv4 only\n"); printf(" -6 IPv6 only\n"); #endif exit(EXIT_SUCCESS); } static void set_reload_flag(int sig) { reload_flag = 1; } static void set_exit_flag(int sig) { exit_flag = sig; } /* basic signal setup to ignore SIGPIPE */ static void setupsignals(void) { sigemptyset(&nut_upsd_sigmask); sa.sa_mask = nut_upsd_sigmask; sa.sa_flags = 0; sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); /* handle shutdown signals */ sa.sa_handler = set_exit_flag; sigaction(SIGINT, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); /* handle reloading */ sa.sa_handler = set_reload_flag; sigaction(SIGHUP, &sa, NULL); } void check_perms(const char *fn) { int ret; struct stat st; ret = stat(fn, &st); if (ret != 0) { fatal_with_errno(EXIT_FAILURE, "stat %s", fn); } /* include the x bit here in case we check a directory */ if (st.st_mode & (S_IROTH | S_IXOTH)) { upslogx(LOG_WARNING, "%s is world readable", fn); } } int main(int argc, char **argv) { int i, cmd = 0; int do_background = 1; char *progname, *chroot_path = NULL; const char *user = NULL; struct passwd *new_uid = NULL; progname = argv[0]; /* pick up a default from configure --with-user */ user = RUN_AS_USER; /* yes, xstrdup - the conf handlers call free on this later */ statepath = xstrdup(dflt_statepath()); datapath = xstrdup(DATADIR); /* set up some things for later */ snprintf(pidfn, sizeof(pidfn), "%s/upsd.pid", altpidpath()); printf("Network UPS Tools upsd %s\n", UPS_VERSION); while ((i = getopt(argc, argv, "+h46p:r:i:fu:Vc:D")) != -1) { switch (i) { case 'h': help(progname); break; case 'p': case 'i': fatalx(EXIT_FAILURE, "Specifying a listening addresses with '-i
' and '-p '\n" "is deprecated. Use 'LISTEN
[]' in 'upsd.conf' instead.\n" "See 'man 8 upsd.conf' for more information."); case 'r': chroot_path = optarg; break; case 'f': do_background = 0; break; case 'u': user = optarg; break; case 'V': /* do nothing - we already printed the banner */ exit(EXIT_SUCCESS); case 'c': if (!strncmp(optarg, "reload", strlen(optarg))) cmd = SIGCMD_RELOAD; if (!strncmp(optarg, "stop", strlen(optarg))) cmd = SIGCMD_STOP; /* bad command given */ if (cmd == 0) help(progname); break; case 'D': do_background = 0; nut_debug_level++; break; #ifdef HAVE_IPV6 case '4': opt_af = AF_INET; break; case '6': opt_af = AF_INET6; break; #endif default: help(progname); break; } } if (cmd) { sendsignalfn(pidfn, cmd); exit(EXIT_SUCCESS); } argc -= optind; argv += optind; if (argc != 0) { help(progname); } setupsignals(); open_syslog("upsd"); /* send logging to the syslog pre-background for later use */ syslogbit_set(); /* do this here, since getpwnam() might not work in the chroot */ new_uid = get_user_pwent(user); if (chroot_path) { chroot_start(chroot_path); } /* handle upsd.conf */ load_upsdconf(0); /* 0 = initial */ /* start server */ server_load(); become_user(new_uid); if (chdir(statepath)) { fatal_with_errno(EXIT_FAILURE, "Can't chdir to %s", statepath); } /* check statepath perms */ check_perms(statepath); /* handle ups.conf */ read_upsconf(); upsconf_add(0); /* 0 = initial */ if (num_ups == 0) { fatalx(EXIT_FAILURE, "Fatal error: at least one UPS must be defined in ups.conf"); } ssl_init(); /* try to bring in the var/cmd descriptions */ desc_load(); /* handle upsd.users */ user_load(); if (do_background) { background(); writepid(pidfn); } else { memset(&pidfn, '\0', sizeof(pidfn)); } while (exit_flag == 0) { mainloop(); } upslogx(LOG_INFO, "Signal %d: exiting", exit_flag); upsd_cleanup(); exit(EXIT_SUCCESS); }