mysqladm/core/
bootstrap.rs1use std::{fs, path::PathBuf};
2
3use anyhow::Context;
4use clap_verbosity_flag::Verbosity;
5use nix::libc::{EXIT_SUCCESS, exit};
6use std::os::unix::net::UnixStream as StdUnixStream;
7use tokio::net::UnixStream as TokioUnixStream;
8
9use crate::{
10 core::common::{
11 DEFAULT_CONFIG_PATH, DEFAULT_SOCKET_PATH, UnixUser, executable_is_suid_or_sgid,
12 },
13 server::{config::read_config_from_path, server_loop::handle_requests_for_single_session},
14};
15
16fn will_connect_to_external_server(
21 server_socket_path: Option<&PathBuf>,
22 #[allow(unused_variables)] config_path: Option<&PathBuf>,
24) -> anyhow::Result<bool> {
25 if server_socket_path.is_some() {
26 return Ok(true);
27 }
28
29 #[cfg(feature = "suid-sgid-mode")]
30 if config_path.is_some() {
31 return Ok(false);
32 }
33
34 if fs::metadata(DEFAULT_SOCKET_PATH).is_ok() {
35 return Ok(true);
36 }
37
38 #[cfg(feature = "suid-sgid-mode")]
39 if fs::metadata(DEFAULT_CONFIG_PATH).is_ok() {
40 return Ok(false);
41 }
42
43 #[cfg(feature = "suid-sgid-mode")]
44 anyhow::bail!("No socket path or config path provided, and no default socket or config found");
45
46 #[cfg(not(feature = "suid-sgid-mode"))]
47 anyhow::bail!("No socket path provided, and no default socket found");
48}
49
50pub fn bootstrap_server_connection_and_drop_privileges(
72 server_socket_path: Option<PathBuf>,
73 config: Option<PathBuf>,
74 verbose: Verbosity,
75) -> anyhow::Result<StdUnixStream> {
76 if will_connect_to_external_server(server_socket_path.as_ref(), config.as_ref())? {
77 assert!(
78 !executable_is_suid_or_sgid()?,
79 "The executable should not be SUID or SGID when connecting to an external server"
80 );
81
82 env_logger::Builder::new()
83 .filter_level(verbose.log_level_filter())
84 .init();
85
86 connect_to_external_server(server_socket_path)
87 } else if cfg!(feature = "suid-sgid-mode") {
88 let server_connection = bootstrap_internal_server_and_drop_privs(config)?;
91
92 env_logger::Builder::new()
93 .filter_level(verbose.log_level_filter())
94 .init();
95
96 Ok(server_connection)
97 } else {
98 anyhow::bail!("SUID/SGID support is not enabled, cannot start internal server");
99 }
100}
101
102fn connect_to_external_server(
103 server_socket_path: Option<PathBuf>,
104) -> anyhow::Result<StdUnixStream> {
105 if let Some(socket_path) = server_socket_path {
107 log::debug!("Connecting to socket at {:?}", socket_path);
108 return match StdUnixStream::connect(socket_path) {
109 Ok(socket) => Ok(socket),
110 Err(e) => match e.kind() {
111 std::io::ErrorKind::NotFound => Err(anyhow::anyhow!("Socket not found")),
112 std::io::ErrorKind::PermissionDenied => Err(anyhow::anyhow!("Permission denied")),
113 _ => Err(anyhow::anyhow!("Failed to connect to socket: {}", e)),
114 },
115 };
116 }
117
118 if fs::metadata(DEFAULT_SOCKET_PATH).is_ok() {
119 log::debug!("Connecting to default socket at {:?}", DEFAULT_SOCKET_PATH);
120 return match StdUnixStream::connect(DEFAULT_SOCKET_PATH) {
121 Ok(socket) => Ok(socket),
122 Err(e) => match e.kind() {
123 std::io::ErrorKind::NotFound => Err(anyhow::anyhow!("Socket not found")),
124 std::io::ErrorKind::PermissionDenied => Err(anyhow::anyhow!("Permission denied")),
125 _ => Err(anyhow::anyhow!("Failed to connect to socket: {}", e)),
126 },
127 };
128 }
129
130 anyhow::bail!("No socket path provided, and no default socket found");
131}
132
133fn drop_privs() -> anyhow::Result<()> {
139 log::debug!("Dropping privileges");
140 let real_uid = nix::unistd::getuid();
141 let real_gid = nix::unistd::getgid();
142
143 nix::unistd::setuid(real_uid).context("Failed to drop privileges")?;
144 nix::unistd::setgid(real_gid).context("Failed to drop privileges")?;
145
146 debug_assert_eq!(nix::unistd::getuid(), real_uid);
147 debug_assert_eq!(nix::unistd::getgid(), real_gid);
148
149 log::debug!("Privileges dropped successfully");
150 Ok(())
151}
152
153fn bootstrap_internal_server_and_drop_privs(
154 config_path: Option<PathBuf>,
155) -> anyhow::Result<StdUnixStream> {
156 if let Some(config_path) = config_path {
157 if !executable_is_suid_or_sgid()? {
158 anyhow::bail!("Executable is not SUID/SGID - refusing to start internal sever");
159 }
160
161 if fs::metadata(&config_path).is_err() {
163 return Err(anyhow::anyhow!("Config file not found or not readable"));
164 }
165
166 log::debug!("Starting server with config at {:?}", config_path);
167 let socket = invoke_server_with_config(config_path)?;
168 drop_privs()?;
169 return Ok(socket);
170 };
171
172 let config_path = PathBuf::from(DEFAULT_CONFIG_PATH);
173 if fs::metadata(&config_path).is_ok() {
174 if !executable_is_suid_or_sgid()? {
175 anyhow::bail!("Executable is not SUID/SGID - refusing to start internal sever");
176 }
177 log::debug!("Starting server with default config at {:?}", config_path);
178 let socket = invoke_server_with_config(config_path)?;
179 drop_privs()?;
180 return Ok(socket);
181 };
182
183 anyhow::bail!("No config path provided, and no default config found");
184}
185
186fn invoke_server_with_config(config_path: PathBuf) -> anyhow::Result<StdUnixStream> {
192 let (server_socket, client_socket) = StdUnixStream::pair()?;
193 let unix_user = UnixUser::from_uid(nix::unistd::getuid().as_raw())?;
194
195 match (unsafe { nix::unistd::fork() }).context("Failed to fork")? {
196 nix::unistd::ForkResult::Parent { child } => {
197 log::debug!("Forked child process with PID {}", child);
198 Ok(client_socket)
199 }
200 nix::unistd::ForkResult::Child => {
201 log::debug!("Running server in child process");
202
203 match run_forked_server(config_path, server_socket, unix_user) {
204 Err(e) => Err(e),
205 Ok(_) => unreachable!(),
206 }
207 }
208 }
209}
210
211fn run_forked_server(
214 config_path: PathBuf,
215 server_socket: StdUnixStream,
216 unix_user: UnixUser,
217) -> anyhow::Result<()> {
218 let config = read_config_from_path(Some(config_path))?;
219
220 let result: anyhow::Result<()> = tokio::runtime::Builder::new_current_thread()
221 .enable_all()
222 .build()
223 .unwrap()
224 .block_on(async {
225 let socket = TokioUnixStream::from_std(server_socket)?;
226 handle_requests_for_single_session(socket, &unix_user, &config).await?;
227 Ok(())
228 });
229
230 result?;
231
232 unsafe {
233 exit(EXIT_SUCCESS);
234 }
235}