2020-06-18 02:30:42 +02:00
#![ deny(warnings) ]
2020-08-01 12:31:05 +02:00
#![ warn(unused_extern_crates) ]
2022-02-20 03:43:38 +01:00
#![ deny(clippy::todo) ]
#![ deny(clippy::unimplemented) ]
2020-08-01 12:31:05 +02:00
#![ deny(clippy::unwrap_used) ]
#![ deny(clippy::expect_used) ]
#![ deny(clippy::panic) ]
#![ deny(clippy::unreachable) ]
#![ deny(clippy::await_holding_lock) ]
#![ deny(clippy::needless_pass_by_value) ]
#![ deny(clippy::trivially_copy_pass_by_ref) ]
2022-10-01 08:08:51 +02:00
use std ::error ::Error ;
use std ::fs ::metadata ;
use std ::io ;
use std ::io ::{ Error as IoError , ErrorKind } ;
use std ::os ::unix ::fs ::MetadataExt ;
use std ::path ::{ Path , PathBuf } ;
2023-03-20 04:15:44 +01:00
use std ::process ::ExitCode ;
2022-10-01 08:08:51 +02:00
use std ::sync ::Arc ;
use std ::time ::Duration ;
2020-02-13 00:43:01 +01:00
use bytes ::{ BufMut , BytesMut } ;
2022-09-08 05:37:03 +02:00
use clap ::{ Arg , ArgAction , Command } ;
2022-10-01 08:08:51 +02:00
use futures ::{ SinkExt , StreamExt } ;
2022-09-08 05:37:03 +02:00
use kanidm_client ::KanidmClientBuilder ;
use kanidm_proto ::constants ::DEFAULT_CLIENT_CONFIG_PATH ;
use kanidm_unix_common ::constants ::DEFAULT_CONFIG_PATH ;
2023-07-24 09:10:37 +02:00
use kanidm_unix_common ::db ::Db ;
2023-07-28 02:48:56 +02:00
use kanidm_unix_common ::idprovider ::kanidm ::KanidmProvider ;
2023-07-24 09:10:37 +02:00
use kanidm_unix_common ::resolver ::Resolver ;
2022-09-08 05:37:03 +02:00
use kanidm_unix_common ::unix_config ::KanidmUnixdConfig ;
2023-06-15 05:24:53 +02:00
use kanidm_unix_common ::unix_passwd ::{ parse_etc_group , parse_etc_passwd } ;
2022-09-08 05:37:03 +02:00
use kanidm_unix_common ::unix_proto ::{ ClientRequest , ClientResponse , TaskRequest , TaskResponse } ;
2023-03-01 04:10:52 +01:00
2023-07-31 14:27:21 +02:00
use kanidm_utils_users ::{ get_current_gid , get_current_uid , get_effective_gid , get_effective_uid } ;
2020-02-15 01:27:25 +01:00
use libc ::umask ;
2022-10-01 08:08:51 +02:00
use sketching ::tracing_forest ::traits ::* ;
use sketching ::tracing_forest ::util ::* ;
use sketching ::tracing_forest ::{ self } ;
2023-06-15 05:24:53 +02:00
use tokio ::fs ::File ;
use tokio ::io ::AsyncReadExt ; // for read_to_end()
2020-02-13 00:43:01 +01:00
use tokio ::net ::{ UnixListener , UnixStream } ;
2023-05-25 02:43:26 +02:00
use tokio ::sync ::broadcast ;
2021-03-13 03:33:15 +01:00
use tokio ::sync ::mpsc ::{ channel , Receiver , Sender } ;
use tokio ::sync ::oneshot ;
use tokio ::time ;
2022-10-01 08:08:51 +02:00
use tokio_util ::codec ::{ Decoder , Encoder , Framed } ;
2021-06-27 03:30:40 +02:00
2023-06-15 05:24:53 +02:00
use notify_debouncer_full ::{ new_debouncer , notify ::RecursiveMode , notify ::Watcher } ;
2020-02-13 00:43:01 +01:00
//=== the codec
2021-03-13 03:33:15 +01:00
type AsyncTaskRequest = ( TaskRequest , oneshot ::Sender < ( ) > ) ;
2023-07-24 02:05:10 +02:00
#[ derive(Default) ]
2020-02-13 00:43:01 +01:00
struct ClientCodec ;
impl Decoder for ClientCodec {
type Error = io ::Error ;
2022-10-01 08:08:51 +02:00
type Item = ClientRequest ;
2020-02-13 00:43:01 +01:00
fn decode ( & mut self , src : & mut BytesMut ) -> Result < Option < Self ::Item > , Self ::Error > {
2023-06-19 07:02:09 +02:00
trace! ( " Attempting to decode request ... " ) ;
2023-01-28 04:52:44 +01:00
match serde_json ::from_slice ::< ClientRequest > ( src ) {
2020-02-13 00:43:01 +01:00
Ok ( msg ) = > {
// Clear the buffer for the next message.
src . clear ( ) ;
Ok ( Some ( msg ) )
}
_ = > Ok ( None ) ,
}
}
}
2020-04-11 02:32:56 +02:00
impl Encoder < ClientResponse > for ClientCodec {
2020-02-13 00:43:01 +01:00
type Error = io ::Error ;
fn encode ( & mut self , msg : ClientResponse , dst : & mut BytesMut ) -> Result < ( ) , Self ::Error > {
2023-06-19 07:02:09 +02:00
trace! ( " Attempting to send response -> {:?} ... " , msg ) ;
2022-05-24 02:49:34 +02:00
let data = serde_json ::to_vec ( & msg ) . map_err ( | e | {
2020-02-13 00:43:01 +01:00
error! ( " socket encoding error -> {:?} " , e ) ;
2022-05-24 02:49:34 +02:00
io ::Error ::new ( io ::ErrorKind ::Other , " JSON encode error " )
2020-02-13 00:43:01 +01:00
} ) ? ;
dst . put ( data . as_slice ( ) ) ;
Ok ( ( ) )
}
}
2023-07-24 02:05:10 +02:00
#[ derive(Default) ]
2021-03-13 03:33:15 +01:00
struct TaskCodec ;
impl Decoder for TaskCodec {
type Error = io ::Error ;
2022-10-01 08:08:51 +02:00
type Item = TaskResponse ;
2021-03-13 03:33:15 +01:00
fn decode ( & mut self , src : & mut BytesMut ) -> Result < Option < Self ::Item > , Self ::Error > {
2023-01-28 04:52:44 +01:00
match serde_json ::from_slice ::< TaskResponse > ( src ) {
2021-03-13 03:33:15 +01:00
Ok ( msg ) = > {
// Clear the buffer for the next message.
src . clear ( ) ;
Ok ( Some ( msg ) )
}
_ = > Ok ( None ) ,
}
}
}
impl Encoder < TaskRequest > for TaskCodec {
type Error = io ::Error ;
fn encode ( & mut self , msg : TaskRequest , dst : & mut BytesMut ) -> Result < ( ) , Self ::Error > {
debug! ( " Attempting to send request -> {:?} ... " , msg ) ;
2022-05-24 02:49:34 +02:00
let data = serde_json ::to_vec ( & msg ) . map_err ( | e | {
2021-03-13 03:33:15 +01:00
error! ( " socket encoding error -> {:?} " , e ) ;
2022-05-24 02:49:34 +02:00
io ::Error ::new ( io ::ErrorKind ::Other , " JSON encode error " )
2021-03-13 03:33:15 +01:00
} ) ? ;
dst . put ( data . as_slice ( ) ) ;
Ok ( ( ) )
}
}
2022-09-08 05:37:03 +02:00
/// Pass this a file path and it'll look for the file and remove it if it's there.
2020-02-13 00:43:01 +01:00
fn rm_if_exist ( p : & str ) {
2021-06-15 03:54:04 +02:00
if Path ::new ( p ) . exists ( ) {
debug! ( " Removing requested file {:?} " , p ) ;
let _ = std ::fs ::remove_file ( p ) . map_err ( | e | {
error! (
" Failure while attempting to attempting to remove {:?} -> {:?} " ,
p , e
) ;
} ) ;
} else {
debug! ( " Path {:?} doesn't exist, not attempting to remove. " , p ) ;
}
2020-02-13 00:43:01 +01:00
}
2021-03-13 03:33:15 +01:00
async fn handle_task_client (
stream : UnixStream ,
task_channel_tx : & Sender < AsyncTaskRequest > ,
task_channel_rx : & mut Receiver < AsyncTaskRequest > ,
) -> Result < ( ) , Box < dyn Error > > {
// setup the codec
2023-07-24 02:05:10 +02:00
let mut reqs = Framed ::new ( stream , TaskCodec ) ;
2021-03-13 03:33:15 +01:00
loop {
// TODO wait on the channel OR the task handler, so we know
// when it closes.
let v = match task_channel_rx . recv ( ) . await {
Some ( v ) = > v ,
None = > return Ok ( ( ) ) ,
} ;
debug! ( " Sending Task -> {:?} " , v . 0 ) ;
// Write the req to the socket.
if let Err ( _e ) = reqs . send ( v . 0. clone ( ) ) . await {
// re-queue the event if not timed out.
// This is indicated by the one shot being dropped.
if ! v . 1. is_closed ( ) {
let _ = task_channel_tx
. send_timeout ( v , Duration ::from_millis ( 100 ) )
. await ;
}
// now return the error.
return Err ( Box ::new ( IoError ::new ( ErrorKind ::Other , " oh no! " ) ) ) ;
}
match reqs . next ( ) . await {
Some ( Ok ( TaskResponse ::Success ) ) = > {
debug! ( " Task was acknowledged and completed. " ) ;
// Send a result back via the one-shot
// Ignore if it fails.
let _ = v . 1. send ( ( ) ) ;
}
other = > {
error! ( " Error -> {:?} " , other ) ;
return Err ( Box ::new ( IoError ::new ( ErrorKind ::Other , " oh no! " ) ) ) ;
}
}
}
}
2020-02-13 00:43:01 +01:00
async fn handle_client (
sock : UnixStream ,
2023-07-28 02:48:56 +02:00
cachelayer : Arc < Resolver < KanidmProvider > > ,
2021-03-13 03:33:15 +01:00
task_channel_tx : & Sender < AsyncTaskRequest > ,
2020-02-13 00:43:01 +01:00
) -> Result < ( ) , Box < dyn Error > > {
debug! ( " Accepted connection " ) ;
2023-07-24 02:05:10 +02:00
let Ok ( ucred ) = sock . peer_cred ( ) else {
return Err ( Box ::new ( IoError ::new ( ErrorKind ::Other , " Unable to verify peer credentials. " ) ) ) ;
} ;
let mut reqs = Framed ::new ( sock , ClientCodec ) ;
2020-02-13 00:43:01 +01:00
2023-06-19 07:02:09 +02:00
trace! ( " Waiting for requests ... " ) ;
2020-02-13 00:43:01 +01:00
while let Some ( Ok ( req ) ) = reqs . next ( ) . await {
2020-02-15 01:27:25 +01:00
let resp = match req {
2020-02-13 00:43:01 +01:00
ClientRequest ::SshKey ( account_id ) = > {
2020-02-15 01:27:25 +01:00
debug! ( " sshkey req " ) ;
cachelayer
. get_sshkeys ( account_id . as_str ( ) )
. await
2020-06-18 02:30:42 +02:00
. map ( ClientResponse ::SshKeys )
2020-02-15 01:27:25 +01:00
. unwrap_or_else ( | _ | {
2020-02-13 00:43:01 +01:00
error! ( " unable to load keys, returning empty set. " ) ;
ClientResponse ::SshKeys ( vec! [ ] )
2020-02-15 01:27:25 +01:00
} )
2020-02-13 00:43:01 +01:00
}
2020-02-15 01:27:25 +01:00
ClientRequest ::NssAccounts = > {
debug! ( " nssaccounts req " ) ;
cachelayer
. get_nssaccounts ( )
2020-08-04 04:58:11 +02:00
. await
2020-06-18 02:30:42 +02:00
. map ( ClientResponse ::NssAccounts )
2020-02-15 01:27:25 +01:00
. unwrap_or_else ( | _ | {
error! ( " unable to enum accounts " ) ;
ClientResponse ::NssAccounts ( Vec ::new ( ) )
} )
}
ClientRequest ::NssAccountByUid ( gid ) = > {
debug! ( " nssaccountbyuid req " ) ;
cachelayer
. get_nssaccount_gid ( gid )
. await
2020-06-18 02:30:42 +02:00
. map ( ClientResponse ::NssAccount )
2020-02-15 01:27:25 +01:00
. unwrap_or_else ( | _ | {
error! ( " unable to load account, returning empty. " ) ;
ClientResponse ::NssAccount ( None )
} )
}
ClientRequest ::NssAccountByName ( account_id ) = > {
debug! ( " nssaccountbyname req " ) ;
cachelayer
. get_nssaccount_name ( account_id . as_str ( ) )
. await
2020-06-18 02:30:42 +02:00
. map ( ClientResponse ::NssAccount )
2020-02-15 01:27:25 +01:00
. unwrap_or_else ( | _ | {
error! ( " unable to load account, returning empty. " ) ;
ClientResponse ::NssAccount ( None )
} )
}
ClientRequest ::NssGroups = > {
debug! ( " nssgroups req " ) ;
cachelayer
. get_nssgroups ( )
2020-08-04 04:58:11 +02:00
. await
2020-06-18 02:30:42 +02:00
. map ( ClientResponse ::NssGroups )
2020-02-15 01:27:25 +01:00
. unwrap_or_else ( | _ | {
error! ( " unable to enum groups " ) ;
ClientResponse ::NssGroups ( Vec ::new ( ) )
} )
}
ClientRequest ::NssGroupByGid ( gid ) = > {
debug! ( " nssgroupbygid req " ) ;
cachelayer
. get_nssgroup_gid ( gid )
. await
2020-06-18 02:30:42 +02:00
. map ( ClientResponse ::NssGroup )
2020-02-15 01:27:25 +01:00
. unwrap_or_else ( | _ | {
error! ( " unable to load group, returning empty. " ) ;
ClientResponse ::NssGroup ( None )
} )
}
ClientRequest ::NssGroupByName ( grp_id ) = > {
debug! ( " nssgroupbyname req " ) ;
cachelayer
. get_nssgroup_name ( grp_id . as_str ( ) )
. await
2020-06-18 02:30:42 +02:00
. map ( ClientResponse ::NssGroup )
2020-02-15 01:27:25 +01:00
. unwrap_or_else ( | _ | {
error! ( " unable to load group, returning empty. " ) ;
ClientResponse ::NssGroup ( None )
} )
}
2020-02-29 05:02:14 +01:00
ClientRequest ::PamAuthenticate ( account_id , cred ) = > {
debug! ( " pam authenticate " ) ;
cachelayer
. pam_account_authenticate ( account_id . as_str ( ) , cred . as_str ( ) )
. await
2020-06-18 02:30:42 +02:00
. map ( ClientResponse ::PamStatus )
2020-02-29 05:02:14 +01:00
. unwrap_or ( ClientResponse ::Error )
}
ClientRequest ::PamAccountAllowed ( account_id ) = > {
debug! ( " pam account allowed " ) ;
cachelayer
. pam_account_allowed ( account_id . as_str ( ) )
. await
2020-06-18 02:30:42 +02:00
. map ( ClientResponse ::PamStatus )
2020-02-29 05:02:14 +01:00
. unwrap_or ( ClientResponse ::Error )
}
2021-03-13 03:33:15 +01:00
ClientRequest ::PamAccountBeginSession ( account_id ) = > {
debug! ( " pam account begin session " ) ;
match cachelayer
. pam_account_beginsession ( account_id . as_str ( ) )
. await
{
Ok ( Some ( info ) ) = > {
let ( tx , rx ) = oneshot ::channel ( ) ;
match task_channel_tx
. send_timeout (
( TaskRequest ::HomeDirectory ( info ) , tx ) ,
Duration ::from_millis ( 100 ) ,
)
. await
{
Ok ( ( ) ) = > {
2022-09-08 05:37:03 +02:00
// Now wait for the other end OR timeout.
2021-03-13 03:33:15 +01:00
match time ::timeout_at (
time ::Instant ::now ( ) + Duration ::from_millis ( 1000 ) ,
rx ,
)
. await
{
Ok ( Ok ( _ ) ) = > {
debug! ( " Task completed, returning to pam ... " ) ;
ClientResponse ::Ok
}
_ = > {
// Timeout or other error.
ClientResponse ::Error
}
}
}
Err ( _ ) = > {
// We could not submit the req. Move on!
ClientResponse ::Error
}
}
}
_ = > ClientResponse ::Error ,
}
}
2020-02-15 01:27:25 +01:00
ClientRequest ::InvalidateCache = > {
debug! ( " invalidate cache " ) ;
cachelayer
. invalidate ( )
2020-08-04 04:58:11 +02:00
. await
2020-02-15 01:27:25 +01:00
. map ( | _ | ClientResponse ::Ok )
. unwrap_or ( ClientResponse ::Error )
}
ClientRequest ::ClearCache = > {
debug! ( " clear cache " ) ;
2023-07-24 02:05:10 +02:00
if ucred . uid ( ) = = 0 {
cachelayer
. clear_cache ( )
. await
. map ( | _ | ClientResponse ::Ok )
. unwrap_or ( ClientResponse ::Error )
} else {
error! ( " Only root may clear the cache " ) ;
ClientResponse ::Error
}
2020-02-15 01:27:25 +01:00
}
ClientRequest ::Status = > {
debug! ( " status check " ) ;
if cachelayer . test_connection ( ) . await {
ClientResponse ::Ok
} else {
ClientResponse ::Error
}
}
} ;
reqs . send ( resp ) . await ? ;
reqs . flush ( ) . await ? ;
debug! ( " flushed response! " ) ;
2020-02-13 00:43:01 +01:00
}
// Disconnect them
debug! ( " Disconnecting client ... " ) ;
Ok ( ( ) )
}
2023-07-28 02:48:56 +02:00
async fn process_etc_passwd_group (
cachelayer : & Resolver < KanidmProvider > ,
) -> Result < ( ) , Box < dyn Error > > {
2023-06-15 05:24:53 +02:00
let mut file = File ::open ( " /etc/passwd " ) . await ? ;
let mut contents = vec! [ ] ;
file . read_to_end ( & mut contents ) . await ? ;
2023-07-05 14:26:39 +02:00
let users = parse_etc_passwd ( contents . as_slice ( ) ) . map_err ( | _ | " Invalid passwd content " ) ? ;
2023-06-15 05:24:53 +02:00
let mut file = File ::open ( " /etc/group " ) . await ? ;
let mut contents = vec! [ ] ;
file . read_to_end ( & mut contents ) . await ? ;
2023-07-05 14:26:39 +02:00
let groups = parse_etc_group ( contents . as_slice ( ) ) . map_err ( | _ | " Invalid group content " ) ? ;
2023-06-15 05:24:53 +02:00
let id_iter = users
. iter ( )
. map ( | user | ( user . name . clone ( ) , user . uid ) )
. chain ( groups . iter ( ) . map ( | group | ( group . name . clone ( ) , group . gid ) ) ) ;
cachelayer . reload_nxset ( id_iter ) . await ;
Ok ( ( ) )
}
2023-03-20 02:47:19 +01:00
#[ tokio::main(flavor = " current_thread " ) ]
2023-03-20 04:15:44 +01:00
async fn main ( ) -> ExitCode {
2020-07-28 08:55:58 +02:00
let cuid = get_current_uid ( ) ;
let ceuid = get_effective_uid ( ) ;
let cgid = get_current_gid ( ) ;
let cegid = get_effective_gid ( ) ;
2022-09-08 05:37:03 +02:00
let clap_args = Command ::new ( " kanidm_unixd " )
. version ( env! ( " CARGO_PKG_VERSION " ) )
. about ( " Kanidm Unix daemon " )
. arg (
Arg ::new ( " skip-root-check " )
. help ( " Allow running as root. Don't use this in production as it is risky! " )
. short ( 'r' )
. long ( " skip-root-check " )
. action ( ArgAction ::SetTrue ) ,
)
. arg (
Arg ::new ( " debug " )
. help ( " Show extra debug information " )
. short ( 'd' )
. long ( " debug " )
. action ( ArgAction ::SetTrue ) ,
)
. arg (
Arg ::new ( " configtest " )
. help ( " Display the configuration and exit " )
. short ( 't' )
. long ( " configtest " )
. action ( ArgAction ::SetTrue ) ,
)
. arg (
Arg ::new ( " unixd-config " )
. help ( " Set the unixd config file path " )
. short ( 'u' )
. long ( " unixd-config " )
. default_value ( DEFAULT_CONFIG_PATH )
. env ( " KANIDM_UNIX_CONFIG " )
2023-07-13 04:19:28 +02:00
. action ( ArgAction ::Set ) ,
2022-09-08 05:37:03 +02:00
)
. arg (
Arg ::new ( " client-config " )
. help ( " Set the client config file path " )
. short ( 'c' )
. long ( " client-config " )
. default_value ( DEFAULT_CLIENT_CONFIG_PATH )
. env ( " KANIDM_CLIENT_CONFIG " )
2023-07-13 04:19:28 +02:00
. action ( ArgAction ::Set ) ,
2022-09-08 05:37:03 +02:00
)
. get_matches ( ) ;
if clap_args . get_flag ( " debug " ) {
std ::env ::set_var ( " RUST_LOG " , " debug " ) ;
2020-07-28 08:55:58 +02:00
}
2023-07-05 14:26:39 +02:00
#[ allow(clippy::expect_used) ]
2022-08-09 05:07:06 +02:00
tracing_forest ::worker_task ( )
. set_global ( true )
// Fall back to stderr
. map_sender ( | sender | sender . or_stderr ( ) )
. build_on ( | subscriber | subscriber
. with ( EnvFilter ::try_from_default_env ( )
. or_else ( | _ | EnvFilter ::try_new ( " info " ) )
. expect ( " Failed to init envfilter " )
)
)
. on ( async {
2023-03-20 02:47:19 +01:00
if clap_args . get_flag ( " skip-root-check " ) {
warn! ( " Skipping root user check, if you're running this for testing, ensure you clean up temporary files. " )
// TODO: this wording is not great m'kay.
} else if cuid = = 0 | | ceuid = = 0 | | cgid = = 0 | | cegid = = 0 {
error! ( " Refusing to run - this process must not operate as root. " ) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2023-03-20 02:47:19 +01:00
} ;
2022-08-09 05:07:06 +02:00
debug! ( " Profile -> {} " , env! ( " KANIDM_PROFILE_NAME " ) ) ;
debug! ( " CPU Flags -> {} " , env! ( " KANIDM_CPU_FLAGS " ) ) ;
2023-03-20 02:47:19 +01:00
let Some ( cfg_path_str ) = clap_args . get_one ::< String > ( " client-config " ) else {
error! ( " Failed to pull the client config path " ) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2023-03-20 02:47:19 +01:00
} ;
2022-09-08 05:37:03 +02:00
let cfg_path : PathBuf = PathBuf ::from ( cfg_path_str ) ;
2022-08-09 05:07:06 +02:00
if ! cfg_path . exists ( ) {
// there's no point trying to start up if we can't read a usable config!
2020-07-28 08:55:58 +02:00
error! (
2022-08-09 05:07:06 +02:00
" Client config missing from {} - cannot start up. Quitting. " ,
cfg_path_str
2020-07-28 08:55:58 +02:00
) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2022-09-08 05:37:03 +02:00
} else {
2022-08-09 05:07:06 +02:00
let cfg_meta = match metadata ( & cfg_path ) {
Ok ( v ) = > v ,
Err ( e ) = > {
error! ( " Unable to read metadata for {} - {:?} " , cfg_path_str , e ) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2022-08-09 05:07:06 +02:00
}
} ;
2023-03-01 04:10:52 +01:00
if ! kanidm_lib_file_permissions ::readonly ( & cfg_meta ) {
2022-08-09 05:07:06 +02:00
warn! ( " permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ... " ,
cfg_path_str
) ;
}
2020-07-28 08:55:58 +02:00
2022-08-09 05:07:06 +02:00
if cfg_meta . uid ( ) = = cuid | | cfg_meta . uid ( ) = = ceuid {
warn! ( " WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ... " ,
cfg_path_str
2020-07-28 08:55:58 +02:00
) ;
2022-08-09 05:07:06 +02:00
}
}
2023-03-20 02:47:19 +01:00
let Some ( unixd_path_str ) = clap_args . get_one ::< String > ( " unixd-config " ) else {
error! ( " Failed to pull the unixd config path " ) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2023-03-20 02:47:19 +01:00
} ;
2022-09-08 05:37:03 +02:00
let unixd_path = PathBuf ::from ( unixd_path_str ) ;
2022-08-09 05:07:06 +02:00
if ! unixd_path . exists ( ) {
// there's no point trying to start up if we can't read a usable config!
2020-07-28 08:55:58 +02:00
error! (
2022-08-09 05:07:06 +02:00
" unixd config missing from {} - cannot start up. Quitting. " ,
unixd_path_str
2020-07-28 08:55:58 +02:00
) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2022-08-09 05:07:06 +02:00
} else {
let unixd_meta = match metadata ( & unixd_path ) {
Ok ( v ) = > v ,
Err ( e ) = > {
error! ( " Unable to read metadata for {} - {:?} " , unixd_path_str , e ) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2022-08-09 05:07:06 +02:00
}
} ;
2023-03-01 04:10:52 +01:00
if ! kanidm_lib_file_permissions ::readonly ( & unixd_meta ) {
2022-08-09 05:07:06 +02:00
warn! ( " permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ... " ,
unixd_path_str ) ;
}
2020-07-28 08:55:58 +02:00
2022-08-09 05:07:06 +02:00
if unixd_meta . uid ( ) = = cuid | | unixd_meta . uid ( ) = = ceuid {
warn! ( " WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ... " ,
unixd_path_str
) ;
}
2020-07-28 08:55:58 +02:00
}
2021-06-15 03:54:04 +02:00
2022-08-09 05:07:06 +02:00
// setup
2022-09-08 05:37:03 +02:00
let cb = match KanidmClientBuilder ::new ( ) . read_options_from_optional_config ( & cfg_path ) {
2022-08-09 05:07:06 +02:00
Ok ( v ) = > v ,
Err ( _ ) = > {
error! ( " Failed to parse {} " , cfg_path_str ) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2022-08-09 05:07:06 +02:00
}
2021-06-15 03:54:04 +02:00
} ;
2022-09-08 05:37:03 +02:00
let cfg = match KanidmUnixdConfig ::new ( ) . read_options_from_optional_config ( & unixd_path ) {
2021-06-15 03:54:04 +02:00
Ok ( v ) = > v ,
2022-08-09 05:07:06 +02:00
Err ( _ ) = > {
error! ( " Failed to parse {} " , unixd_path_str ) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2021-06-15 03:54:04 +02:00
}
} ;
2020-07-28 08:55:58 +02:00
2022-09-08 05:37:03 +02:00
if clap_args . get_flag ( " configtest " ) {
eprintln! ( " ################################### " ) ;
eprintln! ( " Dumping configs: \n ################################### " ) ;
eprintln! ( " kanidm_unixd config (from {:#?} ) " , & unixd_path ) ;
eprintln! ( " {} " , cfg ) ;
eprintln! ( " ################################### " ) ;
eprintln! ( " Client config (from {:#?} ) " , & cfg_path ) ;
eprintln! ( " {} " , cb ) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::SUCCESS ;
2022-09-08 05:37:03 +02:00
}
2022-08-09 05:07:06 +02:00
debug! ( " 🧹 Cleaning up sockets from previous invocations " ) ;
rm_if_exist ( cfg . sock_path . as_str ( ) ) ;
rm_if_exist ( cfg . task_sock_path . as_str ( ) ) ;
2020-09-08 04:46:10 +02:00
2020-02-13 00:43:01 +01:00
2022-09-08 05:37:03 +02:00
// Check the db path will be okay.
2023-01-28 04:52:44 +01:00
if ! cfg . db_path . is_empty ( ) {
2022-08-09 05:07:06 +02:00
let db_path = PathBuf ::from ( cfg . db_path . as_str ( ) ) ;
// We only need to check the parent folder path permissions as the db itself may not exist yet.
if let Some ( db_parent_path ) = db_path . parent ( ) {
if ! db_parent_path . exists ( ) {
error! (
" Refusing to run, DB folder {} does not exist " ,
db_parent_path
. to_str ( )
2023-01-28 04:52:44 +01:00
. unwrap_or ( " <db_parent_path invalid> " )
2022-08-09 05:07:06 +02:00
) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2022-08-09 05:07:06 +02:00
}
let db_par_path_buf = db_parent_path . to_path_buf ( ) ;
let i_meta = match metadata ( & db_par_path_buf ) {
Ok ( v ) = > v ,
Err ( e ) = > {
error! (
" Unable to read metadata for {} - {:?} " ,
db_par_path_buf
. to_str ( )
2023-01-28 04:52:44 +01:00
. unwrap_or ( " <db_par_path_buf invalid> " ) ,
2022-08-09 05:07:06 +02:00
e
) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2021-03-13 03:33:15 +01:00
}
} ;
2022-08-09 05:07:06 +02:00
if ! i_meta . is_dir ( ) {
error! (
" Refusing to run - DB folder {} may not be a directory " ,
db_par_path_buf
. to_str ( )
2023-01-28 04:52:44 +01:00
. unwrap_or ( " <db_par_path_buf invalid> " )
2022-08-09 05:07:06 +02:00
) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2022-08-09 05:07:06 +02:00
}
2023-03-01 04:10:52 +01:00
if ! kanidm_lib_file_permissions ::readonly ( & i_meta ) {
2022-08-09 05:07:06 +02:00
warn! ( " WARNING: DB folder permissions on {} indicate it may not be RW. This could cause the server start up to fail! " , db_par_path_buf . to_str ( )
2023-01-28 04:52:44 +01:00
. unwrap_or ( " <db_par_path_buf invalid> " )
2022-08-09 05:07:06 +02:00
) ;
}
if i_meta . mode ( ) & 0o007 ! = 0 {
warn! ( " WARNING: DB folder {} has 'everyone' permission bits in the mode. This could be a security risk ... " , db_par_path_buf . to_str ( )
2023-01-28 04:52:44 +01:00
. unwrap_or ( " <db_par_path_buf invalid> " )
2022-08-09 05:07:06 +02:00
) ;
2021-03-13 03:33:15 +01:00
}
}
2022-08-09 05:07:06 +02:00
// check to see if the db's already there
if db_path . exists ( ) {
if ! db_path . is_file ( ) {
error! (
" Refusing to run - DB path {} already exists and is not a file. " ,
2023-01-28 04:52:44 +01:00
db_path . to_str ( ) . unwrap_or ( " <db_path invalid> " )
2022-08-09 05:07:06 +02:00
) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2022-08-09 05:07:06 +02:00
} ;
match metadata ( & db_path ) {
Ok ( v ) = > v ,
Err ( e ) = > {
error! (
" Unable to read metadata for {} - {:?} " ,
2023-01-28 04:52:44 +01:00
db_path . to_str ( ) . unwrap_or ( " <db_path invalid> " ) ,
2022-08-09 05:07:06 +02:00
e
) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2022-08-09 05:07:06 +02:00
}
} ;
// TODO: permissions dance to enumerate the user's ability to write to the file? ref #456 - r2d2 will happily keep trying to do things without bailing.
} ;
2021-03-13 03:33:15 +01:00
}
2022-08-09 05:07:06 +02:00
2022-09-08 05:37:03 +02:00
let cb = cb . connect_timeout ( cfg . conn_timeout ) ;
let rsclient = match cb . build ( ) {
Ok ( rsc ) = > rsc ,
Err ( _e ) = > {
error! ( " Failed to build async client " ) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2022-09-08 05:37:03 +02:00
}
} ;
2023-07-28 02:48:56 +02:00
let idprovider = KanidmProvider ::new ( rsclient ) ;
2023-07-24 09:10:37 +02:00
let db = match Db ::new ( cfg . db_path . as_str ( ) , & cfg . tpm_policy ) {
Ok ( db ) = > db ,
Err ( _e ) = > {
error! ( " Failed to create database " ) ;
return ExitCode ::FAILURE
}
} ;
let cl_inner = match Resolver ::new (
db ,
2023-07-28 02:48:56 +02:00
idprovider ,
2022-08-09 05:07:06 +02:00
cfg . cache_timeout ,
cfg . pam_allowed_login_groups . clone ( ) ,
cfg . default_shell . clone ( ) ,
cfg . home_prefix . clone ( ) ,
cfg . home_attr ,
cfg . home_alias ,
cfg . uid_attr_map ,
cfg . gid_attr_map ,
2023-06-15 05:24:53 +02:00
cfg . allow_local_account_override . clone ( ) ,
2022-08-09 05:07:06 +02:00
)
. await
{
Ok ( c ) = > c ,
Err ( _e ) = > {
error! ( " Failed to build cache layer. " ) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2022-08-09 05:07:06 +02:00
}
} ;
let cachelayer = Arc ::new ( cl_inner ) ;
2023-05-31 08:06:26 +02:00
// Setup the root-only socket. Take away all other access bits.
let before = unsafe { umask ( 0o0077 ) } ;
2022-08-09 05:07:06 +02:00
let task_listener = match UnixListener ::bind ( cfg . task_sock_path . as_str ( ) ) {
Ok ( l ) = > l ,
Err ( _e ) = > {
2023-07-10 02:58:35 +02:00
error! ( " Failed to bind UNIX socket {} " , cfg . task_sock_path . as_str ( ) ) ;
2023-03-20 04:15:44 +01:00
return ExitCode ::FAILURE
2022-08-09 05:07:06 +02:00
}
} ;
2023-05-31 08:06:26 +02:00
// Undo umask changes.
2022-08-09 05:07:06 +02:00
let _ = unsafe { umask ( before ) } ;
2023-06-15 05:24:53 +02:00
// Pre-process /etc/passwd and /etc/group for nxset
2023-07-05 14:26:39 +02:00
if process_etc_passwd_group ( & cachelayer ) . await . is_err ( ) {
2023-06-15 05:24:53 +02:00
error! ( " Failed to process system id providers " ) ;
return ExitCode ::FAILURE
}
2023-05-31 08:06:26 +02:00
// Setup the tasks socket first.
2022-08-09 05:07:06 +02:00
let ( task_channel_tx , mut task_channel_rx ) = channel ( 16 ) ;
let task_channel_tx = Arc ::new ( task_channel_tx ) ;
let task_channel_tx_cln = task_channel_tx . clone ( ) ;
2023-05-25 02:43:26 +02:00
// Start to build the worker tasks
let ( broadcast_tx , mut broadcast_rx ) = broadcast ::channel ( 4 ) ;
let mut c_broadcast_rx = broadcast_tx . subscribe ( ) ;
2023-06-21 12:33:01 +02:00
let mut d_broadcast_rx = broadcast_tx . subscribe ( ) ;
2023-05-25 02:43:26 +02:00
let task_b = tokio ::spawn ( async move {
2022-08-09 05:07:06 +02:00
loop {
2023-05-25 02:43:26 +02:00
tokio ::select! {
_ = c_broadcast_rx . recv ( ) = > {
break ;
}
accept_res = task_listener . accept ( ) = > {
match accept_res {
Ok ( ( socket , _addr ) ) = > {
// Did it come from root?
if let Ok ( ucred ) = socket . peer_cred ( ) {
2023-07-24 02:05:10 +02:00
if ucred . uid ( ) ! = 0 {
2023-05-25 02:43:26 +02:00
// move along.
2023-07-24 02:05:10 +02:00
warn! ( " Task handler not running as root, ignoring ... " ) ;
2023-05-25 02:43:26 +02:00
continue ;
}
} else {
// move along.
2023-07-24 02:05:10 +02:00
warn! ( " Unable to determine socked peer cred, ignoring ... " ) ;
2023-05-25 02:43:26 +02:00
continue ;
} ;
debug! ( " A task handler has connected. " ) ;
// It did? Great, now we can wait and spin on that one
// client.
2023-06-21 12:33:01 +02:00
tokio ::select! {
_ = d_broadcast_rx . recv ( ) = > {
break ;
}
// We have to check for signals here else this tasks waits forever.
Err ( e ) = handle_task_client ( socket , & task_channel_tx , & mut task_channel_rx ) = > {
error! ( " Task client error occurred; error = {:?} " , e ) ;
}
2023-05-25 02:43:26 +02:00
}
// If they DC we go back to accept.
}
Err ( err ) = > {
error! ( " Task Accept error -> {:?} " , err ) ;
2022-08-09 05:07:06 +02:00
}
}
}
}
// done
2020-02-13 00:43:01 +01:00
}
2023-06-21 12:33:01 +02:00
info! ( " Stopped task connector " ) ;
2022-08-09 05:07:06 +02:00
} ) ;
// TODO: Setup a task that handles pre-fetching here.
2023-05-31 08:06:26 +02:00
2023-06-15 05:24:53 +02:00
let ( inotify_tx , mut inotify_rx ) = channel ( 4 ) ;
2023-06-21 12:33:01 +02:00
let watcher =
2023-06-15 05:24:53 +02:00
match new_debouncer ( Duration ::from_secs ( 2 ) , None , move | _event | {
let _ = inotify_tx . try_send ( true ) ;
} )
. and_then ( | mut debouncer | {
debouncer . watcher ( ) . watch ( Path ::new ( " /etc/passwd " ) , RecursiveMode ::NonRecursive )
. map ( | ( ) | debouncer )
} )
. and_then ( | mut debouncer | debouncer . watcher ( ) . watch ( Path ::new ( " /etc/group " ) , RecursiveMode ::NonRecursive )
. map ( | ( ) | debouncer )
)
{
Ok ( watcher ) = > {
watcher
}
Err ( e ) = > {
error! ( " Failed to setup inotify {:?} " , e ) ;
return ExitCode ::FAILURE
}
} ;
let mut c_broadcast_rx = broadcast_tx . subscribe ( ) ;
let inotify_cachelayer = cachelayer . clone ( ) ;
let task_c = tokio ::spawn ( async move {
loop {
tokio ::select! {
_ = c_broadcast_rx . recv ( ) = > {
break ;
}
_ = inotify_rx . recv ( ) = > {
2023-07-05 14:26:39 +02:00
if process_etc_passwd_group ( & inotify_cachelayer ) . await . is_err ( ) {
2023-06-15 05:24:53 +02:00
error! ( " Failed to process system id providers " ) ;
}
}
}
}
2023-06-21 12:33:01 +02:00
info! ( " Stopped inotify watcher " ) ;
2023-06-15 05:24:53 +02:00
} ) ;
2023-05-31 08:06:26 +02:00
// Set the umask while we open the path for most clients.
let before = unsafe { umask ( 0 ) } ;
let listener = match UnixListener ::bind ( cfg . sock_path . as_str ( ) ) {
Ok ( l ) = > l ,
Err ( _e ) = > {
error! ( " Failed to bind UNIX socket at {} " , cfg . sock_path . as_str ( ) ) ;
return ExitCode ::FAILURE
}
} ;
// Undo umask changes.
let _ = unsafe { umask ( before ) } ;
2023-05-25 02:43:26 +02:00
let task_a = tokio ::spawn ( async move {
2022-08-09 05:07:06 +02:00
loop {
let tc_tx = task_channel_tx_cln . clone ( ) ;
2023-05-25 02:43:26 +02:00
tokio ::select! {
_ = broadcast_rx . recv ( ) = > {
break ;
2022-08-09 05:07:06 +02:00
}
2023-05-25 02:43:26 +02:00
accept_res = listener . accept ( ) = > {
match accept_res {
Ok ( ( socket , _addr ) ) = > {
let cachelayer_ref = cachelayer . clone ( ) ;
tokio ::spawn ( async move {
if let Err ( e ) = handle_client ( socket , cachelayer_ref . clone ( ) , & tc_tx ) . await
{
error! ( " handle_client error occurred; error = {:?} " , e ) ;
}
} ) ;
}
Err ( err ) = > {
error! ( " Error while handling connection -> {:?} " , err ) ;
}
}
2022-08-09 05:07:06 +02:00
}
}
2023-05-25 02:43:26 +02:00
2020-02-13 00:43:01 +01:00
}
2023-06-21 12:33:01 +02:00
info! ( " Stopped resolver " ) ;
2023-05-25 02:43:26 +02:00
} ) ;
2020-02-13 00:43:01 +01:00
2022-08-09 05:07:06 +02:00
info! ( " Server started ... " ) ;
2020-02-13 00:43:01 +01:00
2023-05-25 02:43:26 +02:00
loop {
tokio ::select! {
Ok ( ( ) ) = tokio ::signal ::ctrl_c ( ) = > {
break
}
Some ( ( ) ) = async move {
let sigterm = tokio ::signal ::unix ::SignalKind ::terminate ( ) ;
2023-07-05 14:26:39 +02:00
#[ allow(clippy::unwrap_used) ]
2023-05-25 02:43:26 +02:00
tokio ::signal ::unix ::signal ( sigterm ) . unwrap ( ) . recv ( ) . await
} = > {
break
}
Some ( ( ) ) = async move {
let sigterm = tokio ::signal ::unix ::SignalKind ::alarm ( ) ;
2023-07-05 14:26:39 +02:00
#[ allow(clippy::unwrap_used) ]
2023-05-25 02:43:26 +02:00
tokio ::signal ::unix ::signal ( sigterm ) . unwrap ( ) . recv ( ) . await
} = > {
// Ignore
}
Some ( ( ) ) = async move {
let sigterm = tokio ::signal ::unix ::SignalKind ::hangup ( ) ;
2023-07-05 14:26:39 +02:00
#[ allow(clippy::unwrap_used) ]
2023-05-25 02:43:26 +02:00
tokio ::signal ::unix ::signal ( sigterm ) . unwrap ( ) . recv ( ) . await
} = > {
// Ignore
}
Some ( ( ) ) = async move {
let sigterm = tokio ::signal ::unix ::SignalKind ::user_defined1 ( ) ;
2023-07-05 14:26:39 +02:00
#[ allow(clippy::unwrap_used) ]
2023-05-25 02:43:26 +02:00
tokio ::signal ::unix ::signal ( sigterm ) . unwrap ( ) . recv ( ) . await
} = > {
// Ignore
}
Some ( ( ) ) = async move {
let sigterm = tokio ::signal ::unix ::SignalKind ::user_defined2 ( ) ;
2023-07-05 14:26:39 +02:00
#[ allow(clippy::unwrap_used) ]
2023-05-25 02:43:26 +02:00
tokio ::signal ::unix ::signal ( sigterm ) . unwrap ( ) . recv ( ) . await
} = > {
// Ignore
}
}
}
2023-06-21 12:33:01 +02:00
info! ( " Signal received, sending down signal to tasks " ) ;
2023-05-25 02:43:26 +02:00
// Send a broadcast that we are done.
if let Err ( e ) = broadcast_tx . send ( true ) {
error! ( " Unable to shutdown workers {:?} " , e ) ;
}
2023-06-21 12:33:01 +02:00
drop ( watcher ) ;
2023-05-25 02:43:26 +02:00
let _ = task_a . await ;
let _ = task_b . await ;
2023-06-15 05:24:53 +02:00
let _ = task_c . await ;
2023-05-25 02:43:26 +02:00
2023-03-20 04:15:44 +01:00
ExitCode ::SUCCESS
2022-08-09 05:07:06 +02:00
} )
2023-03-20 04:15:44 +01:00
. await
2022-09-08 05:37:03 +02:00
// TODO: can we catch signals to clean up sockets etc, especially handy when running as root
2020-02-13 00:43:01 +01:00
}