1#![cfg(target_os = "linux")]
7
8use clap::builder::ValueParser;
9use uucore::error::{UError, UResult};
10use uucore::translate;
11
12use clap::{Arg, ArgAction, Command};
13use selinux::{OpaqueSecurityContext, SecurityClass, SecurityContext};
14use uucore::format_usage;
15
16use std::borrow::Cow;
17use std::ffi::{CStr, CString, OsStr, OsString};
18use std::os::raw::c_char;
19use std::os::unix::ffi::OsStrExt;
20use std::{io, ptr};
21
22mod errors;
23
24use errors::error_exit_status;
25use errors::{Error, Result, RunconError};
26
27pub mod options {
28 pub const COMPUTE: &str = "compute";
29
30 pub const USER: &str = "user";
31 pub const ROLE: &str = "role";
32 pub const TYPE: &str = "type";
33 pub const RANGE: &str = "range";
34}
35
36#[uucore::main]
37pub fn uumain(args: impl uucore::Args) -> UResult<()> {
38 let config = uu_app();
39
40 let options = parse_command_line(config, args)?;
41
42 match &options.mode {
43 CommandLineMode::Print => print_current_context().map_err(|e| RunconError::new(e).into()),
44 CommandLineMode::PlainContext { context, command } => {
45 get_plain_context(context)
46 .and_then(|ctx| set_next_exec_context(&ctx))
47 .map_err(RunconError::new)?;
48 execute_command(command, &options.arguments)
51 }
52 CommandLineMode::CustomContext {
53 compute_transition_context,
54 user,
55 role,
56 the_type,
57 range,
58 command,
59 } => {
60 match command {
61 Some(command) => {
62 get_custom_context(
63 *compute_transition_context,
64 user.as_deref(),
65 role.as_deref(),
66 the_type.as_deref(),
67 range.as_deref(),
68 command,
69 )
70 .and_then(|ctx| set_next_exec_context(&ctx))
71 .map_err(RunconError::new)?;
72 execute_command(command, &options.arguments)
75 }
76 None => print_current_context().map_err(|e| RunconError::new(e).into()),
77 }
78 }
79 }
80}
81
82pub fn uu_app() -> Command {
83 let cmd = Command::new(uucore::util_name())
84 .version(uucore::crate_version!())
85 .about(translate!("runcon-about"))
86 .after_help(translate!("runcon-after-help"))
87 .override_usage(format_usage(&translate!("runcon-usage")))
88 .infer_long_args(true);
89 uucore::clap_localization::configure_localized_command(cmd)
90 .arg(
91 Arg::new(options::COMPUTE)
92 .short('c')
93 .long(options::COMPUTE)
94 .help(translate!("runcon-help-compute"))
95 .action(ArgAction::SetTrue),
96 )
97 .arg(
98 Arg::new(options::USER)
99 .short('u')
100 .long(options::USER)
101 .value_name("USER")
102 .help(translate!("runcon-help-user"))
103 .value_parser(ValueParser::os_string()),
104 )
105 .arg(
106 Arg::new(options::ROLE)
107 .short('r')
108 .long(options::ROLE)
109 .value_name("ROLE")
110 .help(translate!("runcon-help-role"))
111 .value_parser(ValueParser::os_string()),
112 )
113 .arg(
114 Arg::new(options::TYPE)
115 .short('t')
116 .long(options::TYPE)
117 .value_name("TYPE")
118 .help(translate!("runcon-help-type"))
119 .value_parser(ValueParser::os_string()),
120 )
121 .arg(
122 Arg::new(options::RANGE)
123 .short('l')
124 .long(options::RANGE)
125 .value_name("RANGE")
126 .help(translate!("runcon-help-range"))
127 .value_parser(ValueParser::os_string()),
128 )
129 .arg(
130 Arg::new("ARG")
131 .action(ArgAction::Append)
132 .value_parser(ValueParser::os_string())
133 .value_hint(clap::ValueHint::CommandName),
134 )
135 .trailing_var_arg(true)
140}
141
142#[derive(Debug)]
143enum CommandLineMode {
144 Print,
145
146 PlainContext {
147 context: OsString,
148 command: OsString,
149 },
150
151 CustomContext {
152 compute_transition_context: bool,
154
155 user: Option<OsString>,
157
158 role: Option<OsString>,
160
161 the_type: Option<OsString>,
163
164 range: Option<OsString>,
166
167 command: Option<OsString>,
173 },
174}
175
176#[derive(Debug)]
177struct Options {
178 mode: CommandLineMode,
179 arguments: Vec<OsString>,
180}
181
182fn parse_command_line(config: Command, args: impl uucore::Args) -> UResult<Options> {
183 let matches = uucore::clap_localization::handle_clap_result_with_exit_code(config, args, 125)?;
184
185 let compute_transition_context = matches.get_flag(options::COMPUTE);
186
187 let mut args = matches
188 .get_many::<OsString>("ARG")
189 .unwrap_or_default()
190 .map(OsString::from);
191
192 if compute_transition_context
193 || matches.contains_id(options::USER)
194 || matches.contains_id(options::ROLE)
195 || matches.contains_id(options::TYPE)
196 || matches.contains_id(options::RANGE)
197 {
198 let mode = CommandLineMode::CustomContext {
201 compute_transition_context,
202 user: matches.get_one::<OsString>(options::USER).map(Into::into),
203 role: matches.get_one::<OsString>(options::ROLE).map(Into::into),
204 the_type: matches.get_one::<OsString>(options::TYPE).map(Into::into),
205 range: matches.get_one::<OsString>(options::RANGE).map(Into::into),
206 command: args.next(),
207 };
208
209 Ok(Options {
210 mode,
211 arguments: args.collect(),
212 })
213 } else if let Some(context) = args.next() {
214 args.next()
217 .ok_or_else(|| Box::new(Error::MissingCommand) as Box<dyn UError>)
218 .map(move |command| Options {
219 mode: CommandLineMode::PlainContext { context, command },
220 arguments: args.collect(),
221 })
222 } else {
223 Ok(Options {
226 mode: CommandLineMode::Print,
227 arguments: Vec::default(),
228 })
229 }
230}
231
232fn print_current_context() -> Result<()> {
233 let context = SecurityContext::current(false)
234 .map_err(|r| Error::from_selinux("runcon-operation-getting-current-context", r))?;
235
236 let context = context
237 .to_c_string()
238 .map_err(|r| Error::from_selinux("runcon-operation-getting-current-context", r))?;
239
240 if let Some(context) = context {
241 let context = context.as_ref().to_str()?;
242 println!("{context}");
243 } else {
244 println!();
245 }
246 Ok(())
247}
248
249fn set_next_exec_context(context: &OpaqueSecurityContext) -> Result<()> {
250 let c_context = context
251 .to_c_string()
252 .map_err(|r| Error::from_selinux("runcon-operation-creating-context", r))?;
253
254 let sc = SecurityContext::from_c_str(&c_context, false);
255
256 if sc.check() != Some(true) {
257 let ctx = OsStr::from_bytes(c_context.as_bytes());
258 let err = io::ErrorKind::InvalidInput.into();
259 return Err(Error::from_io1(
260 "runcon-operation-checking-context",
261 ctx,
262 err,
263 ));
264 }
265
266 sc.set_for_next_exec()
267 .map_err(|r| Error::from_selinux("runcon-operation-setting-context", r))
268}
269
270fn get_plain_context(context: &OsStr) -> Result<OpaqueSecurityContext> {
271 if !uucore::selinux::is_selinux_enabled() {
272 return Err(Error::SELinuxNotEnabled);
273 }
274
275 let c_context = os_str_to_c_string(context)?;
276
277 OpaqueSecurityContext::from_c_str(&c_context)
278 .map_err(|r| Error::from_selinux("runcon-operation-creating-context", r))
279}
280
281fn get_transition_context(command: &OsStr) -> Result<SecurityContext<'_>> {
282 let sec_class = SecurityClass::from_name("process")
284 .map_err(|r| Error::from_selinux("runcon-operation-getting-process-class", r))?;
285
286 let file_context = match SecurityContext::of_path(command, true, false) {
288 Ok(Some(context)) => context,
289
290 Ok(None) => {
291 let err = io::Error::from_raw_os_error(libc::ENODATA);
292 return Err(Error::from_io1("runcon-operation-getfilecon", command, err));
293 }
294
295 Err(r) => {
296 return Err(Error::from_selinux(
297 "runcon-operation-getting-file-context",
298 r,
299 ));
300 }
301 };
302
303 let process_context = SecurityContext::current(false)
304 .map_err(|r| Error::from_selinux("runcon-operation-getting-current-context", r))?;
305
306 process_context
308 .of_labeling_decision(&file_context, sec_class, "")
309 .map_err(|r| Error::from_selinux("runcon-operation-computing-transition", r))
310}
311
312fn get_initial_custom_opaque_context(
313 compute_transition_context: bool,
314 command: &OsStr,
315) -> Result<OpaqueSecurityContext> {
316 let context = if compute_transition_context {
317 get_transition_context(command)?
318 } else {
319 SecurityContext::current(false)
320 .map_err(|r| Error::from_selinux("runcon-operation-getting-current-context", r))?
321 };
322
323 let c_context = context
324 .to_c_string()
325 .map_err(|r| Error::from_selinux("runcon-operation-getting-context", r))?
326 .unwrap_or_else(|| Cow::Owned(CString::default()));
327
328 OpaqueSecurityContext::from_c_str(c_context.as_ref())
329 .map_err(|r| Error::from_selinux("runcon-operation-creating-context", r))
330}
331
332fn get_custom_context(
333 compute_transition_context: bool,
334 user: Option<&OsStr>,
335 role: Option<&OsStr>,
336 the_type: Option<&OsStr>,
337 range: Option<&OsStr>,
338 command: &OsStr,
339) -> Result<OpaqueSecurityContext> {
340 use OpaqueSecurityContext as OSC;
341 type SetNewValueProc = fn(&OSC, &CStr) -> selinux::errors::Result<()>;
342
343 if !uucore::selinux::is_selinux_enabled() {
344 return Err(Error::SELinuxNotEnabled);
345 }
346
347 let osc = get_initial_custom_opaque_context(compute_transition_context, command)?;
348
349 let list: &[(Option<&OsStr>, SetNewValueProc, &'static str)] = &[
350 (user, OSC::set_user, "runcon-operation-setting-user"),
351 (role, OSC::set_role, "runcon-operation-setting-role"),
352 (the_type, OSC::set_type, "runcon-operation-setting-type"),
353 (range, OSC::set_range, "runcon-operation-setting-range"),
354 ];
355
356 for &(new_value, method, op_key) in list {
357 if let Some(new_value) = new_value {
358 let c_new_value = os_str_to_c_string(new_value)?;
359 method(&osc, &c_new_value).map_err(|r| Error::from_selinux(op_key, r))?;
360 }
361 }
362 Ok(osc)
363}
364
365fn execute_command(command: &OsStr, arguments: &[OsString]) -> UResult<()> {
370 let c_command = os_str_to_c_string(command).map_err(RunconError::new)?;
371
372 let argv_storage: Vec<CString> = arguments
373 .iter()
374 .map(AsRef::as_ref)
375 .map(os_str_to_c_string)
376 .collect::<Result<_>>()
377 .map_err(RunconError::new)?;
378
379 let mut argv: Vec<*const c_char> = Vec::with_capacity(arguments.len().saturating_add(2));
380 argv.push(c_command.as_ptr());
381 argv.extend(argv_storage.iter().map(AsRef::as_ref).map(CStr::as_ptr));
382 argv.push(ptr::null());
383
384 unsafe { libc::execvp(c_command.as_ptr(), argv.as_ptr()) };
385
386 let err = io::Error::last_os_error();
387 let exit_status = if err.kind() == io::ErrorKind::NotFound {
388 error_exit_status::NOT_FOUND
389 } else {
390 error_exit_status::COULD_NOT_EXECUTE
391 };
392
393 let err = Error::from_io1("runcon-operation-executing-command", command, err);
394 Err(RunconError::with_code(exit_status, err).into())
395}
396
397fn os_str_to_c_string(s: &OsStr) -> Result<CString> {
398 CString::new(s.as_bytes()).map_err(|_r| {
399 Error::from_io(
400 "runcon-operation-cstring-new",
401 io::ErrorKind::InvalidInput.into(),
402 )
403 })
404}