mcp-pacemaker 2.0.2 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +171 -1
  2. package/CONTRIBUTING.md +46 -13
  3. package/README.md +109 -524
  4. package/THIRD_PARTY_NOTICES.txt +1 -28
  5. package/autostart/windows/account-task.ps1 +167 -0
  6. package/autostart/windows/inspect-task.ps1 +42 -0
  7. package/autostart/windows/inspect-worker-process-sessions.ps1 +155 -0
  8. package/autostart/windows/legacy-access.ps1 +359 -0
  9. package/autostart/windows/legacy-task.ps1 +301 -0
  10. package/autostart/windows/register-task.ps1 +11 -2
  11. package/bin/account-task-adapter.mjs +47 -0
  12. package/bin/account-upgrade-controller.mjs +198 -0
  13. package/bin/account-upgrade-worker.mjs +141 -0
  14. package/bin/account-worker-session.mjs +215 -0
  15. package/bin/cli-dispatch.mjs +154 -0
  16. package/bin/cli-main.mjs +1063 -0
  17. package/bin/cli-selection.mjs +43 -0
  18. package/bin/cli.mjs +7 -876
  19. package/bin/dependency-lock.json +642 -0
  20. package/bin/legacy-1.3.json +32 -0
  21. package/bin/legacy-access.mjs +73 -0
  22. package/bin/legacy-installation.mjs +273 -0
  23. package/bin/legacy-task.mjs +76 -0
  24. package/bin/legacy-upgrade.mjs +527 -0
  25. package/bin/managed-runtime.mjs +170 -0
  26. package/bin/managed-state.mjs +257 -0
  27. package/bin/managed-upgrade.mjs +581 -0
  28. package/bin/mcp-bridge.mjs +150 -4
  29. package/bin/pooling-batches.mjs +13 -0
  30. package/bin/pooling-writer.mjs +4 -0
  31. package/bin/prewarm.mjs +59 -25
  32. package/bin/runtime-context.mjs +100 -0
  33. package/bin/service-control.mjs +32 -4
  34. package/bin/stable-launcher.mjs +54 -0
  35. package/bin/task-only-controller.mjs +226 -0
  36. package/bin/task-scope.mjs +65 -0
  37. package/bin/task-transaction-protocol.mjs +173 -0
  38. package/bin/top.mjs +22 -10
  39. package/bin/update-check.mjs +13 -2
  40. package/bin/upgrade-autostart.mjs +47 -0
  41. package/bin/upgrade-capability.json +9 -0
  42. package/bin/upgrade-dependencies.mjs +183 -0
  43. package/bin/upgrade-package.mjs +205 -0
  44. package/bin/windows-legacy/LegacyProcessBroker.build.json +42 -0
  45. package/bin/windows-legacy/LegacyProcessBroker.exe +0 -0
  46. package/bin/windows-legacy/src/AssemblyInfo.cs +8 -0
  47. package/bin/windows-legacy/src/LegacyNative.cs +832 -0
  48. package/bin/windows-legacy/src/LegacyProcessBroker.cs +961 -0
  49. package/bin/windows-legacy-process.mjs +248 -0
  50. package/bin/windows-lifetime/ProcessLifetimeHelper.build.json +38 -0
  51. package/bin/windows-lifetime/ProcessLifetimeHelper.exe +0 -0
  52. package/bin/windows-lifetime/src/AssemblyInfo.cs +8 -0
  53. package/bin/windows-lifetime/src/ProcessLifetimeHelper.cs +468 -0
  54. package/bin/windows-process-lifetime.mjs +218 -0
  55. package/bin/windows-task-channel/TaskChannelGuard.build.json +45 -0
  56. package/bin/windows-task-channel/TaskChannelGuard.exe +0 -0
  57. package/bin/windows-task-channel/src/AssemblyInfo.cs +8 -0
  58. package/bin/windows-task-channel/src/ChannelCliContext.cs +210 -0
  59. package/bin/windows-task-channel/src/ChannelFiles.cs +231 -0
  60. package/bin/windows-task-channel/src/ChannelNative.cs +477 -0
  61. package/bin/windows-task-channel/src/ChannelSession.cs +203 -0
  62. package/bin/windows-task-channel/src/TaskChannelGuard.cs +686 -0
  63. package/bin/windows-task-channel.mjs +335 -0
  64. package/docs/account-upgrades.md +218 -0
  65. package/docs/compatibility.md +39 -0
  66. package/docs/configuration.md +146 -0
  67. package/docs/dashboards.md +125 -0
  68. package/docs/execution-context.md +119 -0
  69. package/docs/http-api.md +47 -0
  70. package/docs/install.md +52 -0
  71. package/docs/operations.md +99 -0
  72. package/docs/upgrades.md +324 -0
  73. package/package.json +19 -5
  74. package/supervisor/supervise.mjs +226 -40
  75. package/tools/account-upgrade/qualify-two-account.ps1 +66 -0
  76. package/tools/windows-legacy/README.md +251 -0
  77. package/tools/windows-legacy/build.ps1 +122 -0
  78. package/tools/windows-legacy/inventory.mjs +92 -0
  79. package/tools/windows-process-lifetime/README.md +119 -0
  80. package/tools/windows-process-lifetime/build.ps1 +122 -0
  81. package/tools/windows-process-lifetime/inventory.mjs +86 -0
  82. package/tools/windows-task-channel/README.md +329 -0
  83. package/tools/windows-task-channel/build.ps1 +107 -0
  84. package/tools/windows-task-channel/inventory.mjs +117 -0
  85. package/ui/dist/THIRD_PARTY_NOTICES.txt +1 -28
  86. package/ui/dist/assets/{index-CgPruWYi.js → index-CcwMo2sI.js} +1 -1
  87. package/ui/dist/assets/index-PVcuDNrN.css +1 -0
  88. package/ui/dist/index.html +2 -2
  89. package/ui/dist/third-party-manifest.json +37 -25
  90. package/ui/dist/assets/index-CaLPg0c-.css +0 -1
@@ -0,0 +1,477 @@
1
+ using System;
2
+ using System.ComponentModel;
3
+ using System.Collections.Generic;
4
+ using System.Globalization;
5
+ using System.IO;
6
+ using System.Linq;
7
+ using System.Management;
8
+ using System.Runtime.InteropServices;
9
+ using System.Security.Principal;
10
+ using System.Text;
11
+ using Microsoft.Win32.SafeHandles;
12
+
13
+ internal sealed class ChannelNative
14
+ {
15
+ internal void Require(bool result)
16
+ {
17
+ if (!result) throw new Win32Exception(Marshal.GetLastWin32Error());
18
+ }
19
+
20
+ internal ProcessHandle Open(int pid)
21
+ {
22
+ var handle = ChannelApi.OpenProcess(0x101000, false, pid);
23
+ if (handle.IsInvalid)
24
+ {
25
+ handle.Dispose();
26
+ throw new Win32Exception(Marshal.GetLastWin32Error());
27
+ }
28
+ return handle;
29
+ }
30
+
31
+ internal bool Alive(ProcessHandle handle)
32
+ {
33
+ uint result = ChannelApi.WaitForSingleObject(handle, 0);
34
+ if (result == 0xffffffff) throw new Win32Exception(Marshal.GetLastWin32Error());
35
+ return result == 0x102;
36
+ }
37
+
38
+ internal long Birth(ProcessHandle handle)
39
+ {
40
+ long created, exited, kernel, user;
41
+ Require(ChannelApi.GetProcessTimes(handle, out created, out exited, out kernel, out user));
42
+ return created;
43
+ }
44
+
45
+ internal string Image(ProcessHandle handle)
46
+ {
47
+ var name = new StringBuilder(32768);
48
+ int length = name.Capacity;
49
+ Require(ChannelApi.QueryFullProcessImageNameW(handle, 0, name, ref length));
50
+ return name.ToString();
51
+ }
52
+
53
+ internal ProcessHandle Token(ProcessHandle process)
54
+ {
55
+ ProcessHandle token;
56
+ Require(ChannelApi.OpenProcessToken(process, 8, out token));
57
+ return token;
58
+ }
59
+
60
+ internal T Information<T>(ProcessHandle token, int kind, Func<IntPtr, int, T> read)
61
+ {
62
+ int length;
63
+ ChannelApi.GetTokenInformation(token, kind, IntPtr.Zero, 0, out length);
64
+ if (length < 1 ||
65
+ length > 65536) throw new InvalidOperationException("TOKEN_BOUNDS_" + kind.ToString(CultureInfo.InvariantCulture) +
66
+ "_ERROR_" + Marshal.GetLastWin32Error().ToString(CultureInfo.InvariantCulture));
67
+ var memory = Marshal.AllocHGlobal(length);
68
+ try
69
+ {
70
+ int returned;
71
+ Require(ChannelApi.GetTokenInformation(token, kind, memory, length, out returned));
72
+ if (returned > length) throw new InvalidOperationException("TOKEN_CHANGED");
73
+ return read(memory, returned);
74
+ }
75
+ finally { Marshal.FreeHGlobal(memory); }
76
+ }
77
+
78
+ internal string Sid(ProcessHandle token)
79
+ {
80
+ return Information(token, 1, (memory, length) =>
81
+ new SecurityIdentifier(Marshal.ReadIntPtr(memory)).Value);
82
+ }
83
+
84
+ internal int Number(ProcessHandle token, int kind)
85
+ {
86
+ return Information(token, kind, (memory, length) =>
87
+ {
88
+ if (length == 1) return (int)Marshal.ReadByte(memory);
89
+ if (length != 4) throw new InvalidOperationException("TOKEN_SCALAR_BOUNDS");
90
+ return Marshal.ReadInt32(memory);
91
+ });
92
+ }
93
+
94
+ internal ActorFacts Facts(ProcessHandle process, bool observeThreads, Action checkpoint)
95
+ {
96
+ using (var token = Token(process))
97
+ {
98
+ var threads = "unverified";
99
+ if (observeThreads)
100
+ {
101
+ var observation = ObserveThreads(process, checkpoint);
102
+ if (observation.impersonating) throw new InvalidOperationException("PARENT_THREAD_IMPERSONATING");
103
+ threads = "observed-none";
104
+ }
105
+ bool enabledAdmin = Information(token, 2, (memory, length) =>
106
+ {
107
+ int count = Marshal.ReadInt32(memory);
108
+ int offset = IntPtr.Size == 8 ? 8 : 4;
109
+ int stride = IntPtr.Size == 8 ? 16 : 8;
110
+ if (count < 0 ||
111
+ count > (length - offset) / stride) throw new InvalidOperationException("TOKEN_GROUP_BOUNDS");
112
+ for (int index = 0; index < count; index++)
113
+ {
114
+ var row = IntPtr.Add(memory, offset + index * stride);
115
+ var sid = new SecurityIdentifier(Marshal.ReadIntPtr(row)).Value;
116
+ int flags = Marshal.ReadInt32(row, IntPtr.Size);
117
+ if (sid == "S-1-5-32-544")
118
+ return (flags & 4) != 0 && (flags & 16) == 0;
119
+ }
120
+ return false;
121
+ });
122
+ return new ActorFacts
123
+ {
124
+ ownerSid = Sid(token), sessionId = Number(token, 12),
125
+ elevated = Number(token, 20) != 0, enabledAdministrator = enabledAdmin,
126
+ restricted = ChannelApi.IsTokenRestricted(token) || Number(token, 21) != 0,
127
+ appContainer = Number(token, 29) != 0,
128
+ parentThreadImpersonation = threads,
129
+ parentThreadQueryError = observeThreads ? null : "NOT_OBSERVED_READ_ONLY_ROLE",
130
+ guardThreadImpersonating = ThreadImpersonating(),
131
+ observedAt = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)
132
+ };
133
+ }
134
+ }
135
+
136
+ private long FileTimeValue(ContextApi.FileTime value)
137
+ {
138
+ return ((long)value.high << 32) | value.low;
139
+ }
140
+
141
+ internal List<ContextApi.ThreadEntry> CaptureThreads(ProcessHandle parent, Action checkpoint, out long began)
142
+ {
143
+ checkpoint();
144
+ int entrySize = Marshal.SizeOf(typeof(ContextApi.ThreadEntry));
145
+ if ((IntPtr.Size != 4 && IntPtr.Size != 8) ||
146
+ entrySize != (IntPtr.Size == 8 ? 120 : 92))
147
+ throw new ContextObservationException("THREAD_SNAPSHOT_LAYOUT_UNSUPPORTED");
148
+ ContextApi.GetSystemTimePreciseAsFileTime(out began);
149
+ IntPtr snapshot;
150
+ uint status = ContextApi.PssCaptureSnapshot(parent, 0x80, 0, out snapshot);
151
+ if (status != 0) throw new ContextObservationException("THREAD_SNAPSHOT_FAILED_" + status.ToString(CultureInfo.InvariantCulture));
152
+ try
153
+ {
154
+ var information = new ContextApi.ThreadInformation();
155
+ status = ContextApi.PssQuerySnapshot(snapshot, 5, out information, (uint)Marshal.SizeOf(information));
156
+ RequireSnapshotStatus(status);
157
+ if (information.count < 1 ||
158
+ information.count > 128) throw new ContextObservationException("THREAD_SNAPSHOT_BOUNDS");
159
+ IntPtr marker;
160
+ RequireSnapshotStatus(ContextApi.PssWalkMarkerCreate(IntPtr.Zero, out marker));
161
+ try
162
+ {
163
+ var entries = new List<ContextApi.ThreadEntry>();
164
+ while (true)
165
+ {
166
+ checkpoint();
167
+ var entry = new ContextApi.ThreadEntry();
168
+ status = ContextApi.PssWalkSnapshot(snapshot, 3, marker, out entry, (uint)entrySize);
169
+ if (status == 259) break;
170
+ RequireSnapshotStatus(status);
171
+ if (entry.threadId == 0 ||
172
+ entries.Any(previous => previous.threadId == entry.threadId) ||
173
+ entries.Count >= 128) throw new ContextObservationException("THREAD_SNAPSHOT_INCOMPLETE");
174
+ entries.Add(entry);
175
+ }
176
+ if (entries.Count != information.count) throw new ContextObservationException("THREAD_SNAPSHOT_COUNT_CHANGED");
177
+ entries.Sort((first, second) => first.threadId.CompareTo(second.threadId));
178
+ return entries;
179
+ }
180
+ finally { RequireSnapshotStatus(ContextApi.PssWalkMarkerFree(marker)); }
181
+ }
182
+ finally { RequireSnapshotStatus(ContextApi.PssFreeSnapshot(ContextApi.GetCurrentProcess(), snapshot)); }
183
+ }
184
+
185
+ private void RequireSnapshotStatus(uint status)
186
+ {
187
+ if (status != 0) throw new ContextObservationException("THREAD_SNAPSHOT_FAILED_" + status.ToString(CultureInfo.InvariantCulture));
188
+ }
189
+
190
+ private bool ValidateThread(ContextApi.ThreadEntry entry, ProcessHandle thread, int pid,
191
+ long parentBirth, long began)
192
+ {
193
+ long birth = FileTimeValue(entry.creation);
194
+ long exit = FileTimeValue(entry.exit);
195
+ bool terminated = entry.flags == 1;
196
+ bool validTimes = terminated
197
+ ? parentBirth <= birth && birth < exit && exit < began
198
+ : parentBirth <= birth && birth <= began && exit == 0;
199
+ if ((entry.flags & ~1u) != 0 ||
200
+ !validTimes) throw new ContextObservationException("THREAD_SNAPSHOT_METADATA_INVALID");
201
+ if (entry.processId != pid ||
202
+ ChannelApi.GetProcessIdOfThread(thread) != pid)
203
+ throw new ContextObservationException("THREAD_IDENTITY_CHANGED");
204
+ long heldBirth, heldExit, kernel, user;
205
+ Require(ChannelApi.GetThreadTimes(thread, out heldBirth, out heldExit, out kernel, out user));
206
+ if (heldBirth != birth ||
207
+ heldExit != exit) throw new ContextObservationException("HELD_THREAD_CHANGED_OR_EXITED");
208
+ uint wait = ChannelApi.WaitForSingleObject(thread, 0);
209
+ if (wait == 0xffffffff) throw new Win32Exception(Marshal.GetLastWin32Error());
210
+ if (wait != (terminated ? 0u : 0x102u))
211
+ throw new ContextObservationException("THREAD_LIVENESS_CHANGED");
212
+ return terminated;
213
+ }
214
+
215
+ private bool SameThread(ContextApi.ThreadEntry first, ContextApi.ThreadEntry second)
216
+ {
217
+ return first.threadId == second.threadId &&
218
+ first.processId == second.processId &&
219
+ first.flags == second.flags &&
220
+ FileTimeValue(first.creation) == FileTimeValue(second.creation) &&
221
+ FileTimeValue(first.exit) == FileTimeValue(second.exit);
222
+ }
223
+
224
+ internal ThreadObservation ObserveThreads(ProcessHandle process, Action checkpoint)
225
+ {
226
+ int pid = checked((int)ChannelApi.GetProcessId(process));
227
+ if (pid <= 0) throw new InvalidOperationException("PARENT_PID_UNAVAILABLE");
228
+ long began;
229
+ var before = CaptureThreads(process, checkpoint, out began);
230
+ var held = new List<ProcessHandle>();
231
+ bool impersonating = false;
232
+ int liveCount = 0;
233
+ try
234
+ {
235
+ long parentBirth = Birth(process);
236
+ foreach (var entry in before)
237
+ {
238
+ checkpoint();
239
+ var thread = ContextApi.OpenThread(0x100040, false, entry.threadId);
240
+ held.Add(thread);
241
+ Require(!thread.IsInvalid);
242
+ if (!ValidateThread(entry, thread, pid, parentBirth, began))
243
+ {
244
+ liveCount++;
245
+ impersonating |= HasThreadToken(thread);
246
+ }
247
+ }
248
+ if (liveCount == 0) throw new ContextObservationException("LIVE_THREAD_SET_EMPTY");
249
+ long secondBegan;
250
+ var after = CaptureThreads(process, checkpoint, out secondBegan);
251
+ if (!before.Select(entry => entry.threadId).SequenceEqual(after.Select(entry => entry.threadId)) ||
252
+ !Alive(process)) throw new ContextObservationException("THREAD_SET_CHANGED");
253
+ for (int index = 0; index < held.Count; index++)
254
+ {
255
+ checkpoint();
256
+ if (!SameThread(before[index], after[index]))
257
+ throw new ContextObservationException("HELD_THREAD_CHANGED_OR_EXITED");
258
+ var thread = held[index];
259
+ if (!ValidateThread(after[index], thread, pid, parentBirth, began))
260
+ impersonating |= HasThreadToken(thread);
261
+ }
262
+ return new ThreadObservation { count = before.Count, impersonating = impersonating };
263
+ }
264
+ finally { foreach (var thread in held) thread.Dispose(); }
265
+ }
266
+
267
+ internal bool HasThreadToken(ProcessHandle thread)
268
+ {
269
+ ProcessHandle token;
270
+ bool opened = ChannelApi.OpenHeldThreadToken(thread, 8, true, out token);
271
+ int error = Marshal.GetLastWin32Error();
272
+ if (token != null) token.Dispose();
273
+ if (opened) return true;
274
+ if (error != 1008) throw new Win32Exception(error);
275
+ return false;
276
+ }
277
+
278
+ internal bool ThreadImpersonating()
279
+ {
280
+ ProcessHandle token;
281
+ bool opened = ChannelApi.OpenThreadToken(ChannelApi.GetCurrentThread(), 8, true, out token);
282
+ int error = Marshal.GetLastWin32Error();
283
+ if (opened)
284
+ {
285
+ token.Dispose();
286
+ return true;
287
+ }
288
+ if (token != null) token.Dispose();
289
+ if (error != 1008) throw new Win32Exception(error);
290
+ return false;
291
+ }
292
+
293
+ internal Identity Identity(int pid, ProcessHandle handle)
294
+ {
295
+ using (var token = Token(handle))
296
+ {
297
+ return new Identity
298
+ {
299
+ pid = pid, creationTime = Birth(handle).ToString(CultureInfo.InvariantCulture),
300
+ ownerSid = Sid(token), sessionId = Number(token, 12), imagePath = Image(handle)
301
+ };
302
+ }
303
+ }
304
+
305
+ internal ManagementObject ProcessRow(int pid, ProcessHandle held)
306
+ {
307
+ var options = new EnumerationOptions { Timeout = TimeSpan.FromSeconds(2), ReturnImmediately = false };
308
+ using (var search = new ManagementObjectSearcher(@"root\cimv2",
309
+ "SELECT ProcessId,ParentProcessId,CreationDate,CommandLine FROM Win32_Process WHERE ProcessId = " +
310
+ pid.ToString(CultureInfo.InvariantCulture), options))
311
+ using (var rows = search.Get())
312
+ {
313
+ if (rows.Count != 1) throw new InvalidOperationException("PROCESS_ROW_MISSING");
314
+ foreach (ManagementObject row in rows)
315
+ {
316
+ var date = row["CreationDate"] as string;
317
+ if (date == null) throw new InvalidOperationException("PROCESS_DATE_MISSING");
318
+ long creation = ManagementDateTimeConverter.ToDateTime(date).ToUniversalTime().ToFileTimeUtc();
319
+ if (Math.Abs(creation - Birth(held)) > 10 ||
320
+ !Alive(held)) throw new InvalidOperationException("PROCESS_GENERATION_CHANGED");
321
+ return row;
322
+ }
323
+ }
324
+ throw new InvalidOperationException("PROCESS_ROW_MISSING");
325
+ }
326
+
327
+ internal int Parent(int self, ProcessHandle held)
328
+ {
329
+ using (var row = ProcessRow(self, held)) return Convert.ToInt32(row["ParentProcessId"], CultureInfo.InvariantCulture);
330
+ }
331
+
332
+ internal string[] Arguments(int pid, ProcessHandle held)
333
+ {
334
+ using (var row = ProcessRow(pid, held))
335
+ {
336
+ var command = row["CommandLine"] as string;
337
+ if (string.IsNullOrEmpty(command) ||
338
+ command.Length > 32768) throw new InvalidOperationException("ARGV_UNAVAILABLE");
339
+ int count;
340
+ var memory = ChannelApi.CommandLineToArgvW(command, out count);
341
+ Require(memory != IntPtr.Zero);
342
+ try
343
+ {
344
+ if (count < 1 ||
345
+ count > 128) throw new InvalidOperationException("ARGV_BOUNDS");
346
+ var arguments = new string[count];
347
+ for (int index = 0; index < count; index++)
348
+ arguments[index] = Marshal.PtrToStringUni(Marshal.ReadIntPtr(memory, index * IntPtr.Size));
349
+ return arguments;
350
+ }
351
+ finally { ChannelApi.LocalFree(memory); }
352
+ }
353
+ }
354
+ }
355
+
356
+ internal sealed class ThreadObservation
357
+ {
358
+ internal int count;
359
+ internal bool impersonating;
360
+ }
361
+
362
+ internal sealed class ProcessHandle : SafeHandleZeroOrMinusOneIsInvalid
363
+ {
364
+ public ProcessHandle() : base(true) { }
365
+ protected override bool ReleaseHandle() { return ChannelApi.CloseHandle(handle); }
366
+ }
367
+
368
+ internal sealed class Identity
369
+ {
370
+ public int pid { get; set; }
371
+ public string creationTime { get; set; }
372
+ public string ownerSid { get; set; }
373
+ public int sessionId { get; set; }
374
+ public string imagePath { get; set; }
375
+ public string imageSha256 { get; set; }
376
+ public string argvSha256 { get; set; }
377
+ }
378
+
379
+ internal sealed class ActorFacts
380
+ {
381
+ public string ownerSid { get; set; }
382
+ public int sessionId { get; set; }
383
+ public bool elevated { get; set; }
384
+ public bool enabledAdministrator { get; set; }
385
+ public bool restricted { get; set; }
386
+ public bool appContainer { get; set; }
387
+ public bool guardThreadImpersonating { get; set; }
388
+ public string parentThreadImpersonation { get; set; }
389
+ public string parentThreadQueryError { get; set; }
390
+ public string observedAt { get; set; }
391
+ }
392
+
393
+ internal static class ChannelApi
394
+ {
395
+ [StructLayout(LayoutKind.Sequential)]
396
+ internal struct SecurityAttributes
397
+ {
398
+ internal int length;
399
+ internal IntPtr descriptor;
400
+ [MarshalAs(UnmanagedType.Bool)] internal bool inherit;
401
+ }
402
+ [DllImport("kernel32.dll", SetLastError = true)]
403
+ internal static extern ProcessHandle OpenProcess(uint access, [MarshalAs(UnmanagedType.Bool)] bool inherit, int pid);
404
+ [DllImport("kernel32.dll", SetLastError = true)]
405
+ [return: MarshalAs(UnmanagedType.Bool)]
406
+ internal static extern bool CloseHandle(IntPtr handle);
407
+ [DllImport("kernel32.dll", SetLastError = true)]
408
+ internal static extern uint WaitForSingleObject(ProcessHandle handle, uint milliseconds);
409
+ [DllImport("kernel32.dll", SetLastError = true)]
410
+ internal static extern uint GetProcessId(ProcessHandle process);
411
+ [DllImport("kernel32.dll", SetLastError = true)]
412
+ internal static extern ProcessHandle OpenThread(uint access, [MarshalAs(UnmanagedType.Bool)] bool inherit, int tid);
413
+ [DllImport("kernel32.dll", SetLastError = true)]
414
+ internal static extern uint GetProcessIdOfThread(ProcessHandle thread);
415
+ [DllImport("kernel32.dll", SetLastError = true)]
416
+ [return: MarshalAs(UnmanagedType.Bool)]
417
+ internal static extern bool GetThreadTimes(ProcessHandle thread, out long birth, out long exit, out long kernel, out long user);
418
+ [DllImport("advapi32.dll", EntryPoint = "OpenThreadToken", SetLastError = true)]
419
+ [return: MarshalAs(UnmanagedType.Bool)]
420
+ internal static extern bool OpenHeldThreadToken(ProcessHandle thread, uint access,
421
+ [MarshalAs(UnmanagedType.Bool)] bool openAsSelf, out ProcessHandle token);
422
+ [DllImport("kernel32.dll", SetLastError = true)]
423
+ [return: MarshalAs(UnmanagedType.Bool)]
424
+ internal static extern bool GetProcessTimes(ProcessHandle handle, out long creation, out long exit, out long kernel, out long user);
425
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
426
+ [return: MarshalAs(UnmanagedType.Bool)]
427
+ internal static extern bool QueryFullProcessImageNameW(ProcessHandle handle, int flags, StringBuilder name, ref int length);
428
+ [DllImport("advapi32.dll", SetLastError = true)]
429
+ [return: MarshalAs(UnmanagedType.Bool)]
430
+ internal static extern bool OpenProcessToken(ProcessHandle process, uint access, out ProcessHandle token);
431
+ [DllImport("advapi32.dll", SetLastError = true)]
432
+ [return: MarshalAs(UnmanagedType.Bool)]
433
+ internal static extern bool GetTokenInformation(ProcessHandle token, int kind, IntPtr memory, int length, out int returned);
434
+ [DllImport("advapi32.dll", SetLastError = true)]
435
+ [return: MarshalAs(UnmanagedType.Bool)]
436
+ internal static extern bool IsTokenRestricted(ProcessHandle token);
437
+ [DllImport("advapi32.dll", SetLastError = true)]
438
+ [return: MarshalAs(UnmanagedType.Bool)]
439
+ internal static extern bool OpenThreadToken(IntPtr thread, uint access, [MarshalAs(UnmanagedType.Bool)] bool openAsSelf, out ProcessHandle token);
440
+ [DllImport("kernel32.dll")]
441
+ internal static extern IntPtr GetCurrentThread();
442
+ [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
443
+ internal static extern IntPtr CommandLineToArgvW(string command, out int count);
444
+ [DllImport("kernel32.dll")]
445
+ internal static extern IntPtr LocalFree(IntPtr memory);
446
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
447
+ internal static extern SafePipeHandle CreateNamedPipeW(string name, uint openMode, uint pipeMode,
448
+ int instances, int outputSize, int inputSize, int timeout, ref SecurityAttributes security);
449
+ [DllImport("kernel32.dll", SetLastError = true)]
450
+ [return: MarshalAs(UnmanagedType.Bool)]
451
+ internal static extern bool GetNamedPipeClientProcessId(SafePipeHandle pipe, out uint pid);
452
+ [DllImport("kernel32.dll", SetLastError = true)]
453
+ [return: MarshalAs(UnmanagedType.Bool)]
454
+ internal static extern bool GetNamedPipeClientSessionId(SafePipeHandle pipe, out uint session);
455
+ [DllImport("kernel32.dll", SetLastError = true)]
456
+ [return: MarshalAs(UnmanagedType.Bool)]
457
+ internal static extern bool PeekNamedPipe(SafePipeHandle pipe, IntPtr buffer, uint length,
458
+ IntPtr bytesRead, out uint available, IntPtr bytesLeft);
459
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
460
+ [return: MarshalAs(UnmanagedType.Bool)]
461
+ internal static extern bool CreateDirectoryW(string path, ref SecurityAttributes security);
462
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
463
+ internal static extern SafeFileHandle CreateFileW(string path, uint access, uint share,
464
+ ref SecurityAttributes security, uint creation, uint flags, IntPtr template);
465
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
466
+ internal static extern uint GetFinalPathNameByHandleW(SafeFileHandle handle, StringBuilder name, int length, int flags);
467
+ [StructLayout(LayoutKind.Sequential)]
468
+ internal struct FileInformation
469
+ {
470
+ internal uint attributes;
471
+ internal uint creationLow, creationHigh, accessLow, accessHigh, writeLow, writeHigh;
472
+ internal uint volume, sizeHigh, sizeLow, links, indexHigh, indexLow;
473
+ }
474
+ [DllImport("kernel32.dll", SetLastError = true)]
475
+ [return: MarshalAs(UnmanagedType.Bool)]
476
+ internal static extern bool GetFileInformationByHandle(SafeFileHandle handle, out FileInformation information);
477
+ }
@@ -0,0 +1,203 @@
1
+ using System;
2
+ using System.ComponentModel;
3
+ using System.Globalization;
4
+ using System.Runtime.InteropServices;
5
+ using System.Security.Principal;
6
+ using System.Text;
7
+
8
+ internal sealed class ChannelSession
9
+ {
10
+ internal SessionFact Observe(string expectedSid, int sessionId, string expectedLogonTime)
11
+ {
12
+ if (sessionId <= 0) throw new InvalidOperationException("INTERACTIVE_SESSION_REQUIRED");
13
+ var before = Snapshot(sessionId);
14
+ var user = Text(sessionId, 5);
15
+ var domain = Text(sessionId, 7);
16
+ if (string.IsNullOrWhiteSpace(user) ||
17
+ string.IsNullOrWhiteSpace(domain) ||
18
+ user.IndexOf('\\') >= 0 ||
19
+ domain.IndexOf('\\') >= 0) throw new InvalidOperationException("SESSION_ACCOUNT_UNAVAILABLE");
20
+ var sid = Resolve(domain + "\\" + user);
21
+ var secondUser = Text(sessionId, 5);
22
+ var secondDomain = Text(sessionId, 7);
23
+ var after = Snapshot(sessionId);
24
+ if (!string.Equals(user, secondUser, StringComparison.Ordinal) ||
25
+ !string.Equals(domain, secondDomain, StringComparison.Ordinal))
26
+ throw new InvalidOperationException("SESSION_ACCOUNT_CHANGED");
27
+ MatchName(before.shortUser, user, 20);
28
+ MatchName(after.shortUser, user, 20);
29
+ MatchName(before.shortDomain, domain, 17);
30
+ MatchName(after.shortDomain, domain, 17);
31
+ return Validate(before, after, sid, expectedSid, sessionId, expectedLogonTime);
32
+ }
33
+
34
+ private void MatchName(string snapshot, string full, int bound)
35
+ {
36
+ bool matches = !string.IsNullOrEmpty(snapshot) &&
37
+ snapshot.Length <= bound &&
38
+ (snapshot.Length == bound
39
+ ? full.StartsWith(snapshot, StringComparison.OrdinalIgnoreCase)
40
+ : string.Equals(snapshot, full, StringComparison.OrdinalIgnoreCase));
41
+ if (!matches) throw new InvalidOperationException("SESSION_ACCOUNT_CHANGED");
42
+ }
43
+
44
+ internal SessionFact Validate(SessionSnapshot before, SessionSnapshot after, string resolvedSid,
45
+ string expectedSid, int sessionId, string expectedLogonTime)
46
+ {
47
+ if (sessionId <= 0 ||
48
+ before.sessionId != sessionId ||
49
+ after.sessionId != sessionId) throw new InvalidOperationException("SESSION_ID_MISMATCH");
50
+ bool loggedOn = (before.state == 0 || before.state == 4) &&
51
+ (after.state == 0 || after.state == 4);
52
+ if (!loggedOn ||
53
+ before.logonTime <= 0 ||
54
+ after.logonTime <= 0) throw new InvalidOperationException("SESSION_NOT_LOGGED_ON");
55
+ bool stable = before.logonTime == after.logonTime &&
56
+ before.state == after.state &&
57
+ before.shortUser == after.shortUser &&
58
+ before.shortDomain == after.shortDomain;
59
+ if (!stable) throw new InvalidOperationException("SESSION_GENERATION_CHANGED");
60
+ if (string.IsNullOrEmpty(resolvedSid) ||
61
+ resolvedSid != expectedSid) throw new InvalidOperationException("SESSION_OWNER_MISMATCH");
62
+ var logon = after.logonTime.ToString(CultureInfo.InvariantCulture);
63
+ if (expectedLogonTime != null &&
64
+ expectedLogonTime != logon) throw new InvalidOperationException("SESSION_GENERATION_CHANGED");
65
+ return new SessionFact
66
+ {
67
+ sessionId = sessionId, ownerSid = resolvedSid, state = after.state == 0 ? "active" : "disconnected",
68
+ logonTime = logon, observedAt = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture),
69
+ source = "wts-account-sid-snapshot", atomicRunExBinding = false
70
+ };
71
+ }
72
+
73
+ private T Query<T>(int sessionId, int kind, Func<IntPtr, uint, T> read)
74
+ {
75
+ IntPtr buffer;
76
+ uint length;
77
+ bool succeeded = WtsApi.WTSQuerySessionInformationW(IntPtr.Zero, sessionId, kind, out buffer, out length);
78
+ int error = Marshal.GetLastWin32Error();
79
+ try
80
+ {
81
+ RequireQuery(succeeded, error);
82
+ if (buffer == IntPtr.Zero ||
83
+ length < 2 ||
84
+ length > 65536) throw new InvalidOperationException("SESSION_QUERY_BOUNDS");
85
+ return read(buffer, length);
86
+ }
87
+ finally { if (buffer != IntPtr.Zero) WtsApi.WTSFreeMemory(buffer); }
88
+ }
89
+
90
+ private void RequireQuery(bool succeeded, int error)
91
+ {
92
+ if (!succeeded) throw new InvalidOperationException("SESSION_QUERY_FAILED_" + error.ToString(CultureInfo.InvariantCulture));
93
+ }
94
+
95
+ private void RequireUser(int use)
96
+ {
97
+ if (use != 1) throw new InvalidOperationException("SESSION_ACCOUNT_NOT_USER");
98
+ }
99
+
100
+ private string Text(int sessionId, int kind)
101
+ {
102
+ return Query(sessionId, kind, (buffer, length) =>
103
+ {
104
+ if ((length & 1) != 0) throw new InvalidOperationException("SESSION_TEXT_BOUNDS");
105
+ int count = checked((int)length / 2);
106
+ var text = Marshal.PtrToStringUni(buffer, count);
107
+ if (text[count - 1] != '\0') throw new InvalidOperationException("SESSION_TEXT_UNTERMINATED");
108
+ text = text.Substring(0, count - 1);
109
+ if (text.IndexOf('\0') >= 0) throw new InvalidOperationException("SESSION_TEXT_AMBIGUOUS");
110
+ return text;
111
+ });
112
+ }
113
+
114
+ private SessionSnapshot Snapshot(int sessionId)
115
+ {
116
+ return Query(sessionId, 25, (buffer, length) =>
117
+ {
118
+ if (length < Marshal.SizeOf(typeof(WtsApi.Information)))
119
+ throw new InvalidOperationException("SESSION_GENERATION_UNAVAILABLE");
120
+ var info = (WtsApi.Information)Marshal.PtrToStructure(buffer, typeof(WtsApi.Information));
121
+ if (info.level != 1) throw new InvalidOperationException("SESSION_GENERATION_UNSUPPORTED");
122
+ return new SessionSnapshot
123
+ {
124
+ sessionId = checked((int)info.data.sessionId), state = info.data.state,
125
+ logonTime = info.data.logonTime, shortUser = info.data.user, shortDomain = info.data.domain
126
+ };
127
+ });
128
+ }
129
+
130
+ private string Resolve(string account)
131
+ {
132
+ uint sidSize = 0, domainSize = 0;
133
+ int use;
134
+ WtsApi.LookupAccountNameW(null, account, IntPtr.Zero, ref sidSize, null, ref domainSize, out use);
135
+ int error = Marshal.GetLastWin32Error();
136
+ if (error != 122 ||
137
+ sidSize < 8 ||
138
+ sidSize > 68 ||
139
+ domainSize < 1 ||
140
+ domainSize > 32768) throw new InvalidOperationException("SESSION_SID_LOOKUP_FAILED_" + error.ToString(CultureInfo.InvariantCulture));
141
+ var sid = Marshal.AllocHGlobal(checked((int)sidSize));
142
+ try
143
+ {
144
+ var domain = new StringBuilder(checked((int)domainSize));
145
+ if (!WtsApi.LookupAccountNameW(null, account, sid, ref sidSize, domain, ref domainSize, out use))
146
+ throw new InvalidOperationException("SESSION_SID_LOOKUP_FAILED_" + Marshal.GetLastWin32Error().ToString(CultureInfo.InvariantCulture));
147
+ RequireUser(use);
148
+ return new SecurityIdentifier(sid).Value;
149
+ }
150
+ finally { Marshal.FreeHGlobal(sid); }
151
+ }
152
+ }
153
+
154
+ internal sealed class SessionSnapshot
155
+ {
156
+ public int sessionId { get; set; }
157
+ public int state { get; set; }
158
+ public long logonTime { get; set; }
159
+ public string shortUser { get; set; }
160
+ public string shortDomain { get; set; }
161
+ }
162
+
163
+ internal sealed class SessionFact
164
+ {
165
+ public int sessionId { get; set; }
166
+ public string ownerSid { get; set; }
167
+ public string state { get; set; }
168
+ public string logonTime { get; set; }
169
+ public string observedAt { get; set; }
170
+ public string source { get; set; }
171
+ public bool atomicRunExBinding { get; set; }
172
+ }
173
+
174
+ internal static class WtsApi
175
+ {
176
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
177
+ internal struct LevelOne
178
+ {
179
+ internal uint sessionId;
180
+ internal int state, flags;
181
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] internal string station;
182
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)] internal string user;
183
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 18)] internal string domain;
184
+ internal long logonTime, connectTime, disconnectTime, lastInputTime, currentTime;
185
+ internal uint incomingBytes, outgoingBytes, incomingFrames, outgoingFrames, incomingCompressed, outgoingCompressed;
186
+ }
187
+ [StructLayout(LayoutKind.Sequential)]
188
+ internal struct Information
189
+ {
190
+ internal uint level;
191
+ internal LevelOne data;
192
+ }
193
+ [DllImport("wtsapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
194
+ [return: MarshalAs(UnmanagedType.Bool)]
195
+ internal static extern bool WTSQuerySessionInformationW(IntPtr server, int sessionId, int kind,
196
+ out IntPtr buffer, out uint length);
197
+ [DllImport("wtsapi32.dll")]
198
+ internal static extern void WTSFreeMemory(IntPtr buffer);
199
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
200
+ [return: MarshalAs(UnmanagedType.Bool)]
201
+ internal static extern bool LookupAccountNameW(string system, string account, IntPtr sid, ref uint sidLength,
202
+ StringBuilder domain, ref uint domainLength, out int use);
203
+ }