2 * Execute a program with a restricted access token.
4 * Copyright (C) 2009,2011 Christian Franke
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * See the COPYING file for full license information.
25 #define WINVER 0x0500 // >= W2K
31 #include <sys/cygwin.h>
34 #define AUTHORS "Christian Franke"
36 static const char * progname
;
37 static const char versionID
[] = PACKAGE_VERSION
;
38 static void help (FILE * f
, const char *name
);
39 static void version (FILE * f
, const char *name
);
40 static void license (FILE * f
, const char *name
);
43 usageCore (FILE * f
, const char * name
)
46 "Usage: %s [OPTIONS] COMMAND [ARG ...]\n"
49 " -l Disable local administrator group [default]\n"
50 " (same as '-g S-1-5-32-544').\n"
51 " -d Disable domain administrator group [default]\n"
52 " (same as '-g S-1-5-21-.\\*-512').\n"
53 " -g GROUP Disable group(s) GROUP.\n"
54 " -G GROUP Disable all groups except group(s) GROUP.\n"
55 " -r GROUP Add group(s) GROUP to restricted SIDs.\n"
58 " -m Delete most privileges [default]\n"
59 " (same as '-P SeChangeNotifyPrivilege').\n"
60 " -p PRIV Delete privilege PRIV.\n"
61 " -P PRIV Delete all privileges except privilege PRIV.\n"
64 " --help,-h Print this help.\n"
65 " --usage Display brief usage information.\n"
66 " --version Display version information.\n"
67 " --license Display licensing information.\n"
68 " -v Verbose output, lists groups and privileges changed.\n"
69 " Repeat to list all groups and privileges.\n"
77 usageCore (stderr
, progname
);
82 usageerror (const char * msg
)
84 fprintf(stderr
, "%s: %s\n", progname
, msg
);
89 cygerror (const char * msg
)
96 winerror (const char * msg
)
98 fprintf (stderr
, "%s: error %u\n", msg
, (unsigned)GetLastError());
103 is_strsid (const char * s
)
105 return (!strncmp (s
, "S-", 2)
106 && strspn (s
+ 2, "-0123456789") == strlen (s
) - 2);
110 group_to_strsid (const char * name
)
115 else if (!strncmp (name
, "S-", 2))
119 if (regcomp (&rex
, name
, REG_EXTENDED
))
121 fprintf (stderr
, "%s: invalid regex\n", name
);
129 // Cygwin group id or name.
130 const struct group
* g
;
131 if (strspn (name
, "0123456789") == strlen (name
))
132 g
= getgrgid (atoi (name
));
135 if (!(g
&& !strncmp (g
->gr_passwd
, "S-", 2)))
137 fprintf (stderr
, "%s: unknown group\n", name
);
140 return strdup (g
->gr_passwd
); // TODO: Never freed.
144 static const struct group
*
145 strsid_to_group(const char * strsid
)
148 while (const struct group
* g
= getgrent ())
149 if (!strcmp (strsid
, g
->gr_passwd
))
155 match_strsid (const char * strsid
, const char * pattern
)
157 if (is_strsid (pattern
))
158 return !strcmp (strsid
, pattern
);
160 if (regcomp (&rex
, pattern
, REG_EXTENDED
))
164 if (!regexec (&rex
, strsid
, 1, &m
, 0)
165 && m
.rm_so
== 0 && m
.rm_eo
== (int) strlen (strsid
))
172 match_priv (const char * priv
, const char * pattern
)
174 if (!strcmpi (priv
, pattern
))
176 char buf
[strlen (pattern
) + 16];
177 sprintf (buf
, "Se%sPrivilege", pattern
);
178 return !strcmpi (priv
, buf
);
181 /* use long options for standard options, for
182 * consistency with other cygutils programs
184 #define OPT_USAGE_FLAG 155
185 #define OPT_VERSION_FLAG OPT_USAGE_FLAG+1
186 #define OPT_LICENSE_FLAG OPT_VERSION_FLAG+1
188 static struct option long_options
[] =
190 {"help", no_argument
, 0, 'h'},
191 {"usage", no_argument
, 0, OPT_USAGE_FLAG
},
192 {"version", no_argument
, 0, OPT_VERSION_FLAG
},
193 {"license", no_argument
, 0, OPT_LICENSE_FLAG
},
198 main (int argc
, char **argv
)
205 const int max_groups
= 100, max_privs
= 100;
206 const char * group_args
[max_groups
];
207 const char * restr_args
[max_groups
];
208 bool restr_used
[max_groups
] = {false, };
209 const char * priv_args
[max_privs
];
210 int num_group_args
= 0;
211 int num_restr_args
= 0;
212 int num_priv_args
= 0;
214 bool d_flag
= false, l_flag
= false;
216 bool g_flag
= false, G_flag
= false;
217 bool p_flag
= false, P_flag
= false;
222 int option_index
= 0;
223 while ((opt
= getopt_long(argc
, argv
, "+dlhmvg:G:p:P:r:",
224 long_options
, &option_index
)) != EOF
)
226 if (num_group_args
>= max_groups
227 || num_priv_args
>= max_privs
228 || num_restr_args
>= max_groups
)
229 usageerror ("too many options");
243 if (!(group_args
[num_group_args
++] = group_to_strsid(optarg
)))
248 if (!(group_args
[num_group_args
++] = group_to_strsid(optarg
)))
253 if (!(restr_args
[num_restr_args
++] = group_to_strsid(optarg
)))
258 priv_args
[num_priv_args
++] = optarg
;
262 priv_args
[num_priv_args
++] = optarg
;
268 help (stdout
, progname
);
271 usageCore (stdout
, progname
);
273 case OPT_VERSION_FLAG
:
274 version (stdout
, progname
);
276 case OPT_LICENSE_FLAG
:
277 license (stdout
, progname
);
280 fprintf (stderr
, "Try -h for help\n");
286 return usageerror ("missing COMMAND");
289 if (!(d_flag
|| l_flag
|| m_flag
|| g_flag
290 || G_flag
|| p_flag
|| P_flag
|| r_flag
))
291 d_flag
= l_flag
= m_flag
= true;
297 group_args
[num_group_args
++] = "S-1-5-32-544";
299 group_args
[num_group_args
++] = "S-1-5-21-.*-512";
301 else if (l_flag
|| d_flag
|| g_flag
)
302 return usageerror ("'-G' cannot be used with '-l', '-d', '-g'");
307 priv_args
[num_priv_args
++] = SE_CHANGE_NOTIFY_NAME
;
311 else if (m_flag
|| P_flag
)
312 return usageerror ("'-p' cannot be used with '-m', '-P'");
316 if(!OpenProcessToken (GetCurrentProcess (), TOKEN_ALL_ACCESS
, &proc_token
))
317 return winerror("OpenProcessToken");
321 if (!GetTokenInformation (proc_token
, TokenGroups
, NULL
, 0, &size
)
322 && GetLastError () != ERROR_INSUFFICIENT_BUFFER
)
323 return winerror ("GetTokenInformation");
325 char groups_buf
[size
];
326 TOKEN_GROUPS
* groups
= (TOKEN_GROUPS
*)groups_buf
;
327 if (!GetTokenInformation (proc_token
, TokenGroups
, groups
, sizeof(groups_buf
), &size
))
328 return winerror ("GetTokenInformation");
330 // Collect SIDs of groups to disable / restrict.
331 SID_AND_ATTRIBUTES sids_to_disable
[max_groups
];
332 SID_AND_ATTRIBUTES sids_to_restrict
[max_groups
];
333 int num_sids_to_disable
= 0, num_sids_to_restrict
= 0;
335 for (DWORD i
= 0; i
< groups
->GroupCount
; i
++)
337 const SID_AND_ATTRIBUTES
& grp
= groups
->Groups
[i
];
339 if (!ConvertSidToStringSid (grp
.Sid
, &strsid
))
340 return winerror ("ConvertSidToStringSid");
342 // Any match with group options ?
343 bool disable_sid
= false;
344 for (int j
= 0; j
< num_group_args
&& !disable_sid
; j
++)
345 if (match_strsid (strsid
, group_args
[j
]))
348 disable_sid
= !disable_sid
;
350 bool restrict_sid
= false;
351 for (int j
= 0; j
< num_restr_args
&& !restrict_sid
; j
++)
352 if (match_strsid (strsid
, restr_args
[j
]))
353 restrict_sid
= restr_used
[j
] = true;
355 // Ignore special SIDs.
356 bool ignore_sid
= false;
358 & (SE_GROUP_USE_FOR_DENY_ONLY
|SE_GROUP_LOGON_ID
))
359 && (disable_sid
|| restrict_sid
))
361 disable_sid
= restrict_sid
= false;
365 if (verbose
&& (verbose
> 1 || (disable_sid
|| restrict_sid
)))
368 printf ("%c%c %s%s%s%s%s",
369 (ignore_sid
? 'i' : disable_sid
? 'd' : ' '),
370 (restrict_sid
? 'r' : ' '), strsid
,
371 (grp
.Attributes
& SE_GROUP_ENABLED
372 ? " [enabled]" : ""),
373 (grp
.Attributes
& SE_GROUP_ENABLED_BY_DEFAULT
374 ? " [default]" : ""),
375 (grp
.Attributes
& SE_GROUP_USE_FOR_DENY_ONLY
376 ? " [deny_only]" : ""),
377 (grp
.Attributes
& SE_GROUP_LOGON_ID
378 ? " [logon_id]" : ""));
379 const struct group
* g
= strsid_to_group (strsid
);
381 printf (" gid=%lu(%s)", g
->gr_gid
, g
->gr_name
);
389 sids_to_disable
[num_sids_to_disable
].Sid
= grp
.Sid
;
390 sids_to_disable
[num_sids_to_disable
].Attributes
= 0;
391 num_sids_to_disable
++;
395 sids_to_restrict
[num_sids_to_restrict
].Sid
= grp
.Sid
;
396 sids_to_restrict
[num_sids_to_restrict
].Attributes
= 0;
397 num_sids_to_restrict
++;
401 // Add restricted SIDs not matched above.
402 for (int i
= 0; i
< num_restr_args
; i
++)
406 const char * strsid
= restr_args
[i
];
407 if (!is_strsid (strsid
))
409 PSID sid
; // TODO: Never freed.
410 if (!ConvertStringSidToSid ((char *) strsid
, &sid
))
411 return winerror ("ConvertStringSidToSid");
415 printf ("r %s", strsid
);
416 const struct group
* g
= strsid_to_group (strsid
);
418 printf (" gid=%lu(%s)", g
->gr_gid
, g
->gr_name
);
422 sids_to_restrict
[num_sids_to_restrict
].Sid
= sid
;
423 sids_to_restrict
[num_sids_to_restrict
].Attributes
= 0;
424 num_sids_to_restrict
++;
428 char privs_buf
[sizeof(DWORD
) + max_privs
* sizeof(LUID_AND_ATTRIBUTES
)];
429 TOKEN_PRIVILEGES
* privs
= (TOKEN_PRIVILEGES
*)privs_buf
;
430 if (!GetTokenInformation (proc_token
, TokenPrivileges
, privs
, sizeof(privs_buf
), &size
))
431 return winerror ("GetTokenInformation");
433 // Collect LUIDs of privileges to disable.
434 LUID_AND_ATTRIBUTES privs_to_delete
[max_privs
];
435 int num_privs_to_delete
= 0;
437 for (DWORD i
= 0; i
< privs
->PrivilegeCount
; i
++)
439 LUID_AND_ATTRIBUTES
& priv
= privs
->Privileges
[i
];
442 if (!LookupPrivilegeName (NULL
, &priv
.Luid
, name
, &size
))
443 return winerror ("LookupPrivilegeName");
445 // Any match with privilege options ?
446 bool delete_priv
= false;
447 for (int j
= 0; j
< num_priv_args
&& !delete_priv
; j
++)
448 if (match_priv (name
, priv_args
[j
]))
451 delete_priv
= !delete_priv
;
453 if (verbose
&& (verbose
> 1 || delete_priv
))
454 printf("%c %s%s%s\n", (delete_priv
? 'd' : ' '), name
,
455 (priv
.Attributes
& SE_PRIVILEGE_ENABLED
456 ? " [enabled]" : ""),
457 (priv
.Attributes
& SE_PRIVILEGE_ENABLED_BY_DEFAULT
458 ? " [default]" : ""));
462 privs_to_delete
[num_privs_to_delete
].Luid
= priv
.Luid
;
463 privs_to_delete
[num_privs_to_delete
].Attributes
= 0;
464 num_privs_to_delete
++;
468 // Create restricted token.
470 if (!CreateRestrictedToken (proc_token
, 0,
471 num_sids_to_disable
, sids_to_disable
,
472 num_privs_to_delete
, privs_to_delete
,
473 num_sids_to_restrict
, sids_to_restrict
,
475 return winerror("CreateRestrictedToken");
477 CloseHandle (proc_token
);
479 // Change to restricted token.
480 if (cygwin_internal (CW_SET_EXTERNAL_TOKEN
, restr_token
, CW_TOKEN_RESTRICTED
))
481 return cygerror ("cygwin_internal (CW_SET_EXTERNAL_TOKEN, ...)");
483 if (setuid (geteuid ()))
484 return cygerror ("setuid");
490 for (int i
= optind
; i
< argc
-1; i
++)
491 printf ("'%s' ", argv
[i
]);
492 printf ("'%s'\n", argv
[argc
-1]);
496 execvp (argv
[optind
], argv
+ optind
);
498 return cygerror (argv
[optind
]);
508 printTopDescription (FILE * f
, const char *name
)
510 fprintf (f
, "%s is part of cygutils version %s\n", name
, getVersion ());
511 fprintf (f
, "%s was originally authored by %s\n", name
, AUTHORS
);
512 fprintf (f
, " Execute COMMAND with a restricted access token\n\n");
516 printBottomDescription (FILE * f
, const char *name
)
519 "If no group or privilege option is specified, '-l -d -m' is the default.\n"
520 "Options with GROUP and PRIV parameter may be specified more than once.\n"
521 "GROUP may be specified as a SID, a regular expression matching SIDs\n"
522 "(must start with 'S-'), a numeric group id, or a group name.\n"
523 "PRIV name match is not case sensitive, prefix 'Se' and suffix 'Privilege'\n"
524 "may be omitted.\n\n");
528 printLicense (FILE * f
, const char *name
)
531 "This program is free software: you can redistribute it and/or modify\n"
532 "it under the terms of the GNU General Public License as published by\n"
533 "the Free Software Foundation, either version 3 of the License, or\n"
534 "(at your option) any later version.\n\n"
535 "This program is distributed in the hope that it will be useful,\n"
536 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
537 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
538 "GNU General Public License for more details.\n\n"
539 "You should have received a copy of the GNU General Public License\n"
540 "along with this program. If not, see <http://www.gnu.org/licenses/>.\n\n"
541 "See the COPYING file for full license information.\n");
545 help (FILE * f
, const char *name
)
547 printTopDescription (f
, name
);
549 printBottomDescription (f
, name
);
553 version (FILE * f
, const char *name
)
555 printTopDescription (f
, name
);
559 license (FILE * f
, const char *name
)
561 printTopDescription (f
, name
);
562 printLicense (f
, name
);