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
17 /* Are two strings case-insensitively equivalent? */
\r
18 int str_equiv(const TCHAR *a, const TCHAR *b) {
\r
19 size_t len = _tcslen(a);
\r
20 if (_tcslen(b) != len) return 0;
\r
21 if (_tcsnicmp(a, b, len)) return 0;
\r
25 /* Convert a string to a number. */
\r
26 int str_number(const TCHAR *string, unsigned long *number, TCHAR **bogus) {
\r
27 if (! string) return 1;
\r
29 *number = _tcstoul(string, bogus, 0);
\r
30 if (**bogus) return 2;
\r
35 int str_number(const TCHAR *string, unsigned long *number) {
\r
37 return str_number(string, number, &bogus);
\r
40 /* Does a char need to be escaped? */
\r
41 static bool needs_escape(const TCHAR c) {
\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
48 if (c == _T('|')) return true;
\r
52 /* Does a char need to be quoted? */
\r
53 static bool needs_quote(const TCHAR c) {
\r
54 if (c == _T(' ')) return true;
\r
55 if (c == _T('\t')) return true;
\r
56 if (c == _T('\n')) return true;
\r
57 if (c == _T('\v')) return true;
\r
58 if (c == _T('"')) return true;
\r
59 if (c == _T('*')) return true;
\r
60 return needs_escape(c);
\r
63 /* https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ */
\r
64 /* http://www.robvanderwoude.com/escapechars.php */
\r
65 int quote(const TCHAR *unquoted, TCHAR *buffer, size_t buflen) {
\r
67 size_t len = _tcslen(unquoted);
\r
68 if (len > buflen - 1) return 1;
\r
70 bool escape = false;
\r
71 bool quotes = false;
\r
73 for (i = 0; i < len; i++) {
\r
74 if (needs_escape(unquoted[i])) {
\r
75 escape = quotes = true;
\r
78 if (needs_quote(unquoted[i])) quotes = true;
\r
81 memmove(buffer, unquoted, (len + 1) * sizeof(TCHAR));
\r
86 size_t quoted_len = 2;
\r
87 if (escape) quoted_len += 2;
\r
88 for (i = 0; ; i++) {
\r
91 while (i != len && unquoted[i] == _T('\\')) {
\r
97 quoted_len += n * 2;
\r
100 else if (unquoted[i] == _T('"')) quoted_len += n * 2 + 2;
\r
101 else quoted_len += n + 1;
\r
102 if (needs_escape(unquoted[i])) quoted_len += n;
\r
104 if (quoted_len > buflen - 1) return 1;
\r
107 if (escape) *s++ = _T('^');
\r
110 for (i = 0; ; i++) {
\r
113 while (i != len && unquoted[i] == _T('\\')) {
\r
119 for (j = 0; j < n * 2; j++) {
\r
120 if (escape) *s++ = _T('^');
\r
125 else if (unquoted[i] == _T('"')) {
\r
126 for (j = 0; j < n * 2 + 1; j++) {
\r
127 if (escape) *s++ = _T('^');
\r
130 if (escape && needs_escape(unquoted[i])) *s++ = _T('^');
\r
131 *s++ = unquoted[i];
\r
134 for (j = 0; j < n; j++) {
\r
135 if (escape) *s++ = _T('^');
\r
138 if (escape && needs_escape(unquoted[i])) *s++ = _T('^');
\r
139 *s++ = unquoted[i];
\r
142 if (escape) *s++ = _T('^');
\r
149 /* Remove basename of a path. */
\r
150 void strip_basename(TCHAR *buffer) {
\r
151 size_t len = _tcslen(buffer);
\r
153 for (i = len; i && buffer[i] != _T('\\') && buffer[i] != _T('/'); i--);
\r
155 if (i && buffer[i - 1] == _T(':')) i++;
\r
156 buffer[i] = _T('\0');
\r
159 /* How to use me correctly */
\r
160 int usage(int ret) {
\r
161 if (GetConsoleWindow()) print_message(stderr, NSSM_MESSAGE_USAGE, NSSM_VERSION, NSSM_CONFIGURATION, NSSM_DATE);
\r
162 else popup_message(0, MB_OK, NSSM_MESSAGE_USAGE, NSSM_VERSION, NSSM_CONFIGURATION, NSSM_DATE);
\r
166 void check_admin() {
\r
169 /* Lifted from MSDN examples */
\r
170 PSID AdministratorsGroup;
\r
171 SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
\r
172 if (! AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup)) return;
\r
173 CheckTokenMembership(0, AdministratorsGroup, /*XXX*/(PBOOL) &is_admin);
\r
174 FreeSid(AdministratorsGroup);
\r
177 static int elevate(int argc, TCHAR **argv, unsigned long message) {
\r
178 print_message(stderr, message);
\r
180 SHELLEXECUTEINFO sei;
\r
181 ZeroMemory(&sei, sizeof(sei));
\r
182 sei.cbSize = sizeof(sei);
\r
183 sei.lpVerb = _T("runas");
\r
184 sei.lpFile = (TCHAR *) nssm_imagepath();
\r
186 TCHAR *args = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, EXE_LENGTH * sizeof(TCHAR));
\r
188 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("GetCommandLine()"), _T("elevate()"));
\r
192 /* Get command line, which includes the path to NSSM, and skip that part. */
\r
193 _sntprintf_s(args, EXE_LENGTH, _TRUNCATE, _T("%s"), GetCommandLine());
\r
194 size_t s = _tcslen(argv[0]) + 1;
\r
195 if (args[0] == _T('"')) s += 2;
\r
196 while (isspace(args[s])) s++;
\r
198 sei.lpParameters = args + s;
\r
199 sei.nShow = SW_SHOW;
\r
201 unsigned long exitcode = 0;
\r
202 if (! ShellExecuteEx(&sei)) exitcode = 100;
\r
204 HeapFree(GetProcessHeap(), 0, (void *) args);
\r
209 DWORD_PTR i, affinity, system_affinity;
\r
210 if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) return 64;
\r
211 for (i = 0; system_affinity & (1LL << i); i++);
\r
215 const TCHAR *nssm_unquoted_imagepath() {
\r
216 return unquoted_imagepath;
\r
219 const TCHAR *nssm_imagepath() {
\r
223 const TCHAR *nssm_exe() {
\r
227 int _tmain(int argc, TCHAR **argv) {
\r
228 if (check_console()) setup_utf8();
\r
230 /* Remember if we are admin */
\r
233 /* Set up function pointers. */
\r
234 if (get_imports()) nssm_exit(111);
\r
236 /* Remember our path for later. */
\r
237 _sntprintf_s(imageargv0, _countof(imageargv0), _TRUNCATE, _T("%s"), argv[0]);
\r
238 PathQuoteSpaces(imageargv0);
\r
239 GetModuleFileName(0, unquoted_imagepath, _countof(unquoted_imagepath));
\r
240 GetModuleFileName(0, imagepath, _countof(imagepath));
\r
241 PathQuoteSpaces(imagepath);
\r
246 Valid commands are:
\r
247 start, stop, pause, continue, install, edit, get, set, reset, unset, remove
\r
249 if (str_equiv(argv[1], _T("start"))) nssm_exit(control_service(NSSM_SERVICE_CONTROL_START, argc - 2, argv + 2));
\r
250 if (str_equiv(argv[1], _T("stop"))) nssm_exit(control_service(SERVICE_CONTROL_STOP, argc - 2, argv + 2));
\r
251 if (str_equiv(argv[1], _T("restart"))) {
\r
252 int ret = control_service(SERVICE_CONTROL_STOP, argc - 2, argv + 2);
\r
253 if (ret) nssm_exit(ret);
\r
254 nssm_exit(control_service(NSSM_SERVICE_CONTROL_START, argc - 2, argv + 2));
\r
256 if (str_equiv(argv[1], _T("pause"))) nssm_exit(control_service(SERVICE_CONTROL_PAUSE, argc - 2, argv + 2));
\r
257 if (str_equiv(argv[1], _T("continue"))) nssm_exit(control_service(SERVICE_CONTROL_CONTINUE, argc - 2, argv + 2));
\r
258 if (str_equiv(argv[1], _T("status"))) nssm_exit(control_service(SERVICE_CONTROL_INTERROGATE, argc - 2, argv + 2));
\r
259 if (str_equiv(argv[1], _T("statuscode"))) nssm_exit(control_service(SERVICE_CONTROL_INTERROGATE, argc - 2, argv + 2, true));
\r
260 if (str_equiv(argv[1], _T("rotate"))) nssm_exit(control_service(NSSM_SERVICE_CONTROL_ROTATE, argc - 2, argv + 2));
\r
261 if (str_equiv(argv[1], _T("install"))) {
\r
262 if (! is_admin) nssm_exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_INSTALL));
\r
264 nssm_exit(pre_install_service(argc - 2, argv + 2));
\r
266 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
267 int ret = pre_edit_service(argc - 1, argv + 1);
\r
268 if (ret == 3 && ! is_admin && argc == 3) nssm_exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_EDIT));
\r
269 /* There might be a password here. */
\r
270 for (int i = 0; i < argc; i++) SecureZeroMemory(argv[i], _tcslen(argv[i]) * sizeof(TCHAR));
\r
273 if (str_equiv(argv[1], _T("list"))) nssm_exit(list_nssm_services(argc - 2, argv + 2));
\r
274 if (str_equiv(argv[1], _T("processes"))) nssm_exit(service_process_tree(argc - 2, argv + 2));
\r
275 if (str_equiv(argv[1], _T("remove"))) {
\r
276 if (! is_admin) nssm_exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_REMOVE));
\r
277 nssm_exit(pre_remove_service(argc - 2, argv + 2));
\r
281 /* Thread local storage for error message buffer */
\r
282 tls_index = TlsAlloc();
\r
284 /* Register messages */
\r
285 if (is_admin) create_messages();
\r
288 Optimisation for Windows 2000:
\r
289 When we're run from the command line the StartServiceCtrlDispatcher() call
\r
290 will time out after a few seconds on Windows 2000. On newer versions the
\r
291 call returns instantly. Check for stdin first and only try to call the
\r
292 function if there's no input stream found. Although it's possible that
\r
293 we're running with input redirected it's much more likely that we're
\r
294 actually running as a service.
\r
295 This will save time when running with no arguments from a command prompt.
\r
297 if (! GetStdHandle(STD_INPUT_HANDLE)) {
\r
298 /* Start service magic */
\r
299 SERVICE_TABLE_ENTRY table[] = { { NSSM, service_main }, { 0, 0 } };
\r
300 if (! StartServiceCtrlDispatcher(table)) {
\r
301 unsigned long error = GetLastError();
\r
302 /* User probably ran nssm with no argument */
\r
303 if (error == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) nssm_exit(usage(1));
\r
304 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DISPATCHER_FAILED, error_string(error), 0);
\r
308 else nssm_exit(usage(1));
\r
310 /* And nothing more to do */
\r