3 extern unsigned long tls_index;
\r
4 extern bool is_admin;
\r
5 extern imports_t imports;
\r
7 static TCHAR unquoted_imagepath[PATH_LENGTH];
\r
8 static TCHAR imagepath[PATH_LENGTH];
\r
9 static TCHAR imageargv0[PATH_LENGTH];
\r
11 void nssm_exit(int status) {
\r
16 /* Are two strings case-insensitively equivalent? */
\r
17 int str_equiv(const TCHAR *a, const TCHAR *b) {
\r
18 size_t len = _tcslen(a);
\r
19 if (_tcslen(b) != len) return 0;
\r
20 if (_tcsnicmp(a, b, len)) return 0;
\r
24 /* Convert a string to a number. */
\r
25 int str_number(const TCHAR *string, unsigned long *number, TCHAR **bogus) {
\r
26 if (! string) return 1;
\r
28 *number = _tcstoul(string, bogus, 0);
\r
29 if (**bogus) return 2;
\r
34 int str_number(const TCHAR *string, unsigned long *number) {
\r
36 return str_number(string, number, &bogus);
\r
39 /* Does a char need to be escaped? */
\r
40 static bool needs_escape(const TCHAR c) {
\r
41 if (c == _T('"')) return true;
\r
42 if (c == _T('&')) return true;
\r
43 if (c == _T('%')) return true;
\r
44 if (c == _T('^')) return true;
\r
45 if (c == _T('<')) return true;
\r
46 if (c == _T('>')) return true;
\r
47 if (c == _T('|')) return true;
\r
51 /* Does a char need to be quoted? */
\r
52 static bool needs_quote(const TCHAR c) {
\r
53 if (c == _T(' ')) return true;
\r
54 if (c == _T('\t')) return true;
\r
55 if (c == _T('\n')) return true;
\r
56 if (c == _T('\v')) return true;
\r
57 if (c == _T('"')) return true;
\r
58 if (c == _T('*')) return true;
\r
59 return needs_escape(c);
\r
62 /* https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ */
\r
63 /* http://www.robvanderwoude.com/escapechars.php */
\r
64 int quote(const TCHAR *unquoted, TCHAR *buffer, size_t buflen) {
\r
66 size_t len = _tcslen(unquoted);
\r
67 if (len > buflen - 1) return 1;
\r
69 bool escape = false;
\r
70 bool quotes = false;
\r
72 for (i = 0; i < len; i++) {
\r
73 if (needs_escape(unquoted[i])) {
\r
74 escape = quotes = true;
\r
77 if (needs_quote(unquoted[i])) quotes = true;
\r
80 memmove(buffer, unquoted, (len + 1) * sizeof(TCHAR));
\r
85 size_t quoted_len = 2;
\r
86 if (escape) quoted_len += 2;
\r
87 for (i = 0; ; i++) {
\r
90 while (i != len && unquoted[i] == _T('\\')) {
\r
96 quoted_len += n * 2;
\r
99 else if (unquoted[i] == _T('"')) quoted_len += n * 2 + 2;
\r
100 else quoted_len += n + 1;
\r
101 if (needs_escape(unquoted[i])) quoted_len += n;
\r
103 if (quoted_len > buflen - 1) return 1;
\r
106 if (escape) *s++ = _T('^');
\r
109 for (i = 0; ; i++) {
\r
112 while (i != len && unquoted[i] == _T('\\')) {
\r
118 for (j = 0; j < n * 2; j++) {
\r
119 if (escape) *s++ = _T('^');
\r
124 else if (unquoted[i] == _T('"')) {
\r
125 for (j = 0; j < n * 2 + 1; j++) {
\r
126 if (escape) *s++ = _T('^');
\r
129 if (escape && needs_escape(unquoted[i])) *s++ = _T('^');
\r
130 *s++ = unquoted[i];
\r
133 for (j = 0; j < n; j++) {
\r
134 if (escape) *s++ = _T('^');
\r
137 if (escape && needs_escape(unquoted[i])) *s++ = _T('^');
\r
138 *s++ = unquoted[i];
\r
141 if (escape) *s++ = _T('^');
\r
148 /* Remove basename of a path. */
\r
149 void strip_basename(TCHAR *buffer) {
\r
150 size_t len = _tcslen(buffer);
\r
152 for (i = len; i && buffer[i] != _T('\\') && buffer[i] != _T('/'); i--);
\r
154 if (i && buffer[i - 1] == _T(':')) i++;
\r
155 buffer[i] = _T('\0');
\r
158 /* How to use me correctly */
\r
159 int usage(int ret) {
\r
160 if (GetConsoleWindow()) print_message(stderr, NSSM_MESSAGE_USAGE, NSSM_VERSION, NSSM_CONFIGURATION, NSSM_DATE);
\r
161 else popup_message(0, MB_OK, NSSM_MESSAGE_USAGE, NSSM_VERSION, NSSM_CONFIGURATION, NSSM_DATE);
\r
165 void check_admin() {
\r
168 /* Lifted from MSDN examples */
\r
169 PSID AdministratorsGroup;
\r
170 SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
\r
171 if (! AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup)) return;
\r
172 CheckTokenMembership(0, AdministratorsGroup, /*XXX*/(PBOOL) &is_admin);
\r
173 FreeSid(AdministratorsGroup);
\r
176 static int elevate(int argc, TCHAR **argv, unsigned long message) {
\r
177 print_message(stderr, message);
\r
179 SHELLEXECUTEINFO sei;
\r
180 ZeroMemory(&sei, sizeof(sei));
\r
181 sei.cbSize = sizeof(sei);
\r
182 sei.lpVerb = _T("runas");
\r
183 sei.lpFile = (TCHAR *) nssm_imagepath();
\r
185 TCHAR *args = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, EXE_LENGTH * sizeof(TCHAR));
\r
187 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("GetCommandLine()"), _T("elevate()"));
\r
191 /* Get command line, which includes the path to NSSM, and skip that part. */
\r
192 _sntprintf_s(args, EXE_LENGTH, _TRUNCATE, _T("%s"), GetCommandLine());
\r
193 size_t s = _tcslen(argv[0]) + 1;
\r
194 if (args[0] == _T('"')) s += 2;
\r
195 while (isspace(args[s])) s++;
\r
197 sei.lpParameters = args + s;
\r
198 sei.nShow = SW_SHOW;
\r
200 unsigned long exitcode = 0;
\r
201 if (! ShellExecuteEx(&sei)) exitcode = 100;
\r
203 HeapFree(GetProcessHeap(), 0, (void *) args);
\r
208 DWORD_PTR i, affinity, system_affinity;
\r
209 if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) return 64;
\r
210 for (i = 0; system_affinity & (1LL << i); i++);
\r
214 const TCHAR *nssm_unquoted_imagepath() {
\r
215 return unquoted_imagepath;
\r
218 const TCHAR *nssm_imagepath() {
\r
222 const TCHAR *nssm_exe() {
\r
226 int _tmain(int argc, TCHAR **argv) {
\r
227 if (check_console()) setup_utf8();
\r
229 /* Remember if we are admin */
\r
232 /* Set up function pointers. */
\r
233 if (get_imports()) nssm_exit(111);
\r
235 /* Remember our path for later. */
\r
236 _sntprintf_s(imageargv0, _countof(imageargv0), _TRUNCATE, _T("%s"), argv[0]);
\r
237 PathQuoteSpaces(imageargv0);
\r
238 GetModuleFileName(0, unquoted_imagepath, _countof(unquoted_imagepath));
\r
239 GetModuleFileName(0, imagepath, _countof(imagepath));
\r
240 PathQuoteSpaces(imagepath);
\r
245 Valid commands are:
\r
246 start, stop, pause, continue, install, edit, get, set, reset, unset, remove
\r
248 if (str_equiv(argv[1], _T("start"))) nssm_exit(control_service(NSSM_SERVICE_CONTROL_START, argc - 2, argv + 2));
\r
249 if (str_equiv(argv[1], _T("stop"))) nssm_exit(control_service(SERVICE_CONTROL_STOP, argc - 2, argv + 2));
\r
250 if (str_equiv(argv[1], _T("restart"))) {
\r
251 int ret = control_service(SERVICE_CONTROL_STOP, argc - 2, argv + 2);
\r
252 if (ret) nssm_exit(ret);
\r
253 nssm_exit(control_service(NSSM_SERVICE_CONTROL_START, argc - 2, argv + 2));
\r
255 if (str_equiv(argv[1], _T("pause"))) nssm_exit(control_service(SERVICE_CONTROL_PAUSE, argc - 2, argv + 2));
\r
256 if (str_equiv(argv[1], _T("continue"))) nssm_exit(control_service(SERVICE_CONTROL_CONTINUE, argc - 2, argv + 2));
\r
257 if (str_equiv(argv[1], _T("status"))) nssm_exit(control_service(SERVICE_CONTROL_INTERROGATE, argc - 2, argv + 2));
\r
258 if (str_equiv(argv[1], _T("rotate"))) nssm_exit(control_service(NSSM_SERVICE_CONTROL_ROTATE, argc - 2, argv + 2));
\r
259 if (str_equiv(argv[1], _T("install"))) {
\r
260 if (! is_admin) nssm_exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_INSTALL));
\r
262 nssm_exit(pre_install_service(argc - 2, argv + 2));
\r
264 if (str_equiv(argv[1], _T("edit")) || str_equiv(argv[1], _T("get")) || str_equiv(argv[1], _T("set")) || str_equiv(argv[1], _T("reset")) || str_equiv(argv[1], _T("unset")) || str_equiv(argv[1], _T("dump"))) {
\r
265 int ret = pre_edit_service(argc - 1, argv + 1);
\r
266 if (ret == 3 && ! is_admin && argc == 3) nssm_exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_EDIT));
\r
267 /* There might be a password here. */
\r
268 for (int i = 0; i < argc; i++) SecureZeroMemory(argv[i], _tcslen(argv[i]) * sizeof(TCHAR));
\r
271 if (str_equiv(argv[1], _T("list"))) nssm_exit(list_nssm_services(argc - 2, argv + 2));
\r
272 if (str_equiv(argv[1], _T("processes"))) nssm_exit(service_process_tree(argc - 2, argv + 2));
\r
273 if (str_equiv(argv[1], _T("remove"))) {
\r
274 if (! is_admin) nssm_exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_REMOVE));
\r
275 nssm_exit(pre_remove_service(argc - 2, argv + 2));
\r
279 /* Thread local storage for error message buffer */
\r
280 tls_index = TlsAlloc();
\r
282 /* Register messages */
\r
283 if (is_admin) create_messages();
\r
286 Optimisation for Windows 2000:
\r
287 When we're run from the command line the StartServiceCtrlDispatcher() call
\r
288 will time out after a few seconds on Windows 2000. On newer versions the
\r
289 call returns instantly. Check for stdin first and only try to call the
\r
290 function if there's no input stream found. Although it's possible that
\r
291 we're running with input redirected it's much more likely that we're
\r
292 actually running as a service.
\r
293 This will save time when running with no arguments from a command prompt.
\r
295 if (! GetStdHandle(STD_INPUT_HANDLE)) {
\r
296 /* Start service magic */
\r
297 SERVICE_TABLE_ENTRY table[] = { { NSSM, service_main }, { 0, 0 } };
\r
298 if (! StartServiceCtrlDispatcher(table)) {
\r
299 unsigned long error = GetLastError();
\r
300 /* User probably ran nssm with no argument */
\r
301 if (error == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) nssm_exit(usage(1));
\r
302 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DISPATCHER_FAILED, error_string(error), 0);
\r
307 else nssm_exit(usage(1));
\r
309 /* And nothing more to do */
\r