/*
   Bacula® - The Network Backup Solution

   Copyright (C) 2007-2010 Free Software Foundation Europe e.V.

   The main author of Bacula is Kern Sibbald, with contributions from
   many others, a complete list can be found in the file AUTHORS.
   This program is Free Software; you can redistribute it and/or
   modify it under the terms of version three of the GNU Affero General Public
   License as published by the Free Software Foundation and included
   in the file LICENSE.

   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 Affero General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301, USA.

   Bacula® is a registered trademark of Kern Sibbald.
   The licensor of Bacula is the Free Software Foundation Europe
   (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
   Switzerland, email:ftf@fsfeurope.org.
 */
/* 
 * 
 *  Kern Sibbald, August 2007
 *
 * This is a generic service routine, which is used by all three
 *  of the daemons. Each one compiles it with slightly different
 *  #defines.
 *
 */

#include "bacula.h"
#include "win32.h"

/* Forward reference */
static void set_service_description(SC_HANDLE hSCManager, 
                                    SC_HANDLE hService, LPSTR lpDesc);
  
/* Other Window component dependencies */
#define BAC_DEPENDENCIES __TEXT("tcpip\0afd\0") 

/* Service globals */
SERVICE_STATUS_HANDLE  service_handle;
SERVICE_STATUS service_status;
DWORD service_error = 0;
static bool is_service = false;

/* Forward references */
void WINAPI serviceControlCallback(DWORD ctrlcode);
BOOL ReportStatus(DWORD state, DWORD exitcode, DWORD waithint);
DWORD WINAPI baculaWorkerThread(LPVOID lpwThreadParam);


/*
 * Post a message to a running instance of the app
 */
bool postToBacula(UINT message, WPARAM wParam, LPARAM lParam)
{
   /* Locate the Bacula menu window */
   HWND hservwnd = FindWindow(APP_NAME, NULL);
   if (hservwnd == NULL) {
      return false;
   }

   /* Post the message to Bacula */
   PostMessage(hservwnd, message, wParam, lParam);
   return true;
}


/*
 * Running as a service?
 */
bool isAService()
{
   return is_service;
}

/* 
 * terminate any running Bacula
 */
int stopRunningBacula()
{
   postToBacula(WM_CLOSE, 0, 0);
   sleep(5);
   return 0;
}

/*
 * New style service start callback handler for the OS.
 *   the OS returns control here immediately after starting
 *   the service.
 */
void WINAPI serviceStartCallback(DWORD argc, char **argv)
{
   DWORD dwThreadID;
  
   /* Register our service */
   service_handle = RegisterServiceCtrlHandler(APP_NAME, serviceControlCallback);
   if (!service_handle) {
      log_error_message(_("RegisterServiceCtlHandler failed")); 
      MessageBox(NULL, _("Failure contacting the Service Handler"),
         APP_DESC, MB_OK);
      return;
   }

   service_status.dwServiceType = SERVICE_WIN32;
   service_status.dwServiceSpecificExitCode = 0;

   /* Report status */
   if (!ReportStatus(SERVICE_START_PENDING, NO_ERROR, 45000)) {
       ReportStatus(SERVICE_STOPPED, service_error,  0);
       log_error_message(_("Service start report failed")); 
       return;
   }

   /* Now create the Bacula worker thread */
   (void)CreateThread(NULL, 0, baculaWorkerThread, NULL, 0, &dwThreadID);
   return;
}

/*
 *  Stop our service 
 */
static void serviceStop()
{
   /* Post a quit message our service thread */
   if (service_thread_id != 0) {
      PostThreadMessage(service_thread_id, WM_QUIT, 0, 0);
   }
}

/*
 * Service Control callback handler.  The OS can call us here
 *   at any time, most often to stop the service.
 */
void WINAPI serviceControlCallback(DWORD ctrlcode)
{
   switch(ctrlcode) {
   case SERVICE_CONTROL_STOP:
      service_status.dwCurrentState = SERVICE_STOP_PENDING;
      serviceStop();  /* our stop service routine */
      break;
   }

   /* Report our status */
   ReportStatus(service_status.dwCurrentState, NO_ERROR, 0);
}


/*
 * Run Bacula as a service 
 */
int baculaServiceMain()
{
   is_service = true;                 /* indicate we are running as a service */

   if (have_service_api) {            /* New style service API */
      /* Tell OS where to dispatch service calls to us */
      SERVICE_TABLE_ENTRY dispatchTable[] = {
         {APP_NAME, (LPSERVICE_MAIN_FUNCTION)serviceStartCallback},
         {NULL, NULL}};

      /* Start the service control dispatcher */
      if (!StartServiceCtrlDispatcher(dispatchTable)) {
         log_error_message(_("StartServiceCtrlDispatcher failed."));
      }
      /* Note, this thread continues in the ServiceCallback routine */

   } else {                           /* old style Win95/98/Me */
      HINSTANCE kerneldll = LoadLibrary("KERNEL32.DLL");
      if (kerneldll == NULL) {
         MessageBox(NULL, _("KERNEL32.DLL not found: Bacula service not started"), 
             APP_DESC, MB_OK);
         return 1;
      }

      /* Get entry point for RegisterServiceProcess function */
      DWORD (WINAPI *RegisterService)(DWORD, DWORD);
      RegisterService = (DWORD (WINAPI *)(DWORD, DWORD))
              GetProcAddress(kerneldll, "RegisterServiceProcess");
      if (RegisterService == NULL) {
         MessageBox(NULL, _("Registry service not found: Bacula service not started"),
            APP_DESC, MB_OK);
         log_error_message(_("Registry service entry point not found")); 
         return 1;
      }
      
      RegisterService(0, 1);             /* register us as a service */
      BaculaAppMain();                   /* call the main Bacula code */
      RegisterService(0, 0);             /* terminate the service */
      FreeLibrary(kerneldll);            /* free up kernel dll */
   } 
   return 0;
}


/* 
 * New style service bacula worker thread
 */
DWORD WINAPI baculaWorkerThread(LPVOID lpwThreadParam)
{
   service_thread_id = GetCurrentThreadId();

   if (!ReportStatus(SERVICE_RUNNING, NO_ERROR, 0)) {
      MessageBox(NULL, _("Report Service failure"), APP_DESC, MB_OK);
      log_error_message("ReportStatus RUNNING failed"); 
      return 0;
   }

   /* Call Bacula main code */
   BaculaAppMain();

   /* Mark that we're no longer running */
   service_thread_id = 0;

   /* Tell the service manager that we've stopped */
   ReportStatus(SERVICE_STOPPED, service_error, 0);
   return 0;
}



/*
 * Install the Bacula service on the OS -- very complicated
 */
int installService(const char *cmdOpts)
{
   const int maxlen = 2048;
   char path[maxlen];
   char svcmd[maxlen];

   bsnprintf(svcmd, sizeof(svcmd), "service: install: %s", cmdOpts, APP_DESC, MB_OK);

   /* Get our filename */
   if (GetModuleFileName(NULL, path, maxlen-11) == 0) {
      MessageBox(NULL, _("Unable to install the service"), APP_DESC, MB_ICONEXCLAMATION | MB_OK);
      return 0;
   }

   /* Create a valid command for starting the service */
   if ((int)strlen(path) + (int)strlen(cmdOpts) + 30  < maxlen) {
      bsnprintf(svcmd, sizeof(svcmd), "\"%s\" /service %s", path, cmdOpts);
   } else {
      log_error_message(_("Service command length too long")); 
      MessageBox(NULL, _("Service command length too long. Service not registered."),
          APP_DESC, MB_ICONEXCLAMATION | MB_OK);
      return 0;
   }

   if (have_service_api) {
      SC_HANDLE baculaService, serviceManager;

      /* Open the service control manager */
      serviceManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
      if (!serviceManager) {
         log_error_message("Open Service Manager failed"); 
         MessageBox(NULL,
            _("The Service Control Manager could not be contacted - the service was not installed"),
            APP_DESC, MB_ICONEXCLAMATION | MB_OK);
         return 0;
      }

      /* Now actually create the Bacula service entry */
      baculaService = CreateService(
              serviceManager, 
              APP_NAME,                       /* Our service name */
              APP_DESC,                       /* Display name */
              SERVICE_ALL_ACCESS,
              SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
              SERVICE_AUTO_START,
              SERVICE_ERROR_NORMAL,
              svcmd,                          /* Command string to start the service */
              NULL,
              NULL,
              BAC_DEPENDENCIES,               /* Services to start before us */
              NULL,                           /* Use default SYSTEM account */
              NULL);
      if (!baculaService) {
         CloseServiceHandle(serviceManager);
         log_error_message("CreateService failed for " APP_DESC); 
         MessageBox(NULL, _("The Bacula service: " APP_NAME " could not be installed"),
              APP_DESC, MB_ICONEXCLAMATION | MB_OK);
         return 0;
      }

      /* Set a text description in the service manager's control panel */
      set_service_description(serviceManager, baculaService,
(char *)_("Provides file backup and restore services. Bacula -- the network backup solution."));

      CloseServiceHandle(serviceManager);
      CloseServiceHandle(baculaService);

   } else {
      /* Old style service -- create appropriate registry key path */
      HKEY runservices;
      if (RegCreateKey(HKEY_LOCAL_MACHINE, 
              "Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",
              &runservices) != ERROR_SUCCESS) {
         log_error_message(_("Cannot write System Registry for " APP_DESC)); 
         MessageBox(NULL, _("The System Registry could not be updated - the Bacula service was not installed"), 
             APP_DESC, MB_ICONEXCLAMATION | MB_OK);
         return 0;
      }

      /* Add the Bacula values */
      if (RegSetValueEx(runservices, APP_NAME, 0, REG_SZ, 
                        (unsigned char *)svcmd, strlen(svcmd)+1) != ERROR_SUCCESS) {
         RegCloseKey(runservices);
         log_error_message(_("Cannot add Bacula key to System Registry")); 
         MessageBox(NULL, _("The Bacula service: " APP_NAME " could not be installed"), 
             APP_DESC, MB_ICONEXCLAMATION | MB_OK);
         return 0;
      }
      RegCloseKey(runservices);
   }

   /* At this point the service is installed */
   if (opt_debug) {
      MessageBox(NULL,
           _("The " APP_DESC "was successfully installed.\n"
             "The service may be started by double clicking on the\n"
             "Bacula \"Start\" icon and will be automatically\n"
             "be run the next time this machine is rebooted. "),
         APP_DESC, MB_ICONINFORMATION | MB_OK);
   }
   return 0;
}


/*
 * Remove a service from the OS (normally done when we are installing
 *   a new version).
 */
int removeService()
{
   if (have_service_api) {      /* Newer Windows platform (NT, Win2K, ...) */
      SC_HANDLE serviceManager, baculaService;
      int stat = 0;

      serviceManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
      if (serviceManager) {
         /* Now get the Bacula service entry */
         baculaService = OpenService(serviceManager, APP_NAME, SERVICE_ALL_ACCESS);
         if (baculaService) {
            SERVICE_STATUS status;

            /* If the service is running, stop it */
            if (ControlService(baculaService, SERVICE_CONTROL_STOP, &status)) {
               while(QueryServiceStatus(baculaService, &status)) {
                  if (status.dwCurrentState == SERVICE_STOP_PENDING) {
                     sleep(1);
                  } else {
                     break;
                  }
               }
               if (status.dwCurrentState != SERVICE_STOPPED) {
                  if (opt_debug) {
                     MessageBox(NULL, _("The Bacula service: " APP_NAME " could not be stopped"), 
                        APP_DESC, MB_ICONEXCLAMATION | MB_OK);
                  }
               }
            }

            if (DeleteService(baculaService)) {
               if (opt_debug) {
                  MessageBox(NULL, _("The Bacula service: " APP_NAME " has been removed"), 
                     APP_DESC, MB_ICONINFORMATION | MB_OK);
               }
            } else {
               MessageBox(NULL, _("The Bacula service: " APP_NAME " could not be removed"), 
                  APP_DESC, MB_ICONEXCLAMATION | MB_OK);
               stat = 1; /* error */
            }
            CloseServiceHandle(baculaService);

         } else {
            if (opt_debug) {
               MessageBox(NULL, _("An existing Bacula service: " APP_NAME " could not be found for "
                   "removal. This is not normally an error."),
                   APP_DESC, MB_ICONEXCLAMATION | MB_OK);
            }
         }
         CloseServiceHandle(serviceManager);
         return stat;

      } else {
         MessageBox(NULL, _("The service Manager could not be contacted - the Bacula service was not removed"), 
            APP_DESC, MB_ICONEXCLAMATION | MB_OK);
         return 1; /* error */
      }

   } else {                     /* Old Win95/98/Me OS */
      /* Open the registry path key */
      HKEY runservices;
      if (RegOpenKey(HKEY_LOCAL_MACHINE, 
            "Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",
            &runservices) != ERROR_SUCCESS) {
         if (opt_debug) {
            MessageBox(NULL, 
               _("Could not find registry entry.\nService probably not registerd - the Bacula service was not removed"), 
                  APP_DESC, MB_ICONEXCLAMATION | MB_OK);
         }
      } else {
         /* Now delete the Bacula entry */
         if (RegDeleteValue(runservices, APP_NAME) != ERROR_SUCCESS) {
            RegCloseKey(runservices);
            MessageBox(NULL, _("Could not delete Registry key for " APP_NAME ".\n"
               "The Bacula service could not be removed"), APP_DESC, MB_ICONEXCLAMATION | MB_OK);
         }
         RegCloseKey(runservices);
         return 1;
      }

      /* Stop any running Bacula */
      if (!stopRunningBacula()) {
         if (opt_debug) {
            MessageBox(NULL,
                _("Bacula could not be contacted, probably not running"),
                APP_DESC, MB_ICONEXCLAMATION | MB_OK);
         }
         return 0;   /* not really an error */
      }

      /* At this point, the service has been removed */
      if (opt_debug) {
         MessageBox(NULL, _("The Bacula service has been removed"), APP_DESC, MB_ICONINFORMATION | MB_OK);
      }
   }
   return 0;
}


/*
 * This subroutine is called to report our current status to the
 *  new style service manager
 */
BOOL ReportStatus(DWORD state, DWORD exitcode, DWORD waithint)
{
   static DWORD checkpoint = 1;
   BOOL result = TRUE;

   /* No callbacks until we are started */
   if (state == SERVICE_START_PENDING) {
      service_status.dwControlsAccepted = 0;
   } else {
      service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
   }

   /* Save global service_status state */
   service_status.dwCurrentState = state;
   service_status.dwWin32ExitCode = exitcode;
   service_status.dwWaitHint = waithint;

   /*
    * Update the checkpoint variable so the service manager knows
    *   we are alive.
    */
   if (state == SERVICE_RUNNING || state == SERVICE_STOPPED) {
      service_status.dwCheckPoint = 0;
   } else {
      service_status.dwCheckPoint = checkpoint++;
   }

   /* Send our new status */
   result = SetServiceStatus(service_handle, &service_status);
   if (!result) {
      log_error_message(_("SetServiceStatus failed"));
   }
   return result;
}

/* Log an error message for the last Windows error */
void LogLastErrorMsg(const char *message, const char *fname, int lineno)
{
   char msgbuf[500];
   HANDLE eventHandler;
   const char *strings[3];
   LPTSTR msg;

   service_error = GetLastError();
   FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
                 FORMAT_MESSAGE_FROM_SYSTEM,
                 NULL,
                 service_error,
                 0,
                 (LPTSTR)&msg,
                 0,
                 NULL);

   /* Use the OS event logging to log the error */
   eventHandler = RegisterEventSource(NULL, APP_NAME);

   bsnprintf(msgbuf, sizeof(msgbuf), _("\n\n%s error: %ld at %s:%d"), 
      APP_NAME, service_error, fname, lineno);

   strings[0] = msgbuf;
   strings[1] = message;
   strings[2] = msg;

   if (eventHandler) {
      ReportEvent(eventHandler, EVENTLOG_ERROR_TYPE,
              0,                      /* category */
              0,                      /* ID */
              NULL,                   /* SID */
              3,                      /* Number of strings */
              0,                      /* raw data size */
              (const char **)strings, /* error strings */
              NULL);                  /* raw data */
      DeregisterEventSource(eventHandler);
   }
   LocalFree(msg);
}

typedef BOOL  (WINAPI * WinAPI)(SC_HANDLE, DWORD, LPVOID);

/*
 * This is amazingly complicated just to get a bit of English explanation
 *  in the service manager's dialog box.
 */
static void set_service_description(SC_HANDLE hSCManager, SC_HANDLE hService,
                             LPSTR lpDesc) 
{ 
    SC_LOCK sclLock; 
    LPQUERY_SERVICE_LOCK_STATUS lpqslsBuf; 
    SERVICE_DESCRIPTION sdBuf;
    DWORD dwBytesNeeded;
    WinAPI ChangeServiceDescription;
 
    HINSTANCE hLib = LoadLibrary("ADVAPI32.DLL");
    if (!hLib) {
       return;
    }
    ChangeServiceDescription = (WinAPI)GetProcAddress(hLib,
       "ChangeServiceConfig2A");
    FreeLibrary(hLib);
    if (!ChangeServiceDescription) {
       return;
    }
    
    // Need to acquire database lock before reconfiguring. 
    sclLock = LockServiceDatabase(hSCManager); 
 
    // If the database cannot be locked, report the details. 
    if (sclLock == NULL) {
       // Exit if the database is not locked by another process. 
       if (GetLastError() != ERROR_SERVICE_DATABASE_LOCKED) {
          log_error_message("LockServiceDatabase"); 
          return;
       }
 
       // Allocate a buffer to get details about the lock. 
       lpqslsBuf = (LPQUERY_SERVICE_LOCK_STATUS)LocalAlloc( 
            LPTR, sizeof(QUERY_SERVICE_LOCK_STATUS)+256); 
       if (lpqslsBuf == NULL) {
          log_error_message("LocalAlloc"); 
          return;
       }
 
       // Get and print the lock status information. 
       if (!QueryServiceLockStatus( 
              hSCManager, 
              lpqslsBuf, 
              sizeof(QUERY_SERVICE_LOCK_STATUS)+256, 
              &dwBytesNeeded)) {
          log_error_message("QueryServiceLockStatus"); 
       }
 
       if (lpqslsBuf->fIsLocked) {
          printf(_("Locked by: %s, duration: %ld seconds\n"), 
                lpqslsBuf->lpLockOwner, 
                lpqslsBuf->dwLockDuration); 
       } else {
          printf(_("No longer locked\n")); 
       }
 
       LocalFree(lpqslsBuf); 
       log_error_message(_("Could not lock database")); 
       return;
    } 
 
    // The database is locked, so it is safe to make changes. 
 
    sdBuf.lpDescription = lpDesc;

    if (!ChangeServiceDescription(
         hService,                   // handle to service
         SERVICE_CONFIG_DESCRIPTION, // change: description
         &sdBuf) ) {                 // value: new description
       log_error_message("ChangeServiceConfig2");
    }

    // Release the database lock. 
    UnlockServiceDatabase(sclLock); 
}
