/* NetHalt - NetHalt server * Copyright (C) 2009 Daniel Collins * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of the author nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define FD_SETSIZE 256 #include #include #include #include #include #include extern "C" { #include "lib.h" #include "ntservice.h" #include "globcmp.h" char const *svc_name = "nhserver"; char const *svc_log = "NetHalt Server"; } struct client { int sockfd; time_t event; char hostname[256]; int hsize; }; struct sdtime { int days; int hour; int min; int sec; struct sdtime *next; }; struct section { std::string name; int set_warning, warning; int set_abort, abort; int set_delay, delay; int set_sdtimes; std::vector sdtimes; }; static std::list clients; static int listener = -1; static fd_set read_fds; static std::vector sections; static HANDLE ini_file = INVALID_HANDLE_VALUE; static FILETIME ini_mtime = {0,0}; static HKEY regkey = NULL; static int client_timeout = 30; static void close_client(int sockfd); static void reload_ini(void); static void ini_pint(int *buf, int *svar, char const *str, int lnum); static void ini_bool(int *buf, int *svar, char const *str, int lnum); static void set_sdtimes(std::vector *sdtimes, char const *str, int lnum); static void respond_config(int sockfd, char const *hostname); static std::string reg_get_string(char const *name); static DWORD reg_get_dword(char const *name); void svc_init(void) { DWORD dwval; WSADATA wsinfo; dwval = RegOpenKeyEx( HKEY_LOCAL_MACHINE, "SOFTWARE\\NetHalt Server", 0, KEY_QUERY_VALUE, ®key ); if(dwval != ERROR_SUCCESS) { die("Failed to open registry key: %s", w32_error(dwval)); } std::string bind_addr = reg_get_string("bind_addr"); uint16_t port = reg_get_dword("bind_port"); client_timeout = reg_get_dword("client_timeout"); char filename[1024]; GetModuleFileName(NULL, filename, 1024); strcpy(strrchr(filename, '\\'), "\\nethalt.ini"); ini_file = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if(ini_file == INVALID_HANDLE_VALUE) { die("Failed to open nethalt.ini: %s", w32_error(GetLastError())); } dwval = WSAStartup(MAKEWORD(2,2), &wsinfo); if(dwval) { die("Failed to initialize winsock: %s", w32_error(dwval)); } listener = socket(AF_INET, SOCK_STREAM, 0); if(listener == -1) { die("Failed to create socket: %s", w32_error(WSAGetLastError())); } sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(bind_addr.c_str()); addr.sin_port = htons(port); if(bind(listener, (struct sockaddr*)&addr, sizeof(addr)) == -1) { die("Failed to bind: %s", w32_error(WSAGetLastError())); } if(listen(listener, 16) == -1) { die("Failed to listen: %s", w32_error(WSAGetLastError())); } FD_ZERO(&read_fds); FD_SET(listener, &read_fds); } void svc_main(void) { fd_set select_fds; int fd, i, sret; struct timeval timeout; time_t last = 0, now; FILETIME modified = {0,0}; GetFileTime(ini_file, NULL, NULL, &modified); ini_mtime = modified; reload_ini(); while(svc_status.dwCurrentState == SERVICE_RUNNING) { select_fds = read_fds; timeout.tv_sec = 1; timeout.tv_usec = 0; sret = select(0, &select_fds, NULL, NULL, &timeout); if(sret == -1) { die( "Failed to wait for socket events: %s", w32_error(WSAGetLastError()) ); } GetFileTime(ini_file, NULL, NULL, &modified); if(memcmp(&modified, &ini_mtime, sizeof(FILETIME))) { ini_mtime = modified; reload_ini(); } if(FD_ISSET(listener, &select_fds)) { struct client client; client.event = time(NULL); client.hsize = 0; client.sockfd = accept(listener, NULL, NULL); if(client.sockfd >= 0) { struct linger lopt; lopt.l_onoff = 0; setsockopt(client.sockfd, SOL_SOCKET, SO_LINGER, (const char*)&lopt, sizeof(lopt)); clients.push_back(client); if(clients.size() == FD_SETSIZE) { FD_CLR(listener, &read_fds); } FD_SET(client.sockfd, &read_fds); }else{ LogError("Failed to accept new connection: %s", w32_error(WSAGetLastError())); } } std::list::iterator next = clients.begin(), client; std::list::iterator end = clients.end(); now = time(NULL); while(next != end) { client = next; next++; if(now > client->event + client_timeout) { close_client(client->sockfd); continue; } if(!FD_ISSET(client->sockfd, &select_fds)) { continue; } client->event = now; i = recv(client->sockfd, client->hostname+client->hsize, sizeof(client->hostname)-client->hsize, 0); if(i == -1) { LogError("Error reading from client: %s", w32_error(WSAGetLastError())); } if(i > 0) { client->hsize += i; if(memchr(client->hostname, '\0', client->hsize)) { respond_config(client->sockfd, client->hostname); i = 0; } } if(i <= 0 || client->hsize == sizeof(client->hostname)) { close_client(client->sockfd); } } } } void svc_cleanup(void) { while(!clients.empty()) { closesocket(clients.begin()->sockfd); } if(listener != -1) { closesocket(listener); listener = -1; } if(ini_file != INVALID_HANDLE_VALUE) { CloseHandle(ini_file); ini_file = INVALID_HANDLE_VALUE; } if(regkey) { RegCloseKey(regkey); regkey = NULL; } WSACleanup(); } static void close_client(int sockfd) { std::list::iterator node = clients.begin(); std::list::iterator end = clients.end(); while(node != end) { if(node->sockfd == sockfd) { FD_CLR(sockfd, &read_fds); closesocket(sockfd); if(clients.size() == FD_SETSIZE) { FD_SET(listener, &read_fds); } clients.erase(node); break; } node++; } } /* Load configuration from nethalt.ini * Returns 1 on success, zero if an I/O error occurred */ static void reload_ini(void) { char buf[1024]; DWORD i, n, lnum = 0, sopen = 0; struct section section; std::vector lines; std::string line; SetFilePointer(ini_file, 0, NULL, FILE_BEGIN); while(ReadFile(ini_file, buf, 1024, &i, NULL)) { if(!i) { goto PARSE; } for(n = 0; n < i; n++) { if(buf[n] == '\n') { lines.push_back(line); line.clear(); }else if(buf[n] == '\r') { continue; }else{ line.append(1, buf[n]); } } } if(GetLastError() != ERROR_HANDLE_EOF) { LogError("Error reading nethalt.ini: %s", w32_error(GetLastError())); return; } PARSE: std::vector::iterator next = lines.begin(), cur; std::vector::iterator last = lines.end(); while(next != last) { lnum++; cur = next; next++; std::string name_s(*cur, strspn(cur->c_str(), "\t ")); char const *name = name_s.c_str(), *value = ""; if(name[0] == '[') { if(sopen) { sections.push_back(section); } name_s.erase(0, strspn(name_s.c_str(), "[ ")); name_s.erase(strcspn(name_s.c_str(), " ]")); section.name = name_s; section.set_warning = 0; section.set_abort = 0; section.set_delay = 0; section.set_sdtimes = 0; sopen = 1; continue; } std::string value_s(name+strcspn(name, "\t= ")); value_s.erase(0, strspn(value_s.c_str(), "\t= ")); value = value_s.c_str(); name_s.erase(strcspn(name, "\t= ")); name = name_s.c_str(); if(*name == '\0' || *name == '#' || *name == ';') { continue; } if(ncase_eq(name, "Warning")) { ini_pint(&(section.warning), &(section.set_warning), value, lnum); }else if(ncase_eq(name, "Abort")) { ini_bool(&(section.abort), &(section.set_abort), value, lnum); }else if(ncase_eq(name, "Delay")) { ini_pint(&(section.delay), &(section.set_delay), value, lnum); }else if(ncase_eq(name, "Shutdown")) { section.set_sdtimes = 1; set_sdtimes(&(section.sdtimes), value, lnum); }else{ LogWarning("Parse error at nethalt.ini, line %d:\nUnknown directive '%s'", lnum, name); } } if(sopen) { sections.push_back(section); } } /* Check a string is a valid positive integer, and if so, convert it to an int * and store it in buf. */ static void ini_pint(int *buf, int *svar, char const *str, int lnum) { int n; for(n = 0; str[n] != '\0'; n++) { if(!isdigit(str[n])) { LogWarning("Parse error at nethalt.ini, line %d:\nInvalid integer", lnum); return; } } *buf = atoi(str); *svar = 1; } /* Check a string contains a valid boolean value, and if so, set *buf to 1/0 to * represent true/false. */ static void ini_bool(int *buf, int *svar, char const *str, int lnum) { if(ncase_eq(str, "yes") || ncase_eq(str, "y") || ncase_eq(str, "true")) { *buf = 1; *svar = 1; }else if(ncase_eq(str, "no") || ncase_eq(str, "n") || ncase_eq(str, "false")) { *buf = 0; *svar = 1; }else{ LogWarning("Parse error at nethalt.ini, line %d:\nInvalid boolean value", lnum); } } /* Set shutdown times */ static void set_sdtimes(std::vector *sdtimes, char const *str, int lnum) { struct sdtime sdtime; int n, step; sdtimes->clear(); str += strspn(str, " "); while(*str) { step = 0; sdtime.days = 127; sdtime.sec = 0; std::string disp(str, strcspn(str, " ")); if(!isdigit(str[0])) { goto INVTIME; } for(n = 0; str[n]; n++) { if(isdigit(str[n])) { continue; } if(str[n] == ';' && isdigit(str[n-1]) && isdigit(str[n+1]) && step == 0) { step = 1; continue; } if(str[n] == ':' && isdigit(str[n-1]) && isdigit(str[n+1]) && step < 3) { step = (step == 2 ? 3 : 2); continue; } if(str[n] == ' ' && step >= 2) { break; } goto INVTIME; } if(step < 2) { goto INVTIME; } if(strchr(str, ';') && strchr(str, ';') < strchr(str, ':')) { sdtime.days = atoi(str); str = strchr(str, ';')+1; if(sdtime.days > 127 || sdtime.days < 1) { goto INVTIME; } } sdtime.hour = atoi(str); str = strchr(str, ':')+1; sdtime.min = atoi(str); if(step == 3) { str = strchr(str, ':')+1; sdtime.sec = atoi(str); } if(sdtime.hour < 0 || sdtime.hour > 23 || sdtime.min < 0 || sdtime.min > 59 || sdtime.sec < 0 || sdtime.sec > 59) { goto INVTIME; } sdtimes->push_back(sdtime); NEXT: str += strcspn(str, " "); str += strspn(str, " "); continue; INVTIME: LogWarning("Parse error at nethalt.ini, line %d:\nInvalid shutdown time: '%s'", lnum, disp.c_str()); goto NEXT; } } static void respond_config(int sockfd, char const *hostname) { std::vector::iterator cur = sections.begin(); std::vector::iterator end = sections.end(); struct section config; config.warning = 300; config.abort = 0; config.delay = 0; while(cur != end) { if(!globcmp(hostname, cur->name.c_str(), GLOB_ALL | GLOB_IGNCASE)) { cur++; continue; } if(cur->set_warning) { config.warning = cur->warning; } if(cur->set_abort) { config.abort = cur->abort; } if(cur->set_delay) { config.delay = cur->delay; } if(cur->set_sdtimes) { config.sdtimes = cur->sdtimes; } cur++; } std::string config_text; char buf[256]; sprintf(buf, "warning=%d\n", config.warning); config_text.append(buf); sprintf(buf, "abort=%d\n", config.abort); config_text.append(buf); sprintf(buf, "delay=%d\n", config.delay); config_text.append(buf); std::vector::iterator sdtime = config.sdtimes.begin(); while(sdtime != config.sdtimes.end()) { sprintf(buf, "sdtime=%d;%d:%d:%d\n", sdtime->days, sdtime->hour, sdtime->min ,sdtime->sec); config_text.append(buf); sdtime++; } int size = config_text.length(), sent = 0; while(sent < size) { int i = send(sockfd, config_text.c_str()+sent, size-sent, 0); if(i == -1) { LogError("Error sending to client: %s", w32_error(WSAGetLastError())); break; } sent += i; } } static std::string reg_get_string(char const *name) { DWORD errnum, size = 0; errnum = RegQueryValueEx(regkey, name, NULL, NULL, NULL, &size); if(errnum != ERROR_SUCCESS) { die("Failed to query '%s' value: %s", name, w32_error(errnum)); } char *buf = new char[size+1]; buf[size] = '\0'; errnum = RegQueryValueEx(regkey, name, NULL, NULL, (BYTE*)buf, &size); if(errnum != ERROR_SUCCESS) { die("Failed to read '%s' value: %s", name, w32_error(errnum)); } std::string value = buf; delete buf; return value; } static DWORD reg_get_dword(char const *name) { DWORD errnum, value, size = sizeof(DWORD); errnum = RegQueryValueEx(regkey, name, NULL, NULL, (BYTE*)&value, &size); if(errnum != ERROR_SUCCESS) { die("Failed to read '%s' value: %s", name, w32_error(errnum)); } return value; }