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