Docs: Security release. Bug 3063
[exim.git] / src / OS / unsupported / os.c-cygwin
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4 /* SPDX-License-Identifier: GPL-2.0-or-later */
5
6 /* Cygwin-specific code. December 2002. Updated Jan 2015.
7    This is prefixed to the src/os.c file.
8
9    This code was supplied by Pierre A. Humblet <Pierre.Humblet@ieee.org>
10 */
11
12 /* We need a special mkdir that
13    allows names starting with // */
14 #undef mkdir
15 int cygwin_mkdir( const char *path, mode_t mode )
16 {
17   const char * p = path;
18   if (*p == '/') while(*(p+1) == '/') p++;
19   return mkdir(p, mode);
20 }
21
22 #ifndef COMPILE_UTILITY /* Utilities don't need special code */
23
24 #ifdef INCLUDE_PAM
25 #include "../pam/pam.c"
26 #endif
27 #include <alloca.h>
28
29 unsigned int cygwin_WinVersion;
30
31 /* Conflict between Windows definitions and others */
32 #ifdef NOERROR
33 #undef NOERROR
34 #endif
35 #ifdef DELETE
36 #undef DELETE
37 #endif
38
39 #include <windows.h>
40 #include <ntstatus.h>
41 #include <lmcons.h>
42
43 #define EqualLuid(Luid1, Luid2) \
44   ((Luid1.LowPart == Luid2.LowPart) && (Luid1.HighPart == Luid2.HighPart))
45 #include <sys/cygwin.h>
46
47 /* Special static variables */
48 static BOOL cygwin_debug = FALSE;
49 static int fakesetugid = 1; /* when not privileged, setugid = noop */
50
51 #undef setuid
52 int cygwin_setuid(uid_t uid )
53 {
54   int res = 0;
55   if (fakesetugid == 0) { 
56     res = setuid(uid);
57     if (cygwin_debug)
58       fprintf(stderr, "setuid %u %u %d pid: %d\n",
59               uid, getuid(),res, getpid());
60   }
61   return res;
62 }
63
64 #undef setgid
65 int cygwin_setgid(gid_t gid )
66 {
67   int res = 0;
68   if (fakesetugid == 0) { 
69     res = setgid(gid);
70     if (cygwin_debug)
71       fprintf(stderr, "setgid %u %u %d pid: %d\n",
72               gid, getgid(), res, getpid());
73   }
74   return res;
75 }
76
77 /* Background processes run at lower priority */
78 static void cygwin_setpriority()
79 {
80   if (!SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS))
81     SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS);
82   return;
83 }
84
85
86 /* GetVersion()
87    MSB: 1 for 95/98/ME; Next 7: build number, except for 95/98/ME
88    Next byte: 0
89    Next byte: minor version of OS
90    Low  byte: major version of OS (3 or 4 for for NT, 5 for 2000 and XP) */
91 //#define VERSION_IS_58M(x) (x & 0x80000000) /* 95, 98, Me   */
92 //#define VERSION_IS_NT(x)  ((x & 0XFF) < 5) /* NT 4 or 3.51 */
93
94 /*
95   Routine to find if process or thread is privileged
96 */
97
98 enum {
99   CREATE_BIT = 1,
100 };
101
102 static DWORD get_privileges ()
103 {
104   char buffer[1024];
105   DWORD i, length;
106   HANDLE hToken = NULL;
107   PTOKEN_PRIVILEGES privs;
108   LUID cluid, rluid;
109   DWORD ret = 0;
110
111   privs = (PTOKEN_PRIVILEGES) buffer;
112
113   if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken)
114       && LookupPrivilegeValue (NULL, SE_CREATE_TOKEN_NAME, &cluid)
115       && LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &rluid)
116       && (GetTokenInformation( hToken, TokenPrivileges,
117                                privs, sizeof (buffer), &length)
118           || (GetLastError () == ERROR_INSUFFICIENT_BUFFER
119               && (privs = (PTOKEN_PRIVILEGES) alloca (length))
120               && GetTokenInformation(hToken, TokenPrivileges,
121                                      privs, length, &length)))) {
122     for (i = 0; i < privs->PrivilegeCount; i++) {
123       if (EqualLuid(privs->Privileges[i].Luid, cluid))
124         ret |= CREATE_BIT;
125       if (ret == (CREATE_BIT))
126         break;
127     }
128   }
129   else
130     fprintf(stderr, "has_create_token_privilege %u\n", GetLastError());
131
132   if (hToken)
133     CloseHandle(hToken);
134
135   return ret;
136 }
137
138 /* 
139   We use cygwin_premain to fake a few things 
140         and to provide some debug info 
141 */
142 void cygwin_premain2(int argc, char ** argv, struct per_process * ptr)
143 {
144   int i, res, is_daemon = 0, is_spoolwritable, is_privileged, is_eximuser;
145   uid_t myuid, systemuid;
146   gid_t mygid, adminsgid;
147   struct passwd * pwp = NULL;
148   struct stat buf;
149   char *cygenv;
150   SID(1, SystemSid, SECURITY_LOCAL_SYSTEM_RID);
151   SID(2, AdminsSid, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
152   DWORD priv_flags;
153
154   myuid = getuid();
155   mygid = getgid();
156   cygwin_WinVersion = GetVersion();
157   if ((cygenv = getenv("CYGWIN")) == NULL) cygenv = "";
158   /* Produce some debugging on stderr,
159      cannot yet use exim's debug functions.
160      Exim does not use -c and ignores -n.
161      Set lower priority for daemons */
162   for (i = 1; i < argc; i++) {
163     if (argv[i][0] == '-') {
164       if (argv[i][1] == 'c') {
165         ssize_t size;
166         wchar_t *win32_path;
167         argv[i][1] = 'n';  /* Replace -c by -n */
168         cygwin_debug = TRUE;
169         fprintf(stderr, "CYGWIN = \"%s\".\n", cygenv);
170         if (((size = cygwin_conv_path(CCP_POSIX_TO_WIN_W,"/", win32_path, 0)) > 0)
171          && ((win32_path = store_malloc(size)) != NULL)
172          && (cygwin_conv_path(CCP_POSIX_TO_WIN_W,"/", win32_path, size) == 0)) {
173                 fprintf(stderr, " Root / mapped to %ls.\n", win32_path);
174                 store_free(win32_path);
175         }
176       }
177       else if (argv[i][1] == 'b' && argv[i][2] == 'd') {
178         is_daemon = 1;
179         cygwin_setpriority();
180     }
181   }
182   }
183
184   /* Nt/2000/XP
185      We initially set the exim uid & gid to those of the "exim user",
186        or to the root uid (SYSTEM) and exim gid (ADMINS),
187      If privileged, we setuid to those.
188      We always set the configure uid to the system uid.
189      We always set the root uid to the real uid
190        to allow exim imposed restrictions (bypassable by recompiling)
191        and to avoid exec that cause loss of privilege
192      If not privileged and unable to chown,
193        we set the exim uid to our uid.
194      If unprivileged and /var/spool/exim is writable and not running as listening daemon, 
195        we fake all subsequent setuid. */
196
197   /* Get the system and admins uid from their sids */
198   if ((systemuid = cygwin_internal(CW_GET_UID_FROM_SID, & SystemSid)) == -1) {
199         fprintf(stderr, "Cannot map System sid. Aborting\n");
200         exit(1);
201   }
202   if ((adminsgid = cygwin_internal(CW_GET_GID_FROM_SID, & AdminsSid)) == -1) {
203         fprintf(stderr, "Cannot map Admins sid. Aborting\n");
204         exit(1);
205   }
206
207   priv_flags = get_privileges ();
208   is_privileged = !!(priv_flags & CREATE_BIT);
209
210   /* Call getpwnam for account exim after getting the local exim name */
211   char exim_username[DNLEN + UNLEN + 2];
212   if (cygwin_internal(CW_CYGNAME_FROM_WINNAME, "exim", exim_username, sizeof exim_username) != 0)
213      pwp = getpwnam (exim_username);
214
215   /* If cannot setuid to exim or and is not the daemon (which is assumed to be
216      able to chown or to be the exim user) set the exim ugid to our ugid to avoid
217      chown failures after creating files and to be able to setuid to exim in 
218      exim.c ( "privilege not needed" ). */
219   if ((is_privileged == 0) && (!is_daemon)) {
220     exim_uid = myuid;
221     exim_gid = mygid;
222   }
223   else if (pwp != NULL) {
224     exim_uid = pwp->pw_uid;  /* Set it according to passwd */
225     exim_gid = pwp->pw_gid;
226     is_eximuser = 1;
227   }
228   else {
229     exim_uid = systemuid;
230     exim_gid = adminsgid;
231     is_eximuser = 0;
232   }
233
234   res = stat("/var/spool/exim", &buf);
235   /* Check if writable (and can be stat) */
236   is_spoolwritable = ((res == 0) && ((buf.st_mode & S_IWOTH) != 0));
237
238   fakesetugid = (is_privileged == 0) && (is_daemon == 0) && (is_spoolwritable == 1);
239
240   if (is_privileged) {             /* Can setuid */
241      if (cygwin_setgid(exim_gid) /* Setuid to exim */
242          || cygwin_setuid(exim_uid)) {
243            fprintf(stderr, "Unable to setuid/gid to exim. priv_flags: %x\n", priv_flags);
244            exit(0);          /* Problem... Perhaps not in 544 */
245      }
246   }
247
248   /* Set the configuration file uid and gid to the system uid and admins gid. */
249   config_uid = systemuid;
250   config_gid = adminsgid;
251
252   /* Pretend we are root to avoid useless exec
253      and avoid exim set limitations.
254      We are limited by file access rights */
255   root_uid = getuid ();
256
257   if (cygwin_debug) {
258     fprintf(stderr, "Starting uid %u, gid %u, priv_flags %x, is_privileged %d, is_daemon %d, is_spoolwritable %d.\n",
259             myuid, mygid, priv_flags, is_privileged, is_daemon, is_spoolwritable);
260     fprintf(stderr, "root_uid %u, exim_uid %u, exim_gid %u, config_uid %u, config_gid %u, is_eximuser %d.\n",
261             root_uid, exim_uid, exim_gid, config_uid, config_gid, is_eximuser);
262   }
263   return;
264 }
265
266 #ifndef OS_LOAD_AVERAGE /* Can be set on command line */
267 #define OS_LOAD_AVERAGE /* src/os.c need not provide it */
268
269 /*****************************************************************
270  Functions for average load measurements
271
272  Uses NtQuerySystemInformation.
273  This requires definitions that are not part of
274  standard include files.
275
276  This is discouraged starting with WinXP.
277
278 *************************************************************/
279 /* Structure to compute the load average efficiently */
280 typedef struct {
281   DWORD Lock;
282   unsigned long long Time100ns;   /* Last measurement time */
283   unsigned long long IdleCount;   /* Latest cumulative idle time */
284   unsigned long long LastCounter; /* Last measurement counter */
285   unsigned long long PerfFreq;    /* Perf counter frequency */
286   int LastLoad;                   /* Last reported load, or -1 */
287 } cygwin_perf_t;
288
289 static struct {
290    HANDLE handle;
291    pid_t pid;
292    cygwin_perf_t *perf;
293 } cygwin_load = {NULL, 0, NULL};
294
295 #include <ntdef.h>
296
297 typedef enum _SYSTEM_INFORMATION_CLASS
298 {
299   SystemBasicInformation = 0,
300   SystemPerformanceInformation = 2,
301   SystemTimeOfDayInformation = 3,
302   SystemProcessesAndThreadsInformation = 5,
303   SystemProcessorTimes = 8,
304   SystemPagefileInformation = 18,
305   /* There are a lot more of these... */
306 } SYSTEM_INFORMATION_CLASS;
307
308 typedef struct _SYSTEM_BASIC_INFORMATION
309 {
310   ULONG Unknown;
311   ULONG MaximumIncrement;
312   ULONG PhysicalPageSize;
313   ULONG NumberOfPhysicalPages;
314   ULONG LowestPhysicalPage;
315   ULONG HighestPhysicalPage;
316   ULONG AllocationGranularity;
317   ULONG LowestUserAddress;
318   ULONG HighestUserAddress;
319   ULONG ActiveProcessors;
320   UCHAR NumberProcessors;
321 } SYSTEM_BASIC_INFORMATION, *PSYSTEM_BASIC_INFORMATION;
322
323 typedef struct __attribute__ ((aligned (8))) _SYSTEM_PROCESSOR_TIMES
324 {
325   LARGE_INTEGER IdleTime;
326   LARGE_INTEGER KernelTime;
327   LARGE_INTEGER UserTime;
328   LARGE_INTEGER DpcTime;
329   LARGE_INTEGER InterruptTime;
330   ULONG InterruptCount;
331 } SYSTEM_PROCESSOR_TIMES, *PSYSTEM_PROCESSOR_TIMES;
332
333 typedef NTSTATUS NTAPI (*NtQuerySystemInformation_t) (SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);
334 typedef ULONG NTAPI (*RtlNtStatusToDosError_t) (NTSTATUS);
335
336 static NtQuerySystemInformation_t NtQuerySystemInformation;
337 static RtlNtStatusToDosError_t RtlNtStatusToDosError;
338
339 /*****************************************************************
340  *
341  LoadNtdll()
342  Load special functions from the NTDLL
343  Return TRUE if success.
344
345  *****************************************************************/
346
347 static BOOL LoadNtdll()
348 {
349   HINSTANCE hinstLib;
350
351   if ((hinstLib = LoadLibrary("NTDLL.DLL"))
352       && (NtQuerySystemInformation =
353           (NtQuerySystemInformation_t) GetProcAddress(hinstLib,
354                                                         "NtQuerySystemInformation"))
355       && (RtlNtStatusToDosError =
356           (RtlNtStatusToDosError_t) GetProcAddress(hinstLib,
357                                                      "RtlNtStatusToDosError")))
358     return TRUE;
359
360   DEBUG(D_load)
361     debug_printf("perf: load: %u (Windows)\n", GetLastError());
362   return FALSE;
363 }
364 /*****************************************************************
365  *
366  ReadStat()
367  Measures current Time100ns and IdleCount
368  Return TRUE if success.
369
370  *****************************************************************/
371
372 static BOOL ReadStat(unsigned long long int *Time100nsPtr,
373                      unsigned long long int *IdleCountPtr)
374 {
375   NTSTATUS ret;
376   SYSTEM_BASIC_INFORMATION sbi;
377   PSYSTEM_PROCESSOR_TIMES spt;
378
379   *Time100nsPtr = *IdleCountPtr = 0;
380
381   if ((ret = NtQuerySystemInformation(SystemBasicInformation,
382                                       (PVOID) &sbi, sizeof sbi, NULL))
383       != STATUS_SUCCESS) {
384     DEBUG(D_load)
385       debug_printf("Perf: NtQuerySystemInformation: %u (Windows)\n",
386                    RtlNtStatusToDosError(ret));
387   }
388   else if (!(spt = (PSYSTEM_PROCESSOR_TIMES) alloca(sizeof(spt[0]) * sbi.NumberProcessors))) {
389     DEBUG(D_load)
390       debug_printf("Perf: alloca: errno %d (%s)\n", errno, strerror(errno));
391   }
392   else if ((ret = NtQuerySystemInformation(SystemProcessorTimes, (PVOID) spt,
393                                            sizeof spt[0] * sbi.NumberProcessors, NULL))
394            != STATUS_SUCCESS) {
395     DEBUG(D_load)
396       debug_printf("Perf: NtQuerySystemInformation: %u (Windows)\n",
397                    RtlNtStatusToDosError(ret));
398   }
399   else {
400     int i;
401     for (i = 0; i < sbi.NumberProcessors; i++) {
402       *Time100nsPtr += spt[i].KernelTime.QuadPart;;
403       *Time100nsPtr += spt[i].UserTime.QuadPart;
404       *IdleCountPtr += spt[i].IdleTime.QuadPart;
405     }
406     return TRUE;
407   }
408   return FALSE;
409 }
410
411 /*****************************************************************
412  *
413  InitLoadAvg()
414  Initialize the cygwin_load.perf structure.
415  and set cygwin_load.perf->Flag to TRUE if successful.
416  This is called the first time os_getloadavg is called
417  *****************************************************************/
418 static void InitLoadAvg(cygwin_perf_t *this)
419 {
420   BOOL success = TRUE;
421
422   /* Get perf frequency and counter */
423   QueryPerformanceFrequency((LARGE_INTEGER *)& this->PerfFreq);
424   QueryPerformanceCounter((LARGE_INTEGER *)& this->LastCounter);
425
426   /* Get initial values for Time100ns and IdleCount */
427   success = success
428             && ReadStat( & this->Time100ns,
429                          & this->IdleCount);
430   /* If success, set the Load to 0, else to -1 */
431   if (success) this->LastLoad = 0;
432   else {
433     log_write(0, LOG_MAIN, "Cannot obtain Load Average");
434     this->LastLoad = -1;
435   }
436 }
437
438
439 /*****************************************************************
440  *
441  os_getloadavg()
442
443  Return -1 if not available;
444  Return the previous value if less than AVERAGING sec old.
445  else return the processor load on a [0 - 1000] scale.
446
447  The first time we are called we initialize the counts
448  and return 0 or -1.
449  The initial load cannot be measured as we use the processor 100%
450 *****************************************************************/
451 static SECURITY_ATTRIBUTES sa = {sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
452 #define AVERAGING 10
453
454 int os_getloadavg()
455 {
456   unsigned long long Time100ns, IdleCount, CurrCounter;
457   int value;
458   pid_t newpid;
459
460   /* New process.
461      Reload the dlls and the file mapping */
462   if ((newpid = getpid()) != cygwin_load.pid) {
463     BOOL new;
464     cygwin_load.pid = newpid;
465
466     if (!LoadNtdll()) {
467       log_write(0, LOG_MAIN, "Cannot obtain Load Average");
468       cygwin_load.perf = NULL;
469       return -1;
470     }
471
472     if ((new = !cygwin_load.handle)) {
473       cygwin_load.handle = CreateFileMapping (INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE,
474                                               0, sizeof(cygwin_perf_t), NULL);
475       DEBUG(D_load)
476         debug_printf("Perf: CreateFileMapping: handle %p\n", (void *) cygwin_load.handle);
477     }
478     cygwin_load.perf = (cygwin_perf_t *) MapViewOfFile (cygwin_load.handle,
479                                                         FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
480     DEBUG(D_load)
481       debug_printf("Perf: MapViewOfFile: addr %p\n", (void *) cygwin_load.perf);
482     if (new && cygwin_load.perf)
483       InitLoadAvg(cygwin_load.perf);
484   }
485
486   /* Check if initialized OK */
487   if (!cygwin_load.perf || cygwin_load.perf->LastLoad < 0)
488     return -1;
489
490   /* If we cannot get the lock, we return 0.
491      This is to prevent any lock-up possibility.
492      Finding a lock busy is unlikely, and giving up only
493      results in an immediate delivery .*/
494
495   if (InterlockedCompareExchange(&cygwin_load.perf->Lock, 1, 0)) {
496     DEBUG(D_load)
497       debug_printf("Perf: Lock busy\n");
498     return 0;
499   }
500
501     /* Get the current time (PerfCounter) */
502     QueryPerformanceCounter((LARGE_INTEGER *)& CurrCounter);
503     /* Calls closer than AVERAGING sec apart use the previous value */
504   if (CurrCounter - cygwin_load.perf->LastCounter >
505       AVERAGING * cygwin_load.perf->PerfFreq) {
506       /* Get Time100ns and IdleCount */
507       if (ReadStat( & Time100ns, & IdleCount)) { /* Success */
508         /* Return processor load on 1000 scale */
509       value = 1000 - ((1000 * (IdleCount - cygwin_load.perf->IdleCount)) /
510                       (Time100ns - cygwin_load.perf->Time100ns));
511       cygwin_load.perf->Time100ns = Time100ns;
512       cygwin_load.perf->IdleCount = IdleCount;
513       cygwin_load.perf->LastCounter = CurrCounter;
514       cygwin_load.perf->LastLoad = value;
515       DEBUG(D_load)
516         debug_printf("Perf: New load average %d\n", value);
517       }
518       else { /* Something bad happened.
519                 Refuse to measure the load anymore
520                 but don't bother releasing the buffer */
521         log_write(0, LOG_MAIN, "Cannot obtain Load Average");
522       cygwin_load.perf->LastLoad = -1;
523     }
524   }
525   else
526   DEBUG(D_load)
527       debug_printf("Perf: Old load average %d\n", cygwin_load.perf->LastLoad);
528   cygwin_load.perf->Lock = 0;
529   return cygwin_load.perf->LastLoad;
530 }
531 #endif /* OS_LOAD_AVERAGE */
532 #endif /* COMPILE_UTILITY */