/* Copyright (c) 2008-2010, Dirk Krause 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 opyright 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 Dirk Krause nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. */ /** @file irshdown.c The irshdown program. */ #include "rshdown.h" #include #include #include #include #include #include #include #include "rshdownm.h" $(trace-include) /** Exit status. */ static int exval = 1; /** Command: Install. */ #define ACTION_INSTALL 0 /** Command: Uninstall. */ #define ACTION_UNINSTALL 1 /** Texts used by the program. */ static WCHAR *n[] = { /* 0 */ L"\\rshdown.exe", // binary name /* 1 */ L"RshDown", // display name of service /* 2 */ L"Tcpip\0Dnscache\0", // service dependencies /* 3 */ L"", // empty password for service account /* 4 */ L"Sender host (IP or host name): ", // IP or host name /* 5 */ L"Sender port number: ", // sender port number /* 6 */ L"Local port number: ", // local port number /* 7 */ L"Software\\RshDown", // registry key /* 8 */ L"sender", /* 9 */ L"file", /* 10 */ L"port", /* 11 */ L"Shut down computer when receiving shutdown datagram", /* 12 */ L"Datagram file: ", /* 13 */ L"System\\CurrentControlSet\\Services\\RshDown", /* 14 */ L"Description", /* 15 */ L"\\rshdownm.dll", /* 16 */ L"System\\CurrentControlSet\\Services\\Eventlog\\System\\RshDown", /* 17 */ L"EventMessageFile", // string file /* 18 */ L"CategoryMessageFile", // string file /* 19 */ L"CategoryCount", // DWORD 1 /* 20 */ L"TypesSupported", // DWORD 7 NULL }; /** Options. */ WCHAR *options[] = { L"-i", L"--install", L"-u", L"--uninstall", NULL }; /** Buffer for current working directory. */ static WCHAR b[1024]; /** File name of executable file. */ static WCHAR f[SIZEOF(b,WCHAR)]; /** Sender host name. */ static WCHAR h[1024]; /** Datagram file name */ static WCHAR fn[1024]; /** Find index of string \a s in array \a a. @param a Array of strings. @param s String to search. @return Index of \a s in \a a or -1 (not found). */ int array_index(WCHAR **a, WCHAR *s) { int back = -1; WCHAR **ptr; int i; $? "+ array_index \"%ls\"", s ptr = a; i = 0; while((*ptr != NULL) && (back == -1)) { if(wcscmp(*ptr, s) == 0) { back = i; } ptr++; i++; } $? "- array_index %d", back return back; } /** Chomp a text (Remove leading and trailing whitespaces). @param s Original text. @return Pointer to start of text or NULL. */ static WCHAR * do_chomp(WCHAR *s) { WCHAR *back = NULL; WCHAR *trail = NULL; WCHAR *ptr; ptr = s; while(*ptr) { switch(*ptr) { case L' ': case L'\t': case L'\r': case L'\n': { if(trail == NULL) { trail = ptr; } } break; default: { if(back == NULL) { back = ptr; } trail = NULL; } break; } ptr++; } if(trail) { *trail = L'\0'; } return back; } /** Uninstall rshdown service. */ static void uninstall(void) { BOOL removed; SC_HANDLE hManager = NULL; SC_HANDLE hService = NULL; SERVICE_STATUS svcStatus; DWORD dwCount; LONG res; fprintf(stderr, "Going to uninstall the service.\n"); fflush(stderr); removed = FALSE; dwCount = 0; hManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if(hManager != NULL) { hService = OpenService(hManager, n[1], SERVICE_ALL_ACCESS); if(hService != NULL) { if(QueryServiceStatus(hService, &svcStatus)) { if(svcStatus.dwCurrentState != SERVICE_STOPPED) { if(ControlService(hService, SERVICE_CONTROL_STOP, &svcStatus)) { do { Sleep(1000); if(QueryServiceStatus(hService, &svcStatus)) { if(svcStatus.dwCurrentState == SERVICE_STOPPED) { removed = TRUE; } else { dwCount++; } } else { dwCount = 60; } } while((dwCount < 60) && (removed == FALSE)); if(!removed) { fprintf(stderr, "ERROR: Failed to stop the service!\n"); fflush(stderr); } } } else { removed = TRUE; } if(removed) { removed = FALSE; if(DeleteService(hService)) { removed = TRUE; res = RegDeleteKey(HKEY_LOCAL_MACHINE, n[16]); if(res != ERROR_SUCCESS) { fprintf(stderr, "ERROR: Failed to remove eventlog registry key!\n"); fflush(stderr); } res = RegDeleteKey(HKEY_LOCAL_MACHINE, n[13]); } else { fprintf(stderr, "ERROR: Failed to uninstall the service!\n"); fflush(stderr); } } } else { fprintf(stderr, "ERROR: Failed to query service state!\n"); fflush(stderr); } CloseServiceHandle(hService); } else { fprintf(stderr, "ERROR: Service not found!\n"); fflush(stderr); } CloseServiceHandle(hManager); } else { fprintf(stderr, "ERROR: Failed to connect to service manager!\n"); fflush(stderr); } } /** Install rshdown as a service. @return TRUE on success, FALSE on error. */ static BOOL install_the_service(void) { BOOL back = FALSE; SC_HANDLE hManager; SC_HANDLE hService; hManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if(hManager != NULL) { hService = CreateService( hManager, // manager handle n[1], // service name n[1], // display name SERVICE_ALL_ACCESS, // access rights to service SERVICE_WIN32_OWN_PROCESS, // service type SERVICE_AUTO_START, // SERVICE_DEMAND_START SERVICE_ERROR_NORMAL, // error control f, // binary path name NULL, // load order group NULL, // tag ID n[2], // dependencies NULL, // service account name n[3] // password ); if(hService != NULL) { back = TRUE; CloseServiceHandle(hService); } CloseServiceHandle(hManager); } else { fprintf(stderr, "ERROR: Failed to connect to service manager!\n"); fflush(stderr); } return back; } /** Save expandable text value to registry. @param hk Registry key. @param k Entry name. @param v Entry value. @return TRUE on success, FALSE on error. */ static BOOL saveKeyValueExpand(HKEY hk, WCHAR *k, WCHAR *v) { BOOL back = FALSE; LONG res; res = RegSetValueEx( hk, k, 0, REG_EXPAND_SZ, (const BYTE *)v, (sizeof(WCHAR)*(wcslen(v)+1)) ); if(res == ERROR_SUCCESS) { back = TRUE; } return back; } /** Save text value to registry. @param hk Registry key. @param k Entry name. @param v Entry value. @return TRUE on success, FALSE on error. */ static BOOL saveKeyValue(HKEY hk, WCHAR *k, WCHAR *v) { BOOL back = FALSE; LONG res; res = RegSetValueEx( hk, k, 0, REG_SZ, (const BYTE *)v, (sizeof(WCHAR)*(wcslen(v)+1)) ); if(res == ERROR_SUCCESS) { back = TRUE; } return back; } /** Save DWORD value to registry. @param hk Registry key. @param k Entry name @param v DWORD value. @return TRUE on success, FALSE on error. */ static BOOL saveDword(HKEY hk, WCHAR *k, DWORD v) { BOOL back = FALSE; LONG res; res = RegSetValueEx( hk, k, 0, REG_DWORD, (const BYTE *)(&v), sizeof(DWORD) ); if(res == ERROR_SUCCESS) { back = TRUE; } return back; } /** Install rshdown as a service. */ static void install(void) { WCHAR il[1024], *ptr, *ptr2; BOOL back = FALSE; unsigned u; HKEY hk; DWORD dwDisp; LONG res; fprintf(stderr, "Going to install the service.\n"); fflush(stderr); if(GetModuleFileName(GetModuleHandle(NULL), b, SIZEOF(b,WCHAR))) { ptr = wcsrchr(b, L'\\'); if(ptr) { *ptr = L'\0'; wcscpy(f, b); if((wcslen(f) + wcslen(n[0])) < SIZEOF(f,WCHAR)) { wcscat(f, n[0]); fputws(n[4], stdout); // sender (IP/host) fgetws(il, SIZEOF(il,WCHAR)-1, stdin); ptr = do_chomp(il); if(ptr) { wcscpy(h, il); fputws(n[5], stdout); // sender port fgetws(il, SIZEOF(il,WCHAR)-1, stdin); if(swscanf(il, L"%u", &u) == 1) { #if _MSC_VER >= 1400 swprintf(il, (sizeof(il)/sizeof(WCHAR)), L":%u", u); #else swprintf(il, L":%u", u); #endif if((wcslen(h)+wcslen(il)) < SIZEOF(h,WCHAR)) { wcscat(h, il); fputws(n[6], stdout); // local port fgetws(il, SIZEOF(il,WCHAR)-1, stdin); if(swscanf(il, L"%u", &u) == 1) { #if _MSC_VER >= 1400 swprintf(il, (sizeof(il)/sizeof(WCHAR)), L"%u", u); #else swprintf(il, L"%u", u); #endif fputws(n[12], stdout); fgetws(fn, SIZEOF(fn,WCHAR)-1, stdin); ptr2 = do_chomp(fn); if(ptr2) { res = RegCreateKeyEx( HKEY_LOCAL_MACHINE, // parent key n[7], // key name 0, // reserviced NULL, // class REG_OPTION_NON_VOLATILE, // option KEY_ALL_ACCESS, // access permissions NULL, // security &hk, // result &dwDisp // disposition ); if(res == ERROR_SUCCESS) { if(saveKeyValue(hk, n[8], h)) { if(saveKeyValue(hk, n[9], fn)) { if(saveKeyValue(hk, n[10], il)) { back = install_the_service(); } else { fprintf(stderr, "ERROR: Failed to save local port number!\n"); fflush(stderr); } } else { fprintf(stderr, "ERROR: Failed to save file name!\n"); fflush(stderr); } } else { fprintf(stderr, "ERROR: Failed to save sender host!\n"); fflush(stderr); } RegCloseKey(hk); if(back) { res = RegCreateKeyEx( HKEY_LOCAL_MACHINE, n[13], 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hk, &dwDisp ); if(res == ERROR_SUCCESS) { if(!saveKeyValue(hk, n[14], n[11])) { back = FALSE; fprintf(stderr, "ERROR: Failed to save description for service!\n"); fflush(stderr); } RegCloseKey(hk); } else { back = FALSE; fprintf(stderr, "ERROR: Failed to open service registry key!\n"); fflush(stderr); } } } else { fprintf(stderr, "ERROR: Failed to open registry key for configuration!\n"); fflush(stderr); } } else { fprintf(stderr, "ERROR: File name must not be empty!\n"); fflush(stderr); } } else { fprintf(stderr, "ERROR: Local port must be numeric!\n"); fflush(stderr); } } else { fprintf(stderr, "ERROR: Combination of host:port too long!\n"); fflush(stderr); } } else { fprintf(stderr, "ERROR: Sender port must be numeric!\n"); fflush(stderr); } } } else { fprintf(stderr, "ERROR: Executable name too long!\n"); fflush(stderr); } } else { fprintf(stderr, "ERROR: No backslash in name of irshdown.exe!\n"); fflush(stderr); } } else { fprintf(stderr, "ERROR: Failed to find installation directory!\n"); fflush(stderr); } if(back) { wcscpy(f, b); if((wcslen(f) + wcslen(n[15])) < SIZEOF(f,WCHAR)) { wcscat(f, n[15]); res = RegCreateKeyEx( HKEY_LOCAL_MACHINE, n[16], 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hk, &dwDisp ); if(res == ERROR_SUCCESS) { if(saveKeyValueExpand(hk, n[17], f)) { if(saveKeyValueExpand(hk, n[18], f)) { if(saveDword(hk, n[19], 1)) { dwDisp = EVENTLOG_SUCCESS | EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; if(!saveDword(hk, n[20], dwDisp)) { back = FALSE; fprintf(stderr, "ERROR: Failed to save message types!\n"); fflush(stderr); } } else { back = FALSE; fprintf(stderr, "ERROR: Failed to save category count!\n"); fflush(stderr); } } else { back = FALSE; fprintf(stderr, "ERROR: Failed to save category message file name!\n"); fflush(stderr); } } else { back = FALSE; fprintf(stderr, "ERROR: Failed to save event message file name!\n"); fflush(stderr); } RegCloseKey(hk); } else { back = FALSE; fprintf(stderr, "ERROR: Failed to open registry for messages DLL!\n"); fflush(stderr); } } else { back = FALSE; fprintf(stderr, "ERROR: Message DLL file name too long!\n"); fflush(stderr); } } if(!back) { fprintf(stderr, "Errors occured, uninstalling the RshDown service.\n"); fflush(stderr); uninstall(); } else { exval = 0; fprintf(stderr, "The RshDown service was installed successfully.\n"); fflush(stderr); } } /** Run the required action (install or uninstall). @param action Flag for install or uninstall. */ static void run(int action) { switch(action) { case ACTION_UNINSTALL: { uninstall(); } break; default: { install(); } break; } } /** The main() function of the irshdown program. @param argc Number of command line arguments. @param argv Command line arguments array. @return 0 on success, any other value indicates an error. */ int wmain(int argc, WCHAR *argv[]) { int action = ACTION_INSTALL; $(trace-init irshdown.deb) $? "+ main" if(argc > 1) { switch(array_index(options, argv[1])) { case 2: case 3: { action = ACTION_UNINSTALL; } break; } } run(action); $? "- main %d", exval $(trace-end) exit(exval); return exval; }