1 : #include <iostream>
2 : #include <cctype>
3 :
4 : #include <sys/types.h>
5 : #include <sys/stat.h>
6 : #include <unistd.h>
7 :
8 : #include <pwd.h>
9 : #include <grp.h>
10 :
11 : extern "C" {
12 : #include <aterm2.h>
13 : }
14 :
15 : #include "globals.hh"
16 : #include "shared.hh"
17 :
18 : #include "config.h"
19 :
20 :
21 : volatile sig_atomic_t blockInt = 0;
22 :
23 :
24 : void sigintHandler(int signo)
25 0 : {
26 0 : if (!blockInt) {
27 0 : _isInterrupted = 1;
28 51 : blockInt = 1;
29 102 : }
30 0 : }
31 :
32 :
33 : void setLogType(string lt)
34 0 : {
35 0 : if (lt == "pretty") logType = ltPretty;
36 0 : else if (lt == "escapes") logType = ltEscapes;
37 0 : else if (lt == "flat") logType = ltFlat;
38 0 : else throw UsageError("unknown log type");
39 : }
40 :
41 :
42 : void checkStoreNotSymlink(Path path)
43 51 : {
44 357 : struct stat st;
45 357 : while (path.size()) {
46 306 : if (lstat(path.c_str(), &st))
47 0 : throw SysError(format("getting status of `%1%'") % path);
48 306 : if (S_ISLNK(st.st_mode))
49 0 : throw Error(format(
50 : "the path `%1%' is a symlink; "
51 : "this is not allowed for the Nix store and its parent directories")
52 : % path);
53 306 : path = dirOf(path);
54 : }
55 : }
56 :
57 :
58 : void initStoreExprHelpers();
59 :
60 :
61 : /* Initialize and reorder arguments, then call the actual argument
62 : processor. */
63 : static void initAndRun(int argc, char * * argv)
64 51 : {
65 51 : string root = getEnv("NIX_ROOT");
66 51 : if (root != "") {
67 0 : if (chroot(root.c_str()) != 0)
68 0 : throw SysError(format("changing root to `%1%'") % root);
69 : }
70 :
71 : /* Setup Nix paths. */
72 51 : nixStore = getEnv("NIX_STORE_DIR", canonPath(NIX_STORE_DIR));
73 51 : nixDataDir = getEnv("NIX_DATA_DIR", canonPath(NIX_DATA_DIR));
74 51 : nixLogDir = getEnv("NIX_LOG_DIR", canonPath(NIX_LOG_DIR));
75 51 : nixStateDir = getEnv("NIX_STATE_DIR", canonPath(NIX_STATE_DIR));
76 51 : nixDBPath = getEnv("NIX_DB_DIR", nixStateDir + "/db");
77 :
78 : /* Check that the store directory and its parent are not
79 : symlinks. */
80 51 : if (getEnv("NIX_IGNORE_SYMLINK_STORE") != "1")
81 51 : checkStoreNotSymlink(nixStore);
82 :
83 : /* Catch SIGINT. */
84 51 : struct sigaction act, oact;
85 51 : act.sa_handler = sigintHandler;
86 51 : sigfillset(&act.sa_mask);
87 51 : act.sa_flags = 0;
88 51 : if (sigaction(SIGINT, &act, &oact))
89 0 : throw SysError("installing handler for SIGINT");
90 :
91 : /* Ignore SIGPIPE. */
92 51 : act.sa_handler = SIG_IGN;
93 51 : act.sa_flags = 0;
94 51 : if (sigaction(SIGPIPE, &act, &oact))
95 0 : throw SysError("ignoring SIGPIPE");
96 :
97 : /* There is no privacy in the Nix system ;-) At least not for
98 : now. In particular, store objects should be readable by
99 : everybody. This prevents nasty surprises when using a shared
100 : store (with the setuid() hack). */
101 51 : umask(0022);
102 :
103 : /* Process the NIX_LOG_TYPE environment variable. */
104 51 : string lt = getEnv("NIX_LOG_TYPE");
105 51 : if (lt != "") setLogType(lt);
106 :
107 : /* ATerm stuff. !!! find a better place to put this */
108 51 : initStoreExprHelpers();
109 :
110 : /* Put the arguments in a vector. */
111 51 : Strings args, remaining;
112 189 : while (argc--) args.push_back(*argv++);
113 51 : args.erase(args.begin());
114 :
115 : /* Expand compound dash options (i.e., `-qlf' -> `-q -l -f'), and
116 : ignore options for the ATerm library. */
117 138 : for (Strings::iterator i = args.begin(); i != args.end(); ++i) {
118 87 : string arg = *i;
119 87 : if (string(arg, 0, 4) == "-at-") ;
120 87 : else if (arg.length() > 2 && arg[0] == '-' && arg[1] != '-') {
121 122 : for (unsigned int j = 1; j < arg.length(); j++)
122 106 : if (isalpha(arg[j]))
123 105 : remaining.push_back((string) "-" + arg[j]);
124 : else {
125 1 : remaining.push_back(string(arg, j));
126 1 : break;
127 : }
128 70 : } else remaining.push_back(arg);
129 : }
130 51 : args = remaining;
131 51 : remaining.clear();
132 :
133 : /* Process default options. */
134 226 : for (Strings::iterator i = args.begin(); i != args.end(); ++i) {
135 175 : string arg = *i;
136 175 : if (arg == "--verbose" || arg == "-v")
137 69 : verbosity = (Verbosity) ((int) verbosity + 1);
138 106 : else if (arg == "--log-type") {
139 0 : ++i;
140 0 : if (i == args.end()) throw UsageError("`--log-type' requires an argument");
141 0 : setLogType(*i);
142 : }
143 106 : else if (arg == "--build-output" || arg == "-B")
144 : ; /* !!! obsolete - remove eventually */
145 101 : else if (arg == "--no-build-output" || arg == "-Q")
146 0 : buildVerbosity = lvlVomit;
147 101 : else if (arg == "--help") {
148 0 : printHelp();
149 0 : return;
150 : }
151 101 : else if (arg == "--version") {
152 0 : cout << format("%1% (Nix) %2%") % programId % NIX_VERSION << endl;
153 0 : return;
154 : }
155 101 : else if (arg == "--keep-failed" || arg == "-K")
156 0 : keepFailed = true;
157 101 : else if (arg == "--keep-going" || arg == "-k")
158 0 : keepGoing = true;
159 101 : else if (arg == "--fallback")
160 1 : tryFallback = true;
161 100 : else if (arg == "--max-jobs" || arg == "-j") {
162 1 : ++i;
163 1 : if (i == args.end()) throw UsageError("`--max-jobs' requires an argument");
164 1 : int n;
165 1 : if (!string2Int(*i, n) || n < 0)
166 0 : throw UsageError(format("`--max-jobs' requires a non-negative integer"));
167 1 : maxBuildJobs = n;
168 : }
169 99 : else if (arg == "--readonly-mode")
170 0 : readOnlyMode = true;
171 99 : else remaining.push_back(arg);
172 : }
173 :
174 51 : run(remaining);
175 : }
176 :
177 :
178 : static bool haveSwitched;
179 : static uid_t savedUid, nixUid;
180 : static gid_t savedGid, nixGid;
181 :
182 :
183 : #if HAVE_SETRESUID
184 : #define _setuid(uid) setresuid(uid, uid, savedUid)
185 : #define _setgid(gid) setresgid(gid, gid, savedGid)
186 : #else
187 : /* Only works properly when run by root. */
188 : #define _setuid(uid) setuid(uid)
189 : #define _setgid(gid) setgid(gid)
190 : #endif
191 :
192 :
193 : SwitchToOriginalUser::SwitchToOriginalUser()
194 42 : {
195 : #if SETUID_HACK && HAVE_SETRESUID
196 : /* Temporarily switch the effective uid/gid back to the saved
197 : uid/gid (which is the uid/gid of the user that executed the Nix
198 : program; it's *not* the real uid/gid, since we changed that to
199 : the Nix user in switchToNixUser()). */
200 : if (haveSwitched) {
201 : if (setuid(savedUid) == -1)
202 : throw SysError(format("temporarily restoring uid to `%1%'") % savedUid);
203 : if (setgid(savedGid) == -1)
204 : throw SysError(format("temporarily restoring gid to `%1%'") % savedGid);
205 : }
206 : #endif
207 : }
208 :
209 :
210 : SwitchToOriginalUser::~SwitchToOriginalUser()
211 84 : {
212 : #if SETUID_HACK && HAVE_SETRESUID
213 : /* Switch the effective uid/gid back to the Nix user. */
214 : if (haveSwitched) {
215 : if (setuid(nixUid) == -1)
216 : throw SysError(format("restoring uid to `%1%'") % nixUid);
217 : if (setgid(nixGid) == -1)
218 : throw SysError(format("restoring gid to `%1%'") % nixGid);
219 : }
220 : #endif
221 : }
222 :
223 :
224 : void switchToNixUser()
225 51 : {
226 : #if SETUID_HACK
227 :
228 : /* Don't do anything if this is not a setuid binary. */
229 : if (getuid() == geteuid() && getgid() == getegid()) return;
230 :
231 : /* Here we set the uid and gid to the Nix user and group,
232 : respectively, IF the current (real) user is a member of the Nix
233 : group. Otherwise we just drop all privileges. */
234 :
235 : /* Lookup the Nix gid. */
236 : struct group * gr = getgrnam(NIX_GROUP);
237 : if (!gr) {
238 : cerr << format("missing group `%1%'\n") % NIX_GROUP;
239 : exit(1);
240 : }
241 :
242 : /* Get the supplementary group IDs for the current user. */
243 : int maxGids = 512, nrGids;
244 : gid_t gids[maxGids];
245 : if ((nrGids = getgroups(maxGids, gids)) == -1) {
246 : cerr << format("unable to query gids\n");
247 : exit(1);
248 : }
249 :
250 : /* !!! Apparently it is unspecified whether getgroups() includes
251 : the effective gid. In that case the following test is always
252 : true *if* the program is installed setgid (which we do when we
253 : have setresuid()). On Linux this doesn't appear to be the
254 : case, but we should switch to the real gid before doing this
255 : test, and then switch back to the saved gid. */
256 :
257 : /* Check that the current user is a member of the Nix group. */
258 : bool found = false;
259 : for (int i = 0; i < nrGids; ++i)
260 : if (gids[i] == gr->gr_gid) {
261 : found = true;
262 : break;
263 : }
264 :
265 : if (!found) {
266 : /* Not in the Nix group - drop all root/Nix privileges. */
267 : _setgid(getgid());
268 : _setuid(getuid());
269 : return;
270 : }
271 :
272 : savedUid = getuid();
273 : savedGid = getgid();
274 :
275 : /* Set the real, effective and saved gids to gr->gr_gid. Also
276 : make very sure that this succeeded. We switch the gid first
277 : because we cannot do it after we have dropped root uid. */
278 : nixGid = gr->gr_gid;
279 : if (_setgid(nixGid) != 0 || getgid() != nixGid || getegid() != nixGid) {
280 : cerr << format("unable to set gid to `%1%'\n") % NIX_GROUP;
281 : exit(1);
282 : }
283 :
284 : /* Lookup the Nix uid. */
285 : struct passwd * pw = getpwnam(NIX_USER);
286 : if (!pw) {
287 : cerr << format("missing user `%1%'\n") % NIX_USER;
288 : exit(1);
289 : }
290 :
291 : /* This will drop all root privileges, setting the real, effective
292 : and saved uids to pw->pw_uid. Also make very sure that this
293 : succeeded.*/
294 : nixUid = pw->pw_uid;
295 : if (_setuid(nixUid) != 0 || getuid() != nixUid || geteuid() != nixUid) {
296 : cerr << format("unable to set uid to `%1%'\n") % NIX_USER;
297 : exit(1);
298 : }
299 :
300 : haveSwitched = true;
301 :
302 : #endif
303 : }
304 :
305 :
306 : static char buf[1024];
307 :
308 : int main(int argc, char * * argv)
309 51 : {
310 : /* If we are setuid root, we have to get rid of the excess
311 : privileges ASAP. */
312 51 : switchToNixUser();
313 :
314 : /* ATerm setup. */
315 51 : ATerm bottomOfStack;
316 51 : ATinit(argc, argv, &bottomOfStack);
317 :
318 : /* Turn on buffering for cerr. */
319 : #if HAVE_PUBSETBUF
320 51 : cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));
321 : #endif
322 :
323 51 : try {
324 51 : try {
325 51 : initAndRun(argc, argv);
326 2 : } catch (...) {
327 : /* Subtle: we have to make sure that any `interrupted'
328 : condition is discharged before we reach printMsg()
329 : below, since otherwise it will throw an (uncaught)
330 : exception. */
331 2 : blockInt = 1; /* ignore further SIGINTs */
332 2 : _isInterrupted = 0;
333 2 : throw;
334 : }
335 2 : } catch (UsageError & e) {
336 0 : printMsg(lvlError,
337 : format(
338 : "error: %1%\n"
339 : "Try `%2% --help' for more information.")
340 : % e.what() % programId);
341 0 : return 1;
342 2 : } catch (Error & e) {
343 2 : printMsg(lvlError, format("error: %1%") % e.msg());
344 2 : return 1;
345 0 : } catch (exception & e) {
346 0 : printMsg(lvlError, format("error: %1%") % e.what());
347 0 : return 1;
348 : }
349 :
350 49 : return 0;
351 51 : }
|