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