nss_kanidm/
core.rs
1use kanidm_unix_common::client_sync::DaemonClientBlocking;
2use kanidm_unix_common::constants::{SYSTEM_GROUP_PATH, SYSTEM_PASSWD_PATH};
3use kanidm_unix_common::unix_config::PamNssConfig;
4use kanidm_unix_common::unix_passwd::{
5 read_etc_group_file, read_etc_passwd_file, EtcGroup, EtcUser,
6};
7use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse, NssGroup, NssUser};
8
9use libnss::group::Group;
10use libnss::interop::Response;
11use libnss::passwd::Passwd;
12
13#[cfg(test)]
14use kanidm_unix_common::client_sync::UnixStream;
15
16pub enum RequestOptions {
17 Main {
18 config_path: &'static str,
19 },
20 #[cfg(test)]
21 Test {
22 socket: Option<UnixStream>,
23 users: Vec<EtcUser>,
24 groups: Vec<EtcGroup>,
25 },
26}
27
28enum Source {
29 Daemon(DaemonClientBlocking),
30 Fallback {
31 users: Vec<EtcUser>,
32 groups: Vec<EtcGroup>,
33 },
34}
35
36impl RequestOptions {
37 fn connect_to_daemon(self) -> Source {
38 match self {
39 RequestOptions::Main { config_path } => {
40 let maybe_client = PamNssConfig::new()
41 .read_options_from_optional_config(config_path)
42 .ok()
43 .and_then(|cfg| {
44 DaemonClientBlocking::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout)
45 .ok()
46 });
47
48 if let Some(client) = maybe_client {
49 Source::Daemon(client)
50 } else {
51 let users = read_etc_passwd_file(SYSTEM_PASSWD_PATH).unwrap_or_default();
52
53 let groups = read_etc_group_file(SYSTEM_GROUP_PATH).unwrap_or_default();
54
55 Source::Fallback { users, groups }
56 }
57 }
58 #[cfg(test)]
59 RequestOptions::Test {
60 socket,
61 users,
62 groups,
63 } => {
64 if let Some(socket) = socket {
65 Source::Daemon(DaemonClientBlocking::from(socket))
66 } else {
67 Source::Fallback { users, groups }
68 }
69 }
70 }
71 }
72}
73
74pub fn get_all_user_entries(req_options: RequestOptions) -> Response<Vec<Passwd>> {
75 match req_options.connect_to_daemon() {
76 Source::Daemon(mut daemon_client) => {
77 let req = ClientRequest::NssAccounts;
78
79 daemon_client
80 .call_and_wait(&req, None)
81 .map(|r| match r {
82 ClientResponse::NssAccounts(l) => {
83 l.into_iter().map(passwd_from_nssuser).collect()
84 }
85 _ => Vec::new(),
86 })
87 .map(Response::Success)
88 .unwrap_or_else(|_| Response::Success(vec![]))
89 }
90 Source::Fallback { users, groups: _ } => {
91 if users.is_empty() {
92 return Response::Unavail;
93 }
94
95 let users = users.into_iter().map(passwd_from_etcuser).collect();
96
97 Response::Success(users)
98 }
99 }
100}
101
102pub fn get_user_entry_by_uid(uid: libc::uid_t, req_options: RequestOptions) -> Response<Passwd> {
103 match req_options.connect_to_daemon() {
104 Source::Daemon(mut daemon_client) => {
105 let req = ClientRequest::NssAccountByUid(uid);
106 daemon_client
107 .call_and_wait(&req, None)
108 .map(|r| match r {
109 ClientResponse::NssAccount(opt) => opt
110 .map(passwd_from_nssuser)
111 .map(Response::Success)
112 .unwrap_or_else(|| Response::NotFound),
113 _ => Response::NotFound,
114 })
115 .unwrap_or_else(|_| Response::NotFound)
116 }
117 Source::Fallback { users, groups: _ } => {
118 if users.is_empty() {
119 return Response::Unavail;
120 }
121
122 let user = users
123 .into_iter()
124 .filter_map(|etcuser| {
125 if etcuser.uid == uid {
126 Some(passwd_from_etcuser(etcuser))
127 } else {
128 None
129 }
130 })
131 .next();
132
133 if let Some(user) = user {
134 Response::Success(user)
135 } else {
136 Response::NotFound
137 }
138 }
139 }
140}
141
142pub fn get_user_entry_by_name(name: String, req_options: RequestOptions) -> Response<Passwd> {
143 match req_options.connect_to_daemon() {
144 Source::Daemon(mut daemon_client) => {
145 let req = ClientRequest::NssAccountByName(name);
146 daemon_client
147 .call_and_wait(&req, None)
148 .map(|r| match r {
149 ClientResponse::NssAccount(opt) => opt
150 .map(passwd_from_nssuser)
151 .map(Response::Success)
152 .unwrap_or_else(|| Response::NotFound),
153 _ => Response::NotFound,
154 })
155 .unwrap_or_else(|_| Response::NotFound)
156 }
157 Source::Fallback { users, groups: _ } => {
158 if users.is_empty() {
159 return Response::Unavail;
160 }
161
162 let user = users
163 .into_iter()
164 .filter_map(|etcuser| {
165 if etcuser.name == name {
166 Some(passwd_from_etcuser(etcuser))
167 } else {
168 None
169 }
170 })
171 .next();
172
173 if let Some(user) = user {
174 Response::Success(user)
175 } else {
176 Response::NotFound
177 }
178 }
179 }
180}
181
182pub fn get_all_group_entries(req_options: RequestOptions) -> Response<Vec<Group>> {
183 match req_options.connect_to_daemon() {
184 Source::Daemon(mut daemon_client) => {
185 let req = ClientRequest::NssGroups;
186 daemon_client
187 .call_and_wait(&req, None)
188 .map(|r| match r {
189 ClientResponse::NssGroups(l) => {
190 l.into_iter().map(group_from_nssgroup).collect()
191 }
192 _ => Vec::new(),
193 })
194 .map(Response::Success)
195 .unwrap_or_else(|_| Response::Success(vec![]))
196 }
197 Source::Fallback { users: _, groups } => {
198 if groups.is_empty() {
199 return Response::Unavail;
200 }
201
202 let groups = groups.into_iter().map(group_from_etcgroup).collect();
203
204 Response::Success(groups)
205 }
206 }
207}
208
209pub fn get_group_entry_by_gid(gid: libc::gid_t, req_options: RequestOptions) -> Response<Group> {
210 match req_options.connect_to_daemon() {
211 Source::Daemon(mut daemon_client) => {
212 let req = ClientRequest::NssGroupByGid(gid);
213 daemon_client
214 .call_and_wait(&req, None)
215 .map(|r| match r {
216 ClientResponse::NssGroup(opt) => opt
217 .map(group_from_nssgroup)
218 .map(Response::Success)
219 .unwrap_or_else(|| Response::NotFound),
220 _ => Response::NotFound,
221 })
222 .unwrap_or_else(|_| Response::NotFound)
223 }
224 Source::Fallback { users: _, groups } => {
225 if groups.is_empty() {
226 return Response::Unavail;
227 }
228
229 let group = groups
230 .into_iter()
231 .filter_map(|etcgroup| {
232 if etcgroup.gid == gid {
233 Some(group_from_etcgroup(etcgroup))
234 } else {
235 None
236 }
237 })
238 .next();
239
240 if let Some(group) = group {
241 Response::Success(group)
242 } else {
243 Response::NotFound
244 }
245 }
246 }
247}
248
249pub fn get_group_entry_by_name(name: String, req_options: RequestOptions) -> Response<Group> {
250 match req_options.connect_to_daemon() {
251 Source::Daemon(mut daemon_client) => {
252 let req = ClientRequest::NssGroupByName(name);
253 daemon_client
254 .call_and_wait(&req, None)
255 .map(|r| match r {
256 ClientResponse::NssGroup(opt) => opt
257 .map(group_from_nssgroup)
258 .map(Response::Success)
259 .unwrap_or_else(|| Response::NotFound),
260 _ => Response::NotFound,
261 })
262 .unwrap_or_else(|_| Response::NotFound)
263 }
264 Source::Fallback { users: _, groups } => {
265 if groups.is_empty() {
266 return Response::Unavail;
267 }
268
269 let group = groups
270 .into_iter()
271 .filter_map(|etcgroup| {
272 if etcgroup.name == name {
273 Some(group_from_etcgroup(etcgroup))
274 } else {
275 None
276 }
277 })
278 .next();
279
280 if let Some(group) = group {
281 Response::Success(group)
282 } else {
283 Response::NotFound
284 }
285 }
286 }
287}
288
289fn passwd_from_etcuser(etc: EtcUser) -> Passwd {
290 Passwd {
291 name: etc.name,
292 gecos: etc.gecos,
293 passwd: "x".to_string(),
294 uid: etc.uid,
295 gid: etc.gid,
296 dir: etc.homedir,
297 shell: etc.shell,
298 }
299}
300
301fn passwd_from_nssuser(nu: NssUser) -> Passwd {
302 Passwd {
303 name: nu.name,
304 gecos: nu.gecos,
305 passwd: "x".to_string(),
306 uid: nu.uid,
307 gid: nu.gid,
308 dir: nu.homedir,
309 shell: nu.shell,
310 }
311}
312
313fn group_from_etcgroup(etc: EtcGroup) -> Group {
314 Group {
315 name: etc.name,
316 passwd: "x".to_string(),
317 gid: etc.gid,
318 members: etc.members,
319 }
320}
321
322fn group_from_nssgroup(ng: NssGroup) -> Group {
323 Group {
324 name: ng.name,
325 passwd: "x".to_string(),
326 gid: ng.gid,
327 members: ng.members,
328 }
329}