1 // This file has been adapted to the Win32 version of apcctrl
2 // by Kern E. Sibbald.  Many thanks to ATT and James Weatherall,
3 // the original author, for providing an excellent template.
4 //
5 // Rewrite/Refactoring by Adam Kropelin
6 //
7 // Copyright (2007) Adam D. Kropelin
8 // Copyright (2000) Kern E. Sibbald
9 //
10 
11 // Implementation of service-oriented functionality of apcctrl
12 
13 #include "winapi.h"
14 #include "compat.h"
15 #include "winups.h"
16 #include "winservice.h"
17 #include <stdio.h>
18 
19 // Error message logging
20 void LogErrorMsg(const char *msg, const char *fname, int lineno);
21 #define log_error_message(msg) LogErrorMsg((msg), __FILE__, __LINE__)
22 
23 // No internationalization support
24 #define _(x) x
25 
26 // Internal service state (static)
27 SERVICE_STATUS         upsService::m_srvstatus;
28 SERVICE_STATUS_HANDLE  upsService::m_hstatus;
29 DWORD                  upsService::m_servicethread = 0;
30 
31 // Typedefs for dynamically loaded functions
32 typedef BOOL (WINAPI * ChangeServiceConfig2Func)(SC_HANDLE, DWORD, LPVOID);
33 typedef DWORD (* RegisterServiceProcessFunc)(DWORD, DWORD);
34 
35 // Internal service name
36 static char SERVICE_NAME[] = "apcctrl";
37 
38 // Displayed service name
39 static const char SERVICE_DISPLAYNAME[] = "apcctrl UPS Monitor";
40 
41 // Service description
42 static char APCCTRL_SERVICE_DESCRIPTION[] =
43    "apcctrl provides shutdown of your computer "
44    "in the event of a power failure.";
45 
46 // List other required serves
47 #define SERVICE_DEPENDENCIES __TEXT("tcpip\0afd\0+File System\0")
48 
49 // SERVICE MAIN ROUTINE
apcctrlServiceMain()50 int upsService::apcctrlServiceMain()
51 {
52    // How to run as a service depends upon the OS being used
53    switch (g_os_version_info.dwPlatformId) {
54 
55    // Windows 95/98/Me
56    case VER_PLATFORM_WIN32_WINDOWS:
57       // Obtain a handle to the kernel library
58       HINSTANCE kerneldll;
59       kerneldll = LoadLibrary("KERNEL32.DLL");
60       if (kerneldll == NULL) {
61          MessageBox(NULL,
62                     "KERNEL32.DLL not found: apcctrl service not started",
63                     "apcctrl Service", MB_OK);
64          break;
65       }
66 
67       // And find the RegisterServiceProcess function
68       RegisterServiceProcessFunc RegisterServiceProcess;
69       RegisterServiceProcess =
70          (RegisterServiceProcessFunc)GetProcAddress(
71             kerneldll, "RegisterServiceProcess");
72       if (RegisterServiceProcess == NULL) {
73          MessageBox(NULL,
74                     "Registry service not found: apcctrl service not started",
75                     "apcctrl Service", MB_OK);
76          log_error_message("Registry service not found");
77          FreeLibrary(kerneldll);
78          break;
79       }
80 
81       // Register this process with the OS as a service!
82       RegisterServiceProcess(0, 1);
83 
84       // Run the main program as a service
85       apcctrlAppMain(1);
86 
87       // Then remove the service from the system service table
88       RegisterServiceProcess(0, 0);
89 
90       // Free the kernel library
91       FreeLibrary(kerneldll);
92       break;
93 
94    // Windows NT, Win2K, WinXP
95    case VER_PLATFORM_WIN32_NT:
96       // Create a service entry table
97       SERVICE_TABLE_ENTRY dispatchTable[] = {
98          {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)ServiceMain},
99          {NULL, NULL}
100       };
101 
102      // Call the service control dispatcher with our entry table
103       if (!StartServiceCtrlDispatcher(dispatchTable)) {
104          log_error_message("StartServiceCtrlDispatcher failed.");
105       }
106       break;
107 
108    } /* end switch */
109 
110    return 0;
111 }
112 
113 // SERVICE MAIN ROUTINE
114 // NT/Win2K/WinXP ONLY !!!
ServiceMain(DWORD argc,char ** argv)115 void WINAPI upsService::ServiceMain(DWORD argc, char **argv)
116 {
117     // Register the service control handler
118     m_hstatus = RegisterServiceCtrlHandler(SERVICE_NAME, ServiceCtrl);
119     if (m_hstatus == 0) {
120        log_error_message("RegisterServiceCtlHandler failed");
121        MessageBox(NULL, "Contact Register Service Handler failure",
122                   "apcctrl service", MB_OK);
123        return;
124     }
125 
126     // Set up some standard service state values
127     m_srvstatus.dwServiceType = SERVICE_WIN32 | SERVICE_INTERACTIVE_PROCESS;
128     m_srvstatus.dwServiceSpecificExitCode = 0;
129 
130     // Give this status to the SCM
131     if (!ReportStatus(
132             SERVICE_START_PENDING,    // service state
133             NO_ERROR,                 // exit code
134             45000)) {                 // wait hint
135         log_error_message("ReportStatus STOPPED failed 1");
136         return;
137     }
138 
139     // Now start the service working thread
140     CreateThread(NULL, 0, ServiceWorkThread, NULL, 0, NULL);
141 }
142 
143 // SERVICE START ROUTINE - thread that calls apcctrlAppMain
144 // NT/Win2K/WinXP ONLY !!!
ServiceWorkThread(LPVOID lpwThreadParam)145 DWORD WINAPI upsService::ServiceWorkThread(LPVOID lpwThreadParam)
146 {
147     // report the status to the service control manager.
148     if (!ReportStatus(
149           SERVICE_RUNNING,       // service state
150           NO_ERROR,              // exit code
151           0)) {                  // wait hint
152        MessageBox(NULL, "Report Service failure", "apcctrl Service", MB_OK);
153        log_error_message("ReportStatus RUNNING failed");
154        return 0;
155     }
156 
157     // Save the current thread identifier
158     m_servicethread = GetCurrentThreadId();
159 
160     /* Call apcctrl main code */
161     apcctrlAppMain(1);
162 
163     /* Mark that we're no longer running */
164     m_servicethread = 0;
165 
166     /* Tell the service manager that we've stopped */
167     ReportStatus(SERVICE_STOPPED, 0, 0);
168     return 0;
169 }
170 
171 // SERVICE STOP ROUTINE - NT/Win2K/WinXP ONLY !!!
ServiceStop()172 void upsService::ServiceStop()
173 {
174    apcctrlTerminate();
175 }
176 
177 // SERVICE INSTALL ROUTINE
InstallService(bool quiet)178 int upsService::InstallService(bool quiet)
179 {
180    const unsigned int MAXPATH = 2048;
181 
182    // Get the filename of this executable
183    char path[MAXPATH];
184    if (GetModuleFileName(NULL, path, MAXPATH) == 0) {
185       if (!quiet) {
186          MessageBox(NULL,
187                     "Unable to install apcctrl service", SERVICE_NAME,
188                     MB_ICONEXCLAMATION | MB_OK);
189       }
190       return 0;
191    }
192 
193    // Append the service-start flag to the end of the path
194    // Length is path len plus quotes, space, start flag, and NUL terminator
195    char servicecmd[MAXPATH];
196    if (strlen(path) + 4 + strlen(apcctrlRunService) < MAXPATH) {
197       sprintf(servicecmd, "\"%s\" %s", path, apcctrlRunService);
198    } else {
199       if (!quiet) {
200          MessageBox(NULL,
201                     "Service command length too long. Service not registered.",
202                     SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
203       }
204       return 0;
205    }
206 
207    // How to add the apcctrl service depends upon the OS
208    switch (g_os_version_info.dwPlatformId) {
209 
210    // Windows 95/98/Me
211    case VER_PLATFORM_WIN32_WINDOWS:
212       // Locate the RunService registry entry
213       HKEY runservices;
214       if (RegCreateKey(HKEY_LOCAL_MACHINE,
215               "Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",
216               &runservices) != ERROR_SUCCESS) {
217          log_error_message("Cannot write System Registry");
218          MessageBox(NULL, _("The System Registry could not be updated - "
219                             "the apcctrl service was not installed"),
220                     SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
221          break;
222       }
223 
224       // Attempt to add a apcctrl key
225       if (RegSetValueEx(runservices, SERVICE_NAME, 0, REG_SZ,
226             (unsigned char *)servicecmd, strlen(servicecmd)+1) != ERROR_SUCCESS) {
227          RegCloseKey(runservices);
228          MessageBox(NULL, "The apcctrl service could not be installed",
229                     SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
230          break;
231       }
232 
233       RegCloseKey(runservices);
234 
235       // Indicate that we're installed to run as a service
236       SetServiceFlag(1);
237 
238       // We have successfully installed the service!
239       if (!quiet) {
240          MessageBox(NULL,
241                     _("The apcctrl UPS service was successfully installed.\n"
242                       "The service may be started by double clicking on the\n"
243                       "apcctrl \"Start\" icon and will automatically\n"
244                       "be run the next time this machine is rebooted. "),
245                     SERVICE_NAME, MB_ICONINFORMATION | MB_OK);
246       }
247       break;
248 
249    // Windows NT, Win2K, WinXP
250    case VER_PLATFORM_WIN32_NT:
251       // Open the default, local Service Control Manager database
252       SC_HANDLE hsrvmanager;
253       hsrvmanager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
254       if (hsrvmanager == NULL) {
255          MessageBox(NULL,
256             _("The Service Control Manager could not be contacted - "
257               "the apcctrl service was not installed"),
258             SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
259          break;
260       }
261 
262       // Create an entry for the apcctrl service
263       SC_HANDLE hservice;
264       hservice = CreateService(
265               hsrvmanager,                    // SCManager database
266               SERVICE_NAME,                   // name of service
267               SERVICE_DISPLAYNAME,            // name to display
268               SERVICE_ALL_ACCESS,             // desired access
269               SERVICE_WIN32_OWN_PROCESS |     // service type
270                  SERVICE_INTERACTIVE_PROCESS,
271               SERVICE_AUTO_START,             // start type
272               SERVICE_ERROR_NORMAL,           // error control type
273               servicecmd,                     // service's binary
274               NULL,                           // no load ordering group
275               NULL,                           // no tag identifier
276               SERVICE_DEPENDENCIES,           // dependencies
277               NULL,                           // LocalSystem account
278               NULL);                          // no password
279       if (hservice == NULL) {
280          if (!quiet || GetLastError() != ERROR_SERVICE_EXISTS) {
281             MessageBox(NULL,
282                        "The apcctrl service could not be installed",
283                        SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
284          }
285          CloseServiceHandle(hsrvmanager);
286          break;
287       }
288 
289       SetServiceDescription(hservice, APCCTRL_SERVICE_DESCRIPTION);
290 
291       CloseServiceHandle(hservice);
292       CloseServiceHandle(hsrvmanager);
293 
294       // Indicate that we're installed to run as a service
295       SetServiceFlag(1);
296 
297       // Everything went fine
298       if (!quiet) {
299          MessageBox(NULL,
300               _("The apcctrl UPS service was successfully installed.\n"
301                 "The service may be started from the Control Panel and will\n"
302                 "automatically be run the next time this machine is rebooted."),
303               SERVICE_NAME,
304               MB_ICONINFORMATION | MB_OK);
305       }
306       break;
307 
308    default:
309       MessageBox(NULL,
310                  _("Unknown Windows operating system.\n"
311                  "Cannot install apcctrl service.\n"),
312                  SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
313        break;
314    }
315 
316    return 0;
317 }
318 
319 
320 // SERVICE REMOVE ROUTINE
RemoveService(bool quiet)321 int upsService::RemoveService(bool quiet)
322 {
323    // How to remove the apcctrl service depends upon the OS
324    switch (g_os_version_info.dwPlatformId) {
325 
326    // Windows 95/98/Me
327    case VER_PLATFORM_WIN32_WINDOWS:
328       // Locate the RunService registry entry
329       HKEY runservices;
330       if (RegOpenKey(HKEY_LOCAL_MACHINE,
331               "Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",
332               &runservices) != ERROR_SUCCESS) {
333          if (!quiet) {
334             MessageBox(NULL,
335                        _("Could not find registry entry.\n"
336                          "Service probably not registerd - "
337                          "the apcctrl service was not removed"),
338                        SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
339          }
340       } else {
341          // Attempt to delete the apcctrl key
342          if (RegDeleteValue(runservices, SERVICE_NAME) != ERROR_SUCCESS) {
343             if (!quiet) {
344                MessageBox(NULL, _("Could not delete Registry key.\n"
345                                   "The apcctrl service could not be removed"),
346                           SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
347             }
348          }
349 
350          RegCloseKey(runservices);
351          break;
352       }
353 
354       // Try to kill any running copy of apcctrl
355       apcctrlTerminate();
356 
357       // Indicate that we're no longer installed to run as a service
358       SetServiceFlag(0);
359 
360       // We have successfully removed the service!
361       if (!quiet) {
362          MessageBox(NULL, "The apcctrl service has been removed",
363                     SERVICE_NAME, MB_ICONINFORMATION | MB_OK);
364       }
365       break;
366 
367    // Windows NT, Win2K, WinXP
368    case VER_PLATFORM_WIN32_NT:
369       SC_HANDLE hservice = OpenNTService();
370       if (!StopNTService(hservice)) {
371          // Service could not be stopped
372          MessageBox(NULL, "The apcctrl service could not be stopped",
373                     SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
374       }
375 
376       if(DeleteService(hservice)) {
377          // Indicate that we're no longer installed to run as a service
378          SetServiceFlag(0);
379 
380          // Service successfully removed
381          if (!quiet) {
382             MessageBox(NULL, "The apcctrl service has been removed",
383                        SERVICE_NAME, MB_ICONINFORMATION | MB_OK);
384          }
385       } else {
386          // Failed to remove
387          MessageBox(NULL, "The apcctrl service could not be removed",
388                     SERVICE_NAME, MB_ICONEXCLAMATION | MB_OK);
389       }
390 
391       CloseServiceHandle(hservice);
392       break;
393    }
394 
395    return 0;
396 }
397 
398 // USEFUL SERVICE SUPPORT ROUTINES
399 
400 // Service control routine
ServiceCtrl(DWORD ctrlcode)401 void WINAPI upsService::ServiceCtrl(DWORD ctrlcode)
402 {
403     // What control code have we been sent?
404     switch(ctrlcode) {
405     case SERVICE_CONTROL_STOP:
406         // STOP : The service must stop
407         m_srvstatus.dwCurrentState = SERVICE_STOP_PENDING;
408         ServiceStop();
409         break;
410 
411     case SERVICE_CONTROL_INTERROGATE:
412         // QUERY : Service control manager just wants to know our state
413         break;
414 
415      default:
416         // Control code not recognised
417         break;
418     }
419 
420     // Tell the control manager what we're up to.
421     ReportStatus(m_srvstatus.dwCurrentState, NO_ERROR, 0);
422 }
423 
424 // Service manager status reporting
ReportStatus(DWORD state,DWORD exitcode,DWORD waithint)425 BOOL upsService::ReportStatus(DWORD state,
426                               DWORD exitcode,
427                               DWORD waithint)
428 {
429     static DWORD checkpoint = 1;
430 
431     // If we're in the start state then we don't want the control manager
432     // sending us control messages because they'll confuse us.
433     if (state == SERVICE_START_PENDING)
434        m_srvstatus.dwControlsAccepted = 0;
435     else
436        m_srvstatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
437 
438     // Save the new status we've been given
439     m_srvstatus.dwCurrentState = state;
440     m_srvstatus.dwWin32ExitCode = exitcode;
441     m_srvstatus.dwWaitHint = waithint;
442 
443     // Update the checkpoint variable to let the SCM know that we
444     // haven't died if requests take a long time
445     if ((state == SERVICE_RUNNING) || (state == SERVICE_STOPPED))
446        m_srvstatus.dwCheckPoint = 0;
447     else
448        m_srvstatus.dwCheckPoint = checkpoint++;
449 
450     // Tell the SCM our new status
451     BOOL result = SetServiceStatus(m_hstatus, &m_srvstatus);
452     if (!result)
453        log_error_message("SetServiceStatus failed");
454 
455     return result;
456 }
457 
458 // Error reporting
LogErrorMsg(const char * message,const char * fname,int lineno)459 void LogErrorMsg(const char *message, const char *fname, int lineno)
460 {
461    // Get the error code
462    LPTSTR msg;
463    DWORD error = GetLastError();
464    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
465                  FORMAT_MESSAGE_FROM_SYSTEM,
466                  NULL,
467                  error,
468                  0,
469                  (LPTSTR)&msg,
470                  0,
471                  NULL);
472 
473    // Use event logging to log the error
474    HANDLE heventsrc = RegisterEventSource(NULL, SERVICE_NAME);
475    if (heventsrc == NULL)
476       return;
477 
478    char msgbuff[256];
479    snprintf(msgbuff, sizeof(msgbuff), "\n\n%s error: %ld at %s:%d",
480       SERVICE_NAME, error, fname, lineno);
481 
482    const char *strings[3];
483    strings[0] = msgbuff;
484    strings[1] = message;
485    strings[2] = msg;
486 
487    ReportEvent(heventsrc,              // handle of event source
488                EVENTLOG_ERROR_TYPE,    // event type
489                0,                      // event category
490                0,                      // event ID
491                NULL,                   // current user's SID
492                3,                      // strings in 'strings'
493                0,                      // no bytes of raw data
494                (const char **)strings, // array of error strings
495                NULL);                  // no raw data
496 
497    DeregisterEventSource(heventsrc);
498    LocalFree(msg);
499 }
500 
SetServiceDescription(SC_HANDLE hService,LPSTR lpDesc)501 void upsService::SetServiceDescription(SC_HANDLE hService, LPSTR lpDesc)
502 {
503    HINSTANCE hLib = LoadLibrary("ADVAPI32.DLL");
504    if (!hLib)
505       return;
506 
507    ChangeServiceConfig2Func ChangeServiceConfig2 =
508       (ChangeServiceConfig2Func)GetProcAddress(hLib, "ChangeServiceConfig2A");
509    if (!ChangeServiceConfig2) {
510       FreeLibrary(hLib);
511       return;
512    }
513 
514    SERVICE_DESCRIPTION sdBuf;
515    sdBuf.lpDescription = lpDesc;
516 
517    ChangeServiceConfig2(
518       hService,                   // handle to service
519       SERVICE_CONFIG_DESCRIPTION, // change: description
520       &sdBuf);                    // value: new description
521 
522    FreeLibrary(hLib);
523 }
524 
SetServiceFlag(DWORD flag)525 void upsService::SetServiceFlag(DWORD flag)
526 {
527    // Create or open HKLM\Software\apcctrl key
528    HKEY apcctrl;
529    RegCreateKey(HKEY_LOCAL_MACHINE, "Software\\apcctrl", &apcctrl);
530 
531    // Add InstalledService value
532    RegSetValueEx(
533       apcctrl, "InstalledService", 0, REG_DWORD, (BYTE*)&flag, sizeof(flag));
534 }
535 
StopNTService(SC_HANDLE hservice)536 BOOL upsService::StopNTService(SC_HANDLE hservice)
537 {
538    // Try to stop the apcctrl service
539    SERVICE_STATUS status;
540    status.dwCurrentState = SERVICE_RUNNING;
541    if (ControlService(hservice, SERVICE_CONTROL_STOP, &status)) {
542       while(QueryServiceStatus(hservice, &status)) {
543          if (status.dwCurrentState == SERVICE_STOP_PENDING) {
544             Sleep(1000);
545          } else {
546             break;
547          }
548       }
549    }
550 
551    return status.dwCurrentState == SERVICE_STOPPED;
552 }
553 
OpenNTService()554 SC_HANDLE upsService::OpenNTService()
555 {
556    // Open the SCM
557    SC_HANDLE hscm = OpenSCManager(
558       NULL,                   // machine (NULL == local)
559       NULL,                   // database (NULL == default)
560       SC_MANAGER_ALL_ACCESS); // access required
561    if (hscm == NULL) {
562       return NULL;
563    }
564 
565    // Open the service
566    SC_HANDLE hservice = OpenService(hscm, SERVICE_NAME, SERVICE_ALL_ACCESS);
567 
568    // Close SCM and return service handle
569    CloseServiceHandle(hscm);
570    return hservice;
571 }
572