1use regex::Regex;
18use std::collections::HashMap;
19use std::env;
20use std::ffi::{CStr, CString};
21use std::path::Path;
22use std::process;
23use syslog::{Facility, Formatter3164};
24
25use chrono::{NaiveDate, NaiveDateTime, Utc};
26use nix::sys::signal;
27use nix::sys::signal::*;
28
29use std::fmt;
30use std::fs;
31use std::fs::File;
32use std::io::prelude::*;
33use std::io::BufReader;
34use std::os::unix::io::AsRawFd;
35use std::time::SystemTime;
36use uzers::os::unix::UserExt;
37use uzers::*;
38
39use getopts::{Matches, Options};
40use nix::unistd::{alarm, gethostname, setegid, seteuid, setgid, setuid};
41use pam::Authenticator;
42
43use rand::distributions::Alphanumeric;
44use rand::{thread_rng, Rng};
45
46#[derive(PartialEq, Eq, Clone, Debug)]
47pub enum EditMode {
48 Mode(i32),
49 Keep(bool),
50}
51
52#[derive(PartialEq, Eq, Clone, Debug)]
53pub enum ReasonType {
54 Need(bool),
55 Text(String),
56}
57
58#[derive(Clone, Debug)]
59pub struct EnvOptions {
60 pub name: Option<String>,
61 pub exact_name: Option<String>,
62 pub rule: Option<String>,
63 pub exact_rule: Option<String>,
64 pub notbefore: Option<NaiveDateTime>,
65 pub notafter: Option<NaiveDateTime>,
66 pub datematch: Option<String>,
67 pub target: Option<String>,
68 pub exact_target: Option<String>,
69 pub target_group: Option<String>,
70 pub exact_target_group: Option<String>,
71 pub hostname: Option<String>,
72 pub exact_hostname: Option<String>,
73 pub permit: Option<bool>,
74 pub require_pass: Option<bool>,
75 pub acl_type: Acltype,
76 pub file_name: String,
77 pub section: String,
78 pub group: bool,
79 pub configured: bool,
80 pub dir: Option<String>,
81 pub exact_dir: Option<String>,
82 pub exitcmd: Option<String>,
83 pub edit_mode: Option<EditMode>,
84 pub reason: Option<ReasonType>,
85 pub last: Option<bool>,
86 pub syslog: Option<bool>,
87 pub env_permit: Option<String>,
88 pub env_assign: Option<HashMap<String, String>>,
89 pub timeout: Option<u32>,
90 pub search_path: Option<String>,
91 pub token_timeout: Option<u64>,
92}
93
94impl EnvOptions {
95 pub fn new() -> EnvOptions {
96 EnvOptions {
97 name: None,
98 exact_name: None,
99 rule: Some("^$".to_string()),
100 exact_rule: None,
101 target: Some("root".to_string()),
102 exact_target: None,
103 target_group: None,
104 exact_target_group: None,
105 notbefore: None,
106 notafter: None,
107 datematch: None,
108 hostname: None,
109 exact_hostname: None,
110 file_name: "".to_string(),
111 section: "".to_string(),
112 permit: None,
113 require_pass: None,
114 acl_type: Acltype::Run,
115 group: false,
116 configured: false,
117 dir: None,
118 exact_dir: None,
119 exitcmd: None,
120 edit_mode: None,
121 reason: None,
122 last: None,
123 syslog: None,
124 env_permit: None,
125 env_assign: None,
126 timeout: None,
127 search_path: None,
128 token_timeout: None,
129 }
130 }
131 fn new_deny() -> EnvOptions {
132 let mut opt = EnvOptions::new();
133 opt.permit = Some(false);
134 opt.rule = Some(".".to_string());
135 opt.target = Some("^$".to_string());
136 opt.acl_type = Acltype::List;
137 opt
138 }
139 pub fn permit(&self) -> bool {
140 if self.permit.is_some() && !self.permit.unwrap() {
141 return false;
142 }
143
144 true
145 }
146 pub fn require_pass(&self) -> bool {
147 if self.require_pass.is_some() && !self.require_pass.unwrap() {
148 return false;
149 }
150
151 true
152 }
153}
154
155impl Default for EnvOptions {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161#[derive(Clone, Debug)]
162pub struct RunOptions {
163 pub name: String,
164 pub original_uid: nix::unistd::Uid,
165 pub original_gid: nix::unistd::Gid,
166 pub target: String,
167 pub target_group: Option<String>,
168 pub command: String,
169 pub original_command: Vec<String>,
170 pub hostname: String,
171 pub directory: Option<String>,
172 pub groups: HashMap<String, u32>,
173 pub date: NaiveDateTime,
174 pub acl_type: Acltype,
175 pub reason: Option<String>,
176 pub syslog: bool,
177 pub prompt: bool,
178 pub purge_token: bool,
179 pub warm_token: bool,
180 pub new_args: Vec<String>,
181 pub old_umask: Option<nix::sys::stat::Mode>,
182 pub old_envs: Option<HashMap<String, String>>,
183 pub allow_env_list: Option<Vec<String>>,
184 pub env_options: Option<EnvOptions>,
185 pub cloned_args: Option<Vec<String>>,
186 pub located_bin: HashMap<String, Option<String>>,
187 pub resume: Option<bool>,
188}
189
190impl RunOptions {
191 pub fn new() -> RunOptions {
192 RunOptions {
193 name: "root".to_string(),
194 original_uid: nix::unistd::Uid::from_raw(get_current_uid()),
195 original_gid: nix::unistd::Gid::from_raw(get_current_gid()),
196 target: "".to_string(),
197 target_group: None,
198 command: "".to_string(),
199 original_command: vec![],
200 hostname: "localhost".to_string(),
201 date: Utc::now().naive_utc(),
202 groups: HashMap::new(),
203 directory: None,
204 acl_type: Acltype::Run,
205 reason: None,
206 syslog: true,
207 prompt: true,
208 purge_token: false,
209 warm_token: false,
210 new_args: vec![],
211 old_umask: None,
212 old_envs: None,
213 allow_env_list: None,
214 env_options: None,
215 cloned_args: None,
216 located_bin: HashMap::new(),
217 resume: None,
218 }
219 }
220}
221
222impl Default for RunOptions {
223 fn default() -> Self {
224 Self::new()
225 }
226}
227
228struct PamConvo {
229 login: String,
230 passwd: Option<String>,
231 service: String,
232}
233
234impl pam::Converse for PamConvo {
235 fn prompt_echo(&mut self, _msg: &CStr) -> Result<CString, ()> {
236 CString::new(self.login.clone()).map_err(|_| ())
237 }
238 fn prompt_blind(&mut self, _msg: &CStr) -> Result<CString, ()> {
239 match rpassword::prompt_password(format!(
240 "[{}] password for {}: ",
241 self.service, self.login
242 )) {
243 Ok(password) => self.passwd = Some(password),
244 Err(_) => {
245 println!("Cannot read from terminal");
246 std::process::exit(1);
247 }
248 }
249
250 CString::new(self.passwd.clone().unwrap()).map_err(|_| ())
251 }
252 fn info(&mut self, _msg: &CStr) {}
253 fn error(&mut self, msg: &CStr) {
254 println!("[{} pam error] {}", self.service, msg.to_string_lossy());
255 }
256 fn username(&self) -> &str {
257 &self.login
258 }
259}
260
261#[derive(PartialEq, Eq, Clone, Debug)]
262pub enum Acltype {
263 Run,
264 List,
265 Edit,
266}
267
268impl fmt::Display for Acltype {
269 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
270 match *self {
271 Acltype::Run => write!(f, "run"),
272 Acltype::List => write!(f, "list"),
273 Acltype::Edit => write!(f, "edit"),
274 }
275 }
276}
277
278pub fn print_may_not(ro: &RunOptions) {
279 println!(
280 "You may not {} \"{}\" on {} as {}",
281 if ro.acl_type == Acltype::Run {
282 "execute".to_string()
283 } else {
284 ro.acl_type.to_string()
285 },
286 &ro.command,
287 &ro.hostname,
288 &ro.target
289 );
290}
291
292pub fn regex_build(
294 v: &str,
295 ro: &RunOptions,
296 config_path: &str,
297 section: &str,
298 line: Option<i32>,
299) -> Option<Regex> {
300 let rule = Regex::new(&format!(
301 "^{}$",
302 &v.replace("%{USER}", &ro.name)
303 .replace("%{HOSTNAME}", &ro.hostname)
304 ));
305 if rule.is_err() {
306 println!(
307 "Error parsing {}{}",
308 config_path,
309 if line.is_some() {
310 format!(": {}:{}", section, line.unwrap())
311 } else {
312 "".to_string()
313 }
314 );
315 return None;
316 }
317 Some(rule.unwrap())
318}
319
320pub fn can_dir_include(file: &str) -> bool {
322 let p = Path::new(file);
323
324 if !p.is_file() {
325 return false;
326 }
327 can_include_file_pattern(file)
328}
329
330pub fn can_include_file_pattern(file: &str) -> bool {
331 let dir_pattern = Regex::new(r".*\.ini$").unwrap();
332
333 if dir_pattern.is_match(file) {
334 let p = Path::new(file);
335
336 if p.file_name().is_none() {
337 return false;
338 }
339
340 match p.file_name().unwrap().to_str() {
341 None => {
342 return false;
343 }
344 Some(f) => {
345 if f.starts_with('.') {
346 return false;
347 }
348 }
349 }
350 return true;
351 }
352 false
353}
354
355pub fn print_usage(opts: &Options, header: &str) {
357 println!("usage:");
358 println!("{}", opts.usage(header));
359}
360
361pub fn credits(service: &str) {
363 let mut contributors = [
364 "All of the Debian Rust Maintainers, and especially Sylvestre Ledru",
365 "Andy Kluger, for your feedback",
366 "Cyrus Wyett, jim was better than ed",
367 "@unmellow, for your early testing",
368 "noproto, for your detailed report",
369 "pin, for work with pkgsrc",
370 "Stanley Dziegiel, for ini suggestions",
371 "My wife and child, for putting up with me",
372 "The SUSE Security Team, especially Matthias Gerstner",
373 ];
374
375 print_version(service);
376
377 contributors.sort_unstable();
378
379 println!("\nWith thanks to the following teams and people, you got us where we are today.\n");
380 println!("If your name is missing, or incorrect, please get in contact.\n");
381 println!("In sort order:\n");
382
383 for i in contributors.iter() {
384 println!("\t{}", i);
385 }
386
387 println!("\nYou too of course, for motivating me.");
388 println!("\nI thank you all for your help.\n\n\t-- Edward Neville");
389}
390
391pub fn common_opt_arguments(
393 matches: &Matches,
394 opts: &Options,
395 ro: &mut RunOptions,
396 service: &str,
397 header: &str,
398) {
399 ro.new_args.clone_from(&matches.free);
400
401 if matches.opt_present("r") {
402 ro.reason = Some(matches.opt_str("r").unwrap());
403 }
404 if matches.opt_present("t") {
405 ro.target = matches.opt_str("t").unwrap();
406 }
407 if matches.opt_present("g") {
408 ro.target_group = Some(matches.opt_str("g").unwrap());
409 }
410 if matches.opt_present("u") {
411 ro.target = matches.opt_str("u").unwrap();
412 }
413
414 if matches.opt_str("u").is_some()
415 && matches.opt_str("t").is_some()
416 && matches.opt_str("t").unwrap() != matches.opt_str("u").unwrap()
417 {
418 println!("Cannot use -t and -u with conflicting values");
419 print_usage(opts, header);
420 std::process::exit(1);
421 }
422
423 if matches.opt_present("p") {
424 ro.purge_token = true;
425 }
426 if matches.opt_present("v") {
427 print_version(service);
428 std::process::exit(0);
429 }
430 if matches.opt_present("w") {
431 ro.warm_token = true;
432 }
433
434 if matches.opt_present("n") {
435 ro.prompt = false;
436 }
437
438 if matches.opt_present("h") {
439 if ro.new_args == ["credits"] {
440 credits(service);
441 std::process::exit(0);
442 }
443
444 print_usage(opts, header);
445 print_version(service);
446 std::process::exit(0);
447 }
448
449 if ro.purge_token {
450 if !esc_privs() {
451 std::process::exit(1);
452 }
453 remove_token(&ro.name);
454 if !drop_privs(ro) {
455 std::process::exit(1);
456 }
457 std::process::exit(0);
458 }
459
460 if ro.warm_token {
461 if ro.prompt {
462 challenge_password(ro, &EnvOptions::new(), service);
463 }
464 std::process::exit(0);
465 }
466
467 ro.hostname = gethostname()
468 .expect("Failed getting hostname")
469 .into_string()
470 .expect("Hostname wasn't valid UTF-8");
471}
472
473pub fn read_ini(
475 conf: &str,
476 vec_eo: &mut Vec<EnvOptions>,
477 ro: &RunOptions,
478 fail_error: bool,
479 config_path: &str,
480 bytes: &mut u64,
481 ini_list: &mut HashMap<String, bool>,
482) -> bool {
483 let parse_datetime_from_str = NaiveDateTime::parse_from_str;
484 let parse_date_from_str = NaiveDate::parse_from_str;
485 let mut faulty = false;
486 let mut section = String::from("no section defined");
487 let mut in_section = false;
488 let mut opt = EnvOptions::new();
489
490 if ini_list.contains_key(config_path) {
491 println!("Error parsing already read file {}", config_path);
492 return false;
493 }
494
495 ini_list.insert(config_path.to_string(), true);
496
497 for (mut line_number, l) in conf.split('\n').enumerate() {
498 line_number += 1;
499 let line = l.trim();
500
501 if line.is_empty() || line.starts_with('#') {
502 continue;
503 }
504
505 if line.starts_with('[') && line.ends_with(']') {
506 in_section = true;
507 section = line[1..line.len() - 1].to_string();
508 if opt.configured {
509 vec_eo.push(opt);
510 }
511 opt = EnvOptions::new();
512 opt.section.clone_from(§ion);
513 opt.file_name = String::from(config_path);
514 continue;
515 }
516
517 let equals_pos = line.find('=');
518 if equals_pos.is_none() {
519 continue;
520 }
521
522 let key = line[0..equals_pos.unwrap()].trim();
523 let value = line[equals_pos.unwrap() + 1..].trim();
524
525 if !in_section {
526 println!("Error parsing {}:{}", config_path, line_number);
527 faulty = true;
528 continue;
529 }
530
531 if key.starts_with("env_assign.") {
534 let period_pos = key.find('.');
535 let env_name = key[period_pos.unwrap() + 1..].trim();
536 if !value.is_empty() {
537 if opt.clone().env_assign.is_none() {
538 opt.env_assign = Some(HashMap::new());
539 }
540 opt.env_assign
541 .as_mut()
542 .unwrap()
543 .entry(env_name.to_string())
544 .or_insert_with(|| value.to_string());
545 }
546 continue;
547 }
548
549 match key {
550 "include" => {
551 if !value.starts_with('/') {
552 println!("Includes should start with /");
553 return true;
554 }
555 if read_ini_config_file(value, vec_eo, ro, fail_error, bytes, ini_list) {
556 println!("Could not include file");
557 return true;
558 }
559 continue;
560 }
561 "includedir" => {
562 if !value.starts_with('/') {
563 println!("Includes should start with /");
564 return true;
565 }
566 match fs::read_dir(value) {
567 Err(_x) => {
568 faulty = true;
569 }
570 Ok(inc) => {
571 let mut collect = vec![];
572 for file in inc {
573 collect.push(file.unwrap().path().to_str().unwrap().to_string());
574 }
575 collect.sort();
576 for file in collect {
577 if !can_dir_include(&file) {
578 continue;
579 }
580 if read_ini_config_file(&file, vec_eo, ro, fail_error, bytes, ini_list)
581 {
582 println!("Could not include file");
583 return true;
584 }
585 }
586 }
587 }
588
589 continue;
590 }
591 "name" => {
592 opt.name = Some(value.to_string());
593 opt.configured = true;
594 if fail_error
595 && regex_build(value, ro, config_path, §ion, Some(line_number as i32))
596 .is_none()
597 {
598 faulty = true;
599 }
600 }
601 "exact_name" => {
602 opt.exact_name = Some(value.to_string());
603 opt.configured = true;
604 }
605 "hostname" => {
606 opt.hostname = Some(value.to_string());
607 opt.configured = true;
608 if fail_error
609 && regex_build(value, ro, config_path, §ion, Some(line_number as i32))
610 .is_none()
611 {
612 faulty = true;
613 }
614 }
615 "exact_hostname" => {
616 opt.exact_hostname = Some(value.to_string());
617 opt.configured = true;
618 }
619 "target" => {
620 opt.target = Some(value.to_string());
621 if fail_error
622 && regex_build(value, ro, config_path, §ion, Some(line_number as i32))
623 .is_none()
624 {
625 faulty = true;
626 }
627 }
628 "exact_target" => {
629 opt.exact_target = Some(value.to_string());
630 }
631 "target_group" => {
632 opt.target_group = Some(value.to_string());
633 if fail_error
634 && regex_build(value, ro, config_path, §ion, Some(line_number as i32))
635 .is_none()
636 {
637 faulty = true;
638 }
639 }
640 "exact_target_group" => {
641 opt.exact_target_group = Some(value.to_string());
642 }
643 "permit" => opt.permit = Some(value == "true"),
644 "require_pass" => opt.require_pass = Some(value != "false"),
645 "type" => match value.to_lowercase().as_str() {
646 "edit" => opt.acl_type = Acltype::Edit,
647 "list" => opt.acl_type = Acltype::List,
648 _ => opt.acl_type = Acltype::Run,
649 },
650 "group" => opt.group = value == "true",
651 "regex" | "rule" => {
652 opt.rule = Some(value.to_string());
653 if fail_error
654 && regex_build(value, ro, config_path, §ion, Some(line_number as i32))
655 .is_none()
656 {
657 faulty = true;
658 }
659 }
660 "exact_regex" | "exact_rule" => {
661 opt.exact_rule = Some(value.to_string());
662 opt.configured = true;
663 }
664 "notbefore" if value.len() == 8 => {
665 opt.notbefore = Some(
666 parse_date_from_str(value, "%Y%m%d")
667 .unwrap()
668 .and_hms_opt(0, 0, 0)
669 .unwrap(),
670 )
671 }
672 "notafter" if value.len() == 8 => {
673 opt.notafter = Some(
674 parse_date_from_str(value, "%Y%m%d")
675 .unwrap()
676 .and_hms_opt(23, 59, 59)
677 .unwrap(),
678 )
679 }
680 "notbefore" if value.len() == 14 => {
681 opt.notbefore = Some(parse_datetime_from_str(value, "%Y%m%d%H%M%S").unwrap())
682 }
683 "notafter" if value.len() == 14 => {
684 opt.notafter = Some(parse_datetime_from_str(value, "%Y%m%d%H%M%S").unwrap())
685 }
686 "datematch" => {
687 opt.datematch = Some(value.to_string());
688 if fail_error
689 && regex_build(value, ro, config_path, §ion, Some(line_number as i32))
690 .is_none()
691 {
692 faulty = true;
693 }
694 }
695 "dir" => {
696 opt.dir = Some(value.to_string());
697 if fail_error
698 && regex_build(value, ro, config_path, §ion, Some(line_number as i32))
699 .is_none()
700 {
701 faulty = true;
702 }
703 }
704 "exact_dir" => {
705 opt.exact_dir = Some(value.to_string());
706 if fail_error
707 && regex_build(value, ro, config_path, §ion, Some(line_number as i32))
708 .is_none()
709 {
710 faulty = true;
711 }
712 }
713 "permit_env" => {
714 if !value.is_empty() {
715 opt.env_permit = Some(value.to_string());
716 }
717 }
718 "exitcmd" => {
719 if !value.is_empty() {
720 opt.exitcmd = Some(value.to_string());
721 }
722 }
723 "editmode" => {
724 if !value.is_empty() {
725 if value.parse::<i16>().is_ok() {
726 opt.edit_mode = Some(EditMode::Mode(
727 i32::from_str_radix(value.trim_start_matches('0'), 8)
728 .expect("unable to parse editmode"),
729 ));
730 } else if value.to_lowercase() == "keep" {
731 opt.edit_mode = Some(EditMode::Keep(true));
732 } else {
733 println!("Could not convert {} to numerical file mode", value);
734 faulty = true;
735 }
736 }
737 }
738 "reason" => {
739 if value == "true" || value == "false" {
740 opt.reason = Some(ReasonType::Need(value == "true"));
741 } else {
742 opt.reason = Some(ReasonType::Text(value.to_string()));
743 }
744 }
745 "last" => opt.last = Some(value == "true"),
746 "syslog" => opt.syslog = Some(value == "true"),
747 "timeout" => {
748 let timeout: Result<u32, core::num::ParseIntError> = value.parse();
749 if fail_error && timeout.is_err() {
750 faulty = true;
751 } else {
752 opt.timeout = Some(timeout.unwrap());
753 }
754 }
755 "search_path" => {
756 opt.search_path = Some(value.to_string());
757 }
758 "token_timeout" => {
759 let token_timeout: Result<u64, core::num::ParseIntError> = value.parse();
760 if fail_error && token_timeout.is_err() {
761 faulty = true;
762 } else {
763 opt.token_timeout = Some(token_timeout.unwrap());
764 }
765 }
766 &_ => {
767 println!("Error parsing {}:{}", config_path, line_number);
768 faulty = true;
769 }
770 }
771 }
772
773 if opt.configured {
774 vec_eo.push(opt);
775 }
776
777 fail_error && faulty
778}
779
780pub fn read_ini_config_file(
783 config_path: &str,
784 vec_eo: &mut Vec<EnvOptions>,
785 ro: &RunOptions,
786 fail_error: bool,
787 bytes: &mut u64,
788 ini_list: &mut HashMap<String, bool>,
789) -> bool {
790 let path = Path::new(config_path);
791 let display = path.display();
792
793 let file = match File::open(path) {
794 Err(why) => {
795 println!("Could not open {}: {}", display, why);
796 return true;
797 }
798 Ok(file) => file,
799 };
800
801 match nix::sys::stat::fstat(file.as_raw_fd()) {
802 Err(why) => {
803 println!("Could not stat {}: {}", display, why);
804 return true;
805 }
806 Ok(stat_data) => {
807 if stat_data.st_mode & libc::S_IFREG != libc::S_IFREG {
808 println!("Refusing to open non-regular file");
809 return true;
810 }
811
812 if (stat_data.st_mode & !libc::S_IFMT) & (0o022) != 0 {
813 println!("Refusing to parse file as group or other write permission bits are set");
814 return true;
815 }
816 }
817 }
818
819 let byte_limit = 1024 * 1024 * 10;
820
821 if *bytes >= byte_limit {
822 println!("Exiting as too much config has already been read.");
823 std::process::exit(1);
824 }
825
826 let mut s = String::new();
827 let reader = BufReader::new(file).take(byte_limit).read_to_string(&mut s);
828
829 match reader {
830 Ok(n) => {
831 *bytes += s.as_bytes().len() as u64;
832 if n >= byte_limit as usize {
833 println!("Exiting as too much config has already been read.");
834 std::process::exit(1);
835 }
836 }
837 Err(why) => {
838 println!("Could not read {}: {}", display, why);
839 return true;
840 }
841 }
842
843 read_ini(&s, vec_eo, ro, fail_error, config_path, bytes, ini_list)
844}
845
846pub fn read_ini_config_str(
847 config: &str,
848 vec_eo: &mut Vec<EnvOptions>,
849 ro: &RunOptions,
850 fail_error: bool,
851 bytes: &mut u64,
852 ini_list: &mut HashMap<String, bool>,
853) -> bool {
854 read_ini(config, vec_eo, ro, fail_error, "static", bytes, ini_list)
855}
856
857pub fn hostname_ok(item: &EnvOptions, ro: &RunOptions, line: Option<i32>) -> bool {
859 if item.exact_hostname.is_some() {
860 let hostname = item.exact_hostname.as_ref().unwrap();
861
862 if hostname != &ro.hostname
863 && hostname.ne(&"any".to_string())
864 && hostname.ne(&"localhost".to_string())
865 {
866 return false;
868 }
869 return true;
870 }
871
872 if item.hostname.is_some() {
873 let hostname_re = match regex_build(
874 item.hostname.as_ref().unwrap(),
875 ro,
876 &item.file_name,
877 &item.section,
878 line,
879 ) {
880 Some(check) => check,
881 None => {
882 println!("Could not compile {}", &item.hostname.as_ref().unwrap());
883 return false;
884 }
885 };
886
887 if !hostname_re.is_match(&ro.hostname)
888 && !hostname_re.is_match("any")
889 && !hostname_re.is_match("localhost")
890 {
891 return false;
893 }
894 }
895 true
896}
897
898pub fn target_ok(item: &EnvOptions, ro: &RunOptions, line: Option<i32>) -> bool {
899 if item.exact_target.is_some() {
900 let exact_target = item.exact_target.as_ref().unwrap();
901 if exact_target == &ro.target {
902 return true;
903 }
904
905 return false;
907 }
908
909 if item.target.is_some() {
910 let target_re = match regex_build(
911 item.target.as_ref().unwrap(),
912 ro,
913 &item.file_name,
914 &item.section,
915 line,
916 ) {
917 Some(check) => check,
918 None => {
919 println!("Could not compile {}", &item.target.as_ref().unwrap());
920 return false;
921 }
922 };
923
924 if target_re.is_match(&ro.target) {
925 return true;
926 }
927 return false;
928 }
929 false
930}
931
932pub fn target_group_ok(item: &EnvOptions, ro: &RunOptions, line: Option<i32>) -> bool {
933 if (item.target_group.is_some() || item.exact_target_group.is_some())
934 && ro.target_group.is_none()
935 {
936 return false;
938 }
939
940 if ro.target_group.is_none() {
941 return true;
943 }
944
945 if item.exact_target_group.is_some() {
946 let exact_target_group = item.exact_target_group.as_ref().unwrap();
947 if exact_target_group == ro.target_group.as_ref().unwrap() {
948 return true;
949 }
950
951 return false;
953 }
954
955 if item.target_group.is_some() {
956 let target_group_re = match regex_build(
957 item.target_group.as_ref().unwrap(),
958 ro,
959 &item.file_name,
960 &item.section,
961 line,
962 ) {
963 Some(check) => check,
964 None => {
965 println!("Could not compile {}", &item.target_group.as_ref().unwrap());
966 return false;
967 }
968 };
969
970 if target_group_re.is_match(ro.target_group.as_ref().unwrap()) {
971 return true;
972 }
973 return false;
974 }
975 false
976}
977
978pub fn rule_match(item: &EnvOptions, ro: &RunOptions, line: Option<i32>) -> bool {
979 if item.exact_rule.is_some() {
980 let exact_rule = item.exact_rule.as_ref().unwrap();
981 if exact_rule == &ro.command {
982 return true;
984 }
985 return false;
987 }
988
989 if item.rule.is_some() {
990 let rule_re = match regex_build(
991 item.rule.as_ref().unwrap(),
992 ro,
993 &item.file_name,
994 &item.section,
995 line,
996 ) {
997 Some(check) => check,
998 None => {
999 println!("Could not compile {}", &item.rule.as_ref().unwrap());
1000 return false;
1001 }
1002 };
1003
1004 if rule_re.is_match(&ro.command) {
1005 return true;
1008 }
1009 return false;
1011 }
1012 false
1013}
1014
1015pub fn directory_check_ok(item: &EnvOptions, ro: &RunOptions, line: Option<i32>) -> bool {
1017 if item.exact_dir.is_some() {
1018 if ro.directory.as_ref().is_none() {
1019 return false;
1020 }
1021
1022 let exact_dir = item.exact_dir.as_ref().unwrap();
1023
1024 if (ro.directory.as_ref()).is_some() && exact_dir != ro.directory.as_ref().unwrap() {
1025 return false;
1026 }
1027 return true;
1028 }
1029
1030 if item.dir.is_some() {
1031 if ro.directory.as_ref().is_none() {
1032 return false;
1033 }
1034
1035 let dir_re = match regex_build(
1036 item.dir.as_ref().unwrap(),
1037 ro,
1038 &item.file_name,
1039 &item.section,
1040 line,
1041 ) {
1042 Some(check) => check,
1043 None => {
1044 println!("Could not compile {}", &item.dir.as_ref().unwrap());
1045 return false;
1046 }
1047 };
1048
1049 if (ro.directory.as_ref()).is_some() && !dir_re.is_match(ro.directory.as_ref().unwrap()) {
1050 return false;
1052 }
1053 return true;
1054 }
1055 if ro.directory.is_some() {
1056 return false;
1057 }
1058 true
1059}
1060
1061pub fn environment_ok(item: &EnvOptions, ro: &RunOptions, line: Option<i32>) -> bool {
1063 if ro.allow_env_list.is_none() {
1064 return true;
1066 }
1067
1068 if item.env_permit.is_none() && ro.allow_env_list.is_some() {
1069 return false;
1071 }
1072
1073 let env_re = match regex_build(
1074 item.env_permit.as_ref().unwrap(),
1075 ro,
1076 &item.file_name,
1077 &item.section,
1078 line,
1079 ) {
1080 Some(check) => check,
1081 None => {
1082 println!("Could not compile {}", &item.env_permit.as_ref().unwrap());
1083 return false;
1084 }
1085 };
1086
1087 for permit_env in ro.allow_env_list.as_ref().unwrap() {
1088 if !env_re.is_match(permit_env) {
1090 return false;
1092 }
1093 }
1094
1095 true
1096}
1097
1098pub fn permitted_dates_ok(item: &EnvOptions, ro: &RunOptions, line: Option<i32>) -> bool {
1100 if item.notbefore.is_some() && item.notbefore.unwrap() > ro.date {
1101 return false;
1103 }
1104
1105 if item.notafter.is_some() && item.notafter.unwrap() < ro.date {
1106 return false;
1108 }
1109
1110 if item.datematch.is_some() {
1111 let datematch_re = match regex_build(
1112 item.datematch.as_ref().unwrap(),
1113 ro,
1114 &item.file_name,
1115 &item.section,
1116 line,
1117 ) {
1118 Some(check) => check,
1119 None => {
1120 println!("Could not compile {}", &item.datematch.as_ref().unwrap());
1121 return false;
1122 }
1123 };
1124
1125 if !datematch_re.is_match(&ro.date.format("%a %e %b %T UTC %Y").to_string()) {
1126 return false;
1128 }
1129 }
1130 true
1131}
1132
1133pub fn name_matches(item: &EnvOptions, ro: &RunOptions, line: Option<i32>) -> bool {
1134 if item.exact_name.is_some() {
1135 let name = item.exact_name.as_ref().unwrap();
1136 if name == &ro.name {
1137 return true;
1138 }
1139 return false;
1141 }
1142
1143 if item.name.is_some() {
1144 let name_re = match regex_build(
1145 item.name.as_ref().unwrap(),
1146 ro,
1147 &item.file_name,
1148 &item.section,
1149 line,
1150 ) {
1151 Some(check) => check,
1152 None => {
1153 println!("Could not compile {}", &item.name.as_ref().unwrap());
1154 return false;
1155 }
1156 };
1157
1158 if name_re.is_match(&ro.name) {
1159 return true;
1161 }
1162
1163 return false;
1164 }
1165 false
1166}
1167
1168pub fn group_matches(item: &EnvOptions, ro: &RunOptions, line: Option<i32>) -> bool {
1169 if item.exact_name.is_some() {
1170 let name = item.exact_name.as_ref().unwrap();
1171 for (k, _) in ro.groups.iter() {
1172 if name == k {
1173 return true;
1175 }
1176 }
1177 return false;
1179 }
1180
1181 if item.name.is_some() {
1182 let name_re = match regex_build(
1183 item.name.as_ref().unwrap(),
1184 ro,
1185 &item.file_name,
1186 &item.section,
1187 line,
1188 ) {
1189 Some(check) => check,
1190 None => {
1191 println!("Could not compile {}", &item.name.as_ref().unwrap());
1192 return false;
1193 }
1194 };
1195
1196 for (k, _) in ro.groups.iter() {
1197 if name_re.is_match(k) {
1198 return true;
1200 }
1201 }
1202 return false;
1203 }
1204
1205 false
1206}
1207
1208pub fn matching(item: &EnvOptions, ro: &mut RunOptions, line_error: Option<i32>) -> bool {
1209 if !permitted_dates_ok(item, ro, line_error) {
1210 return false;
1212 }
1213
1214 if !item.group && !name_matches(item, ro, line_error) {
1215 return false;
1217 }
1218
1219 if item.group && !group_matches(item, ro, line_error) {
1220 return false;
1222 }
1223
1224 if !hostname_ok(item, ro, line_error) {
1225 return false;
1227 }
1228
1229 if !directory_check_ok(item, ro, line_error) {
1230 return false;
1232 }
1233
1234 if !environment_ok(item, ro, line_error) {
1235 return false;
1237 }
1238
1239 if !target_ok(item, ro, line_error) {
1240 return false;
1242 }
1243
1244 if !target_group_ok(item, ro, line_error) {
1245 return false;
1247 }
1248
1249 if item.acl_type == Acltype::List {
1250 return true;
1252 }
1253
1254 if ro.cloned_args.is_some() {
1261 ro.cloned_args = None;
1262 }
1263 ro.command = replace_new_args(ro.new_args.clone());
1264
1265 if item.acl_type == Acltype::Run {
1266 match search_path(ro, item) {
1267 None => {
1268 return false;
1269 }
1270 Some(x) => {
1271 ro.cloned_args = Some(ro.new_args.clone());
1272 ro.cloned_args.as_mut().unwrap()[0] = x;
1273 ro.command = replace_new_args(ro.cloned_args.as_ref().unwrap().clone());
1274 }
1275 }
1276 }
1277 if item.acl_type == Acltype::Edit {
1278 let edit_file = vec![ro.new_args[0].clone()];
1279 ro.command = replace_new_args(edit_file);
1280 }
1281
1282 rule_match(item, ro, line_error)
1283}
1284
1285pub fn merge_default(default: &EnvOptions, item: &EnvOptions) -> EnvOptions {
1286 let mut merged = item.clone();
1287
1288 if default.syslog.is_some() && item.syslog.is_none() {
1289 merged.syslog = default.syslog;
1291 }
1292
1293 if default.reason.is_some() && item.reason.is_none() {
1294 merged.reason.clone_from(&default.reason);
1296 }
1297
1298 if default.require_pass.is_some() && item.require_pass.is_none() {
1299 merged.require_pass = default.require_pass;
1301 }
1302
1303 if default.last.is_some() && item.last.is_none() {
1304 merged.last = default.last;
1306 }
1307
1308 if default.exitcmd.is_some() && item.exitcmd.is_none() {
1309 merged.exitcmd.clone_from(&default.exitcmd);
1311 }
1312
1313 if default.edit_mode.is_some() && item.edit_mode.is_none() {
1314 merged.edit_mode.clone_from(&default.edit_mode);
1316 }
1317
1318 if default.timeout.is_some() && item.timeout.is_none() {
1319 merged.timeout = default.timeout;
1321 }
1322
1323 if default.env_permit.is_some() && item.env_permit.is_none() {
1324 merged.env_permit.clone_from(&default.env_permit);
1326 }
1327
1328 if default.env_assign.is_some() && item.env_assign.is_none() {
1329 merged.env_assign.clone_from(&default.env_assign);
1331 }
1332
1333 if default.permit.is_some() && item.permit.is_none() {
1334 merged.permit = default.permit;
1336 }
1337
1338 if default.search_path.is_some() && item.search_path.is_none() {
1339 merged.search_path.clone_from(&default.search_path);
1341 }
1342
1343 if default.token_timeout.is_some() && item.token_timeout.is_none() {
1344 merged.token_timeout = default.token_timeout;
1346 }
1347
1348 merged
1349}
1350
1351pub fn can(vec_eo: &[EnvOptions], ro: &mut RunOptions) -> EnvOptions {
1353 let mut opt = EnvOptions::new_deny();
1354 let mut default = EnvOptions::new();
1355
1356 for item in vec_eo {
1357 if item.acl_type != ro.acl_type {
1359 continue;
1361 }
1362
1363 if !matching(item, ro, None) {
1364 continue;
1366 }
1367
1368 if item.section.starts_with("default") {
1369 default = merge_default(&default, item);
1370 }
1371
1372 opt = merge_default(&default, item);
1373
1374 match opt.last {
1375 None => {}
1376 Some(last) => {
1377 if last {
1378 break;
1379 }
1380 }
1381 }
1382 }
1384 opt
1385}
1386
1387pub fn reason_ok(item: &EnvOptions, ro: &RunOptions) -> bool {
1389 if item.reason.is_none() {
1390 return true;
1391 }
1392
1393 match &item.reason.as_ref().unwrap() {
1394 ReasonType::Text(value) => {
1395 let m_re = match regex_build(value, ro, &item.file_name, &item.section, None) {
1396 Some(check) => check,
1397 None => {
1398 println!("Could not compile {}", &value);
1399 return false;
1400 }
1401 };
1402
1403 if ro.reason.is_some() && m_re.is_match(ro.reason.as_ref().unwrap()) {
1404 return true;
1405 }
1406
1407 println!(
1408 "Sorry but there is no reason match to {} \"{}\" on {} as {}",
1409 &ro.acl_type, &ro.command, &ro.hostname, &ro.target
1410 );
1411
1412 false
1413 }
1414 ReasonType::Need(value) => {
1415 if value == &true && ro.reason.is_none() {
1416 println!(
1417 "Sorry but no reason was given to {} \"{}\" on {} as {}",
1418 &ro.acl_type,
1419 if ro.acl_type == Acltype::List {
1420 &ro.target
1421 } else {
1422 &ro.command
1423 },
1424 &ro.hostname,
1425 &ro.target
1426 );
1427 return false;
1428 }
1429 true
1430 }
1431 }
1432}
1433
1434pub fn get_editor() -> String {
1436 let editor = "/usr/bin/vi";
1437
1438 for prog in [String::from("VISUAL"), String::from("EDITOR")].iter() {
1439 if let Ok(val) = std::env::var(prog) {
1440 return val;
1441 }
1442 }
1443
1444 editor.to_string()
1445}
1446
1447#[cfg(target_os = "linux")]
1449pub fn handler_shim<T: pam::Converse>(
1450 _ro: &RunOptions,
1451 handler: &mut Authenticator<T>,
1452) -> Result<(), pam::PamError> {
1453 handler.authenticate()
1454}
1455
1456#[cfg(not(target_os = "linux"))]
1458pub fn handler_shim<T: pam::Converse>(
1459 ro: &RunOptions,
1460 handler: &mut Authenticator<T>,
1461) -> Result<(), pam::PamError> {
1462 if !esc_privs() {
1463 std::process::exit(1);
1464 }
1465 let auth = handler.authenticate();
1466 if !drop_privs(&ro) {
1467 std::process::exit(1);
1468 }
1469 auth
1470}
1471
1472pub fn challenge_password(ro: &RunOptions, entry: &EnvOptions, service: &str) -> bool {
1475 if entry.require_pass() {
1476 if tty_name().is_none() {
1477 println!("Cannot read password without tty");
1478 return false;
1479 }
1480
1481 let mut retry_counter = 0;
1482
1483 if !esc_privs() {
1484 std::process::exit(1);
1485 }
1486
1487 if valid_token(&ro.name, entry) {
1488 update_token(&ro.name);
1489 return true;
1490 }
1491
1492 if !drop_privs(ro) {
1493 std::process::exit(1);
1494 }
1495
1496 if !ro.prompt {
1497 return false;
1498 }
1499
1500 let convo = PamConvo {
1501 login: ro.name.to_string(),
1502 passwd: None,
1503 service: service.to_string(),
1504 };
1505
1506 if entry.timeout.is_some() {
1507 extern "C" fn alarm_signal_handler(_: nix::libc::c_int) {
1508 println!("Timed out getting password");
1509
1510 let tty = std::fs::File::open("/dev/tty");
1511 if tty.is_ok() {
1512 let term_res = nix::sys::termios::tcgetattr(tty.as_ref().unwrap());
1513 if let Ok(mut term) = term_res {
1514 term.local_flags
1515 .set(nix::sys::termios::LocalFlags::ECHO, true);
1516
1517 let res = nix::sys::termios::tcsetattr(
1518 tty.as_ref().unwrap(),
1519 nix::sys::termios::SetArg::TCSANOW,
1520 &term,
1521 );
1522 if res.is_err() {
1523 println!("Couldn't return terminal to original settings");
1524 }
1525 }
1526 }
1527
1528 std::process::exit(1);
1529 }
1530
1531 let sa = SigAction::new(
1532 SigHandler::Handler(alarm_signal_handler),
1533 SaFlags::SA_RESTART,
1534 SigSet::empty(),
1535 );
1536
1537 unsafe {
1538 match sigaction(Signal::SIGALRM, &sa) {
1539 Ok(_) => {}
1540 Err(_) => {
1541 println!("Couldn't reset alarm");
1542 std::process::exit(1);
1543 }
1544 }
1545 }
1546 }
1547
1548 let mut handler = Authenticator::with_handler(service, convo).expect("Cannot init PAM");
1549
1550 loop {
1551 if let Some(timeout) = entry.timeout {
1552 alarm::set(timeout);
1553 }
1554
1555 let auth = handler_shim(ro, &mut handler);
1556
1557 if entry.timeout.is_some() {
1558 alarm::cancel();
1559 }
1560
1561 if auth.is_ok() {
1562 if handler.get_handler().passwd.is_some() {
1563 unsafe { signal::signal(signal::SIGALRM, signal::SigHandler::SigDfl).unwrap() };
1564 if !esc_privs() {
1565 std::process::exit(1);
1566 }
1567
1568 update_token(&ro.name);
1569
1570 if !drop_privs(ro) {
1571 std::process::exit(1);
1572 }
1573 }
1574 return true;
1575 }
1576 retry_counter += 1;
1577 if retry_counter == 3 {
1578 println!("Authentication failed :-(");
1579
1580 return false;
1581 }
1582 }
1583 }
1584 true
1585}
1586
1587pub fn list_rule(eo: &EnvOptions) -> String {
1589 if eo.exact_rule.is_some() {
1590 return format!("exact({})", eo.exact_rule.as_ref().unwrap());
1591 }
1592 if eo.rule.is_some() {
1593 return eo.rule.as_ref().unwrap().to_string();
1594 }
1595 "".to_string()
1596}
1597
1598pub fn list_target(eo: &EnvOptions) -> String {
1600 if eo.exact_target.is_some() {
1601 return format!("exact({})", eo.exact_target.as_ref().unwrap());
1602 }
1603 if eo.target.is_some() {
1604 return eo.target.as_ref().unwrap().to_string();
1605 }
1606 "".to_string()
1607}
1608
1609pub fn list_dir(eo: &EnvOptions) -> String {
1611 if eo.exact_dir.is_some() {
1612 return format!("exact({})", eo.exact_dir.as_ref().unwrap());
1613 }
1614 if eo.dir.is_some() {
1615 return eo.dir.as_ref().unwrap().to_string();
1616 }
1617 "".to_string()
1618}
1619
1620pub fn list(vec_eo: &[EnvOptions], ro: &RunOptions) {
1622 for s in produce_list(vec_eo, ro) {
1624 println!("{}", s);
1625 }
1626}
1627
1628pub fn produce_list(vec_eo: &[EnvOptions], ro: &RunOptions) -> Vec<String> {
1630 let mut str_list = vec![];
1631 let mut ro = ro.clone();
1632
1633 if !ro.target.is_empty() {
1634 ro.name.clone_from(&ro.target);
1635 }
1636
1637 let mut last_file = "";
1638
1639 for item in vec_eo {
1640 if !item.group && !name_matches(item, &ro, None) {
1641 continue;
1642 }
1643
1644 if item.group && !group_matches(item, &ro, None) {
1645 continue;
1646 }
1647
1648 let mut prefixes = vec![];
1649 if item.notbefore.is_some() && item.notbefore.unwrap() > ro.date {
1650 prefixes.push(format!("upcomming({})", item.notbefore.unwrap()));
1651 }
1652
1653 if item.notafter.is_some() && item.notafter.unwrap() < ro.date {
1654 prefixes.push(format!("expired({})", item.notafter.unwrap()));
1655 }
1656
1657 match &item.reason {
1658 Some(r) => {
1659 if *r != ReasonType::Need(false) {
1660 prefixes.push(String::from("reason_required"));
1661 }
1662 }
1663 None => {}
1664 }
1665
1666 if item.acl_type != ro.acl_type {
1667 continue;
1668 }
1669
1670 if !item.permit() {
1671 prefixes.push(String::from("not permitted"));
1672 }
1673
1674 if !hostname_ok(item, &ro, None) {
1675 continue;
1676 }
1677
1678 if item.last.is_some() && item.last.unwrap() {
1679 prefixes.push(String::from("last"));
1680 }
1681
1682 let mut prefix = prefixes.join(", ");
1683 if !prefix.is_empty() {
1684 if item.acl_type != Acltype::List {
1685 prefix = format!(" {} as ", prefix);
1686 } else {
1687 prefix = format!(" {} to ", prefix);
1688 }
1689 }
1690 if last_file != item.file_name {
1691 str_list.push(format!(" in file: {}", item.file_name));
1692 last_file = &item.file_name;
1693 }
1694
1695 if item.acl_type == Acltype::List {
1696 str_list.push(format!(
1697 " {}:{}list: {}",
1698 item.section,
1699 prefix,
1700 item.target.as_ref().unwrap()
1701 ));
1702 continue;
1703 }
1704
1705 str_list.push(format!(
1706 " {}:{}{} (pass={},dirs={}): {}",
1707 item.section,
1708 prefix,
1709 list_target(item),
1710 item.require_pass(),
1711 list_dir(item),
1712 list_rule(item)
1713 ));
1714 }
1715 str_list
1716}
1717
1718pub fn search_path_cache(ro: &RunOptions, binary: &str) -> Option<String> {
1720 match ro.located_bin.get(binary) {
1721 Some(k) => {
1722 if k.is_none() {
1723 return None;
1725 }
1726 return Some(k.as_ref().unwrap().to_string());
1728 }
1729 None => {
1730 None
1732 }
1733 }
1734}
1735
1736pub fn search_path(ro: &mut RunOptions, item: &EnvOptions) -> Option<String> {
1738 let binary = &ro.new_args[0];
1739 let p = Path::new(&binary);
1740 if binary.starts_with('/') || binary.starts_with("./") {
1743 let lookup = search_path_cache(ro, binary);
1744 if lookup.is_some() {
1745 return lookup;
1746 }
1747
1748 if !p.exists() {
1749 ro.located_bin.insert(binary.to_string(), None);
1750 return None;
1751 } else {
1752 ro.located_bin
1753 .insert(binary.to_string(), Some(binary.to_string()));
1754 return Some(binary.to_string());
1755 }
1756 }
1757
1758 let dirs = if item.search_path.is_some() {
1759 item.search_path.as_ref().unwrap()
1760 } else {
1761 "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
1762 };
1763
1764 for dir in dirs.split(':') {
1765 if dir.trim() == "" {
1766 continue;
1767 }
1768 let dir = dir.trim_end_matches('/');
1769 let path_name = format!("{}/{}", &dir, &binary);
1770
1771 if let Some(lookup) = search_path_cache(ro, binary) {
1772 return Some(lookup);
1773 }
1774
1775 let p = Path::new(&path_name);
1776
1777 if !p.exists() {
1778 ro.located_bin.insert(binary.to_string(), None);
1779 continue;
1780 }
1781
1782 ro.located_bin
1784 .insert(binary.to_string(), Some(path_name.clone()));
1785 return Some(path_name);
1786 }
1787
1788 None
1789}
1790
1791pub fn clean_environment(ro: &mut RunOptions) {
1793 ro.old_umask = Some(nix::sys::stat::umask(
1794 nix::sys::stat::Mode::from_bits(0o077).unwrap(),
1795 ));
1796
1797 for (key, val) in std::env::vars() {
1798 if ro.acl_type == Acltype::Edit {
1799 if ro.old_envs.is_none() {
1800 ro.old_envs = Some(HashMap::new());
1801 }
1802
1803 ro.old_envs
1804 .as_mut()
1805 .unwrap()
1806 .entry(key.to_string())
1807 .or_insert(val);
1808 }
1809
1810 if key == "LANGUAGE"
1811 || key == "XAUTHORITY"
1812 || key == "LANG"
1813 || key == "LS_COLORS"
1814 || key == "TERM"
1815 || key == "DISPLAY"
1816 || key == "LOGNAME"
1817 {
1818 continue;
1819 }
1820
1821 let mut skip = false;
1822
1823 if ro.allow_env_list.is_some() {
1824 for env in ro.allow_env_list.as_ref().unwrap() {
1825 if key == *env {
1826 skip = true;
1827 break;
1828 }
1829 }
1830 }
1831 if skip {
1832 continue;
1833 }
1834
1835 if ro.acl_type == Acltype::Edit && (key == "EDITOR" || key == "VISUAL") {
1836 continue;
1837 }
1838 std::env::remove_var(key);
1839 }
1840}
1841
1842pub fn set_env_if_not_passed_through(ro: &RunOptions, key: &str, value: &str) {
1844 if ro.allow_env_list.is_some() {
1845 for env in ro.allow_env_list.as_ref().unwrap() {
1846 if key == *env {
1847 return;
1849 }
1850 }
1851 }
1852
1853 std::env::set_var(key, value);
1854}
1855
1856pub fn set_environment(
1858 ro: &RunOptions,
1859 entry: &EnvOptions,
1860 original_user: &User,
1861 original_uid: u32,
1862 lookup_name: &User,
1863) {
1864 std::env::set_var("PLEASE_USER", original_user.name());
1865 std::env::set_var("PLEASE_UID", original_uid.to_string());
1866 std::env::set_var("PLEASE_GID", original_user.primary_group_id().to_string());
1867 std::env::set_var("PLEASE_COMMAND", &ro.command);
1868
1869 std::env::set_var("SUDO_USER", original_user.name());
1870 std::env::set_var("SUDO_UID", original_uid.to_string());
1871 std::env::set_var("SUDO_GID", original_user.primary_group_id().to_string());
1872 std::env::set_var("SUDO_COMMAND", &ro.command);
1873
1874 set_env_if_not_passed_through(
1875 ro,
1876 "PATH",
1877 "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
1878 );
1879
1880 set_env_if_not_passed_through(ro, "HOME", lookup_name.home_dir().to_str().unwrap());
1881 set_env_if_not_passed_through(ro, "MAIL", &format!("/var/mail/{}", ro.target));
1882 set_env_if_not_passed_through(ro, "SHELL", lookup_name.shell().to_str().unwrap());
1883 set_env_if_not_passed_through(ro, "USER", &ro.target);
1884 set_env_if_not_passed_through(ro, "LOGNAME", &ro.target);
1885
1886 if entry.env_assign.is_some() {
1887 for (k, v) in entry.env_assign.as_ref().unwrap() {
1888 std::env::set_var(k, v);
1889 }
1890 }
1891}
1892
1893pub fn bad_priv_msg() {
1894 println!("I cannot set privs. Exiting as not installed correctly.");
1895}
1896
1897pub fn set_privs(user: &str, target_uid: nix::unistd::Uid, target_gid: nix::unistd::Gid) -> bool {
1899 let name = CString::new(user.as_bytes()).unwrap();
1900
1901 unsafe {
1902 if libc::setgroups(0, std::ptr::null()) != 0 {
1903 bad_priv_msg();
1904 return false;
1905 }
1906
1907 #[cfg(not(target_os = "macos"))]
1908 if libc::initgroups(name.as_ptr(), target_gid.as_raw()) != 0 {
1909 bad_priv_msg();
1910 return false;
1911 }
1912
1913 #[cfg(target_os = "macos")]
1914 if libc::initgroups(name.as_ptr(), target_gid.as_raw() as i32) != 0 {
1915 bad_priv_msg();
1916 return false;
1917 }
1918 }
1919
1920 if setgid(target_gid).is_err() {
1921 bad_priv_msg();
1922 return false;
1923 }
1924
1925 if setuid(target_uid).is_err() {
1926 bad_priv_msg();
1927 return false;
1928 }
1929 true
1930}
1931
1932pub fn set_eprivs(target_uid: nix::unistd::Uid, target_gid: nix::unistd::Gid) -> bool {
1934 if setegid(target_gid).is_err() {
1935 bad_priv_msg();
1936 return false;
1937 }
1938 if seteuid(target_uid).is_err() {
1939 bad_priv_msg();
1940 return false;
1941 }
1942
1943 true
1944}
1945
1946pub fn drop_privs(ro: &RunOptions) -> bool {
1948 esc_privs() && set_eprivs(ro.original_uid, ro.original_gid)
1949}
1950
1951pub fn esc_privs() -> bool {
1953 set_eprivs(nix::unistd::Uid::from_raw(0), nix::unistd::Gid::from_raw(0))
1954}
1955
1956pub fn tty_name() -> Option<String> {
1958 let mut ttyname = None;
1959
1960 for n in 0..3 {
1962 let ptr;
1963 unsafe {
1964 ptr = libc::ttyname(n);
1965 }
1966 if ptr.is_null() {
1967 continue;
1968 }
1969
1970 let s;
1971 unsafe {
1972 s = CStr::from_ptr(ptr).to_str();
1973 }
1974 match s {
1975 Err(_x) => ttyname = None,
1976 Ok(x) => ttyname = Some(x.to_string()),
1977 }
1978 break;
1979 }
1980
1981 ttyname
1982}
1983
1984pub fn escape_log(message: &str) -> String {
1986 message.replace('\"', "\\\"")
1987}
1988
1989pub fn log_action(service: &str, result: &str, ro: &RunOptions, command: &str) -> bool {
1991 if !ro.syslog {
1992 return false;
1993 }
1994
1995 let formatter = Formatter3164 {
1996 facility: Facility::LOG_USER,
1997 hostname: None,
1998 process: service.into(),
1999 pid: process::id(),
2000 };
2001
2002 let cwd = match env::current_dir() {
2003 Err(_) => "unable to get cwd".to_string(),
2004 Ok(x) => x.to_string_lossy().to_string(),
2005 };
2006
2007 let matching_env = match &ro.env_options {
2008 Some(env_options) => {
2009 format!("{}:{}", env_options.file_name, env_options.section)
2010 }
2011 None => "".to_string(),
2012 };
2013
2014 match syslog::unix(formatter) {
2015 Err(_e) => println!("Could not connect to syslog"),
2016 Ok(mut writer) => {
2017 let tty_name = tty_name();
2018
2019 writer
2020 .err(format!(
2021 "user=\"{}\" cwd=\"{}\" tty=\"{}\" action=\"{}\" target=\"{}\" type=\"{}\" reason=\"{}\" command=\"{}\" matching_section=\"{}\"",
2022 escape_log( &ro.name ),
2023 escape_log( &cwd ),
2024 if tty_name.is_none() {
2025 "no_tty".to_string()
2026 } else {
2027 tty_name.unwrap()
2028 },
2029 result,
2030 escape_log( &ro.target ),
2031 ro.acl_type,
2032 if ro.reason.as_ref().is_some() {
2033 escape_log( ro.reason.as_ref().unwrap() )
2034 } else {
2035 String::from("")
2036 },
2037 escape_log( command ),
2038 matching_env,
2039 ))
2040 .expect("could not write error message");
2041 }
2042 }
2043 false
2044}
2045
2046pub fn token_dir() -> String {
2048 "/var/run/please/token".to_string()
2049}
2050
2051pub fn token_path(user: &str) -> Option<String> {
2053 let tty_name = tty_name();
2054 tty_name.as_ref()?;
2055 let ppid = nix::unistd::getppid();
2056 Some(format!(
2057 "{}/{}:{}:{}",
2058 token_dir(),
2059 user,
2060 tty_name.unwrap().replace('/', "_"),
2061 ppid
2062 ))
2063}
2064
2065pub fn create_token_dir() -> bool {
2066 if !Path::new(&token_dir()).is_dir() && fs::create_dir_all(token_dir()).is_err() {
2067 println!("Could not create token directory");
2068 return false;
2069 }
2070
2071 true
2072}
2073
2074pub fn boot_secs() -> libc::timespec {
2075 let mut tp = libc::timespec {
2076 tv_sec: 0,
2077 tv_nsec: 0,
2078 };
2079 #[cfg(target_os = "linux")]
2080 unsafe {
2081 libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut tp)
2082 };
2083 #[cfg(not(target_os = "linux"))]
2084 unsafe {
2085 libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut tp)
2086 };
2087 tp
2088}
2089
2090pub fn valid_token(user: &str, eo: &EnvOptions) -> bool {
2094 if !create_token_dir() {
2095 return false;
2096 }
2097
2098 let token_path = token_path(user);
2099 if token_path.is_none() {
2100 return false;
2101 }
2102
2103 let secs = eo.token_timeout.unwrap_or(600);
2104
2105 let token_path = token_path.unwrap();
2106 match fs::metadata(token_path) {
2107 Ok(meta) => {
2108 match meta.modified() {
2109 Ok(t) => {
2110 let tp = boot_secs();
2111
2112 match t.duration_since(SystemTime::UNIX_EPOCH) {
2113 Ok(s) => {
2114 if (tp.tv_sec as u64) < s.as_secs() {
2115 return false;
2117 }
2118 if ((tp.tv_sec as u64) - s.as_secs()) < secs {
2119 match SystemTime::now().duration_since(meta.accessed().unwrap()) {
2122 Ok(a) => return a.as_secs() <= secs,
2123 Err(_) => return false,
2124 }
2125 }
2126 }
2127 Err(_) => {
2128 return false;
2129 }
2130 }
2131
2132 false
2133 }
2134 Err(_e) => false,
2135 }
2136 }
2137 Err(_) => false,
2138 }
2139}
2140
2141pub fn update_token(user: &str) {
2143 if !create_token_dir() {
2144 return;
2145 }
2146
2147 let token_path = token_path(user);
2148 if token_path.is_none() {
2149 return;
2150 }
2151
2152 let old_mode = nix::sys::stat::umask(nix::sys::stat::Mode::from_bits(0o077).unwrap());
2153 let token_path = token_path.unwrap();
2154 let token_path_tmp = format!("{}.tmp", &token_path);
2155 match fs::File::create(&token_path_tmp) {
2156 Ok(_x) => {}
2157 Err(x) => println!("Error creating token: {}", x),
2158 }
2159 nix::sys::stat::umask(old_mode);
2160
2161 let tp = boot_secs();
2162
2163 let tv_mtime = nix::sys::time::TimeVal::from(libc::timeval {
2164 tv_sec: tp.tv_sec,
2165 tv_usec: 0,
2166 });
2167
2168 #[cfg_attr(target_env = "musl", allow(deprecated))]
2170 let tv_atime = nix::sys::time::TimeVal::from(libc::timeval {
2171 tv_sec: SystemTime::now()
2172 .duration_since(SystemTime::UNIX_EPOCH)
2173 .unwrap()
2174 .as_secs() as libc::time_t,
2175 tv_usec: 0,
2176 });
2177
2178 if nix::sys::stat::utimes(Path::new(&token_path_tmp), &tv_atime, &tv_mtime).is_err() {
2179 return;
2180 }
2181
2182 if std::fs::rename(token_path_tmp.as_str(), token_path).is_err() {
2183 println!("Could not update token");
2184 }
2185}
2186
2187pub fn remove_token(user: &str) {
2189 if !create_token_dir() {
2190 return;
2191 }
2192
2193 let token_location = token_path(user);
2194 if token_location.is_none() {
2195 return;
2196 }
2197
2198 let token_location = token_location.unwrap();
2199
2200 let p = Path::new(&token_location);
2201 if p.is_file() {
2202 match fs::remove_file(p) {
2203 Ok(_x) => {}
2204 Err(x) => println!("Error removing token {}: {}", p.to_str().unwrap(), x),
2205 }
2206 }
2207}
2208
2209pub fn group_hash(groups: Vec<Group>) -> HashMap<String, u32> {
2211 let mut hm: HashMap<String, u32> = HashMap::new();
2212 for group in groups {
2213 hm.entry(String::from(group.name().to_string_lossy()))
2214 .or_insert_with(|| group.gid());
2215 }
2216 hm
2217}
2218
2219pub fn replace_new_args(new_args: Vec<String>) -> String {
2222 let mut args = vec![];
2223 for arg in &new_args {
2224 args.push(arg.replace('\\', r"\\").replace(' ', r"\ "));
2225 }
2226
2227 args.join(" ")
2228}
2229
2230pub fn print_version(program: &str) {
2232 println!("{} version {}", &program, env!("CARGO_PKG_VERSION"));
2233}
2234
2235pub fn prng_alpha_num_string(n: usize) -> String {
2237 let rng = thread_rng();
2238 rng.sample_iter(&Alphanumeric)
2239 .take(n)
2240 .map(|x| x as char)
2241 .collect()
2242}
2243
2244pub fn runopt_target_gid(ro: &RunOptions, lookup_name: &uzers::User) -> nix::unistd::Gid {
2245 if ro.target_group.is_some() {
2246 match nix::unistd::Group::from_name(ro.target_group.as_ref().unwrap()) {
2247 Ok(x) => match x {
2248 Some(g) => g.gid,
2249 None => {
2250 println!("Cannot assign group {}", &ro.target_group.as_ref().unwrap());
2251 std::process::exit(1);
2252 }
2253 },
2254 Err(_) => {
2255 println!("Cannot assign group {}", &ro.target_group.as_ref().unwrap());
2256 std::process::exit(1);
2257 }
2258 }
2259 } else {
2260 nix::unistd::Gid::from_raw(lookup_name.primary_group_id())
2261 }
2262}