1
use clap::{Parser, Subcommand};
2
use clap_complete::ArgValueCompleter;
3
use futures_util::{SinkExt, StreamExt};
4
use std::path::PathBuf;
5

            
6
use std::os::unix::net::UnixStream as StdUnixStream;
7
use tokio::net::UnixStream as TokioUnixStream;
8

            
9
use crate::{
10
    client::{
11
        commands::{erroneous_server_response, read_password_from_stdin_with_double_check},
12
        mysql_admutils_compatibility::{
13
            common::trim_user_name_to_32_chars,
14
            error_messages::{
15
                handle_create_user_error, handle_drop_user_error, handle_list_users_error,
16
            },
17
        },
18
    },
19
    core::{
20
        bootstrap::bootstrap_server_connection_and_drop_privileges,
21
        completion::{mysql_user_completer, prefix_completer},
22
        protocol::{
23
            ClientToServerMessageStream, ListUsersRequest, PasswordSource, Request, Response,
24
            create_client_to_server_message_stream,
25
        },
26
        types::MySQLUser,
27
    },
28
    server::sql::user_operations::DatabaseUser,
29
};
30

            
31
/// Create, delete or change password for the USER(s),
32
/// as determined by the COMMAND.
33
///
34
/// This is a compatibility layer for the 'mysql-useradm' command.
35
/// Please consider using the newer 'muscl' command instead.
36
#[derive(Parser)]
37
#[command(
38
    bin_name = "mysql-useradm",
39
    version,
40
    about,
41
    disable_help_subcommand = true,
42
    verbatim_doc_comment
43
)]
44
pub struct Args {
45
    #[command(subcommand)]
46
    pub command: Option<Command>,
47

            
48
    /// Path to the socket of the server, if it already exists.
49
    #[arg(
50
        short,
51
        long,
52
        value_name = "PATH",
53
        value_hint = clap::ValueHint::FilePath,
54
        global = true,
55
        hide_short_help = true
56
    )]
57
    server_socket_path: Option<PathBuf>,
58

            
59
    /// Config file to use for the server.
60
    #[arg(
61
        short,
62
        long,
63
        value_name = "PATH",
64
        value_hint = clap::ValueHint::FilePath,
65
        global = true,
66
        hide_short_help = true
67
    )]
68
    config: Option<PathBuf>,
69
}
70

            
71
#[derive(Subcommand)]
72
pub enum Command {
73
    /// create the USER(s).
74
    Create(CreateArgs),
75

            
76
    /// delete the USER(s).
77
    Delete(DeleteArgs),
78

            
79
    /// change the `MySQL` password for the USER(s).
80
    Passwd(PasswdArgs),
81

            
82
    /// give information about the USERS(s), or, if
83
    /// none are given, all the users you have.
84
    Show(ShowArgs),
85
}
86

            
87
#[derive(Parser)]
88
pub struct CreateArgs {
89
    /// The name of the USER(s) to create.
90
    #[arg(num_args = 1..)]
91
    #[cfg_attr(not(feature = "suid-sgid-mode"), arg(add = ArgValueCompleter::new(prefix_completer)))]
92
    name: Vec<MySQLUser>,
93
}
94

            
95
#[derive(Parser)]
96
pub struct DeleteArgs {
97
    /// The name of the USER(s) to delete.
98
    #[arg(num_args = 1..)]
99
    #[cfg_attr(not(feature = "suid-sgid-mode"), arg(add = ArgValueCompleter::new(mysql_user_completer)))]
100
    name: Vec<MySQLUser>,
101
}
102

            
103
#[derive(Parser)]
104
pub struct PasswdArgs {
105
    /// The name of the USER(s) to change the password for.
106
    #[arg(num_args = 1..)]
107
    #[cfg_attr(not(feature = "suid-sgid-mode"), arg(add = ArgValueCompleter::new(mysql_user_completer)))]
108
    name: Vec<MySQLUser>,
109
}
110

            
111
#[derive(Parser)]
112
pub struct ShowArgs {
113
    /// The name of the USER(s) to show.
114
    #[arg(num_args = 0..)]
115
    #[cfg_attr(not(feature = "suid-sgid-mode"), arg(add = ArgValueCompleter::new(mysql_user_completer)))]
116
    name: Vec<MySQLUser>,
117
}
118

            
119
/// **WARNING:** This function may be run with elevated privileges.
120
pub fn main() -> anyhow::Result<()> {
121
    let args: Args = Args::parse();
122

            
123
    let Some(command) = args.command else {
124
        println!(
125
            "Try `{} --help' for more information.",
126
            std::env::args()
127
                .next()
128
                .unwrap_or("mysql-useradm".to_string())
129
        );
130
        return Ok(());
131
    };
132

            
133
    let server_connection = bootstrap_server_connection_and_drop_privileges(
134
        args.server_socket_path,
135
        args.config,
136
        Default::default(),
137
    )?;
138

            
139
    tokio_run_command(command, server_connection)?;
140

            
141
    Ok(())
142
}
143

            
144
fn tokio_run_command(command: Command, server_connection: StdUnixStream) -> anyhow::Result<()> {
145
    tokio::runtime::Builder::new_current_thread()
146
        .enable_all()
147
        .build()
148
        .unwrap()
149
        .block_on(async {
150
            let tokio_socket = TokioUnixStream::from_std(server_connection)?;
151
            let mut message_stream = create_client_to_server_message_stream(tokio_socket);
152

            
153
            while let Some(Ok(message)) = message_stream.next().await {
154
                match message {
155
                    Response::Error(err) => {
156
                        anyhow::bail!("{err}");
157
                    }
158
                    Response::Ready => break,
159
                    message => {
160
                        eprintln!("Unexpected message from server: {message:?}");
161
                    }
162
                }
163
            }
164

            
165
            match command {
166
                Command::Create(args) => create_user(args, message_stream).await,
167
                Command::Delete(args) => drop_users(args, message_stream).await,
168
                Command::Passwd(args) => passwd_users(args, message_stream).await,
169
                Command::Show(args) => show_users(args, message_stream).await,
170
            }
171
        })
172
}
173

            
174
async fn create_user(
175
    args: CreateArgs,
176
    mut server_connection: ClientToServerMessageStream,
177
) -> anyhow::Result<()> {
178
    let db_users = args.name.iter().map(trim_user_name_to_32_chars).collect();
179

            
180
    let message = Request::CreateUsers(db_users);
181
    server_connection.send(message).await?;
182

            
183
    let result = match server_connection.next().await {
184
        Some(Ok(Response::CreateUsers(result))) => result,
185
        response => return erroneous_server_response(response),
186
    };
187

            
188
    server_connection.send(Request::Exit).await?;
189

            
190
    for (name, result) in result {
191
        match result {
192
            Ok(()) => println!("User '{name}' created."),
193
            Err(err) => handle_create_user_error(&err, &name),
194
        }
195
    }
196

            
197
    Ok(())
198
}
199

            
200
async fn drop_users(
201
    args: DeleteArgs,
202
    mut server_connection: ClientToServerMessageStream,
203
) -> anyhow::Result<()> {
204
    let db_users = args.name.iter().map(trim_user_name_to_32_chars).collect();
205

            
206
    let message = Request::DropUsers(db_users);
207
    server_connection.send(message).await?;
208

            
209
    let result = match server_connection.next().await {
210
        Some(Ok(Response::DropUsers(result))) => result,
211
        response => return erroneous_server_response(response),
212
    };
213

            
214
    server_connection.send(Request::Exit).await?;
215

            
216
    for (name, result) in result {
217
        match result {
218
            Ok(()) => println!("User '{name}' deleted."),
219
            Err(err) => handle_drop_user_error(&err, &name),
220
        }
221
    }
222

            
223
    Ok(())
224
}
225

            
226
async fn passwd_users(
227
    args: PasswdArgs,
228
    mut server_connection: ClientToServerMessageStream,
229
) -> anyhow::Result<()> {
230
    let db_users = args.name.iter().map(trim_user_name_to_32_chars).collect();
231

            
232
    let message = Request::ListUsers(ListUsersRequest::new(Some(db_users), false));
233
    server_connection.send(message).await?;
234

            
235
    let response = match server_connection.next().await {
236
        Some(Ok(Response::ListUsers(result))) => result,
237
        response => return erroneous_server_response(response),
238
    };
239

            
240
    let argv0 = std::env::args()
241
        .next()
242
        .unwrap_or("mysql-useradm".to_string());
243

            
244
    let users = response
245
        .into_iter()
246
        .filter_map(|(name, result)| match result {
247
            Ok(user) => Some(user),
248
            Err(err) => {
249
                handle_list_users_error(&err, &name);
250
                None
251
            }
252
        })
253
        .collect::<Vec<_>>();
254

            
255
    for user in users {
256
        let password = read_password_from_stdin_with_double_check(&user.user)?;
257
        let message = Request::PasswdUser((user.user.clone(), PasswordSource::Explicit(password)));
258
        server_connection.send(message).await?;
259
        match server_connection.next().await {
260
            Some(Ok(Response::SetUserPassword(result))) => match result {
261
                Ok(_) => println!("Password updated for user '{}'.", user.user),
262
                Err(_) => eprintln!(
263
                    "{}: Failed to update password for user '{}'.",
264
                    argv0, user.user,
265
                ),
266
            },
267
            response => return erroneous_server_response(response),
268
        }
269
    }
270

            
271
    server_connection.send(Request::Exit).await?;
272

            
273
    Ok(())
274
}
275

            
276
async fn show_users(
277
    args: ShowArgs,
278
    mut server_connection: ClientToServerMessageStream,
279
) -> anyhow::Result<()> {
280
    let db_users: Vec<_> = args.name.iter().map(trim_user_name_to_32_chars).collect();
281

            
282
    let message = if db_users.is_empty() {
283
        Request::ListUsers(ListUsersRequest::new(None, false))
284
    } else {
285
        Request::ListUsers(ListUsersRequest::new(Some(db_users), false))
286
    };
287
    server_connection.send(message).await?;
288

            
289
    let users: Vec<DatabaseUser> = match server_connection.next().await {
290
        Some(Ok(Response::ListAllUsers(result))) => match result {
291
            Ok(users) => users,
292
            Err(err) => {
293
                eprintln!("Failed to list users: {err:?}");
294
                return Ok(());
295
            }
296
        },
297
        Some(Ok(Response::ListUsers(result))) => result
298
            .into_iter()
299
            .filter_map(|(name, result)| match result {
300
                Ok(user) => Some(user),
301
                Err(err) => {
302
                    handle_list_users_error(&err, &name);
303
                    None
304
                }
305
            })
306
            .collect(),
307
        response => return erroneous_server_response(response),
308
    };
309

            
310
    server_connection.send(Request::Exit).await?;
311

            
312
    for user in users {
313
        if user.has_password {
314
            println!("User '{}': password set.", user.user);
315
        } else {
316
            println!("User '{}': no password set.", user.user);
317
        }
318
    }
319

            
320
    Ok(())
321
}