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