1 | /* Copyright © 2005-2009 Roger Leigh <rleigh@debian.org> |
---|
2 | * |
---|
3 | * schroot is free software: you can redistribute it and/or modify it |
---|
4 | * under the terms of the GNU General Public License as published by |
---|
5 | * the Free Software Foundation, either version 3 of the License, or |
---|
6 | * (at your option) any later version. |
---|
7 | * |
---|
8 | * schroot is distributed in the hope that it will be useful, but |
---|
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
---|
11 | * General Public License for more details. |
---|
12 | * |
---|
13 | * You should have received a copy of the GNU General Public License |
---|
14 | * along with this program. If not, see |
---|
15 | * <http://www.gnu.org/licenses/>. |
---|
16 | * |
---|
17 | *********************************************************************/ |
---|
18 | |
---|
19 | #include <config.h> |
---|
20 | |
---|
21 | #include "sbuild-chroot-config.h" |
---|
22 | #include "sbuild-chroot-facet-personality.h" |
---|
23 | #include "sbuild-chroot-facet-session.h" |
---|
24 | #include "sbuild-chroot-facet-session-clonable.h" |
---|
25 | #include "sbuild-auth-null.h" |
---|
26 | #include "sbuild-ctty.h" |
---|
27 | #include "sbuild-run-parts.h" |
---|
28 | #include "sbuild-session.h" |
---|
29 | #include "sbuild-util.h" |
---|
30 | |
---|
31 | #include <cassert> |
---|
32 | #include <cerrno> |
---|
33 | #include <cstdlib> |
---|
34 | #include <cstring> |
---|
35 | #include <iostream> |
---|
36 | #include <memory> |
---|
37 | |
---|
38 | #include <sys/types.h> |
---|
39 | #include <sys/stat.h> |
---|
40 | #include <fcntl.h> |
---|
41 | #include <termios.h> |
---|
42 | #include <unistd.h> |
---|
43 | |
---|
44 | #include <syslog.h> |
---|
45 | |
---|
46 | #include <boost/format.hpp> |
---|
47 | |
---|
48 | using std::cout; |
---|
49 | using std::endl; |
---|
50 | using boost::format; |
---|
51 | using namespace sbuild; |
---|
52 | |
---|
53 | namespace |
---|
54 | { |
---|
55 | |
---|
56 | typedef std::pair<sbuild::session::error_code,const char *> emap; |
---|
57 | |
---|
58 | /** |
---|
59 | * This is a list of the supported error codes. It's used to |
---|
60 | * construct the real error codes map. |
---|
61 | */ |
---|
62 | emap init_errors[] = |
---|
63 | { |
---|
64 | // TRANSLATORS: %1% = directory |
---|
65 | emap(session::CHDIR, N_("Failed to change to directory '%1%'")), |
---|
66 | // TRANSLATORS: %4% = directory |
---|
67 | emap(session::CHDIR_FB, N_("Falling back to directory '%4%'")), |
---|
68 | emap(session::CHILD_CORE, N_("Child dumped core")), |
---|
69 | emap(session::CHILD_FAIL, N_("Child exited abnormally (reason unknown; not a signal or core dump)")), |
---|
70 | emap(session::CHILD_FORK, N_("Failed to fork child")), |
---|
71 | // TRANSLATORS: %4% = signal name |
---|
72 | emap(session::CHILD_SIGNAL, N_("Child terminated by signal '%4%'")), |
---|
73 | emap(session::CHILD_WAIT, N_("Wait for child failed")), |
---|
74 | // TRANSLATORS: %1% = directory |
---|
75 | emap(session::CHROOT, N_("Failed to change root to directory '%1%'")), |
---|
76 | // TRANSLATORS: %1% = chroot name |
---|
77 | emap(session::CHROOT_ALIAS, N_("No chroot found matching name or alias '%1%'")), |
---|
78 | emap(session::CHROOT_LOCK, N_("Failed to lock chroot")), |
---|
79 | emap(session::CHROOT_SETUP, N_("Chroot setup failed")), |
---|
80 | // TRANSLATORS: %1% = chroot name |
---|
81 | emap(session::CHROOT_UNKNOWN, N_("Failed to find chroot '%1%'")), |
---|
82 | emap(session::CHROOT_UNLOCK, N_("Failed to unlock chroot")), |
---|
83 | // TRANSLATORS: %1% = command |
---|
84 | emap(session::COMMAND_ABS, N_("Command \"%1%\" must have an absolute path")), |
---|
85 | // TRANSLATORS: %1% = command |
---|
86 | emap(session::EXEC, N_("Failed to execute \"%1%\"")), |
---|
87 | // TRANSLATORS: A supplementary group is the list of additional |
---|
88 | // system groups a user belongs to, in addition to their default |
---|
89 | // group. |
---|
90 | emap(session::GROUP_GET_SUP, N_("Failed to get supplementary groups")), |
---|
91 | // TRANSLATORS: A supplementary group is the list of additional |
---|
92 | // system groups a user belongs to, in addition to their default |
---|
93 | // group. |
---|
94 | emap(session::GROUP_GET_SUPC, N_("Failed to get supplementary group count")), |
---|
95 | // TRANSLATORS: %1% = integer group ID |
---|
96 | emap(session::GROUP_SET, N_("Failed to set group '%1%'")), |
---|
97 | emap(session::GROUP_SET_SUP, N_("Failed to set supplementary groups")), |
---|
98 | // TRANSLATORS: %1% = group name |
---|
99 | emap(session::GROUP_UNKNOWN, N_("Group '%1%' not found")), |
---|
100 | emap(session::PAM, N_("PAM error")), |
---|
101 | emap(session::ROOT_DROP, N_("Failed to drop root permissions")), |
---|
102 | // TRANSLATORS: %1% = chroot name |
---|
103 | // TRANSLATORS: %4% = session identifier |
---|
104 | emap(session::SET_SESSION_ID, N_("%1%: Chroot does not support setting a session ID; ignoring session ID '%4%'")), |
---|
105 | // TRANSLATORS: %1% = command |
---|
106 | emap(session::SHELL, N_("Shell '%1%' not available")), |
---|
107 | // TRANSLATORS: %4% = command |
---|
108 | emap(session::SHELL_FB, N_("Falling back to shell '%4%'")), |
---|
109 | // TRANSLATORS: %4% = signal name |
---|
110 | emap(session::SIGNAL_CATCH, N_("Caught signal '%4%'")), |
---|
111 | // TRANSLATORS: %4% = signal name |
---|
112 | emap(session::SIGNAL_SET, N_("Failed to set signal handler '%4%'")), |
---|
113 | // TRANSLATORS: %1% = integer user ID |
---|
114 | emap(session::USER_SET, N_("Failed to set user '%1%'")), |
---|
115 | // TRANSLATORS: %1% = user name |
---|
116 | // TRANSLATORS: %2% = user name |
---|
117 | // TRANSLATORS: Please translate "->" as a right arrow, e.g. U+2192 |
---|
118 | emap(session::USER_SWITCH, N_("(%1%->%2%): User switching is not permitted")), |
---|
119 | }; |
---|
120 | |
---|
121 | /** |
---|
122 | * Check group membership. |
---|
123 | * |
---|
124 | * @param group the group to check for. |
---|
125 | * @returns true if the user is a member of group, otherwise false. |
---|
126 | */ |
---|
127 | bool |
---|
128 | is_group_member (std::string const& groupname) |
---|
129 | { |
---|
130 | errno = 0; |
---|
131 | sbuild::group grp(groupname); |
---|
132 | if (!grp) |
---|
133 | { |
---|
134 | if (errno == 0) |
---|
135 | { |
---|
136 | session::error e(groupname, session::GROUP_UNKNOWN); |
---|
137 | log_exception_warning(e); |
---|
138 | } |
---|
139 | else |
---|
140 | { |
---|
141 | session::error e(groupname, session::GROUP_UNKNOWN, strerror(errno)); |
---|
142 | log_exception_warning(e); |
---|
143 | } |
---|
144 | return false; |
---|
145 | } |
---|
146 | |
---|
147 | bool group_member = false; |
---|
148 | if (grp.gr_gid == getgid()) |
---|
149 | { |
---|
150 | group_member = true; |
---|
151 | } |
---|
152 | else |
---|
153 | { |
---|
154 | int supp_group_count = getgroups(0, 0); |
---|
155 | if (supp_group_count < 0) |
---|
156 | throw session::error(session::GROUP_GET_SUPC, strerror(errno)); |
---|
157 | if (supp_group_count > 0) |
---|
158 | { |
---|
159 | gid_t *supp_groups = new gid_t[supp_group_count]; |
---|
160 | assert (supp_groups); |
---|
161 | if (getgroups(supp_group_count, supp_groups) < 1) |
---|
162 | { |
---|
163 | // Free supp_groups before throwing to avoid leak. |
---|
164 | delete[] supp_groups; |
---|
165 | throw session::error(session::GROUP_GET_SUP, strerror(errno)); |
---|
166 | } |
---|
167 | |
---|
168 | for (int i = 0; i < supp_group_count; ++i) |
---|
169 | { |
---|
170 | if (grp.gr_gid == supp_groups[i]) |
---|
171 | group_member = true; |
---|
172 | } |
---|
173 | delete[] supp_groups; |
---|
174 | } |
---|
175 | } |
---|
176 | |
---|
177 | return group_member; |
---|
178 | } |
---|
179 | |
---|
180 | volatile bool sighup_called = false; |
---|
181 | volatile bool sigterm_called = false; |
---|
182 | |
---|
183 | /** |
---|
184 | * Handle the SIGHUP signal. |
---|
185 | * |
---|
186 | * @param ignore the signal number. |
---|
187 | */ |
---|
188 | void |
---|
189 | sighup_handler (int ignore) |
---|
190 | { |
---|
191 | /* This exists so that system calls get interrupted. */ |
---|
192 | sighup_called = true; |
---|
193 | } |
---|
194 | |
---|
195 | /** |
---|
196 | * Handle the SIGTERM signal. |
---|
197 | * |
---|
198 | * @param ignore the signal number. |
---|
199 | */ |
---|
200 | void |
---|
201 | sigterm_handler (int ignore) |
---|
202 | { |
---|
203 | /* This exists so that system calls get interrupted. */ |
---|
204 | sigterm_called = true; |
---|
205 | } |
---|
206 | |
---|
207 | #ifdef SBUILD_DEBUG |
---|
208 | volatile bool child_wait = true; |
---|
209 | #endif |
---|
210 | |
---|
211 | } |
---|
212 | |
---|
213 | template<> |
---|
214 | error<session::error_code>::map_type |
---|
215 | error<session::error_code>::error_strings |
---|
216 | (init_errors, |
---|
217 | init_errors + (sizeof(init_errors) / sizeof(init_errors[0]))); |
---|
218 | |
---|
219 | session::session (std::string const& service, |
---|
220 | config_ptr& config, |
---|
221 | operation operation, |
---|
222 | sbuild::string_list const& chroots): |
---|
223 | authstat(auth_null::create(service)), |
---|
224 | config(config), |
---|
225 | chroots(chroots), |
---|
226 | chroot_status(true), |
---|
227 | lock_status(true), |
---|
228 | child_status(EXIT_FAILURE), |
---|
229 | session_operation(operation), |
---|
230 | session_id(), |
---|
231 | force(false), |
---|
232 | saved_sighup_signal(), |
---|
233 | saved_sigterm_signal(), |
---|
234 | saved_termios(), |
---|
235 | termios_ok(false), |
---|
236 | cwd(sbuild::getcwd()) |
---|
237 | { |
---|
238 | } |
---|
239 | |
---|
240 | session::~session () |
---|
241 | { |
---|
242 | } |
---|
243 | |
---|
244 | auth::ptr const& |
---|
245 | session::get_auth () const |
---|
246 | { |
---|
247 | return this->authstat; |
---|
248 | } |
---|
249 | |
---|
250 | void |
---|
251 | session::set_auth (auth::ptr& auth) |
---|
252 | { |
---|
253 | this->authstat = auth; |
---|
254 | } |
---|
255 | |
---|
256 | session::config_ptr const& |
---|
257 | session::get_config () const |
---|
258 | { |
---|
259 | return this->config; |
---|
260 | } |
---|
261 | |
---|
262 | void |
---|
263 | session::set_config (config_ptr& config) |
---|
264 | { |
---|
265 | this->config = config; |
---|
266 | } |
---|
267 | |
---|
268 | string_list const& |
---|
269 | session::get_chroots () const |
---|
270 | { |
---|
271 | return this->chroots; |
---|
272 | } |
---|
273 | |
---|
274 | void |
---|
275 | session::set_chroots (string_list const& chroots) |
---|
276 | { |
---|
277 | this->chroots = chroots; |
---|
278 | } |
---|
279 | |
---|
280 | session::operation |
---|
281 | session::get_operation () const |
---|
282 | { |
---|
283 | return this->session_operation; |
---|
284 | } |
---|
285 | |
---|
286 | void |
---|
287 | session::set_operation (operation operation) |
---|
288 | { |
---|
289 | this->session_operation = operation; |
---|
290 | } |
---|
291 | |
---|
292 | std::string const& |
---|
293 | session::get_session_id () const |
---|
294 | { |
---|
295 | return this->session_id; |
---|
296 | } |
---|
297 | |
---|
298 | void |
---|
299 | session::set_session_id (std::string const& session_id) |
---|
300 | { |
---|
301 | this->session_id = session_id; |
---|
302 | } |
---|
303 | |
---|
304 | bool |
---|
305 | session::get_force () const |
---|
306 | { |
---|
307 | return this->force; |
---|
308 | } |
---|
309 | |
---|
310 | void |
---|
311 | session::set_force (bool force) |
---|
312 | { |
---|
313 | this->force = force; |
---|
314 | } |
---|
315 | |
---|
316 | void |
---|
317 | session::save_termios () |
---|
318 | { |
---|
319 | string_list const& command(this->authstat->get_command()); |
---|
320 | |
---|
321 | this->termios_ok = false; |
---|
322 | |
---|
323 | // Save if running a login shell and have a controlling terminal. |
---|
324 | if (CTTY_FILENO >= 0 && |
---|
325 | (command.empty() || command[0].empty())) |
---|
326 | { |
---|
327 | if (tcgetattr(CTTY_FILENO, &this->saved_termios) < 0) |
---|
328 | { |
---|
329 | sbuild::log_warning() |
---|
330 | << _("Error saving terminal settings") |
---|
331 | << endl; |
---|
332 | } |
---|
333 | else |
---|
334 | this->termios_ok = true; |
---|
335 | } |
---|
336 | } |
---|
337 | |
---|
338 | void |
---|
339 | session::restore_termios () |
---|
340 | { |
---|
341 | string_list const& command(this->authstat->get_command()); |
---|
342 | |
---|
343 | // Restore if running a login shell and have a controlling terminal, |
---|
344 | // and have previously saved the terminal state. |
---|
345 | if (CTTY_FILENO >= 0 && |
---|
346 | (command.empty() || command[0].empty()) && |
---|
347 | termios_ok) |
---|
348 | { |
---|
349 | if (tcsetattr(CTTY_FILENO, TCSANOW, &this->saved_termios) < 0) |
---|
350 | sbuild::log_warning() |
---|
351 | << _("Error restoring terminal settings") |
---|
352 | << endl; |
---|
353 | } |
---|
354 | } |
---|
355 | |
---|
356 | int |
---|
357 | session::get_child_status () const |
---|
358 | { |
---|
359 | return this->child_status; |
---|
360 | } |
---|
361 | |
---|
362 | void |
---|
363 | session::get_chroot_membership (chroot::ptr const& chroot, |
---|
364 | bool& in_users, |
---|
365 | bool& in_root_users, |
---|
366 | bool& in_groups, |
---|
367 | bool& in_root_groups) const |
---|
368 | { |
---|
369 | string_list const& users = chroot->get_users(); |
---|
370 | string_list const& root_users = chroot->get_root_users(); |
---|
371 | string_list const& groups = chroot->get_groups(); |
---|
372 | string_list const& root_groups = chroot->get_root_groups(); |
---|
373 | |
---|
374 | in_users = false; |
---|
375 | in_root_users = false; |
---|
376 | in_groups = false; |
---|
377 | in_root_groups = false; |
---|
378 | |
---|
379 | sbuild::string_list::const_iterator upos = |
---|
380 | find(users.begin(), users.end(), this->authstat->get_ruser()); |
---|
381 | if (upos != users.end()) |
---|
382 | in_users = true; |
---|
383 | |
---|
384 | sbuild::string_list::const_iterator rupos = |
---|
385 | find(root_users.begin(), root_users.end(), this->authstat->get_ruser()); |
---|
386 | if (rupos != root_users.end()) |
---|
387 | in_root_users = true; |
---|
388 | |
---|
389 | if (!groups.empty()) |
---|
390 | { |
---|
391 | for (string_list::const_iterator gp = groups.begin(); |
---|
392 | gp != groups.end(); |
---|
393 | ++gp) |
---|
394 | if (is_group_member(*gp)) |
---|
395 | in_groups = true; |
---|
396 | } |
---|
397 | |
---|
398 | if (!root_groups.empty()) |
---|
399 | { |
---|
400 | for (string_list::const_iterator gp = root_groups.begin(); |
---|
401 | gp != root_groups.end(); |
---|
402 | ++gp) |
---|
403 | if (is_group_member(*gp)) |
---|
404 | in_root_groups = true; |
---|
405 | } |
---|
406 | |
---|
407 | log_debug(DEBUG_INFO) |
---|
408 | << "In users: " << in_users << endl |
---|
409 | << "In groups: " << in_groups << endl |
---|
410 | << "In root-users: " << in_root_users << endl |
---|
411 | << "In root-groups: " << in_root_groups << endl; |
---|
412 | |
---|
413 | } |
---|
414 | |
---|
415 | auth::status |
---|
416 | session::get_chroot_auth_status (auth::status status, |
---|
417 | chroot::ptr const& chroot) const |
---|
418 | { |
---|
419 | bool in_users = false; |
---|
420 | bool in_root_users = false; |
---|
421 | bool in_groups = false; |
---|
422 | bool in_root_groups = false; |
---|
423 | |
---|
424 | get_chroot_membership(chroot, |
---|
425 | in_users, in_root_users, |
---|
426 | in_groups, in_root_groups); |
---|
427 | |
---|
428 | /* |
---|
429 | * No auth required if in root users or root groups and |
---|
430 | * changing to root, or if the uid is not changing. If not |
---|
431 | * in user or group, authentication fails immediately. |
---|
432 | */ |
---|
433 | if ((in_users == true || in_groups == true || |
---|
434 | in_root_users == true || in_root_groups == true) && |
---|
435 | this->authstat->get_ruid() == this->authstat->get_uid()) |
---|
436 | { |
---|
437 | status = auth::change_auth(status, auth::STATUS_NONE); |
---|
438 | } |
---|
439 | else if ((in_root_users == true || in_root_groups == true) && |
---|
440 | this->authstat->get_uid() == 0) |
---|
441 | { |
---|
442 | status = auth::change_auth(status, auth::STATUS_NONE); |
---|
443 | } |
---|
444 | else if (in_users == true || in_groups == true) |
---|
445 | // Auth required if not in root group |
---|
446 | { |
---|
447 | status = auth::change_auth(status, auth::STATUS_USER); |
---|
448 | } |
---|
449 | else // Not in any groups |
---|
450 | { |
---|
451 | if (this->authstat->get_ruid() == 0) |
---|
452 | status = auth::change_auth(status, auth::STATUS_USER); |
---|
453 | else |
---|
454 | status = auth::change_auth(status, auth::STATUS_FAIL); |
---|
455 | } |
---|
456 | |
---|
457 | return status; |
---|
458 | } |
---|
459 | |
---|
460 | auth::status |
---|
461 | session::get_auth_status () const |
---|
462 | { |
---|
463 | assert(!this->chroots.empty()); |
---|
464 | if (this->config.get() == 0) return auth::STATUS_FAIL; |
---|
465 | |
---|
466 | /* |
---|
467 | * Note that the root user can't escape authentication. This is |
---|
468 | * because pam_rootok.so should be used in the PAM configuration if |
---|
469 | * root should automatically be granted access. The only exception |
---|
470 | * is that the root group doesn't need to be added to the groups or |
---|
471 | * root groups lists. |
---|
472 | */ |
---|
473 | |
---|
474 | auth::status status = auth::STATUS_NONE; |
---|
475 | |
---|
476 | /** @todo Use set difference rather than iteration and |
---|
477 | * is_group_member. |
---|
478 | */ |
---|
479 | for (string_list::const_iterator cur = this->chroots.begin(); |
---|
480 | cur != this->chroots.end(); |
---|
481 | ++cur) |
---|
482 | { |
---|
483 | const chroot::ptr chroot = this->config->find_alias(*cur); |
---|
484 | if (!chroot) // Should never happen, but cater for it anyway. |
---|
485 | { |
---|
486 | error e(*cur, CHROOT_ALIAS); |
---|
487 | log_exception_warning(e); |
---|
488 | status = auth::change_auth(status, auth::STATUS_FAIL); |
---|
489 | } |
---|
490 | |
---|
491 | status = auth::change_auth(status, get_chroot_auth_status(status, chroot)); |
---|
492 | } |
---|
493 | |
---|
494 | return status; |
---|
495 | } |
---|
496 | |
---|
497 | void |
---|
498 | session::run () |
---|
499 | { |
---|
500 | try |
---|
501 | { |
---|
502 | this->authstat->start(); |
---|
503 | this->authstat->authenticate(get_auth_status()); |
---|
504 | this->authstat->setupenv(); |
---|
505 | this->authstat->account(); |
---|
506 | try |
---|
507 | { |
---|
508 | this->authstat->cred_establish(); |
---|
509 | |
---|
510 | run_impl(); |
---|
511 | |
---|
512 | /* The session is now finished, either |
---|
513 | successfully or not. All PAM operations are |
---|
514 | now for cleanup and shutdown, and we must |
---|
515 | clean up whether or not errors were raised at |
---|
516 | any previous point. This means only the |
---|
517 | first error is reported back to the user. */ |
---|
518 | |
---|
519 | /* Don't cope with failure, since we are now |
---|
520 | already bailing out, and an error may already |
---|
521 | have been raised */ |
---|
522 | } |
---|
523 | catch (auth::error const& e) |
---|
524 | { |
---|
525 | try |
---|
526 | { |
---|
527 | this->authstat->cred_delete(); |
---|
528 | } |
---|
529 | catch (auth::error const& discard) |
---|
530 | { |
---|
531 | } |
---|
532 | throw; |
---|
533 | } |
---|
534 | this->authstat->cred_delete(); |
---|
535 | } |
---|
536 | catch (auth::error const& e) |
---|
537 | { |
---|
538 | try |
---|
539 | { |
---|
540 | /* Don't cope with failure, since we are now already bailing out, |
---|
541 | and an error may already have been raised */ |
---|
542 | this->authstat->stop(); |
---|
543 | } |
---|
544 | catch (auth::error const& discard) |
---|
545 | { |
---|
546 | } |
---|
547 | throw; |
---|
548 | } |
---|
549 | this->authstat->stop(); |
---|
550 | } |
---|
551 | |
---|
552 | void |
---|
553 | session::run_impl () |
---|
554 | { |
---|
555 | assert(this->config.get() != 0); |
---|
556 | assert(!this->chroots.empty()); |
---|
557 | |
---|
558 | try |
---|
559 | { |
---|
560 | sighup_called = false; |
---|
561 | set_sighup_handler(); |
---|
562 | sigterm_called = false; |
---|
563 | set_sigterm_handler(); |
---|
564 | |
---|
565 | for (string_list::const_iterator cur = this->chroots.begin(); |
---|
566 | cur != this->chroots.end(); |
---|
567 | ++cur) |
---|
568 | { |
---|
569 | log_debug(DEBUG_NOTICE) |
---|
570 | << format("Running session in %1% chroot:") % *cur |
---|
571 | << endl; |
---|
572 | |
---|
573 | const chroot::ptr ch = this->config->find_alias(*cur); |
---|
574 | if (!ch) // Should never happen, but cater for it anyway. |
---|
575 | throw error(*cur, CHROOT_UNKNOWN); |
---|
576 | |
---|
577 | // For now, use a copy of the chroot; if we create a session |
---|
578 | // later, we will replace it. |
---|
579 | chroot::ptr chroot(ch->clone()); |
---|
580 | assert(chroot); |
---|
581 | |
---|
582 | /* Create a session using randomly-generated session ID. */ |
---|
583 | if (ch->get_session_flags() & chroot::SESSION_CREATE) |
---|
584 | { |
---|
585 | chroot_facet_session_clonable::ptr session_clonable |
---|
586 | (ch->get_facet<chroot_facet_session_clonable>()); |
---|
587 | assert(session_clonable); |
---|
588 | |
---|
589 | std::string new_session_id; |
---|
590 | |
---|
591 | if (!get_session_id().empty()) |
---|
592 | { |
---|
593 | new_session_id = get_session_id(); |
---|
594 | } |
---|
595 | else |
---|
596 | { |
---|
597 | new_session_id = |
---|
598 | ch->get_name() + '-' + unique_identifier(); |
---|
599 | } |
---|
600 | |
---|
601 | // Replace clone of chroot with cloned session. |
---|
602 | |
---|
603 | bool in_users = false; |
---|
604 | bool in_root_users = false; |
---|
605 | bool in_groups = false; |
---|
606 | bool in_root_groups = false; |
---|
607 | |
---|
608 | get_chroot_membership(chroot, |
---|
609 | in_users, in_root_users, |
---|
610 | in_groups, in_root_groups); |
---|
611 | |
---|
612 | chroot = ch->clone_session(new_session_id, |
---|
613 | this->authstat->get_ruser(), |
---|
614 | (in_root_users || in_root_groups)); |
---|
615 | chroot_facet_session::ptr session |
---|
616 | (chroot->get_facet<chroot_facet_session>()); |
---|
617 | assert(session); |
---|
618 | assert(chroot->get_active()); |
---|
619 | } |
---|
620 | assert(chroot); |
---|
621 | |
---|
622 | // Following authentication success, default child status to |
---|
623 | // success so that operations such as beginning, ending and |
---|
624 | // recovering sessions will return success unless an |
---|
625 | // exception is thrown. |
---|
626 | this->child_status = EXIT_SUCCESS; |
---|
627 | |
---|
628 | try |
---|
629 | { |
---|
630 | /* Run setup-start chroot setup scripts. */ |
---|
631 | setup_chroot(chroot, chroot::SETUP_START); |
---|
632 | if (this->session_operation == OPERATION_BEGIN) |
---|
633 | { |
---|
634 | cout << chroot->get_session_id() << endl; |
---|
635 | } |
---|
636 | |
---|
637 | /* Run recover scripts. */ |
---|
638 | setup_chroot(chroot, chroot::SETUP_RECOVER); |
---|
639 | |
---|
640 | /* Run session if setup succeeded. */ |
---|
641 | if (this->session_operation == OPERATION_AUTOMATIC || |
---|
642 | this->session_operation == OPERATION_RUN) |
---|
643 | { |
---|
644 | try |
---|
645 | { |
---|
646 | this->authstat->open_session(); |
---|
647 | save_termios(); |
---|
648 | run_chroot(chroot); |
---|
649 | } |
---|
650 | catch (std::runtime_error const& e) |
---|
651 | { |
---|
652 | log_debug(DEBUG_WARNING) |
---|
653 | << "Chroot session failed" << endl; |
---|
654 | restore_termios(); |
---|
655 | this->authstat->close_session(); |
---|
656 | throw; |
---|
657 | } |
---|
658 | restore_termios(); |
---|
659 | this->authstat->close_session(); |
---|
660 | } |
---|
661 | } |
---|
662 | catch (error const& e) |
---|
663 | { |
---|
664 | log_debug(DEBUG_WARNING) |
---|
665 | << "Chroot setup scripts, exec scripts or session failed" << endl; |
---|
666 | try |
---|
667 | { |
---|
668 | setup_chroot(chroot, chroot::SETUP_STOP); |
---|
669 | } |
---|
670 | catch (error const& discard) |
---|
671 | { |
---|
672 | log_debug(DEBUG_WARNING) |
---|
673 | << "Chroot setup scripts failed during stop" << endl; |
---|
674 | } |
---|
675 | throw; |
---|
676 | } |
---|
677 | |
---|
678 | /* Run setup-stop chroot setup scripts whether or not there |
---|
679 | was an error. */ |
---|
680 | setup_chroot(chroot, chroot::SETUP_STOP); |
---|
681 | } |
---|
682 | } |
---|
683 | catch (error const& e) |
---|
684 | { |
---|
685 | clear_sigterm_handler(); |
---|
686 | clear_sighup_handler(); |
---|
687 | |
---|
688 | /* If a command was not run, but something failed, the exit |
---|
689 | status still needs setting. */ |
---|
690 | if (this->child_status == 0) |
---|
691 | this->child_status = EXIT_FAILURE; |
---|
692 | throw; |
---|
693 | } |
---|
694 | |
---|
695 | clear_sigterm_handler(); |
---|
696 | clear_sighup_handler(); |
---|
697 | } |
---|
698 | |
---|
699 | string_list |
---|
700 | session::get_login_directories () const |
---|
701 | { |
---|
702 | string_list ret; |
---|
703 | |
---|
704 | std::string const& wd(this->authstat->get_wd()); |
---|
705 | if (!wd.empty()) |
---|
706 | { |
---|
707 | // Set specified working directory. |
---|
708 | ret.push_back(wd); |
---|
709 | } |
---|
710 | else |
---|
711 | { |
---|
712 | // Set current working directory. |
---|
713 | ret.push_back(this->cwd); |
---|
714 | |
---|
715 | // Set $HOME. |
---|
716 | environment env = this->authstat->get_auth_environment(); |
---|
717 | std::string home; |
---|
718 | if (env.get("HOME", home) && |
---|
719 | std::find(ret.begin(), ret.end(), home) == ret.end()) |
---|
720 | ret.push_back(home); |
---|
721 | |
---|
722 | // Set passwd home. |
---|
723 | if (std::find(ret.begin(), ret.end(), this->authstat->get_home()) == ret.end()) |
---|
724 | ret.push_back(this->authstat->get_home()); |
---|
725 | |
---|
726 | // Final fallback to root. |
---|
727 | if (std::find(ret.begin(), ret.end(), "/") == ret.end()) |
---|
728 | ret.push_back("/"); |
---|
729 | } |
---|
730 | |
---|
731 | return ret; |
---|
732 | } |
---|
733 | |
---|
734 | string_list |
---|
735 | session::get_command_directories () const |
---|
736 | { |
---|
737 | string_list ret; |
---|
738 | |
---|
739 | std::string const& wd(this->authstat->get_wd()); |
---|
740 | if (!wd.empty()) |
---|
741 | // Set specified working directory. |
---|
742 | ret.push_back(wd); |
---|
743 | else |
---|
744 | // Set current working directory. |
---|
745 | ret.push_back(this->cwd); |
---|
746 | |
---|
747 | return ret; |
---|
748 | } |
---|
749 | |
---|
750 | std::string |
---|
751 | session::get_shell () const |
---|
752 | { |
---|
753 | assert (!this->authstat->get_shell().empty()); |
---|
754 | std::string shell = this->authstat->get_shell(); |
---|
755 | |
---|
756 | try |
---|
757 | { |
---|
758 | stat(shell).check(); |
---|
759 | } |
---|
760 | catch (std::runtime_error const& e) |
---|
761 | { |
---|
762 | error e1(shell, SHELL, e.what()); |
---|
763 | log_exception_warning(e1); |
---|
764 | |
---|
765 | if (shell != "/bin/sh") |
---|
766 | { |
---|
767 | shell = "/bin/sh"; |
---|
768 | error e2(SHELL_FB, shell); |
---|
769 | log_exception_warning(e2); |
---|
770 | } |
---|
771 | } |
---|
772 | |
---|
773 | return shell; |
---|
774 | } |
---|
775 | |
---|
776 | void |
---|
777 | session::get_command (sbuild::chroot::ptr& session_chroot, |
---|
778 | std::string& file, |
---|
779 | string_list& command) const |
---|
780 | { |
---|
781 | /* Run login shell */ |
---|
782 | if (command.empty() || |
---|
783 | command[0].empty()) // No command |
---|
784 | get_login_command(session_chroot, file, command); |
---|
785 | else |
---|
786 | get_user_command(session_chroot, file, command); |
---|
787 | } |
---|
788 | |
---|
789 | void |
---|
790 | session::get_login_command (sbuild::chroot::ptr& session_chroot, |
---|
791 | std::string& file, |
---|
792 | string_list& command) const |
---|
793 | { |
---|
794 | command.clear(); |
---|
795 | |
---|
796 | std::string shell = get_shell(); |
---|
797 | file = shell; |
---|
798 | |
---|
799 | if (this->authstat->get_environment().empty() && |
---|
800 | session_chroot->get_command_prefix().empty()) |
---|
801 | // Not keeping environment and can setup argv correctly; login shell |
---|
802 | { |
---|
803 | std::string shellbase = basename(shell, '/'); |
---|
804 | std::string loginshell = "-" + shellbase; |
---|
805 | command.push_back(loginshell); |
---|
806 | |
---|
807 | log_debug(DEBUG_NOTICE) |
---|
808 | << format("Running login shell: %1%") % shell << endl; |
---|
809 | if (this->authstat->get_uid() == 0 || this->authstat->get_ruid() != this->authstat->get_uid()) |
---|
810 | syslog(LOG_USER|LOG_NOTICE, |
---|
811 | "[%s chroot] (%s->%s) Running login shell: '%s'", |
---|
812 | session_chroot->get_name().c_str(), |
---|
813 | this->authstat->get_ruser().c_str(), this->authstat->get_user().c_str(), |
---|
814 | shell.c_str()); |
---|
815 | } |
---|
816 | else |
---|
817 | { |
---|
818 | command.push_back(shell); |
---|
819 | log_debug(DEBUG_NOTICE) |
---|
820 | << format("Running shell: %1%") % shell << endl; |
---|
821 | if (this->authstat->get_uid() == 0 || this->authstat->get_ruid() != this->authstat->get_uid()) |
---|
822 | syslog(LOG_USER|LOG_NOTICE, |
---|
823 | "[%s chroot] (%s->%s) Running shell: '%s'", |
---|
824 | session_chroot->get_name().c_str(), |
---|
825 | this->authstat->get_ruser().c_str(), this->authstat->get_user().c_str(), |
---|
826 | shell.c_str()); |
---|
827 | } |
---|
828 | |
---|
829 | if (this->authstat->get_verbosity() == auth::VERBOSITY_VERBOSE) |
---|
830 | { |
---|
831 | std::string format_string; |
---|
832 | if (this->authstat->get_ruid() == this->authstat->get_uid()) |
---|
833 | { |
---|
834 | if (this->authstat->get_environment().empty() && |
---|
835 | session_chroot->get_command_prefix().empty()) |
---|
836 | // TRANSLATORS: %1% = chroot name |
---|
837 | // TRANSLATORS: %4% = command |
---|
838 | format_string = _("[%1% chroot] Running login shell: '%4%'"); |
---|
839 | else |
---|
840 | // TRANSLATORS: %1% = chroot name |
---|
841 | // TRANSLATORS: %4% = command |
---|
842 | format_string = _("[%1% chroot] Running shell: '%4%'"); |
---|
843 | } |
---|
844 | else |
---|
845 | { |
---|
846 | if (this->authstat->get_environment().empty() && |
---|
847 | session_chroot->get_command_prefix().empty()) |
---|
848 | // TRANSLATORS: %1% = chroot name |
---|
849 | // TRANSLATORS: %2% = user name |
---|
850 | // TRANSLATORS: %3% = user name |
---|
851 | // TRANSLATORS: %4% = command |
---|
852 | // TRANSLATORS: Please translate "->" as a right arrow, e.g. U+2192 |
---|
853 | format_string = _("[%1% chroot] (%2%->%3%) Running login shell: '%4%'"); |
---|
854 | else |
---|
855 | // TRANSLATORS: %1% = chroot name |
---|
856 | // TRANSLATORS: %2% = user name |
---|
857 | // TRANSLATORS: %3% = user name |
---|
858 | // TRANSLATORS: %4% = command |
---|
859 | // TRANSLATORS: Please translate "->" as a right arrow, e.g. U+2192 |
---|
860 | format_string = _("[%1% chroot] (%2%->%3%) Running shell: '%4%'"); |
---|
861 | } |
---|
862 | |
---|
863 | format fmt(format_string); |
---|
864 | fmt % session_chroot->get_name() |
---|
865 | % this->authstat->get_ruser() % this->authstat->get_user() |
---|
866 | % shell; |
---|
867 | log_info() << fmt << endl; |
---|
868 | } |
---|
869 | } |
---|
870 | |
---|
871 | void |
---|
872 | session::get_user_command (sbuild::chroot::ptr& session_chroot, |
---|
873 | std::string& file, |
---|
874 | string_list& command) const |
---|
875 | { |
---|
876 | /* Search for program in path. */ |
---|
877 | environment env = this->authstat->get_auth_environment(); |
---|
878 | std::string path; |
---|
879 | if (!env.get("PATH", path)) |
---|
880 | path.clear(); |
---|
881 | |
---|
882 | file = find_program_in_path(command[0], path, ""); |
---|
883 | if (file.empty()) |
---|
884 | file = command[0]; |
---|
885 | std::string commandstring = string_list_to_string(command, " "); |
---|
886 | log_debug(DEBUG_NOTICE) |
---|
887 | << format("Running command: %1%") % commandstring << endl; |
---|
888 | if (this->authstat->get_uid() == 0 || this->authstat->get_ruid() != this->authstat->get_uid()) |
---|
889 | syslog(LOG_USER|LOG_NOTICE, "[%s chroot] (%s->%s) Running command: \"%s\"", |
---|
890 | session_chroot->get_name().c_str(), this->authstat->get_ruser().c_str(), this->authstat->get_user().c_str(), commandstring.c_str()); |
---|
891 | |
---|
892 | if (this->authstat->get_verbosity() == auth::VERBOSITY_VERBOSE) |
---|
893 | { |
---|
894 | std::string format_string; |
---|
895 | if (this->authstat->get_ruid() == this->authstat->get_uid()) |
---|
896 | // TRANSLATORS: %1% = chroot name |
---|
897 | // TRANSLATORS: %4% = command |
---|
898 | format_string = _("[%1% chroot] Running command: \"%4%\""); |
---|
899 | else |
---|
900 | // TRANSLATORS: %1% = chroot name |
---|
901 | // TRANSLATORS: %2% = user name |
---|
902 | // TRANSLATORS: %3% = user name |
---|
903 | // TRANSLATORS: %4% = command |
---|
904 | // TRANSLATORS: Please translate "->" as a right arrow, e.g. U+2192 |
---|
905 | format_string = (_("[%1% chroot] (%2%->%3%) Running command: \"%4%\"")); |
---|
906 | |
---|
907 | format fmt(format_string); |
---|
908 | fmt % session_chroot->get_name() |
---|
909 | % this->authstat->get_ruser() % this->authstat->get_user() |
---|
910 | % commandstring; |
---|
911 | log_info() << fmt << endl; |
---|
912 | } |
---|
913 | } |
---|
914 | |
---|
915 | void |
---|
916 | session::setup_chroot (sbuild::chroot::ptr& session_chroot, |
---|
917 | sbuild::chroot::setup_type setup_type) |
---|
918 | { |
---|
919 | assert(!session_chroot->get_name().empty()); |
---|
920 | |
---|
921 | log_debug(DEBUG_INFO) << format("setup_chroot: chroot=%1%, setup_type=%2%, chroot_status=%3%, lock_status=%4%") |
---|
922 | % session_chroot->get_name() % setup_type % chroot_status % lock_status |
---|
923 | << std::endl; |
---|
924 | |
---|
925 | if (!((this->session_operation == OPERATION_BEGIN && |
---|
926 | setup_type == chroot::SETUP_START) || |
---|
927 | (this->session_operation == OPERATION_RECOVER && |
---|
928 | setup_type == chroot::SETUP_RECOVER) || |
---|
929 | (this->session_operation == OPERATION_END && |
---|
930 | setup_type == chroot::SETUP_STOP) || |
---|
931 | (this->session_operation == OPERATION_AUTOMATIC && |
---|
932 | (setup_type == chroot::SETUP_START || |
---|
933 | setup_type == chroot::SETUP_STOP)))) |
---|
934 | return; |
---|
935 | |
---|
936 | // Don't clean up chroot on a lock failure--it's actually in use. |
---|
937 | if (this->lock_status == false) |
---|
938 | return; |
---|
939 | |
---|
940 | if ((setup_type == chroot::SETUP_START || |
---|
941 | setup_type == chroot::SETUP_RECOVER || |
---|
942 | setup_type == chroot::SETUP_STOP) && |
---|
943 | session_chroot->get_run_setup_scripts() == false) |
---|
944 | return; |
---|
945 | |
---|
946 | if (setup_type == chroot::SETUP_START) |
---|
947 | this->chroot_status = true; |
---|
948 | |
---|
949 | try |
---|
950 | { |
---|
951 | session_chroot->lock(setup_type); |
---|
952 | } |
---|
953 | catch (chroot::error const& e) |
---|
954 | { |
---|
955 | this->chroot_status = false; |
---|
956 | this->lock_status = false; |
---|
957 | try |
---|
958 | { |
---|
959 | // Release lock, which also removes session metadata. |
---|
960 | session_chroot->unlock(setup_type, 0); |
---|
961 | } |
---|
962 | catch (chroot::error const& ignore) |
---|
963 | { |
---|
964 | } |
---|
965 | throw error(session_chroot->get_name(), CHROOT_LOCK, e); |
---|
966 | } |
---|
967 | |
---|
968 | std::string setup_type_string; |
---|
969 | if (setup_type == chroot::SETUP_START) |
---|
970 | setup_type_string = "setup-start"; |
---|
971 | else if (setup_type == chroot::SETUP_RECOVER) |
---|
972 | setup_type_string = "setup-recover"; |
---|
973 | else if (setup_type == chroot::SETUP_STOP) |
---|
974 | setup_type_string = "setup-stop"; |
---|
975 | |
---|
976 | std::string chroot_status_string; |
---|
977 | if (this->chroot_status) |
---|
978 | chroot_status_string = "ok"; |
---|
979 | else |
---|
980 | chroot_status_string = "fail"; |
---|
981 | |
---|
982 | string_list arg_list; |
---|
983 | arg_list.push_back(setup_type_string); |
---|
984 | arg_list.push_back(chroot_status_string); |
---|
985 | |
---|
986 | /* Get a complete list of environment variables to set. We need to |
---|
987 | query the chroot here, since this can vary depending upon the |
---|
988 | chroot type. */ |
---|
989 | environment env; |
---|
990 | session_chroot->setup_env(env); |
---|
991 | env.add("AUTH_USER", this->authstat->get_user()); |
---|
992 | env.add("AUTH_RUSER", this->authstat->get_ruser()); |
---|
993 | env.add("AUTH_RGROUP", this->authstat->get_rgroup()); |
---|
994 | env.add("AUTH_UID", this->authstat->get_uid()); |
---|
995 | env.add("AUTH_GID", this->authstat->get_gid()); |
---|
996 | env.add("AUTH_RUID", this->authstat->get_ruid()); |
---|
997 | env.add("AUTH_RGID", this->authstat->get_rgid()); |
---|
998 | env.add("AUTH_HOME", this->authstat->get_home()); |
---|
999 | env.add("AUTH_SHELL", this->authstat->get_shell()); |
---|
1000 | { |
---|
1001 | const char *verbosity = 0; |
---|
1002 | switch (this->authstat->get_verbosity()) |
---|
1003 | { |
---|
1004 | case auth::VERBOSITY_QUIET: |
---|
1005 | verbosity = "quiet"; |
---|
1006 | break; |
---|
1007 | case auth::VERBOSITY_NORMAL: |
---|
1008 | verbosity = "normal"; |
---|
1009 | break; |
---|
1010 | case auth::VERBOSITY_VERBOSE: |
---|
1011 | verbosity = "verbose"; |
---|
1012 | break; |
---|
1013 | default: |
---|
1014 | log_debug(DEBUG_CRITICAL) << format("Invalid verbosity level: %1%, falling back to 'normal'") |
---|
1015 | % static_cast<int>(this->authstat->get_verbosity()) |
---|
1016 | << endl; |
---|
1017 | verbosity = "normal"; |
---|
1018 | break; |
---|
1019 | } |
---|
1020 | env.add("AUTH_VERBOSITY", verbosity); |
---|
1021 | } |
---|
1022 | |
---|
1023 | env.add("MOUNT_DIR", SCHROOT_MOUNT_DIR); |
---|
1024 | env.add("LIBEXEC_DIR", SCHROOT_LIBEXEC_DIR); |
---|
1025 | env.add("PID", getpid()); |
---|
1026 | env.add("SESSION_ID", session_chroot->get_session_id()); |
---|
1027 | |
---|
1028 | run_parts rp(SCHROOT_CONF_SETUP_D, |
---|
1029 | true, true, 022); |
---|
1030 | rp.set_reverse(setup_type == chroot::SETUP_STOP); |
---|
1031 | rp.set_verbose(this->authstat->get_verbosity() == auth::VERBOSITY_VERBOSE); |
---|
1032 | |
---|
1033 | log_debug(DEBUG_INFO) << rp << std::endl; |
---|
1034 | |
---|
1035 | int exit_status = 0; |
---|
1036 | pid_t pid; |
---|
1037 | |
---|
1038 | if ((pid = fork()) == -1) |
---|
1039 | { |
---|
1040 | this->chroot_status = false; |
---|
1041 | throw error(session_chroot->get_name(), CHILD_FORK, strerror(errno)); |
---|
1042 | } |
---|
1043 | else if (pid == 0) |
---|
1044 | { |
---|
1045 | try |
---|
1046 | { |
---|
1047 | // The setup scripts don't use our syslog fd. |
---|
1048 | closelog(); |
---|
1049 | |
---|
1050 | chdir("/"); |
---|
1051 | /* This is required to ensure the scripts run with uid=0 and gid=0, |
---|
1052 | otherwise setuid programs such as mount(8) will fail. This |
---|
1053 | should always succeed, because our euid=0 and egid=0.*/ |
---|
1054 | setuid(0); |
---|
1055 | setgid(0); |
---|
1056 | initgroups("root", 0); |
---|
1057 | |
---|
1058 | int status = rp.run(arg_list, env); |
---|
1059 | |
---|
1060 | _exit (status); |
---|
1061 | } |
---|
1062 | catch (std::exception const& e) |
---|
1063 | { |
---|
1064 | sbuild::log_exception_error(e); |
---|
1065 | } |
---|
1066 | catch (...) |
---|
1067 | { |
---|
1068 | sbuild::log_error() |
---|
1069 | << _("An unknown exception occurred") << std::endl; |
---|
1070 | } |
---|
1071 | _exit(EXIT_FAILURE); |
---|
1072 | } |
---|
1073 | else |
---|
1074 | { |
---|
1075 | wait_for_child(pid, exit_status); |
---|
1076 | } |
---|
1077 | |
---|
1078 | try |
---|
1079 | { |
---|
1080 | session_chroot->unlock(setup_type, exit_status); |
---|
1081 | } |
---|
1082 | catch (chroot::error const& e) |
---|
1083 | { |
---|
1084 | this->chroot_status = false; |
---|
1085 | this->lock_status = false; |
---|
1086 | throw error(session_chroot->get_name(), CHROOT_UNLOCK, e); |
---|
1087 | } |
---|
1088 | |
---|
1089 | if (exit_status != 0) |
---|
1090 | { |
---|
1091 | this->chroot_status = false; |
---|
1092 | |
---|
1093 | format fmt(_("stage=%1%")); |
---|
1094 | fmt % setup_type_string; |
---|
1095 | throw error(session_chroot->get_name(), CHROOT_SETUP, fmt.str()); |
---|
1096 | } |
---|
1097 | } |
---|
1098 | |
---|
1099 | void |
---|
1100 | session::run_child (sbuild::chroot::ptr& session_chroot) |
---|
1101 | { |
---|
1102 | assert(!session_chroot->get_name().empty()); |
---|
1103 | |
---|
1104 | assert(!this->authstat->get_user().empty()); |
---|
1105 | assert(!get_shell().empty()); |
---|
1106 | assert(this->authstat->is_initialised()); // PAM must be initialised |
---|
1107 | |
---|
1108 | // Store before chroot call. |
---|
1109 | this->cwd = sbuild::getcwd(); |
---|
1110 | log_debug(DEBUG_INFO) << "CWD=" << this->cwd << std::endl; |
---|
1111 | |
---|
1112 | std::string location(session_chroot->get_path()); |
---|
1113 | log_debug(DEBUG_INFO) << "location=" << location << std::endl; |
---|
1114 | |
---|
1115 | /* Set group ID and supplementary groups */ |
---|
1116 | if (setgid (this->authstat->get_gid())) |
---|
1117 | throw error(this->authstat->get_gid(), GROUP_SET, strerror(errno)); |
---|
1118 | log_debug(DEBUG_NOTICE) << "Set GID=" << this->authstat->get_gid() << std::endl; |
---|
1119 | if (initgroups (this->authstat->get_user().c_str(), this->authstat->get_gid())) |
---|
1120 | throw error(GROUP_SET_SUP, strerror(errno)); |
---|
1121 | log_debug(DEBUG_NOTICE) << "Set supplementary groups" << std::endl; |
---|
1122 | |
---|
1123 | /* Set the process execution domain. */ |
---|
1124 | /* Will throw on failure. */ |
---|
1125 | chroot_facet_personality::const_ptr pfac = |
---|
1126 | session_chroot->get_facet<chroot_facet_personality>(); |
---|
1127 | if (pfac) |
---|
1128 | { |
---|
1129 | pfac->get_persona().set(); |
---|
1130 | log_debug(DEBUG_NOTICE) << "Set personality=" |
---|
1131 | << pfac->get_persona() << std::endl; |
---|
1132 | } |
---|
1133 | else |
---|
1134 | { |
---|
1135 | log_debug(DEBUG_NOTICE) << "Personality support unavailable" << std::endl; |
---|
1136 | } |
---|
1137 | |
---|
1138 | /* Enter the chroot */ |
---|
1139 | if (chdir (location.c_str())) |
---|
1140 | throw error(location, CHDIR, strerror(errno)); |
---|
1141 | log_debug(DEBUG_NOTICE) << "Changed directory to " << location << std::endl; |
---|
1142 | if (::chroot (location.c_str())) |
---|
1143 | throw error(location, CHROOT, strerror(errno)); |
---|
1144 | log_debug(DEBUG_NOTICE) << "Changed root to " << location << std::endl; |
---|
1145 | |
---|
1146 | /* Set uid and check we are not still root */ |
---|
1147 | if (setuid (this->authstat->get_uid())) |
---|
1148 | throw error(this->authstat->get_uid(), USER_SET, strerror(errno)); |
---|
1149 | log_debug(DEBUG_NOTICE) << "Set UID=" << this->authstat->get_uid() << std::endl; |
---|
1150 | if (!setuid (0) && this->authstat->get_uid()) |
---|
1151 | throw error(ROOT_DROP); |
---|
1152 | if (this->authstat->get_uid()) |
---|
1153 | log_debug(DEBUG_NOTICE) << "Dropped root privileges" << std::endl; |
---|
1154 | |
---|
1155 | std::string file; |
---|
1156 | string_list command(this->authstat->get_command()); |
---|
1157 | |
---|
1158 | string_list dlist; |
---|
1159 | if (command.empty() || |
---|
1160 | command[0].empty()) // No command |
---|
1161 | dlist = get_login_directories(); |
---|
1162 | else |
---|
1163 | dlist = get_command_directories(); |
---|
1164 | log_debug(DEBUG_INFO) |
---|
1165 | << format("Directory fallbacks: %1%") % string_list_to_string(dlist, ", ") << endl; |
---|
1166 | |
---|
1167 | /* Attempt to chdir to current directory. */ |
---|
1168 | for (string_list::const_iterator dpos = dlist.begin(); |
---|
1169 | dpos != dlist.end(); |
---|
1170 | ++dpos) |
---|
1171 | { |
---|
1172 | if (chdir ((*dpos).c_str()) < 0) |
---|
1173 | { |
---|
1174 | error e(*dpos, CHDIR, strerror(errno)); |
---|
1175 | |
---|
1176 | if (dpos + 1 == dlist.end()) |
---|
1177 | throw e; |
---|
1178 | else |
---|
1179 | log_exception_warning(e); |
---|
1180 | } |
---|
1181 | else |
---|
1182 | { |
---|
1183 | log_debug(DEBUG_NOTICE) << "Changed directory to " |
---|
1184 | << *dpos << std::endl; |
---|
1185 | if (dpos != dlist.begin()) |
---|
1186 | { |
---|
1187 | error e(CHDIR_FB, *dpos); |
---|
1188 | log_exception_warning(e); |
---|
1189 | } |
---|
1190 | break; |
---|
1191 | } |
---|
1192 | } |
---|
1193 | |
---|
1194 | /* Fix up the command for exec. */ |
---|
1195 | get_command(session_chroot, file, command); |
---|
1196 | log_debug(DEBUG_NOTICE) << "command=" |
---|
1197 | << string_list_to_string(command, ", ") |
---|
1198 | << std::endl; |
---|
1199 | |
---|
1200 | /* Set up environment */ |
---|
1201 | environment env; |
---|
1202 | env.set_filter(session_chroot->get_environment_filter()); |
---|
1203 | env += this->authstat->get_auth_environment(); |
---|
1204 | |
---|
1205 | // Add equivalents to sudo's SUDO_USER, SUDO_UID, SUDO_GID, and |
---|
1206 | // SUDO_COMMAND. |
---|
1207 | env.add(std::make_pair("SCHROOT_COMMAND", |
---|
1208 | string_list_to_string(command, " "))); |
---|
1209 | env.add(std::make_pair("SCHROOT_USER", this->authstat->get_ruser())); |
---|
1210 | env.add(std::make_pair("SCHROOT_GROUP", this->authstat->get_rgroup())); |
---|
1211 | env.add("SCHROOT_UID", this->authstat->get_ruid()); |
---|
1212 | env.add("SCHROOT_GID", this->authstat->get_rgid()); |
---|
1213 | // Add session ID. |
---|
1214 | env.add("SCHROOT_SESSION_ID", session_chroot->get_session_id()); |
---|
1215 | |
---|
1216 | log_debug(DEBUG_INFO) << "Set environment:\n" << env; |
---|
1217 | |
---|
1218 | // The user's command does not use our syslog fd. |
---|
1219 | closelog(); |
---|
1220 | |
---|
1221 | // Add command prefix. |
---|
1222 | string_list full_command(session_chroot->get_command_prefix()); |
---|
1223 | if (full_command.size() > 0) |
---|
1224 | { |
---|
1225 | std::string path; |
---|
1226 | if (!env.get("PATH", path)) |
---|
1227 | path.clear(); |
---|
1228 | file = find_program_in_path(full_command[0], path, ""); |
---|
1229 | } |
---|
1230 | for (string_list::const_iterator pos = command.begin(); |
---|
1231 | pos != command.end(); |
---|
1232 | ++pos) |
---|
1233 | full_command.push_back(*pos); |
---|
1234 | |
---|
1235 | /* Execute */ |
---|
1236 | if (exec (file, full_command, env)) |
---|
1237 | throw error(file, EXEC, strerror(errno)); |
---|
1238 | |
---|
1239 | /* This should never be reached */ |
---|
1240 | _exit(EXIT_FAILURE); |
---|
1241 | } |
---|
1242 | |
---|
1243 | void |
---|
1244 | session::wait_for_child (pid_t pid, |
---|
1245 | int& child_status) |
---|
1246 | { |
---|
1247 | child_status = EXIT_FAILURE; // Default exit status |
---|
1248 | |
---|
1249 | int status; |
---|
1250 | bool child_killed = false; |
---|
1251 | |
---|
1252 | while (1) |
---|
1253 | { |
---|
1254 | if ((sighup_called || sigterm_called) && !child_killed) |
---|
1255 | { |
---|
1256 | if (sighup_called) |
---|
1257 | { |
---|
1258 | error e(SIGNAL_CATCH, strsignal(SIGHUP), |
---|
1259 | _("terminating immediately")); |
---|
1260 | log_exception_error(e); |
---|
1261 | kill(pid, SIGHUP); |
---|
1262 | } |
---|
1263 | else // SIGTERM |
---|
1264 | { |
---|
1265 | error e(SIGNAL_CATCH, strsignal(SIGTERM), |
---|
1266 | _("terminating immediately")); |
---|
1267 | log_exception_error(e); |
---|
1268 | kill(pid, SIGTERM); |
---|
1269 | } |
---|
1270 | this->chroot_status = false; |
---|
1271 | child_killed = true; |
---|
1272 | } |
---|
1273 | |
---|
1274 | if (waitpid(pid, &status, 0) == -1) |
---|
1275 | { |
---|
1276 | if (errno == EINTR && (sighup_called || sigterm_called)) |
---|
1277 | continue; // Kill child and wait again. |
---|
1278 | else |
---|
1279 | throw error(CHILD_WAIT, strerror(errno)); |
---|
1280 | } |
---|
1281 | else if (sighup_called) |
---|
1282 | { |
---|
1283 | sighup_called = false; |
---|
1284 | throw error(SIGNAL_CATCH, strsignal(SIGHUP)); |
---|
1285 | } |
---|
1286 | else if (sigterm_called) |
---|
1287 | { |
---|
1288 | sigterm_called = false; |
---|
1289 | throw error(SIGNAL_CATCH, strsignal(SIGTERM)); |
---|
1290 | } |
---|
1291 | else |
---|
1292 | break; |
---|
1293 | } |
---|
1294 | |
---|
1295 | if (!WIFEXITED(status)) |
---|
1296 | { |
---|
1297 | if (WIFSIGNALED(status)) |
---|
1298 | throw error(CHILD_SIGNAL, strsignal(WTERMSIG(status))); |
---|
1299 | else if (WCOREDUMP(status)) |
---|
1300 | throw error(CHILD_CORE); |
---|
1301 | else |
---|
1302 | throw error(CHILD_FAIL); |
---|
1303 | } |
---|
1304 | |
---|
1305 | child_status = WEXITSTATUS(status); |
---|
1306 | } |
---|
1307 | |
---|
1308 | void |
---|
1309 | session::run_chroot (sbuild::chroot::ptr& session_chroot) |
---|
1310 | { |
---|
1311 | assert(!session_chroot->get_name().empty()); |
---|
1312 | |
---|
1313 | pid_t pid; |
---|
1314 | if ((pid = fork()) == -1) |
---|
1315 | { |
---|
1316 | throw error(CHILD_FORK, strerror(errno)); |
---|
1317 | } |
---|
1318 | else if (pid == 0) |
---|
1319 | { |
---|
1320 | #ifdef SBUILD_DEBUG |
---|
1321 | while (child_wait) |
---|
1322 | ; |
---|
1323 | #endif |
---|
1324 | try |
---|
1325 | { |
---|
1326 | run_child(session_chroot); |
---|
1327 | } |
---|
1328 | catch (std::runtime_error const& e) |
---|
1329 | { |
---|
1330 | log_exception_error(e); |
---|
1331 | } |
---|
1332 | catch (...) |
---|
1333 | { |
---|
1334 | sbuild::log_error() |
---|
1335 | << _("An unknown exception occurred") << std::endl; |
---|
1336 | } |
---|
1337 | _exit (EXIT_FAILURE); |
---|
1338 | } |
---|
1339 | else |
---|
1340 | { |
---|
1341 | wait_for_child(pid, this->child_status); |
---|
1342 | } |
---|
1343 | } |
---|
1344 | |
---|
1345 | void |
---|
1346 | session::set_sighup_handler () |
---|
1347 | { |
---|
1348 | set_signal_handler(SIGHUP, &this->saved_sighup_signal, sighup_handler); |
---|
1349 | } |
---|
1350 | |
---|
1351 | void |
---|
1352 | session::clear_sighup_handler () |
---|
1353 | { |
---|
1354 | clear_signal_handler(SIGHUP, &this->saved_sighup_signal); |
---|
1355 | } |
---|
1356 | |
---|
1357 | void |
---|
1358 | session::set_sigterm_handler () |
---|
1359 | { |
---|
1360 | set_signal_handler(SIGTERM, &this->saved_sigterm_signal, sigterm_handler); |
---|
1361 | } |
---|
1362 | |
---|
1363 | void |
---|
1364 | session::clear_sigterm_handler () |
---|
1365 | { |
---|
1366 | clear_signal_handler(SIGTERM, &this->saved_sigterm_signal); |
---|
1367 | } |
---|
1368 | |
---|
1369 | void |
---|
1370 | session::set_signal_handler (int signal, |
---|
1371 | struct sigaction *saved_signal, |
---|
1372 | void (*handler)(int)) |
---|
1373 | { |
---|
1374 | struct sigaction new_sa; |
---|
1375 | sigemptyset(&new_sa.sa_mask); |
---|
1376 | new_sa.sa_flags = 0; |
---|
1377 | new_sa.sa_handler = handler; |
---|
1378 | |
---|
1379 | if (sigaction(signal, &new_sa, saved_signal) != 0) |
---|
1380 | throw error(SIGNAL_SET, strsignal(signal), strerror(errno)); |
---|
1381 | } |
---|
1382 | |
---|
1383 | void |
---|
1384 | session::clear_signal_handler (int signal, |
---|
1385 | struct sigaction *saved_signal) |
---|
1386 | { |
---|
1387 | /* Restore original handler */ |
---|
1388 | sigaction (signal, saved_signal, 0); |
---|
1389 | } |
---|