Skip to content
Snippets Groups Projects
ntsvcs.c 27.3 KiB
Newer Older
/* ntsrvcs.c */

/* Synchronet BBS as a set of Windows NT Services */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright 2004 Rob Swindell - http://www.synchro.net/copyright.html		*
 *																			*
 * 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.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

/* Synchronet-specific headers */
#include "sbbs.h"		/* various */
#include "sbbs_ini.h"	/* sbbs_read_ini() */
#include "ftpsrvr.h"	/* ftp_startup_t, ftp_server */
#include "websrvr.h"	/* web_startup_t, web_server */
#include "mailsrvr.h"	/* mail_startup_t, mail_server */
#include "services.h"	/* services_startup_t, services_thread */
#include "ntsvcs.h"		/* NT service names */

/* Windows-specific headers */
#include <winsvc.h>

#define NTSVC_TIMEOUT_STARTUP	30000	/* Milliseconds */
#define NTSVC_TIMEOUT_TERMINATE	30000	/* Milliseconds */

static void WINAPI bbs_ctrl_handler(DWORD dwCtrlCode);
static void WINAPI ftp_ctrl_handler(DWORD dwCtrlCode);
static void WINAPI web_ctrl_handler(DWORD dwCtrlCode);
static void WINAPI mail_ctrl_handler(DWORD dwCtrlCode);
static void WINAPI services_ctrl_handler(DWORD dwCtrlCode);
bbs_startup_t		bbs_startup;
ftp_startup_t		ftp_startup;
mail_startup_t		mail_startup;
services_startup_t	services_startup;
web_startup_t		web_startup;

typedef struct {
	char*					name;
	char*					display_name;
	char*					description;
	void*					startup;
	DWORD*					log_mask;
	void					(WINAPI *ctrl_handler)(DWORD);
	HANDLE					event_handle;
	SERVICE_STATUS			status;
	SERVICE_STATUS_HANDLE	status_handle;
} sbbs_ntsvc_t;

sbbs_ntsvc_t bbs ={	
	"Synchronet Telnet/RLogin Server",
	"Provides support for Telnet and RLogin clients and executes timed events. " \
		"This service provides the critical functions of your Synchronet BBS.",
	&bbs_startup,
	&bbs_startup.log_mask,
	bbs_ctrl_handler,
	INVALID_HANDLE_VALUE
/* This is not (currently) a separate service, use this for logging only */
sbbs_ntsvc_t event ={	
	NTSVC_NAME_EVENT,
	NULL,
	NULL,
	NULL,
	NULL,
	&bbs_startup.log_mask,
	"Synchronet FTP Server",
	"Provides support for FTP clients (including web browsers) for file transfers.",
	&ftp_startup,
	&ftp_startup.log_mask,
	ftp_ctrl_handler,
	INVALID_HANDLE_VALUE
	"Synchronet Web Server",
	"Provides support for Web (HTML/HTTP) clients (browsers).",
	&web_startup,
	&web_startup.log_mask,
	web_ctrl_handler,
	INVALID_HANDLE_VALUE
	"Synchronet SMTP/POP3 Mail Server",
	"Sends and receives Internet e-mail (using SMTP) and allows users to remotely " \
		"access their e-mail using an Internet mail client (using POP3).",
	&mail_startup,
	&mail_startup.log_mask,
	mail_ctrl_handler,
	INVALID_HANDLE_VALUE
	"Synchronet Services",
	"Plugin servers (usually in JavaScript) for any TCP/UDP protocol. " \
		"Stock services include Finger, Gopher, NNTP, and IRC. Edit your ctrl/services.ini " \
		"file for configuration of individual Synchronet Services.",
	&services_startup,
	&services_startup.log_mask,
	services_ctrl_handler,
	INVALID_HANDLE_VALUE
/* This list is used for enumerating all services */
sbbs_ntsvc_t* ntsvc_list[] = {
	&bbs,
	&ftp,
	&web,
	&mail,
	&services,
	NULL
};
							
/****************************************/
/* Service Control Handlers (Callbacks) */
/****************************************/
/* Common control handler for all services */
static void svc_ctrl_handler(sbbs_ntsvc_t* svc, DWORD dwCtrlCode)
{
	switch(dwCtrlCode) {
		case SERVICE_CONTROL_RECYCLE:
			*svc->recycle_now=TRUE;
			break;
		case SERVICE_CONTROL_STOP:
		case SERVICE_CONTROL_SHUTDOWN:
			svc->status.dwWaitHint=NTSVC_TIMEOUT_TERMINATE;
			svc->status.dwCurrentState=SERVICE_STOP_PENDING;
			break;
	}
	SetServiceStatus(svc->status_handle, &svc->status);
}

/* Service-specific control handler stub functions */
static void WINAPI bbs_ctrl_handler(DWORD dwCtrlCode)
{
	svc_ctrl_handler(&bbs, dwCtrlCode);
}

static void WINAPI ftp_ctrl_handler(DWORD dwCtrlCode)
{
	svc_ctrl_handler(&ftp, dwCtrlCode);
static void WINAPI web_ctrl_handler(DWORD dwCtrlCode)
	svc_ctrl_handler(&web, dwCtrlCode);
static void WINAPI mail_ctrl_handler(DWORD dwCtrlCode)
	svc_ctrl_handler(&mail, dwCtrlCode);
static void WINAPI services_ctrl_handler(DWORD dwCtrlCode)
	svc_ctrl_handler(&services, dwCtrlCode);
static WORD event_type(int level)
{
	switch(level) {
		case LOG_WARNING:
			return(EVENTLOG_WARNING_TYPE);
		case LOG_NOTICE:
		case LOG_INFO:
		case LOG_DEBUG:
			return(EVENTLOG_INFORMATION_TYPE);
	}
/*
	LOG_EMERG
	LOG_ALERT
	LOG_CRIT
	LOG_ERR
*/
	return(EVENTLOG_ERROR_TYPE);
}

/**************************************/
/* Common Service Log Ouptut Function */
/**************************************/
static int svc_lputs(void* p, int level, char* str)
	sbbs_ntsvc_t* svc = (sbbs_ntsvc_t*)p;
	/* Debug Logging */
	if(svc==NULL || svc->debug) {
		snprintf(debug,sizeof(debug),"%s: %s",svc==NULL ? "Synchronet" : svc->name, str);
		OutputDebugString(debug);
	}
	if(svc==NULL)
		return(0);

	len = strlen(str);

	/* Mailslot Logging (for sbbsctrl) */
	if(svc->log_handle != INVALID_HANDLE_VALUE /* Invalid log handle? */
		&& !GetMailslotInfo(svc->log_handle, NULL, NULL, NULL, NULL)) {
		/* Close and try to re-open */
		CloseHandle(svc->log_handle);
		svc->log_handle=INVALID_HANDLE_VALUE;
	}

	if(svc->log_handle == INVALID_HANDLE_VALUE) {
		sprintf(fname,"\\\\.\\mailslot\\%s.log",svc->name);
		svc->log_handle = CreateFile(
			fname,					// pointer to name of the file
			GENERIC_WRITE,			// access (read-write) mode
			FILE_SHARE_READ,		// share mode
			NULL,					// pointer to security attributes
			OPEN_EXISTING,			// how to create
			FILE_ATTRIBUTE_NORMAL,  // file attributes
			NULL					// handle to file with attributes to copy
			);
	}
	if(svc->log_handle != INVALID_HANDLE_VALUE) {
		if(!WriteFile(svc->log_handle,str,len,&wr,NULL) || wr!=len) {
			/* This most likely indicates the server closed the mailslot */
			sprintf(debug,"!ERROR %d writing %u bytes to %s pipe (wr=%d)"
				,GetLastError(),len,svc->name,wr);
			OutputDebugString(debug);
			/* So close the handle and attempt re-open next time */
			CloseHandle(svc->log_handle);
			svc->log_handle=INVALID_HANDLE_VALUE;
	
	/* Event Logging */
	if((*svc->log_mask)&(1<<level)) {
		if(svc->event_handle == NULL)
			svc->event_handle = RegisterEventSource(
				NULL,		// server name for source (NULL = local computer)
				svc->name   // source name for registered handle
				);
		if(svc->event_handle != NULL)
			ReportEvent(svc->event_handle,  // event log handle
				event_type(level),		// event type
				0,						// category zero
				0,						// event identifier
				NULL,					// no user security identifier
				1,						// one string
				0,						// no data
				&str,					// pointer to string array
				NULL);					// pointer to data
	}

    return(0);
/****************************************************************************/
/* Event thread local/log print routine										*/
/****************************************************************************/
static int event_lputs(int level, char *str)
    return(0);
}

/************************************/
/* Shared Service Callback Routines */
/************************************/

	sbbs_ntsvc_t* svc = (sbbs_ntsvc_t*)p;

	svc->status.dwCurrentState=SERVICE_RUNNING;
	svc->status.dwControlsAccepted|=SERVICE_ACCEPT_STOP;
	SetServiceStatus(svc->status_handle, &svc->status);
static void svc_recycle(void *p)
{
	char	str[MAX_PATH*2];
	FILE*	fp;
	sbbs_ntsvc_t* svc = (sbbs_ntsvc_t*)p;
	bbs_startup_t*		bbs_startup=NULL;
	ftp_startup_t*		ftp_startup=NULL;
	mail_startup_t*		mail_startup=NULL;
	services_startup_t*	services_startup=NULL;
	web_startup_t*		web_startup=NULL;

	if(svc==&bbs)
		bbs_startup=svc->startup;
	else if(svc==&ftp)
		ftp_startup=svc->startup;
	else if(svc==&web)
		web_startup=svc->startup;
	else if(svc==&mail)
		mail_startup=svc->startup;
	else if(svc==&services)
		services_startup=svc->startup;

	SAFEPRINTF(str,"Reading %s",ini_file);
	svc_lputs(svc,LOG_INFO,str);

	/* Read .ini file here */
	fp=fopen(ini_file,"r");

	/* We call this function to set defaults, even if there's no .ini file */
	sbbs_read_ini(fp 
		,NULL	/* global_startup */
		,NULL	,bbs_startup
		,NULL	,ftp_startup 
		,NULL	,web_startup
		,NULL	,mail_startup 
		,NULL	,services_startup
		);

	/* close .ini file here */
	if(fp!=NULL)
		fclose(fp);
}

static void svc_terminated(void* p, int code)
	sbbs_ntsvc_t* svc = (sbbs_ntsvc_t*)p;

	if(code) {
		svc->status.dwWin32ExitCode=ERROR_SERVICE_SPECIFIC_ERROR;
		svc->status.dwServiceSpecificExitCode=code;
		SetServiceStatus(svc->status_handle, &svc->status);
static void svc_clients(void* p, int active)
{
	sbbs_ntsvc_t* svc = (sbbs_ntsvc_t*)p;
}

/***************/
/* ServiceMain */
/***************/

/* Common ServiceMain for all services */
static void WINAPI svc_main(sbbs_ntsvc_t* svc, DWORD argc, LPTSTR *argv)
	DWORD	i;
	char*	arg;

	for(i=0;i<argc;i++) {
		arg=argv[i];
		if(*arg=='-' || *arg=='/')
			arg++;
		if(!stricmp(arg,"debug"))
			svc->debug=TRUE;
		if(!stricmp(arg,"logmask") && i+1<argc)
			(*svc->log_mask)=strtol(argv[++i],NULL,0);
	sprintf(str,"Starting NT Service: %s",svc->display_name);
    if((svc->status_handle = RegisterServiceCtrlHandler(svc->name, svc->ctrl_handler))==0) {
		sprintf(str,"!ERROR %d registering service control handler",GetLastError());
	memset(&svc->status,0,sizeof(SERVICE_STATUS));
	svc->status.dwServiceType=SERVICE_WIN32_SHARE_PROCESS;
	svc->status.dwControlsAccepted=SERVICE_ACCEPT_SHUTDOWN;
	svc->status.dwWaitHint=NTSVC_TIMEOUT_STARTUP;
	svc->status.dwCurrentState=SERVICE_START_PENDING;
	SetServiceStatus(svc->status_handle, &svc->status);
	svc->thread(svc->startup);
	svc->status.dwCurrentState=SERVICE_STOPPED;
	SetServiceStatus(svc->status_handle, &svc->status);
	if(svc->log_handle!=INVALID_HANDLE_VALUE) {
		CloseHandle(svc->log_handle);
		svc->log_handle=INVALID_HANDLE_VALUE;

	if(svc->event_handle!=NULL) {
		DeregisterEventSource(svc->event_handle);
		svc->event_handle=NULL;
	}
/* Service-specific ServiceMain stub functions */
static void WINAPI bbs_start(DWORD dwArgc, LPTSTR *lpszArgv)

	/* Events are (currently) part of the BBS service */
	if(event.log_handle!=INVALID_HANDLE_VALUE) {
		CloseHandle(event.log_handle);
		event.log_handle=INVALID_HANDLE_VALUE;
	}
static void WINAPI ftp_start(DWORD dwArgc, LPTSTR *lpszArgv)
static void WINAPI web_start(DWORD dwArgc, LPTSTR *lpszArgv)
static void WINAPI mail_start(DWORD dwArgc, LPTSTR *lpszArgv)
	svc_main(&mail, dwArgc, lpszArgv);
static void WINAPI services_start(DWORD dwArgc, LPTSTR *lpszArgv)
	svc_main(&services, dwArgc, lpszArgv);
}

/******************************************/
/* NT Serivce Install/Uninstall Functions */
/******************************************/

/* ChangeServiceConfig2 is a Win2K+ API function, must call dynamically */
typedef WINADVAPI BOOL (WINAPI *ChangeServiceConfig2_t)(SC_HANDLE, DWORD, LPCVOID);

static void describe_service(HANDLE hSCMlib, SC_HANDLE hService, char* description)
{
	ChangeServiceConfig2_t changeServiceConfig2;
	static SERVICE_DESCRIPTION service_desc;
  
	if(hSCMlib==NULL)
		return;

	service_desc.lpDescription=description;

	if((changeServiceConfig2 = (ChangeServiceConfig2_t)GetProcAddress(hSCMlib, "ChangeServiceConfig2A"))!=NULL)
		changeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &service_desc);
}

static BOOL register_event_source(char* name, char* path)
{
	char	keyname[256];
	HKEY	hKey;
	DWORD	type;
	DWORD	disp;
	LONG	retval;
	char*	value;

	sprintf(keyname,"system\\CurrentControlSet\\services\\eventlog\\application\\%s",name);

	retval=RegCreateKeyEx(
		HKEY_LOCAL_MACHINE,			// handle to an open key
		keyname,			// address of subkey name
		0,				// reserved
		"",				// address of class string
		0,				// special options flag
		KEY_ALL_ACCESS, // desired security access
		NULL,           // address of key security structure
		&hKey,			// address of buffer for opened handle
		&disp			// address of disposition value buffer
		);

	if(retval!=ERROR_SUCCESS) {
		fprintf(stderr,"!Error %d creating/opening registry key (HKLM\\%s)\n"
			,retval,keyname);
		return(FALSE);
	}

	value="EventMessageFile";
	retval=RegSetValueEx(
		hKey,			// handle to key to set value for
		value,			// name of the value to set
		0,				// reserved
		REG_SZ,			// flag for value type
		path,			// address of value data
		strlen(path)	// size of value data
		);

	if(retval!=ERROR_SUCCESS) {
		RegCloseKey(hKey);
		fprintf(stderr,"!Error %d setting registry key value (%s)\n"
			,retval,value);
		return(FALSE);
	}

	value="TypesSupported";
	type=EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
	retval=RegSetValueEx(
		hKey,			// handle to key to set value for
		value,			// name of the value to set
		0,				// reserved
		REG_DWORD,		// flag for value type
		(BYTE*)&type,	// address of value data
		sizeof(type)	// size of value data
		);

	RegCloseKey(hKey);

	if(retval!=ERROR_SUCCESS) {
		fprintf(stderr,"!Error %d setting registry key value (%s)\n"
			,retval,value);
		return(FALSE);
	}

	return(TRUE);
}

/****************************************************************************/
/* Utility function to create a service with description (on Win2K+)		*/
/****************************************************************************/
static SC_HANDLE create_service(HANDLE hSCMlib, SC_HANDLE hSCManager
								,char* name, char* display_name, char* description, char* path
								,BOOL autostart)
{
    SC_HANDLE   hService;

	printf("Installing service: %-40s ... ", display_name);

    hService = CreateService(
        hSCManager,						// SCManager database
        name,							// name of service
        display_name,					// name to display
        SERVICE_ALL_ACCESS,				// desired access
        SERVICE_WIN32_SHARE_PROCESS,	// service type
		autostart						// start type (auto or manual)
			? SERVICE_AUTO_START : SERVICE_DEMAND_START,				
        SERVICE_ERROR_NORMAL,			// error control type
        path,							// service's binary
        NULL,							// no load ordering group
        NULL,							// no tag identifier
        "",								// dependencies
        NULL,							// LocalSystem account
        NULL);							// no password

	if(hService==NULL)
		printf("!ERROR %d\n",GetLastError());
	else {
		describe_service(hSCMlib, hService,description);
		CloseServiceHandle(hService);
		printf("Successful\n");
/****************************************************************************/
/* Install one or all services												*/
/****************************************************************************/
static int install(const char* svc_name)
	HANDLE		hSCMlib;
    SC_HANDLE   hSCManager;
    char		path[MAX_PATH+1];

	printf("Installing Synchronet NT Services...\n");

	hSCMlib = LoadLibrary("ADVAPI32.DLL");

    if(GetModuleFileName(NULL,path,sizeof(path))==0)
    {
        fprintf(stderr,"!ERROR %d getting module file name\n",GetLastError());
        return(-1);
    }

    hSCManager = OpenSCManager(
                        NULL,                   // machine (NULL == local)
                        NULL,                   // database (NULL == default)
                        SC_MANAGER_ALL_ACCESS   // access required
                        );
    if(hSCManager==NULL) {
		fprintf(stderr,"!ERROR %d opening SC manager\n",GetLastError());
		return(-1);
	}

		if(svc_name==NULL	/* All? */
			|| !stricmp(ntsvc_list[i]->name, svc_name))
			create_service(hSCMlib
				,hSCManager
				,ntsvc_list[i]->name
				,ntsvc_list[i]->display_name
				,ntsvc_list[i]->description
				,path
				,ntsvc_list[i]->autostart);

	if(hSCMlib!=NULL)
		FreeLibrary(hSCMlib);

	CloseServiceHandle(hSCManager);

	return(0);
}

/****************************************************************************/
/* Utility function to remove a service cleanly (stopping if necessary)		*/
/****************************************************************************/
static void remove_service(SC_HANDLE hSCManager, char* name, char* disp_name)
{
    SC_HANDLE		hService;
	SERVICE_STATUS	status;

	printf("Removing service: %-40s ... ", disp_name);

    hService = OpenService(hSCManager, name, SERVICE_ALL_ACCESS);

	if(hService==NULL) {
		printf("\n!ERROR %d opening service: %s\n",GetLastError(),name);
		return;
	}

    // try to stop the service
    if(ControlService( hService, SERVICE_CONTROL_STOP, &status))
    {
        printf("\nStopping: %s ... ",name);

        while(QueryServiceStatus(hService, &status) && status.dwCurrentState == SERVICE_STOP_PENDING)
			Sleep(1000);

        if(status.dwCurrentState == SERVICE_STOPPED)
            printf("Stopped.\n");
        else
            printf("FAILED!\n");
    }

    // now remove the service
    if(DeleteService(hService))
		printf("Successful\n");
	else
		printf("!ERROR %d\n",GetLastError());
    CloseServiceHandle(hService);
}

/****************************************************************************/
/* Uninstall one or all services											*/
/****************************************************************************/
static int uninstall(const char* svc_name)
    SC_HANDLE   hSCManager;

    hSCManager = OpenSCManager(
                        NULL,                   // machine (NULL == local)
                        NULL,                   // database (NULL == default)
                        SC_MANAGER_ALL_ACCESS   // access required
                        );
    if(hSCManager==NULL) {
		fprintf(stderr,"!ERROR %d opening SC manager\n",GetLastError());
		return(-1);
	}

		if(svc_name==NULL	/* All? */
			|| !stricmp(ntsvc_list[i]->name, svc_name))
			remove_service(hSCManager
				,ntsvc_list[i]->name
				,ntsvc_list[i]->display_name);

	CloseServiceHandle(hSCManager);

	return(0);
}

/****************************************************************************/
/* Utility function to disable a service									*/
/****************************************************************************/
static void set_service_start_type(SC_HANDLE hSCManager, char* name
								   ,char* disp_name, DWORD start_type)
{
    SC_HANDLE		hService;

	printf("%s service: %-40s ... "
		,start_type==SERVICE_DISABLED ? "Disabling" : "Enabling", disp_name);

    hService = OpenService(hSCManager, name, SERVICE_ALL_ACCESS);

	if(hService==NULL) {
		printf("\n!ERROR %d opening service: %s\n",GetLastError(),name);
		return;
	}

	if(!ChangeServiceConfig(
		hService,				// handle to service
		SERVICE_NO_CHANGE,		// type of service
		start_type,				// when to start service
		SERVICE_NO_CHANGE,		// severity if service fails to start
		NULL,					// pointer to service binary file name
		NULL,					// pointer to load ordering group name
		NULL,					// pointer to variable to get tag identifier
		NULL,					// pointer to array of dependency names
		NULL,					// pointer to account name of service
		NULL,					// pointer to password for service account
		NULL					// pointer to display name
		))
		printf("\n!ERROR %d changing service config for: %s\n",GetLastError(),name);
	else
		printf("Successful\n");

    CloseServiceHandle(hService);
}

/****************************************************************************/
/* Enable (set to auto-start) or disable one or all services				*/
/****************************************************************************/
static int enable(const char* svc_name, BOOL enabled)
{
	int			i;
    SC_HANDLE   hSCManager;

    hSCManager = OpenSCManager(
                        NULL,                   // machine (NULL == local)
                        NULL,                   // database (NULL == default)
                        SC_MANAGER_ALL_ACCESS   // access required
                        );
    if(hSCManager==NULL) {
		fprintf(stderr,"!ERROR %d opening SC manager\n",GetLastError());
		return(-1);
	}

	for(i=0;ntsvc_list[i]!=NULL;i++)
		if(svc_name==NULL	/* All? */
			|| !stricmp(ntsvc_list[i]->name, svc_name))
			set_service_start_type(hSCManager
				,ntsvc_list[i]->name
				,ntsvc_list[i]->display_name
				,enabled ? SERVICE_AUTO_START : SERVICE_DISABLED);

/****************************************************************************/
/* Main Entry Point															*/
/****************************************************************************/
int main(int argc, char** argv)
{
	char*	ctrl_dir;
	char*	arg;
	char	str[MAX_PATH+1];
	int		i;
	FILE*	fp=NULL;

	SERVICE_TABLE_ENTRY  ServiceDispatchTable[] = 
    { 
        { bbs.name,			bbs_start		}, 
		{ ftp.name,			ftp_start		},
		{ web.name,			web_start		},
		{ mail.name,		mail_start		},
		{ services.name,	services_start	},
        { NULL,				NULL			}	/* Terminator */
    }; 

	printf("\nSynchronet NT Services  Version %s%c  %s\n\n"
		,VERSION,REVISION,COPYRIGHT_NOTICE);

	ctrl_dir=getenv("SBBSCTRL");	/* read from environment variable */
	if(ctrl_dir==NULL || ctrl_dir[0]==0) {
		ctrl_dir="\\sbbs\\ctrl";		/* Not set? Use default */
		printf("!SBBSCTRL environment variable not set, using default value: %s\n\n"
			,ctrl_dir);
	}

	sbbs_get_ini_fname(ini_file, ctrl_dir, NULL /* auto-host_name */);

	/* Initialize BBS startup structure */
    memset(&bbs_startup,0,sizeof(bbs_startup));
    bbs_startup.size=sizeof(bbs_startup);
	bbs_startup.cbdata=&bbs;
	bbs_startup.event_lputs=event_lputs;
    bbs_startup.terminated=svc_terminated;
    strcpy(bbs_startup.ctrl_dir,ctrl_dir);

	/* Initialize FTP startup structure */
    memset(&ftp_startup,0,sizeof(ftp_startup));
	ftp_startup.cbdata=&ftp;
    ftp_startup.size=sizeof(ftp_startup);
	ftp_startup.lputs=svc_lputs;
    ftp_startup.started=svc_started;
    ftp_startup.terminated=svc_terminated;
    strcpy(ftp_startup.ctrl_dir,ctrl_dir);

	/* Initialize Web Server startup structure */
    memset(&web_startup,0,sizeof(web_startup));
	web_startup.cbdata=&web;
    web_startup.size=sizeof(web_startup);
	web_startup.lputs=svc_lputs;
    web_startup.started=svc_started;
    web_startup.terminated=svc_terminated;
    strcpy(web_startup.ctrl_dir,ctrl_dir);

	/* Initialize Mail Server startup structure */
    memset(&mail_startup,0,sizeof(mail_startup));
	mail_startup.cbdata=&mail;
    mail_startup.size=sizeof(mail_startup);
	mail_startup.lputs=svc_lputs;
    mail_startup.started=svc_started;
	mail_startup.recycle=svc_recycle;
    mail_startup.terminated=svc_terminated;
    strcpy(mail_startup.ctrl_dir,ctrl_dir);

	/* Initialize Services startup structure */
    memset(&services_startup,0,sizeof(services_startup));
	services_startup.cbdata=&services;
    services_startup.size=sizeof(services_startup);
	services_startup.lputs=svc_lputs;
    services_startup.started=svc_started;
	services_startup.recycle=svc_recycle;
    services_startup.terminated=svc_terminated;
	services_startup.clients=svc_clients;
    strcpy(services_startup.ctrl_dir,ctrl_dir);

	/* Read .ini file here */
	if((fp=fopen(ini_file,"r"))!=NULL) {
		sprintf(str,"Reading %s",ini_file);
	}

	/* We call this function to set defaults, even if there's no .ini file */
		,&bbs.autostart			,&bbs_startup
		,&ftp.autostart			,&ftp_startup 
		,&web.autostart			,&web_startup
		,&mail.autostart		,&mail_startup 
		,&services.autostart	,&services_startup
		);

	/* close .ini file here */
	if(fp!=NULL)
		fclose(fp);

	ctrl_dir = bbs_startup.ctrl_dir;
	if(chdir(ctrl_dir)!=0) {
		sprintf(str,"!ERROR %d changing directory to: %s", errno, ctrl_dir);
	for(i=1;i<argc;i++) {
		arg=argv[i];
		while(*arg=='-')
			arg++;
		if(!stricmp(arg,"install"))
			return install(argv[i+1]);

		if(!stricmp(arg,"remove"))
			return uninstall(argv[i+1]);
	}

	printf("Available Services:\n\n");
	printf("%-20s %s\n","Name","Description");
	printf("%-20s %s\n","----","-----------");
	for(i=0;ntsvc_list[i]!=NULL;i++)
		printf("%-20s %s\n",ntsvc_list[i]->name,ntsvc_list[i]->display_name);

	SAFECOPY(str,getfname(argv[0]));

	printf("\nUsage: %s [command] [service]\n", str);

	printf("\nCommands:\n");
    printf("\tinstall\t to install a service by name (default: ALL services)\n");
    printf("\tremove\t to remove a service by name (default: ALL services)\n");
    printf("\tdisable\t to disable a service by name (default: ALL services)\n");
    printf("\tenable\t to re-enable a disabled service (default: ALL services)\n");
    printf("\nStartServiceCtrlDispatcher being called.\n" );
    printf("This may take several seconds.  Please wait.\n" );

	if(!StartServiceCtrlDispatcher(ServiceDispatchTable)) 
    { 
		sprintf(str,"!StartServiceCtrlDispatcher ERROR %d",GetLastError());
		printf("%s\n",str);
        OutputDebugString(str); 
    } 

	return(0);
}