1use std::collections::BTreeMap;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct ProcessIdentity {
5 pub uid: u32,
6 pub gid: u32,
7 pub euid: u32,
8 pub egid: u32,
9 pub suid: u32,
10 pub sgid: u32,
11 pub supplementary_gids: Vec<u32>,
12}
13
14impl Default for ProcessIdentity {
15 fn default() -> Self {
16 Self {
17 uid: 1000,
18 gid: 1000,
19 euid: 1000,
20 egid: 1000,
21 suid: 1000,
22 sgid: 1000,
23 supplementary_gids: vec![1000],
24 }
25 }
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct UserAccount {
30 pub uid: u32,
31 pub gid: u32,
32 pub username: String,
33 pub homedir: String,
34 pub shell: String,
35 pub gecos: String,
36 pub supplementary_gids: Vec<u32>,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct GroupRecord {
41 pub gid: u32,
42 pub name: String,
43 pub members: Vec<String>,
44}
45
46#[derive(Debug, Clone, Default, PartialEq, Eq)]
47pub struct UserConfig {
48 pub uid: Option<u32>,
49 pub gid: Option<u32>,
50 pub euid: Option<u32>,
51 pub egid: Option<u32>,
52 pub username: Option<String>,
53 pub homedir: Option<String>,
54 pub shell: Option<String>,
55 pub gecos: Option<String>,
56 pub group_name: Option<String>,
57 pub supplementary_gids: Vec<u32>,
60 pub accounts: Vec<UserAccount>,
61 pub groups: Vec<GroupRecord>,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct UserManager {
66 pub uid: u32,
67 pub gid: u32,
68 pub euid: u32,
69 pub egid: u32,
70 pub username: String,
71 pub homedir: String,
72 pub shell: String,
73 pub gecos: String,
74 pub group_name: String,
75 pub supplementary_gids: Vec<u32>,
76 accounts_by_uid: BTreeMap<u32, UserAccount>,
77 account_uids_by_name: BTreeMap<String, u32>,
78 groups_by_gid: BTreeMap<u32, GroupRecord>,
79 group_gids_by_name: BTreeMap<String, u32>,
80}
81
82impl Default for UserManager {
83 fn default() -> Self {
84 Self::from_config(UserConfig::default())
85 }
86}
87
88impl UserManager {
89 pub fn new() -> Self {
90 Self::default()
91 }
92
93 pub fn from_config(config: UserConfig) -> Self {
94 let uid = config.uid.unwrap_or(1000);
95 let gid = config.gid.unwrap_or(1000);
96 let username = config.username.unwrap_or_else(|| String::from("agentos"));
97 let supplementary_gids = normalize_supplementary_gids(gid, config.supplementary_gids);
98
99 let primary_account = UserAccount {
100 uid,
101 gid,
102 username: username.clone(),
103 homedir: config
104 .homedir
105 .clone()
106 .unwrap_or_else(|| String::from("/home/agentos")),
107 shell: config
108 .shell
109 .clone()
110 .unwrap_or_else(|| String::from("/bin/sh")),
111 gecos: config.gecos.clone().unwrap_or_default(),
112 supplementary_gids: supplementary_gids.clone(),
113 };
114 let mut accounts_by_uid = BTreeMap::new();
115 let mut account_uids_by_name = BTreeMap::new();
116 for mut account in config.accounts {
117 account.supplementary_gids =
118 normalize_supplementary_gids(account.gid, account.supplementary_gids);
119 account_uids_by_name.insert(account.username.clone(), account.uid);
120 accounts_by_uid.insert(account.uid, account);
121 }
122 account_uids_by_name.insert(primary_account.username.clone(), primary_account.uid);
123 accounts_by_uid.insert(primary_account.uid, primary_account.clone());
124
125 let primary_group_name = config.group_name.unwrap_or_else(|| username.clone());
126 let mut groups_by_gid = BTreeMap::new();
127 let mut group_gids_by_name = BTreeMap::new();
128 for group in config.groups {
129 group_gids_by_name.insert(group.name.clone(), group.gid);
130 groups_by_gid.insert(group.gid, group);
131 }
132 groups_by_gid.entry(gid).or_insert_with(|| GroupRecord {
133 gid,
134 name: primary_group_name.clone(),
135 members: vec![username.clone()],
136 });
137 group_gids_by_name.insert(primary_group_name.clone(), gid);
138
139 Self {
140 uid,
141 gid,
142 euid: config.euid.unwrap_or(uid),
143 egid: config.egid.unwrap_or(gid),
144 username: username.clone(),
145 homedir: primary_account.homedir,
146 shell: primary_account.shell,
147 gecos: primary_account.gecos,
148 group_name: primary_group_name,
149 supplementary_gids,
150 accounts_by_uid,
151 account_uids_by_name,
152 groups_by_gid,
153 group_gids_by_name,
154 }
155 }
156
157 pub fn identity(&self) -> ProcessIdentity {
158 ProcessIdentity {
159 uid: self.uid,
160 gid: self.gid,
161 euid: self.euid,
162 egid: self.egid,
163 suid: self.euid,
164 sgid: self.egid,
165 supplementary_gids: self.supplementary_gids.clone(),
166 }
167 }
168
169 pub fn getgroups(&self) -> Vec<u32> {
170 self.supplementary_gids.clone()
171 }
172
173 pub fn getpwuid(&self, uid: u32) -> Option<String> {
174 self.accounts_by_uid.get(&uid).map(render_passwd)
175 }
176
177 pub fn getpwnam(&self, username: &str) -> Option<String> {
178 self.account_uids_by_name
179 .get(username)
180 .and_then(|uid| self.getpwuid(*uid))
181 }
182
183 pub fn getgrgid(&self, gid: u32) -> Option<String> {
184 self.groups_by_gid.get(&gid).map(render_group).or_else(|| {
185 let members = self
186 .accounts_by_uid
187 .values()
188 .filter(|account| account.supplementary_gids.contains(&gid))
189 .map(|account| account.username.as_str())
190 .collect::<Vec<_>>();
191 (!members.is_empty()).then(|| format!("group{gid}:x:{gid}:{}", members.join(",")))
192 })
193 }
194
195 pub fn getgrnam(&self, name: &str) -> Option<String> {
196 self.group_gids_by_name
197 .get(name)
198 .and_then(|gid| self.getgrgid(*gid))
199 }
200
201 pub fn passwd_entries(&self) -> Vec<String> {
202 self.accounts_by_uid.values().map(render_passwd).collect()
203 }
204
205 pub fn group_entries(&self) -> Vec<String> {
206 self.groups_by_gid.values().map(render_group).collect()
207 }
208
209 pub fn account(&self, uid: u32) -> Option<&UserAccount> {
210 self.accounts_by_uid.get(&uid)
211 }
212}
213
214fn render_passwd(account: &UserAccount) -> String {
215 format!(
216 "{}:x:{}:{}:{}:{}:{}",
217 account.username, account.uid, account.gid, account.gecos, account.homedir, account.shell
218 )
219}
220
221fn render_group(group: &GroupRecord) -> String {
222 format!("{}:x:{}:{}", group.name, group.gid, group.members.join(","))
223}
224
225fn normalize_supplementary_gids(primary_gid: u32, supplementary_gids: Vec<u32>) -> Vec<u32> {
226 let mut normalized = Vec::with_capacity(supplementary_gids.len() + 1);
227 normalized.push(primary_gid);
228 for gid in supplementary_gids {
229 if !normalized.contains(&gid) {
230 normalized.push(gid);
231 }
232 }
233 normalized
234}