kanidm_unixd_tasks/
kanidm_unixd_tasks.rs

1#![deny(warnings)]
2#![warn(unused_extern_crates)]
3#![deny(clippy::todo)]
4#![deny(clippy::unimplemented)]
5#![deny(clippy::unwrap_used)]
6#![deny(clippy::expect_used)]
7#![deny(clippy::panic)]
8#![deny(clippy::unreachable)]
9#![deny(clippy::await_holding_lock)]
10#![deny(clippy::needless_pass_by_value)]
11#![deny(clippy::trivially_copy_pass_by_ref)]
12
13use futures::{SinkExt, StreamExt};
14use kanidm_unix_common::constants::{
15    DEFAULT_CONFIG_PATH, SYSTEM_GROUP_PATH, SYSTEM_PASSWD_PATH, SYSTEM_SHADOW_PATH,
16};
17use kanidm_unix_common::json_codec::JsonCodec;
18use kanidm_unix_common::unix_config::{HomeStrategy, UnixdConfig};
19use kanidm_unix_common::unix_passwd::{parse_etc_group, parse_etc_passwd, parse_etc_shadow, EtcDb};
20use kanidm_unix_common::unix_proto::{
21    HomeDirectoryInfo, TaskRequest, TaskRequestFrame, TaskResponse,
22};
23use kanidm_utils_users::{get_effective_gid, get_effective_uid};
24use libc::{lchown, umask};
25use notify_debouncer_full::notify::RecommendedWatcher;
26use notify_debouncer_full::Debouncer;
27use notify_debouncer_full::RecommendedCache;
28use notify_debouncer_full::{new_debouncer, notify::RecursiveMode, DebouncedEvent};
29use sketching::tracing_forest::traits::*;
30use sketching::tracing_forest::util::*;
31use sketching::tracing_forest::{self};
32use std::ffi::CString;
33use std::os::unix::ffi::OsStrExt;
34use std::os::unix::fs::symlink;
35use std::path::{Path, PathBuf};
36use std::process::ExitCode;
37use std::time::Duration;
38use std::{fs, io};
39use tokio::fs::File;
40use tokio::io::AsyncReadExt;
41use tokio::net::UnixStream;
42use tokio::sync::broadcast;
43use tokio::sync::watch;
44use tokio::time;
45use tokio_util::codec::Framed;
46use tracing::instrument;
47use walkdir::WalkDir;
48
49#[cfg(all(target_family = "unix", feature = "selinux"))]
50use kanidm_unix_common::selinux_util;
51
52fn chown(path: &Path, gid: u32) -> Result<(), String> {
53    let path_os = CString::new(path.as_os_str().as_bytes())
54        .map_err(|_| "Unable to create c-string".to_string())?;
55
56    // Change the owner to the gid - remember, kanidm ONLY has gid's, the uid is implied.
57    if unsafe { lchown(path_os.as_ptr(), gid, gid) } != 0 {
58        return Err("Unable to set ownership".to_string());
59    }
60    Ok(())
61}
62
63fn create_home_directory(
64    info: &HomeDirectoryInfo,
65    home_prefix_path: &Path,
66    home_mount_prefix_path: Option<&PathBuf>,
67    home_strategy: &HomeStrategy,
68    use_etc_skel: bool,
69    use_selinux: bool,
70) -> Result<(), String> {
71    // Final sanity check to prevent certain classes of attacks. This should *never*
72    // be possible, but we assert this to be sure.
73    let name = info.name.trim_start_matches('.').replace(['/', '\\'], "");
74
75    debug!(?home_prefix_path, ?home_mount_prefix_path, ?info);
76
77    // This is where the users home dir "is" and aliases from here go to the true storage
78    // mounts
79    let home_prefix_path = home_prefix_path
80        .canonicalize()
81        .map_err(|e| format!("{e:?}"))?;
82
83    // This is where the storage is *mounted*. If not set, falls back to the home_prefix.
84    let home_mount_prefix_path = home_mount_prefix_path
85        .unwrap_or(&home_prefix_path)
86        .canonicalize()
87        .map_err(|e| format!("{e:?}"))?;
88
89    // Does our home_prefix actually exist?
90    if !home_prefix_path.exists() || !home_prefix_path.is_dir() || !home_prefix_path.is_absolute() {
91        return Err("Invalid home_prefix from configuration - home_prefix path must exist, must be a directory, and must be absolute (not relative)".to_string());
92    }
93
94    if !home_mount_prefix_path.exists()
95        || !home_mount_prefix_path.is_dir()
96        || !home_mount_prefix_path.is_absolute()
97    {
98        return Err("Invalid home_mount_prefix from configuration - home_prefix path must exist, must be a directory, and must be absolute (not relative)".to_string());
99    }
100
101    // This is now creating the actual home directory in the home_mount path.
102    // First we want to validate that the path is legitimate and hasn't tried
103    // to escape the home_mount prefix.
104    let hd_mount_path = Path::join(&home_mount_prefix_path, &name);
105
106    debug!(?hd_mount_path);
107
108    if let Some(pp) = hd_mount_path.parent() {
109        if pp != home_mount_prefix_path {
110            return Err("Invalid home directory name - not within home_mount_prefix".to_string());
111        }
112    } else {
113        return Err("Invalid/Corrupt home directory path - no prefix found".to_string());
114    }
115
116    // Get a handle to the SELinux labeling interface
117    debug!(?use_selinux, "selinux for home dir labeling");
118    #[cfg(all(target_family = "unix", feature = "selinux"))]
119    let labeler = if use_selinux {
120        selinux_util::SelinuxLabeler::new(info.gid, &home_mount_prefix_path)?
121    } else {
122        selinux_util::SelinuxLabeler::new_noop()
123    };
124
125    // In the ZFS strategy, we will need to check if the filesystem
126    // exists, rather than the location it's mounted to.
127    let hd_mount_path_exists = match home_strategy {
128        HomeStrategy::Symlink => hd_mount_path.exists(),
129    };
130
131    // Does the home directory exist? This is checking the *true* home mount storage.
132    if !hd_mount_path_exists {
133        // Set the SELinux security context for file creation
134        #[cfg(all(target_family = "unix", feature = "selinux"))]
135        labeler.do_setfscreatecon_for_path()?;
136
137        // This is affected by the mount strategy
138        // because in a future ZFS home dir setup, we'll need to be able to make
139        // the zfs volume for the user in this step.
140        match home_strategy {
141            HomeStrategy::Symlink => {
142                // Set a umask
143                let before = unsafe { umask(0o0027) };
144
145                // Create the home directory.
146                if let Err(e) = fs::create_dir_all(&hd_mount_path) {
147                    let _ = unsafe { umask(before) };
148                    error!(err = ?e, ?hd_mount_path, "Unable to create directory");
149                    return Err(format!("{e:?}"));
150                }
151                let _ = unsafe { umask(before) };
152
153                chown(&hd_mount_path, info.gid)?;
154            }
155        }
156
157        // Copy in structure from /etc/skel/ if present
158        let skel_dir = Path::new("/etc/skel/");
159        if use_etc_skel && skel_dir.exists() {
160            info!("preparing homedir using /etc/skel");
161            for entry in WalkDir::new(skel_dir).into_iter().filter_map(|e| e.ok()) {
162                let dest = &hd_mount_path.join(
163                    entry
164                        .path()
165                        .strip_prefix(skel_dir)
166                        .map_err(|e| e.to_string())?,
167                );
168
169                #[cfg(all(target_family = "unix", feature = "selinux"))]
170                {
171                    let p = entry
172                        .path()
173                        .strip_prefix(skel_dir)
174                        .map_err(|e| e.to_string())?;
175                    labeler.label_path(p)?;
176                }
177
178                if entry.path().is_dir() {
179                    fs::create_dir_all(dest).map_err(|e| {
180                        error!(err = ?e, ?dest, "Unable to create directory from /etc/skel");
181                        e.to_string()
182                    })?;
183                } else {
184                    fs::copy(entry.path(), dest).map_err(|e| {
185                        error!(err = ?e, ?dest, "Unable to copy from /etc/skel");
186                        e.to_string()
187                    })?;
188                }
189                chown(dest, info.gid)?;
190
191                // Create equivalence rule in the SELinux policy
192                #[cfg(all(target_family = "unix", feature = "selinux"))]
193                labeler.setup_equivalence_rule(&hd_mount_path)?;
194            }
195        }
196    }
197
198    // Reset object creation SELinux context to default
199    #[cfg(all(target_family = "unix", feature = "selinux"))]
200    labeler.set_default_context_for_fs_objects()?;
201
202    let Some(alias) = info.alias.as_ref() else {
203        // No alias for the home dir, lets go.
204        debug!("No home directory alias present, sucess.");
205        return Ok(());
206    };
207
208    // Sanity check the alias.
209    // let alias = alias.replace(".", "").replace("/", "").replace("\\", "");
210    let alias = alias.trim_start_matches('.').replace(['/', '\\'], "");
211
212    let alias_path = Path::join(&home_prefix_path, &alias);
213
214    // Assert the resulting alias path is consistent and correct within the home_prefix.
215    if let Some(pp) = alias_path.parent() {
216        if pp != home_prefix_path {
217            return Err("Invalid home directory alias - not within home_prefix".to_string());
218        }
219    } else {
220        return Err("Invalid/Corrupt alias directory path - no prefix found".to_string());
221    }
222
223    match home_strategy {
224        HomeStrategy::Symlink => home_alias_update_symlink(&alias_path, &hd_mount_path),
225    }
226}
227
228fn home_alias_update_symlink(alias_path: &Path, hd_mount_path: &Path) -> Result<(), String> {
229    if !alias_path.exists() {
230        // Does not exist. Create.
231        debug!("creating symlink {:?} -> {:?}", alias_path, hd_mount_path);
232        if let Err(e) = symlink(hd_mount_path, alias_path) {
233            error!(err = ?e, ?alias_path, "Unable to create alias path");
234            return Err(format!("{e:?}"));
235        }
236        return Ok(());
237    }
238
239    debug!("checking symlink {:?} -> {:?}", alias_path, hd_mount_path);
240    let attr = match fs::symlink_metadata(alias_path) {
241        Ok(a) => a,
242        Err(e) => {
243            error!(err = ?e, ?alias_path, "Unable to read alias path metadata");
244            return Err(format!("{e:?}"));
245        }
246    };
247
248    if !attr.file_type().is_symlink() {
249        warn!(
250            ?alias_path,
251            ?hd_mount_path,
252            "home directory alias path is not a symlink, unable to update"
253        );
254        return Ok(());
255    }
256
257    // If already correct, skip churn.
258    match fs::read_link(alias_path) {
259        Ok(current_target) if current_target == hd_mount_path => {
260            debug!(
261                ?alias_path,
262                ?current_target,
263                "alias symlink already correct, skipping update"
264            );
265            return Ok(());
266        }
267        Ok(current_target) => {
268            debug!(
269                ?alias_path,
270                ?current_target,
271                ?hd_mount_path,
272                "alias symlink target differs, updating atomically"
273            );
274        }
275        Err(e) => {
276            warn!(
277                err=?e, ?alias_path,
278                "unable to read existing symlink target, will replace atomically"
279            );
280        }
281    }
282
283    // Atomic replace: create temp symlink in same dir, then rename over existing.
284    let alias_path_tmp = alias_path.with_extension("tmp");
285
286    if alias_path_tmp.exists() {
287        debug!("checking symlink temp {:?}", alias_path_tmp);
288        let attr = match fs::symlink_metadata(&alias_path_tmp) {
289            Ok(a) => a,
290            Err(e) => {
291                error!(err = ?e, ?alias_path_tmp, "Unable to read alias path temp metadata");
292                return Err(format!("{e:?}"));
293            }
294        };
295
296        if !attr.file_type().is_symlink() {
297            warn!(
298                ?alias_path,
299                ?alias_path_tmp,
300                ?hd_mount_path,
301                "home directory alias path temporary update location already exists, and is not a symlink, unable to update"
302            );
303            return Ok(());
304        }
305    }
306
307    // Best-effort cleanup of any stale tmp.
308    let _ = fs::remove_file(&alias_path_tmp);
309
310    if let Err(e) = symlink(hd_mount_path, &alias_path_tmp) {
311        error!(err=?e, ?alias_path_tmp, "Unable to create temporary alias symlink");
312        return Err(format!("{e:?}"));
313    }
314
315    // Rename is atomic within the same directory; no disappearance window.
316    if let Err(e) = fs::rename(&alias_path_tmp, alias_path) {
317        error!(err=?e, from=?alias_path_tmp, to=?alias_path, "Unable to atomically replace alias symlink");
318        // Cleanup temp on failure.
319        let _ = fs::remove_file(&alias_path_tmp);
320        return Err(format!("{e:?}"));
321    }
322
323    debug!(
324        "alias symlink updated atomically {:?} -> {:?}",
325        alias_path, hd_mount_path
326    );
327
328    Ok(())
329}
330
331async fn shadow_reload_task(
332    shadow_data_watch_tx: watch::Sender<EtcDb>,
333    mut shadow_broadcast_rx: broadcast::Receiver<bool>,
334) {
335    debug!("shadow reload task has started ...");
336
337    while shadow_broadcast_rx.recv().await.is_ok() {
338        match process_etc_passwd_group().await {
339            Ok(etc_db) => {
340                shadow_data_watch_tx.send_replace(etc_db);
341                debug!("shadow reload task sent");
342            }
343            Err(()) => {
344                error!("Unable to process etc db");
345                continue;
346            }
347        }
348    }
349
350    debug!("shadow reload task has stopped");
351}
352
353async fn handle_shadow_reload(shadow_data_watch_rx: &mut watch::Receiver<EtcDb>) -> TaskResponse {
354    debug!("Received shadow reload event.");
355    let etc_db: EtcDb = {
356        let etc_db_ref = shadow_data_watch_rx.borrow_and_update();
357        (*etc_db_ref).clone()
358    };
359    // process etc shadow and send it here.
360    TaskResponse::NotifyShadowChange(etc_db)
361}
362
363async fn handle_unixd_request(
364    request: Option<Result<TaskRequestFrame, io::Error>>,
365    cfg: &UnixdConfig,
366) -> Result<TaskResponse, ()> {
367    debug!("Received unixd event.");
368    match request {
369        Some(Ok(TaskRequestFrame {
370            id,
371            req: TaskRequest::HomeDirectory(info),
372        })) => {
373            debug!("Received task -> HomeDirectory({:?})", info);
374
375            match create_home_directory(
376                &info,
377                cfg.home_prefix.as_ref(),
378                cfg.home_mount_prefix.as_ref(),
379                &cfg.home_strategy,
380                cfg.use_etc_skel,
381                cfg.selinux,
382            ) {
383                Ok(()) => Ok(TaskResponse::Success(id)),
384                Err(msg) => Ok(TaskResponse::Error(msg)),
385            }
386        }
387        other => {
388            error!("Error -> got un-handled Request Frame {other:?}");
389            Err(())
390        }
391    }
392}
393
394async fn handle_tasks(
395    stream: UnixStream,
396    ctl_broadcast_rx: &mut broadcast::Receiver<bool>,
397    shadow_data_watch_rx: &mut watch::Receiver<EtcDb>,
398    cfg: &UnixdConfig,
399) {
400    let codec: JsonCodec<TaskRequestFrame, TaskResponse> = JsonCodec::default();
401
402    let mut reqs = Framed::new(stream, codec);
403
404    // Immediately trigger that we should reload the shadow files for the new connected handler
405    shadow_data_watch_rx.mark_changed();
406
407    debug!("Task handler loop has started ...");
408
409    loop {
410        let msg = tokio::select! {
411            biased; // tell tokio to poll these in order
412            _ = ctl_broadcast_rx.recv() => {
413                // We received a shutdown signal.
414                debug!("Received shutdown signal, breaking task handler loop ...");
415                return
416            }
417            // We bias to *sending* messages in tasks.
418            Ok(_) = shadow_data_watch_rx.changed() => {
419                handle_shadow_reload(shadow_data_watch_rx).await
420            }
421            request = reqs.next() => {
422                match handle_unixd_request(request,  cfg).await {
423                    Ok(response) => {
424                       response
425                    }
426                    Err(_) => {
427                        error!("Error handling request, exiting task handler loop ...");
428                        return;
429                    }
430                }
431            }
432        };
433
434        if let Err(e) = reqs.send(msg).await {
435            error!(?e, "Error sending response to kanidm_unixd");
436            return;
437        }
438    }
439}
440
441#[instrument(level = "debug", skip_all)]
442async fn process_etc_passwd_group() -> Result<EtcDb, ()> {
443    let mut file = File::open(SYSTEM_PASSWD_PATH).await.map_err(|err| {
444        error!(?err);
445    })?;
446    let mut contents = vec![];
447    file.read_to_end(&mut contents).await.map_err(|err| {
448        error!(?err);
449    })?;
450
451    let users = parse_etc_passwd(contents.as_slice())
452        .map_err(|_| "Invalid passwd content")
453        .map_err(|err| {
454            error!(?err);
455        })?;
456
457    let mut file = File::open(SYSTEM_SHADOW_PATH).await.map_err(|err| {
458        error!(?err);
459    })?;
460    let mut contents = vec![];
461    file.read_to_end(&mut contents).await.map_err(|err| {
462        error!(?err);
463    })?;
464
465    let shadow = parse_etc_shadow(contents.as_slice())
466        .map_err(|_| "Invalid passwd content")
467        .map_err(|err| {
468            error!(?err);
469        })?;
470
471    let mut file = File::open(SYSTEM_GROUP_PATH).await.map_err(|err| {
472        error!(?err);
473    })?;
474    let mut contents = vec![];
475    file.read_to_end(&mut contents).await.map_err(|err| {
476        error!(?err);
477    })?;
478
479    let groups = parse_etc_group(contents.as_slice())
480        .map_err(|_| "Invalid group content")
481        .map_err(|err| {
482            error!(?err);
483        })?;
484
485    Ok(EtcDb {
486        users,
487        shadow,
488        groups,
489    })
490}
491
492fn setup_shadow_inotify_watcher(
493    shadow_broadcast_tx: broadcast::Sender<bool>,
494) -> Result<Debouncer<RecommendedWatcher, RecommendedCache>, ExitCode> {
495    let watcher = new_debouncer(
496        Duration::from_secs(5),
497        None,
498        move |event: Result<Vec<DebouncedEvent>, _>| {
499            let array_of_events = match event {
500                Ok(events) => events,
501                Err(array_errors) => {
502                    for err in array_errors {
503                        error!(?err, "inotify debounce error");
504                    }
505                    return;
506                }
507            };
508
509            let mut path_of_interest_was_changed = false;
510
511            for inode_event in array_of_events.iter() {
512                if !inode_event.kind.is_access()
513                    && inode_event.paths.iter().any(|path| {
514                        path == Path::new(SYSTEM_GROUP_PATH)
515                            || path == Path::new(SYSTEM_PASSWD_PATH)
516                            || path == Path::new(SYSTEM_SHADOW_PATH)
517                    })
518                {
519                    debug!(?inode_event, "Handling inotify modification event");
520
521                    path_of_interest_was_changed = true
522                }
523            }
524
525            if path_of_interest_was_changed {
526                let _ = shadow_broadcast_tx.send(true);
527            } else {
528                trace!(?array_of_events, "IGNORED");
529            }
530        },
531    )
532    .and_then(|mut debouncer| {
533        debouncer
534            .watch(Path::new("/etc"), RecursiveMode::Recursive)
535            .map(|()| debouncer)
536    });
537
538    watcher.map_err(|err| {
539        error!(?err, "Failed to setup inotify");
540        ExitCode::FAILURE
541    })
542}
543
544#[tokio::main(flavor = "current_thread")]
545async fn main() -> ExitCode {
546    // On linux when debug assertions are disabled, prevent ptrace
547    // from attaching to us.
548    #[cfg(all(target_os = "linux", not(debug_assertions)))]
549    if let Err(code) = prctl::set_dumpable(false) {
550        error!(?code, "CRITICAL: Unable to set prctl flags");
551        return ExitCode::FAILURE;
552    }
553    // let cuid = get_current_uid();
554    // let cgid = get_current_gid();
555    // We only need to check effective id
556    let ceuid = get_effective_uid();
557    let cegid = get_effective_gid();
558
559    for arg in std::env::args() {
560        if arg.contains("--version") {
561            println!("kanidm_unixd_tasks {}", env!("CARGO_PKG_VERSION"));
562            return ExitCode::SUCCESS;
563        } else if arg.contains("--help") {
564            println!("kanidm_unixd_tasks {}", env!("CARGO_PKG_VERSION"));
565            println!("Usage: kanidm_unixd_tasks");
566            println!("  --version");
567            println!("  --help");
568            return ExitCode::SUCCESS;
569        }
570    }
571
572    #[allow(clippy::expect_used)]
573    tracing_forest::worker_task()
574        .set_global(true)
575        // Fall back to stderr
576        .map_sender(|sender| sender.or_stderr())
577        .build_on(|subscriber| {
578            subscriber.with(
579                EnvFilter::try_from_default_env()
580                    .or_else(|_| EnvFilter::try_new("info"))
581                    .expect("Failed to init envfilter"),
582            )
583        })
584        .on(async {
585            if ceuid != 0 || cegid != 0 {
586                error!("Refusing to run - this process *MUST* operate as root.");
587                return ExitCode::FAILURE;
588            }
589
590            let unixd_path = Path::new(DEFAULT_CONFIG_PATH);
591            let unixd_path_str = match unixd_path.to_str() {
592                Some(cps) => cps,
593                None => {
594                    error!("Unable to turn unixd_path to str");
595                    return ExitCode::FAILURE;
596                }
597            };
598
599            let cfg = match UnixdConfig::new().read_options_from_optional_config(unixd_path) {
600                Ok(v) => v,
601                Err(_) => {
602                    error!("Failed to parse {}", unixd_path_str);
603                    return ExitCode::FAILURE;
604                }
605            };
606
607            let task_sock_path = cfg.task_sock_path.clone();
608            debug!("Attempting to use {} ...", task_sock_path);
609
610            // This is the startup/shutdown control channel
611            let (broadcast_tx, mut broadcast_rx) = broadcast::channel(4);
612            let mut d_broadcast_rx = broadcast_tx.subscribe();
613
614            // This is to broadcast when we need to reload the shadow
615            // files.
616            let (shadow_broadcast_tx, shadow_broadcast_rx) = broadcast::channel(4);
617
618            let watcher = match setup_shadow_inotify_watcher(shadow_broadcast_tx.clone()) {
619                Ok(w) => w,
620                Err(exit) => return exit,
621            };
622
623            // Setup the etcdb watch
624            let etc_db = match process_etc_passwd_group().await {
625                Ok(etc_db) => etc_db,
626                Err(err) => {
627                    warn!(?err, "unable to process {SYSTEM_PASSWD_PATH} and related files.");
628                    // Return an empty set instead.
629                    EtcDb::default()
630                }
631            };
632
633            let (shadow_data_watch_tx, mut shadow_data_watch_rx) = watch::channel(etc_db);
634
635            let _shadow_task = tokio::spawn(async move {
636                shadow_reload_task(
637                    shadow_data_watch_tx, shadow_broadcast_rx
638                ).await
639            });
640
641            let server = tokio::spawn(async move {
642                loop {
643                    info!("Attempting to connect to kanidm_unixd ...");
644
645                    tokio::select! {
646                        _ = broadcast_rx.recv() => {
647                            break;
648                        }
649                        connect_res = UnixStream::connect(&task_sock_path) => {
650                            match connect_res {
651                                Ok(stream) => {
652                                    info!("Found kanidm_unixd, waiting for tasks ...");
653
654                                    // Yep! Now let the main handler do its job.
655                                    // If it returns (dc, etc, then we loop and try again).
656                                    handle_tasks(stream, &mut d_broadcast_rx, &mut shadow_data_watch_rx, &cfg).await;
657                                    continue;
658                                }
659                                Err(e) => {
660                                    debug!("\\---> {:?}", e);
661                                    error!("Unable to find kanidm_unixd, sleeping ...");
662                                    // Back off.
663                                    time::sleep(Duration::from_millis(5000)).await;
664                                }
665                            }
666                        }
667                    } // select
668                } // loop
669            });
670
671            info!("Server started ...");
672
673            // On linux, notify systemd.
674            #[cfg(target_os = "linux")]
675            let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
676
677            loop {
678                tokio::select! {
679                    Ok(()) = tokio::signal::ctrl_c() => {
680                        break
681                    }
682                    Some(()) = async move {
683                        let sigterm = tokio::signal::unix::SignalKind::terminate();
684                        #[allow(clippy::unwrap_used)]
685                        tokio::signal::unix::signal(sigterm).unwrap().recv().await
686                    } => {
687                        break
688                    }
689                    Some(()) = async move {
690                        let sigterm = tokio::signal::unix::SignalKind::alarm();
691                        #[allow(clippy::unwrap_used)]
692                        tokio::signal::unix::signal(sigterm).unwrap().recv().await
693                    } => {
694                        // Ignore
695                    }
696                    Some(()) = async move {
697                        let sigterm = tokio::signal::unix::SignalKind::hangup();
698                        #[allow(clippy::unwrap_used)]
699                        tokio::signal::unix::signal(sigterm).unwrap().recv().await
700                    } => {
701                        // Ignore
702                    }
703                    Some(()) = async move {
704                        let sigterm = tokio::signal::unix::SignalKind::user_defined1();
705                        #[allow(clippy::unwrap_used)]
706                        tokio::signal::unix::signal(sigterm).unwrap().recv().await
707                    } => {
708                        // Ignore
709                    }
710
711                    Some(()) = async move {
712                        let sigterm = tokio::signal::unix::SignalKind::user_defined2();
713                        #[allow(clippy::unwrap_used)]
714                        tokio::signal::unix::signal(sigterm).unwrap().recv().await
715                    } => {
716                        // Ignore
717                    }
718                }
719            }
720            info!("Signal received, shutting down");
721            // Send a broadcast that we are done.
722            if let Err(e) = broadcast_tx.send(true) {
723                error!("Unable to shutdown workers {:?}", e);
724            }
725
726            debug!("Dropping inotify watcher ...");
727            drop(watcher);
728
729            let _ = server.await;
730            ExitCode::SUCCESS
731        })
732        .await
733}