chiark / gitweb /
found on davenant in /usr/local/src/misc
[chiark-utils.git] / cprogs / really.c
1 /**/
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <limits.h>
6 #include <unistd.h>
7 #include <pwd.h>
8 #include <grp.h>
9 #include <errno.h>
10 #include <sys/types.h>
11
12 #include "myopt.h"
13
14 void usagemessage(void) {
15   if (fputs("usage: really [<user-option>] [<group-option> ...] [--]"
16             " [<command> [<argument/option> ...]]\n"
17             "user-options:\n"
18             " if no options given, set the uid to 0;\n"
19             " -u|--user <username>     also sets their default group list\n"
20             " -i|--useronly <username> } set the uid\n"
21             " -I|--uidonly <uid>       }  but inherits the group list\n"
22             "group-options:\n"
23             " -z|--groupsclear         only groups specified are to be used\n"
24             " -g|--group <groupname>   } add this to\n"
25             " -G|--gid <gid>           }  the group list\n",
26             stderr) == EOF) { perror("write usage"); exit(-1); }
27 }
28
29 static const char *opt_user, *opt_useronly;
30 static int opt_groupsclear= 0, opt_ngids= 0, opt_uidonly= -1;
31 static int opt_gids[512];
32
33 static void af_group(const struct cmdinfo *cip, const char *value) {
34   struct group *gr;
35
36   if (opt_ngids >= sizeof(opt_gids)/sizeof(opt_gids[0]))
37     badusage("too many groups specified");
38   gr= getgrnam(value);
39   if (!gr) { fprintf(stderr,"unknown group `%s'\n",value); exit(-1); }
40   opt_gids[opt_ngids++]= gr->gr_gid;
41 }
42
43 static void af_gid(const struct cmdinfo *cip, const char *value) {
44   char *ep;
45   unsigned long ul;
46
47   if (opt_ngids >= sizeof(opt_gids)/sizeof(opt_gids[0]))
48     badusage("too many gids specified");
49   ul= strtoul(value,&ep,0);
50   if ((*ep) || (uid_t)ul != ul || ul>INT_MAX) badusage("bad gid `%s'",value);
51   opt_gids[opt_ngids++]= ul;
52 }
53
54 static void af_help(const struct cmdinfo *cip, const char *value) {
55   usagemessage(); exit(0);
56 }
57
58 static const struct cmdinfo cmdinfos[]= {
59   { "user",         'u',  1,  0, &opt_user,          0,           },
60   { "useronly",     'i',  1,  0, &opt_useronly,      0            },
61   { "uidonly",      'I',  1,  &opt_uidonly, 0,       0            },
62   { "groupsclear",  'z',  0,  &opt_groupsclear, 0,   0,        1  },
63   { "group",        'g',  1,  0, 0,                  af_group     },
64   { "gid",          'G',  1,  0, 0,                  af_gid       },
65   { "help",         'h',  0,  0, 0,                  af_help      },
66   {  0,              0                                            }
67 };
68
69 #ifdef REALLY_CHECK_FILE
70 static void checkroot(void) {
71   int r;
72   r= access(REALLY_CHECK_FILE,W_OK);
73   if (r) { perror("sorry"); exit(-1); }
74 }
75 #endif
76 #ifdef REALLY_CHECK_GID
77 static void checkroot(void) {
78   gid_t groups[512];
79   int r;
80
81   r= getgid(); if (r==REALLY_CHECK_GID) return;
82   if (r<0) { perror("getgid check"); exit(-1); }
83   r= getgroups(sizeof(groups)/sizeof(groups[0]),groups);
84   if (r<0) { perror("getgroups check"); exit(-1); }
85   for (i=0; i<r; i++)
86     if (groups[i] == REALLY_CHECK_GID) return;
87   fputs("sorry\n",stderr); exit(-1);
88 }
89 #endif
90 #ifdef REALLY_CHECK_NONE
91 static void checkroot(void) {
92 }
93 #endif
94
95 int main(int argc, const char *const *argv) {
96   struct passwd *pw= 0;
97   gid_t groups[512];
98   int i, j, ngroups, ngroups_in, maingid, orgmaingid, mainuid, orgmainuid, r;
99   const char *cp;
100   
101   checkroot();
102   myopt(&argv,cmdinfos);
103
104   if (opt_groupsclear && !opt_ngids)
105     badusage("-z|--groupsclear must be accompanied by some groups");
106   if (opt_user && (opt_useronly || opt_uidonly!=-1))
107     badusage("-u|--user may not be used with -i|--useronly or -I|--uidonly");
108   if (opt_user && opt_groupsclear)
109     badusage("-u|--user may not be used with -z|--groupsclear");
110   if (opt_uidonly != -1 && (uid_t)opt_uidonly != opt_uidonly)
111     badusage("-I|--uidonly value %d is out of range for a uid",opt_uidonly);
112
113   if (!opt_user && !opt_useronly && opt_uidonly==-1 && !opt_ngids) {
114     opt_uidonly= 0;
115   }
116   if (opt_user || opt_useronly) {
117     cp= opt_user ? opt_user : opt_useronly;
118     pw= getpwnam(cp);
119     if (!pw) { fprintf(stderr,"unknown user `%s'\n",cp); exit(-1); }
120     opt_uidonly= pw->pw_uid;
121   }
122   orgmaingid= getgid();
123   orgmainuid= getuid();
124   if (orgmaingid<0) { perror("getgid failed"); exit(-1); }
125   if (opt_user) {
126     r= initgroups(opt_user,pw->pw_gid);
127     if (r) { perror("initgroups failed"); exit(-1); }
128     maingid= pw->pw_gid;
129   } else {
130     maingid= -1;
131   }
132   if (opt_groupsclear) {
133     ngroups= 0;
134     if (opt_ngids > sizeof(groups)/sizeof(groups[0])) {
135       fputs("too many groups to set\n",stderr);
136       exit(-1);
137     }
138   } else {
139     ngroups= getgroups(0,0);
140     if (ngroups<0) { perror("getgroups(0,0) failed"); exit(-1); }
141     if (ngroups+opt_ngids > sizeof(groups)/sizeof(groups[0])) {
142       fputs("too many groups already set for total to fit\n",stderr);
143       exit(-1);
144     }
145     ngroups= getgroups(ngroups,groups);
146     if (ngroups<0) { perror("getgroups failed"); exit(-1); }
147   }
148   if (opt_ngids) {
149     maingid= opt_gids[0];
150   }
151   if (opt_ngids || opt_groupsclear) {
152     ngroups_in= ngroups; ngroups= 0;
153     for (i=0; i<ngroups_in; i++) {
154       for (j=0; j<ngroups && groups[j] != groups[i]; j++);
155       if (j<ngroups) continue;
156       groups[ngroups++]= groups[i];
157     }
158     for (i=0; i<opt_ngids; i++) {
159       for (j=0; j<ngroups && groups[j] != opt_gids[i]; j++);
160       if (j<ngroups) continue;
161       groups[ngroups++]= opt_gids[i];
162     }
163     r= setgroups(ngroups,groups);
164     if (r) { perror("setgroups failed"); exit(-1); }
165   }
166   if (maingid != -1) {
167     r= setgid(maingid); if (r) { perror("setgid failed"); exit(-1); }
168     r= setgid(maingid); if (r) { perror("2nd setgid failed"); exit(-1); }
169   }
170   if (opt_uidonly != -1) {
171     mainuid= opt_uidonly;
172   } else {
173     mainuid= orgmainuid;
174   }
175   r= setuid(mainuid); if (r) { perror("setuid failed"); exit(-1); }
176   r= setuid(mainuid); if (r) { perror("2nd setuid failed"); exit(-1); }
177   if (mainuid != 0) {
178     r= seteuid(0); if (r>=0) { fputs("could seteuid 0",stderr); exit(-1); }
179     if (errno != EPERM) {
180       perror("unexpected failure mode for seteuid 0"); exit(-1);
181     }
182   }
183   r= getuid(); if (r<0) { perror("getuid failed"); exit(-1); }
184   if (r != mainuid) { fputs("getuid mismatch",stderr); exit(-1); }
185   r= geteuid(); if (r<0) { perror("geteuid failed"); exit(-1); }
186   if (r != mainuid) { fputs("geteuid mismatch",stderr); exit(-1); }
187   if (maingid != -1) {
188     for (i=0; i<ngroups && maingid != groups[i]; i++);
189     if (i>=ngroups && maingid != orgmaingid) {
190       r= setgid(orgmaingid);
191       if (r>=0) { fputs("could setgid back",stderr); exit(-1); }
192       if (errno != EPERM) {
193         perror("unexpected failure mode for setgid back"); exit(-1);
194       }
195     }
196     r= getgid(); if (r<0) { perror("getgid failed"); exit(-1); }
197     if (r != maingid) { fputs("getgid mismatch",stderr); exit(-1); }
198     r= getegid(); if (r<0) { perror("getegid failed"); exit(-1); }
199     if (r != maingid) { fputs("getegid mismatch",stderr); exit(-1); }
200   }
201   if (!*argv) {
202     cp= getenv("SHELL");
203     if (!cp) cp= "sh";
204     execlp(cp,cp,"-i",(const char*)0);
205   } else {
206     execvp(argv[0],(char**)argv);
207   }
208   perror("exec failed");
209   exit(-1);
210 }