1
mod create_db;
2
mod create_user;
3
mod drop_db;
4
mod drop_user;
5
mod edit_privs;
6
mod lock_user;
7
mod passwd_user;
8
mod show_db;
9
mod show_privs;
10
mod show_user;
11
mod unlock_user;
12

            
13
pub use create_db::*;
14
pub use create_user::*;
15
pub use drop_db::*;
16
pub use drop_user::*;
17
pub use edit_privs::*;
18
pub use lock_user::*;
19
pub use passwd_user::*;
20
pub use show_db::*;
21
pub use show_privs::*;
22
pub use show_user::*;
23
pub use unlock_user::*;
24

            
25
use clap::Parser;
26

            
27
use crate::core::protocol::{ClientToServerMessageStream, Response};
28

            
29
#[derive(Parser, Debug, Clone)]
30
pub enum ClientCommand {
31
    /// Create one or more databases
32
    #[command()]
33
    CreateDb(CreateDbArgs),
34

            
35
    /// Delete one or more databases
36
    #[command()]
37
    DropDb(DropDbArgs),
38

            
39
    /// Print information about one or more databases
40
    ///
41
    /// If no database name is provided, all databases you have access will be shown.
42
    #[command()]
43
    ShowDb(ShowDbArgs),
44

            
45
    /// Print user privileges for one or more databases
46
    ///
47
    /// If no database names are provided, all databases you have access to will be shown.
48
    #[command()]
49
    ShowPrivs(ShowPrivsArgs),
50

            
51
    /// Change user privileges for one or more databases. See `edit-privs --help` for details.
52
    ///
53
    /// This command has two modes of operation:
54
    ///
55
    /// 1. Interactive mode: If nothing else is specified, the user will be prompted to edit the privileges using a text editor.
56
    ///
57
    ///    You can configure your preferred text editor by setting the `VISUAL` or `EDITOR` environment variables.
58
    ///
59
    ///    Follow the instructions inside the editor for more information.
60
    ///
61
    /// 2. Non-interactive mode: If the `-p` flag is specified, the user can write privileges using arguments.
62
    ///
63
    ///    The privilege arguments should be formatted as `<db>:<user>:<op><privileges>`
64
    ///    where the privileges are a string of characters, each representing a single privilege.
65
    ///    The character `A` is an exception - it represents all privileges.
66
    ///
67
    ///    The `<op>` character is optional and can be either `+` to grant additional privileges
68
    ///    or `-` to revoke privileges. If omitted, the privileges will be set exactly as specified,
69
    ///    removing any privileges not listed, and adding any that are.
70
    ///
71
    ///    The character-to-privilege mapping is defined as follows:
72
    ///
73
    ///    - `s` - SELECT
74
    ///    - `i` - INSERT
75
    ///    - `u` - UPDATE
76
    ///    - `d` - DELETE
77
    ///    - `c` - CREATE
78
    ///    - `D` - DROP
79
    ///    - `a` - ALTER
80
    ///    - `I` - INDEX
81
    ///    - `t` - CREATE TEMPORARY TABLES
82
    ///    - `l` - LOCK TABLES
83
    ///    - `r` - REFERENCES
84
    ///    - `A` - ALL PRIVILEGES
85
    ///
86
    ///   If you provide a database name, you can omit it from the privilege string,
87
    ///   e.g. `edit-privs my_db -p my_user:siu` is equivalent to `edit-privs -p my_db:my_user:siu`.
88
    ///   While it doesn't make much of a difference for a single edit, it can be useful for editing multiple users
89
    ///   on the same database at once.
90
    ///
91
    ///   Example usage of non-interactive mode:
92
    ///
93
    ///     Enable privileges `SELECT`, `INSERT`, and `UPDATE` for user `my_user` on database `my_db`:
94
    ///
95
    ///       `muscl edit-privs -p my_db:my_user:siu`
96
    ///
97
    ///     Enable all privileges for user `my_other_user` on database `my_other_db`:
98
    ///
99
    ///       `muscl edit-privs -p my_other_db:my_other_user:A`
100
    ///
101
    ///     Set miscellaneous privileges for multiple users on database `my_db`:
102
    ///
103
    ///       `muscl edit-privs my_db -p my_user:siu my_other_user:ct``
104
    ///
105
    ///     Add the `DELETE` privilege for user `my_user` on database `my_db`:
106
    ///
107
    ///       `muscl edit-privs my_db -p my_user:+d
108
    ///
109
    #[command(verbatim_doc_comment)]
110
    EditPrivs(EditPrivsArgs),
111

            
112
    /// Create one or more users
113
    #[command()]
114
    CreateUser(CreateUserArgs),
115

            
116
    /// Delete one or more users
117
    #[command()]
118
    DropUser(DropUserArgs),
119

            
120
    /// Change the MySQL password for a user
121
    #[command()]
122
    PasswdUser(PasswdUserArgs),
123

            
124
    /// Print information about one or more users
125
    ///
126
    /// If no username is provided, all users you have access will be shown.
127
    #[command()]
128
    ShowUser(ShowUserArgs),
129

            
130
    /// Lock account for one or more users
131
    #[command()]
132
    LockUser(LockUserArgs),
133

            
134
    /// Unlock account for one or more users
135
    #[command()]
136
    UnlockUser(UnlockUserArgs),
137
}
138

            
139
pub async fn handle_command(
140
    command: ClientCommand,
141
    server_connection: ClientToServerMessageStream,
142
) -> anyhow::Result<()> {
143
    match command {
144
        ClientCommand::CreateDb(args) => create_databases(args, server_connection).await,
145
        ClientCommand::DropDb(args) => drop_databases(args, server_connection).await,
146
        ClientCommand::ShowDb(args) => show_databases(args, server_connection).await,
147
        ClientCommand::ShowPrivs(args) => show_database_privileges(args, server_connection).await,
148
        ClientCommand::EditPrivs(args) => edit_database_privileges(args, server_connection).await,
149
        ClientCommand::CreateUser(args) => create_users(args, server_connection).await,
150
        ClientCommand::DropUser(args) => drop_users(args, server_connection).await,
151
        ClientCommand::PasswdUser(args) => passwd_user(args, server_connection).await,
152
        ClientCommand::ShowUser(args) => show_users(args, server_connection).await,
153
        ClientCommand::LockUser(args) => lock_users(args, server_connection).await,
154
        ClientCommand::UnlockUser(args) => unlock_users(args, server_connection).await,
155
    }
156
}
157

            
158
pub fn erroneous_server_response(
159
    response: Option<Result<Response, std::io::Error>>,
160
) -> anyhow::Result<()> {
161
    match response {
162
        Some(Ok(Response::Error(e))) => {
163
            anyhow::bail!("Server returned error: {}", e);
164
        }
165
        Some(Err(e)) => {
166
            anyhow::bail!(e);
167
        }
168
        Some(response) => {
169
            anyhow::bail!("Unexpected response from server: {:?}", response);
170
        }
171
        None => {
172
            anyhow::bail!("No response from server");
173
        }
174
    }
175
}