/* Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file @brief MySQL Configuration Utility */ /* ORACLE_WELCOME_COPYRIGHT_NOTICE */ #include "my_config.h" #include #include #include #include #include "my_aes.h" #include "client_priv.h" #include "my_default.h" #include "my_default_priv.h" #define MYSQL_CONFIG_EDITOR_VERSION "1.0" #define MY_LINE_MAX 4096 #define MAX_COMMAND_LIMIT 100 /* Header length for the login file. 4-byte (unused) + LOGIN_KEY_LEN */ #define MY_LOGIN_HEADER_LEN (4 + LOGIN_KEY_LEN) static int g_fd; /* Length of the contents in login file excluding the header part. */ static size_t file_size; static const char *opt_user= NULL, *opt_password= NULL, *opt_host=NULL, *opt_login_path= "client", *opt_socket= NULL, *opt_port= NULL; static char my_login_file[FN_REFLEN]; static char my_key[LOGIN_KEY_LEN]; static my_bool opt_verbose, opt_all, tty_password= 0, opt_warn, opt_remove_host, opt_remove_pass, opt_remove_user, opt_remove_socket, opt_remove_port, login_path_specified= FALSE; static int execute_commands(int command); static int set_command(void); static int remove_command(void); static int print_command(void); static void print_login_path(DYNAMIC_STRING *file_buf, const char *path_name); static void remove_login_path(DYNAMIC_STRING *file_buf, const char *path_name); static char* locate_login_path(DYNAMIC_STRING *file_buf, const char *path_name); static my_bool check_and_create_login_file(void); static void mask_password_and_print(char *buf); static int reset_login_file(bool gen_key); static int encrypt_buffer(const char *plain, int plain_len, char cipher[], const int aes_len); static int decrypt_buffer(const char *cipher, int cipher_len, char plain[]); static int encrypt_and_write_file(DYNAMIC_STRING *file_buf); static int read_and_decrypt_file(DYNAMIC_STRING *file_buf); static int do_handle_options(int argc, char *argv[]); static void remove_options(DYNAMIC_STRING *file_buf, const char *path_name); static void remove_option(DYNAMIC_STRING *file_buf, const char *path_name, const char* option_name); void generate_login_key(void); static int read_login_key(void); static int add_header(void); static void my_perror(const char *msg); static void verbose_msg(const char *fmt, ...); static void print_version(void); static void usage_program(void); static void usage_command(int command); extern "C" my_bool get_one_option(int optid, const struct my_option *opt, char *argument); enum commands { MY_CONFIG_SET, MY_CONFIG_REMOVE, MY_CONFIG_PRINT, MY_CONFIG_RESET, MY_CONFIG_HELP }; struct my_command_data { const int id; const char *name; const char *description; my_option *options; my_bool (*get_one_option_func)(int optid, const struct my_option *opt, char *argument); }; /* mysql_config_editor utility options. */ static struct my_option my_program_long_options[]= { #ifdef DBUG_OFF {"debug", '#', "This is a non-debug version. Catch this and exit.", 0, 0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0}, #else {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.", 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, #endif {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"verbose", 'v', "Write more information.", &opt_verbose, &opt_verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; /* Command-specific options. */ /* SET command options. */ static struct my_option my_set_command_options[]= { {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"host", 'h', "Host name to be entered into the login file.", &opt_host, &opt_host, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"login-path", 'G', "Name of the login path to use in the login file. " "(Default : client)", &opt_login_path, &opt_login_path, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"password", 'p', "Prompt for password to be entered into the login file.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"user", 'u', "User name to be entered into the login file.", &opt_user, &opt_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"socket", 'S', "Socket path to be entered into login file.", &opt_socket, &opt_socket, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"port", 'P', "Port number to be entered into login file.", &opt_port, &opt_port, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"warn", 'w', "Warn and ask for confirmation if set command attempts to " "overwrite an existing login path (enabled by default).", &opt_warn, &opt_warn, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; /* REMOVE command options. */ static struct my_option my_remove_command_options[]= { {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"host", 'h', "Remove host name from the login path.", &opt_remove_host, &opt_remove_host, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"login-path", 'G', "Name of the login path from which options to " "be removed (entire path would be removed if none of user, password, " "host, socket, or port options are specified). (Default : client)", &opt_login_path, &opt_login_path, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"password", 'p', "Remove password from the login path.", &opt_remove_pass, &opt_remove_pass, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"user", 'u', "Remove user name from the login path.", &opt_remove_user, &opt_remove_user, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"warn", 'w', "Warn and ask for confirmation if remove command attempts " "to remove the default login path (client) if no login path is specified " "(enabled by default).", &opt_warn, &opt_warn, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, {"socket", 'S', "Remove socket path from the login path.", &opt_remove_socket, &opt_remove_socket, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"port", 'P', "Remove port number from the login path.", &opt_remove_port, &opt_remove_port, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; /* PRINT command options. */ static struct my_option my_print_command_options[]= { {"all", OPT_CONFIG_ALL, "Used with print command to print all login paths.", &opt_all, &opt_all, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"login-path", 'G', "Name of the login path to use in the login file. " "(Default : client)", &opt_login_path, &opt_login_path, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; /* RESET command options. */ static struct my_option my_reset_command_options[]= { {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; /* HELP command options. */ static struct my_option my_help_command_options[]= { {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; my_bool my_program_get_one_option(int optid, const struct my_option *opt MY_ATTRIBUTE((unused)), char *argument) { switch(optid) { case '#': DBUG_PUSH(argument ? argument : "d:t:o,/tmp/mysql_config_editor.trace"); break; case 'V': print_version(); exit(0); break; case '?': usage_program(); exit(0); break; } return 0; } my_bool my_set_command_get_one_option(int optid, const struct my_option *opt MY_ATTRIBUTE((unused)), char *argument) { switch(optid) { case 'p': tty_password= 1; break; case 'G': if (login_path_specified) { /* Error, we do not support multiple login paths. */ my_perror("Error: Use of multiple login paths is not supported. " "Exiting.."); return 1; } login_path_specified= TRUE; break; case '?': usage_command(MY_CONFIG_SET); exit(0); break; } return 0; } my_bool my_remove_command_get_one_option(int optid, const struct my_option *opt MY_ATTRIBUTE((unused)), char *argument) { switch(optid) { case 'G': if (login_path_specified) { /* Error, we do not support multiple login paths. */ my_perror("Error: Use of multiple login paths is not supported. " "Exiting.."); return 1; } login_path_specified= TRUE; break; case '?': usage_command(MY_CONFIG_REMOVE); exit(0); break; } return 0; } my_bool my_print_command_get_one_option(int optid, const struct my_option *opt MY_ATTRIBUTE((unused)), char *argument) { switch(optid) { case 'G': if (login_path_specified) { /* Error, we do not support multiple login paths. */ my_perror("Error: Use of multiple login paths is not supported. " "Exiting.."); return 1; } login_path_specified= TRUE; break; case '?': usage_command(MY_CONFIG_PRINT); exit(0); break; } return 0; } my_bool my_reset_command_get_one_option(int optid, const struct my_option *opt MY_ATTRIBUTE((unused)), char *argument) { switch(optid) { case '?': usage_command(MY_CONFIG_RESET); exit(0); break; } return 0; } static struct my_command_data command_data[]= { { MY_CONFIG_SET, "set", "Write a login path to the login file.", my_set_command_options, my_set_command_get_one_option }, { MY_CONFIG_REMOVE, "remove", "Remove a login path from the login file.", my_remove_command_options, my_remove_command_get_one_option }, { MY_CONFIG_PRINT, "print", "Print the contents of login file in unencrypted form.", my_print_command_options, my_print_command_get_one_option }, { MY_CONFIG_RESET, "reset", "Empty the contents of the login file. The file is created\n" "if it does not exist.", my_reset_command_options, my_reset_command_get_one_option }, { MY_CONFIG_HELP, "help", "Display a help message and exit.", my_help_command_options, NULL }, { 0, NULL, NULL, NULL, NULL } }; int main(int argc, char *argv[]) { MY_INIT(argv[0]); DBUG_ENTER("main"); int command, rc= 0; command= do_handle_options(argc, argv); if (command > -1) rc= execute_commands(command); if (rc != 0) { my_perror("operation failed."); DBUG_RETURN(1); } DBUG_RETURN(0); } /** Handle all the command line arguments. program_name [program options] [command [command options]] */ static int do_handle_options(int argc, char *argv[]) { DBUG_ENTER("do_handle_options"); const char *command_list[MAX_COMMAND_LIMIT + 1]; char **saved_argv= argv; char **argv_cmd; char *ptr; /* for free. */ int argc_cmd; int rc, i, command= -1; if (argc < 2) { usage_program(); exit(1); } if (!(ptr= (char *) my_malloc(PSI_NOT_INSTRUMENTED, (argc + 2) * sizeof(char *), MYF(MY_WME)))) goto error; /* Handle program options. */ /* Prepare a list of supported commands to be used by my_handle_options(). */ for (i= 0; (command_data[i].name != NULL) && (i < MAX_COMMAND_LIMIT); i++) command_list[i]= (char *) command_data[i].name; command_list[i]= NULL; if ((rc= my_handle_options(&argc, &argv, my_program_long_options, my_program_get_one_option, command_list, FALSE))) exit(rc); if (argc == 0) /* No command specified. */ goto done; for (i= 0; command_data[i].name != NULL; i++) { if (!strcmp(command_data[i].name, argv[0])) { command= i; break; } } if (command == -1) goto error; /* Handle command options. */ argv_cmd= (char **)ptr; argc_cmd= argc + 1; /* Prepare a command line (argv) using the rest of the options. */ argv_cmd[0]= saved_argv[0]; memcpy((uchar *) (argv_cmd + 1), (uchar *) (argv), (argc * sizeof(char *))); argv_cmd[argc_cmd]= 0; if ((rc= handle_options(&argc_cmd, &argv_cmd, command_data[command].options, command_data[command].get_one_option_func))) exit(rc); /* Do not allow multiple commands. */ if ( argc_cmd > 1) goto error; done: my_free(ptr); DBUG_RETURN(command); error: my_free(ptr); usage_program(); exit(1); } static int execute_commands(int command) { DBUG_ENTER("execute_commands"); int rc= 0; if ((rc= check_and_create_login_file())) goto done; switch(command_data[command].id) { case MY_CONFIG_SET : verbose_msg("Executing set command.\n"); rc= set_command(); break; case MY_CONFIG_REMOVE : verbose_msg("Executing remove command.\n"); rc= remove_command(); break; case MY_CONFIG_PRINT : verbose_msg("Executing print command.\n"); rc= print_command(); break; case MY_CONFIG_RESET : verbose_msg("Resetting login file.\n"); rc= reset_login_file(1); break; case MY_CONFIG_HELP : verbose_msg("Printing usage info.\n"); usage_program(); break; default : my_perror("unknown command."); exit(1); } done: my_close(g_fd, MYF(MY_WME)); DBUG_RETURN(rc); } /** Execute 'set' command. @param void @return -1 Error 0 Success */ static int set_command(void) { DBUG_ENTER("set_command"); DYNAMIC_STRING file_buf, path_buf; init_dynamic_string(&path_buf, "", MY_LINE_MAX, MY_LINE_MAX); init_dynamic_string(&file_buf, "", file_size, 3 * MY_LINE_MAX); if (tty_password) opt_password= get_tty_password(NullS); if (file_size) { if (read_and_decrypt_file(&file_buf) == -1) goto error; } dynstr_append(&path_buf, "["); /* --login=path */ if (opt_login_path) dynstr_append(&path_buf, opt_login_path); else dynstr_append(&path_buf, "client"); dynstr_append(&path_buf, "]"); if (opt_user) /* --user */ { dynstr_append(&path_buf, "\nuser = "); dynstr_append(&path_buf, opt_user); } if (opt_password) /* --password */ { dynstr_append(&path_buf, "\npassword = "); dynstr_append(&path_buf, opt_password); } if (opt_host) /* --host */ { dynstr_append(&path_buf, "\nhost = "); dynstr_append(&path_buf, opt_host); } if (opt_socket) { dynstr_append(&path_buf, "\nsocket = "); dynstr_append(&path_buf, opt_socket); } if (opt_port) { dynstr_append(&path_buf, "\nport = "); dynstr_append(&path_buf, opt_port); } dynstr_append(&path_buf, "\n"); /* Warn if login path already exists */ if (opt_warn && ((locate_login_path (&file_buf, opt_login_path)) != NULL)) { int choice; printf ("WARNING : \'%s\' path already exists and will be " "overwritten. \n Continue? (Press y|Y for Yes, any " "other key for No) : ", opt_login_path); choice= getchar(); if (choice != (int) 'y' && choice != (int) 'Y') goto done; /* skip */ } /* Remove the login path. */ remove_login_path(&file_buf, opt_login_path); /* Append the new login path to the file buffer. */ dynstr_append(&file_buf, path_buf.str); if (encrypt_and_write_file(&file_buf) == -1) goto error; done: dynstr_free(&file_buf); dynstr_free(&path_buf); DBUG_RETURN(0); error: dynstr_free(&file_buf); dynstr_free(&path_buf); DBUG_RETURN(-1); } static int remove_command(void) { DBUG_ENTER("remove_command"); DYNAMIC_STRING file_buf, path_buf; init_dynamic_string(&path_buf, "", MY_LINE_MAX, MY_LINE_MAX); init_dynamic_string(&file_buf, "", file_size, 3 * MY_LINE_MAX); if (file_size) { if (read_and_decrypt_file(&file_buf) == -1) goto error; } else goto done; /* Nothing to remove, skip.. */ /* Warn if no login path is specified. */ if (opt_warn && ((locate_login_path (&file_buf, opt_login_path)) != NULL) && (login_path_specified == FALSE) ) { int choice; printf ("WARNING : No login path specified, so options from the default " "login path will be removed.\nContinue? (Press y|Y for Yes, " "any other key for No) : "); choice= getchar(); if (choice != (int) 'y' && choice != (int) 'Y') goto done; /* skip */ } remove_options(&file_buf, opt_login_path); if (encrypt_and_write_file(&file_buf) == -1) goto error; done: dynstr_free(&file_buf); dynstr_free(&path_buf); DBUG_RETURN(0); error: dynstr_free(&file_buf); dynstr_free(&path_buf); DBUG_RETURN(-1); } /** Execute 'print' command. @param void @return -1 Error 0 Success */ static int print_command(void) { DBUG_ENTER("print_command"); DYNAMIC_STRING file_buf; init_dynamic_string(&file_buf, "", file_size, 3 * MY_LINE_MAX); if (file_size) { if (read_and_decrypt_file(&file_buf) == -1) goto error; } else goto done; /* Nothing to print, skip..*/ print_login_path(&file_buf, opt_login_path); done: dynstr_free(&file_buf); DBUG_RETURN(0); error: dynstr_free(&file_buf); DBUG_RETURN(-1); } /** Create the login file if it does not exist, check and set its permissions and modes. @param void @return TRUE Error FALSE Success */ static my_bool check_and_create_login_file(void) { DBUG_ENTER("check_and_create_login_file"); MY_STAT stat_info; // This is a hack to make it compile. File permissions are different on Windows. #ifdef _WIN32 #define S_IRUSR 00400 #define S_IWUSR 00200 #define S_IRWXU 00700 #define S_IRWXG 00070 #define S_IRWXO 00007 #endif const int access_flag= (O_RDWR | O_BINARY); const ushort create_mode= (S_IRUSR | S_IWUSR ); /* Get the login file name. */ if (! my_default_get_login_file(my_login_file, sizeof(my_login_file))) { my_perror("failed to set login file name"); goto error; } /* NOTE : MYSQL_TEST_LOGIN_FILE env must be a full path, where the directory structure must exist. However the login file will be created if it does not exist. */ #ifdef _WIN32 if (! (getenv("MYSQL_TEST_LOGIN_FILE"))) { /* Check if 'MySQL' directory is in place. */ MY_STAT stat_info_dir; char login_dir[FN_REFLEN]; size_t size; dirname_part(login_dir, my_login_file, &size); /* Remove the trailing '\' */ if (is_directory_separator(login_dir[-- size])) login_dir[size]= 0; /* Now check if directory exists? */ if ( my_stat(login_dir, &stat_info_dir, MYF(0))) { verbose_msg("%s directory exists.\n", login_dir); } else { /* Create the login directory. */ verbose_msg("%s directory doesn't exist, creating it.\n", login_dir); if (my_mkdir(login_dir, 0, MYF(0))) { my_perror("failed to create the directory"); goto error; } } } #endif /* Check for login file's existence and permissions (0600). */ if (my_stat(my_login_file, &stat_info, MYF(0))) { verbose_msg("File exists.\n"); file_size= (size_t) stat_info.st_size; #ifdef _WIN32 if (1) #else if (!(stat_info.st_mode & (S_IXUSR | S_IRWXG | S_IRWXO))) #endif { verbose_msg("File has the required permission.\nOpening the file.\n"); if ((g_fd= my_open(my_login_file, access_flag, MYF(MY_WME))) == -1) { my_perror("couldn't open the file"); goto error; } } else { verbose_msg("File does not have the required permission.\n"); printf ("WARNING : Login file does not have the required" " file permissions.\nPlease set the mode to 600 &" " run the command again.\n"); goto error; } } else { verbose_msg("File does not exist.\nCreating login file.\n"); if ((g_fd= my_create(my_login_file, create_mode, access_flag, MYF(MY_WME))) == -1) { my_perror("couldn't create the login file"); goto error; } else { verbose_msg("Login file created.\n"); my_close(g_fd, MYF(MY_WME)); verbose_msg("Opening the file.\n"); if((g_fd= my_open(my_login_file, access_flag, MYF(MY_WME))) == -1) { my_perror("couldn't open the file"); goto error; } file_size= 0; } } if (file_size == 0) { generate_login_key(); if(add_header() == -1) goto error; } else { if (read_login_key() == -1) goto error; } DBUG_RETURN(FALSE); error: DBUG_RETURN(TRUE); } /** Print options under the specified login path. If '--all' option is used, print all the optins stored in the login file. @param file_buf [in] Buffer storing the unscrambled login file contents. @param path_name [in] Path name. @return void */ static void print_login_path(DYNAMIC_STRING *file_buf, const char *path_name) { DBUG_ENTER("print_login_path"); char *start= NULL, *end= NULL, temp= '\0'; if (file_buf->length == 0) goto done; /* Nothing to print. */ if (opt_all) { start= file_buf->str; end= file_buf->str + file_buf->length; } else { start= locate_login_path(file_buf, path_name); if (! start) /* login path not found, skip..*/ goto done; end= strstr(start, "\n["); } if (end) { temp= *end; *end= '\0'; } mask_password_and_print(start); if (temp != '\0') *end= temp; done: DBUG_VOID_RETURN; } /** Print the specified buffer by masking the actual password string. @param buf [in] Buffer to be printed. @raturn void */ static void mask_password_and_print(char *buf) { DBUG_ENTER("mask_password_and_print"); const char *password_str= "\npassword = ", *mask = "*****"; char *next= NULL; while ((next= strstr(buf, password_str)) != NULL) { while ( *buf != 0 && buf != next) putc( *(buf ++), stdout); printf("%s", password_str); printf("%s\n", mask); if (*buf == '\n') /* Move past \n' */ buf ++; /* Ignore the password. */ while( *buf && *(buf ++) != '\n') {} if ( !opt_all) break; } /* Now print the rest of the buffer. */ while ( *buf) putc( *(buf ++), stdout); // And a new line.. if required. if (* (buf - 1) != '\n') putc('\n', stdout); DBUG_VOID_RETURN; } /** Remove multiple options from a login path. */ static void remove_options(DYNAMIC_STRING *file_buf, const char *path_name) { /* If nope of the options are specified remove the entire path. */ if (!opt_remove_host && !opt_remove_pass && !opt_remove_user && !opt_remove_socket && !opt_remove_port) { remove_login_path(file_buf, path_name); return; } if (opt_remove_user) remove_option(file_buf, path_name, "user"); if (opt_remove_pass) remove_option(file_buf, path_name, "password"); if (opt_remove_host) remove_option(file_buf, path_name, "host"); if (opt_remove_socket) remove_option(file_buf, path_name, "socket"); if (opt_remove_port) remove_option(file_buf, path_name, "port"); } /** Remove an option from a login path. */ static void remove_option(DYNAMIC_STRING *file_buf, const char *path_name, const char* option_name) { DBUG_ENTER("remove_option"); char *start= NULL, *end= NULL; char *search_str; size_t search_len, shift_len; bool option_found= FALSE; search_str= (char *) my_malloc(PSI_NOT_INSTRUMENTED, (uint) strlen(option_name) + 2, MYF(MY_WME)); sprintf(search_str, "\n%s", option_name); if ((start= locate_login_path(file_buf, path_name)) == NULL) /* login path was not found, skip.. */ goto done; end= strstr(start, "\n["); /* Next path. */ if (end) search_len= end - start; else search_len= file_buf->length - (start - file_buf->str); while(search_len > 1) { if (!strncmp(start, search_str, strlen(search_str))) { /* Option found. */ end= start; while(*(++ end) != '\n') {} option_found= TRUE; break; } else { /* Move to next line. */ while( (-- search_len > 1) && (*(++ start) != '\n')) {} } } if (option_found) { shift_len= file_buf->length - (end - file_buf->str); file_buf->length -= (end - start); while(shift_len --) *(start ++)= *(end ++); *start= '\0'; } done: my_free(search_str); DBUG_VOID_RETURN; } /** Remove the specified login path from the login file. @param file_buf [in] Buffer storing the unscrambled login file contents. @param path_name [in] Path name. @return void */ static void remove_login_path(DYNAMIC_STRING *file_buf, const char *path_name) { DBUG_ENTER("remove_login_path"); char *start=NULL, *end= NULL; int to_move, len, diff; if((start= locate_login_path(file_buf, path_name)) == NULL) /* login path was not found, skip.. */ goto done; end= strstr(start, "\n["); if (end) { end ++; /* Move past '\n' */ len= ((diff= (start - end)) > 0) ? diff : - diff; to_move= file_buf->length - (end - file_buf->str) ; } else { *start= '\0'; file_buf->length= ((diff= (file_buf->str - start)) > 0) ? diff : - diff; goto done; } while(to_move --) *(start ++)= *(end ++); *start= '\0'; file_buf->length -= len; done: DBUG_VOID_RETURN; } /** Remove all the contents from the login file. @param gen_key [in] Flag to control the generation of a new key. @return -1 Error 0 Success */ static int reset_login_file(bool gen_key) { DBUG_ENTER("reset_login_file"); if (my_chsize(g_fd, 0, 0, MYF(MY_WME))) { verbose_msg("Error while truncating the file.\n"); goto error; } /* Seek to the beginning of the file. */ if (my_seek(g_fd, 0L, SEEK_SET, MYF(MY_WME) == MY_FILEPOS_ERROR)) goto error; /* Error. */ if (gen_key) generate_login_key(); /* Generate a new key. */ if (add_header() == -1) goto error; DBUG_RETURN(0); error: DBUG_RETURN(0); } /** Find the specified login path in the login file buffer and return the starting address. @param file_buf [in] Buffer storing the unscrambled login file contents. @param path_name [in] Path name. @return If found, the starting address of the login path, NULL otherwise. */ static char* locate_login_path(DYNAMIC_STRING *file_buf, const char *path_name) { DBUG_ENTER("locate_login_path"); char *addr= NULL; DYNAMIC_STRING dy_path_name; init_dynamic_string(&dy_path_name, "", 512, 512); dynstr_append(&dy_path_name, "\n["); dynstr_append(&dy_path_name, path_name); dynstr_append(&dy_path_name, "]"); /* First check if it is the very first login path. */ if (file_buf->str == strstr(file_buf->str, dy_path_name.str + 1)) addr= file_buf->str; /* If not, scan through the file. */ else { addr= strstr(file_buf->str, dy_path_name.str); if (addr) addr ++; /* Move past '\n' */ } dynstr_free(&dy_path_name); DBUG_RETURN(addr); } /** Encrypt the file buffer and write it to the login file. @param file_buf [in] Buffer storing the unscrambled login file contents. @return -1 Error 0 Success @note The contents of the file buffer are encrypted on a line-by-line basis with each line having the following format : [|] */ static int encrypt_and_write_file(DYNAMIC_STRING *file_buf) { DBUG_ENTER("encrypt_and_write_file"); my_bool done= FALSE; char cipher[MY_LINE_MAX], *tmp= NULL; uint bytes_read=0, len= 0; int enc_len= 0; // Can be negative. if (reset_login_file(0) == -1) goto error; /* Move past key first. */ if (my_seek(g_fd, MY_LOGIN_HEADER_LEN, SEEK_SET, MYF(MY_WME)) != (MY_LOGIN_HEADER_LEN)) goto error; /* Error while seeking. */ tmp= &file_buf->str[bytes_read]; while(! done) { len= 0; while(*tmp++ != '\n') if (len < (file_buf->length - bytes_read)) len ++; else { done= TRUE; break; } if (done) break; if ((enc_len= my_aes_get_size(len + 1, my_aes_128_ecb)) > (MY_LINE_MAX - (int)MAX_CIPHER_STORE_LEN)) { my_perror("A parameter to mysql_config_editor exceeds the maximum " "accepted length. Please review the data you've supplied " "and try to shorten them permissible length.\n"); goto error; } if (encrypt_buffer(&file_buf->str[bytes_read], ++len, cipher + MAX_CIPHER_STORE_LEN, enc_len) < 0) goto error; bytes_read += len; /* Store cipher length first. */ int4store(cipher, enc_len); if ((my_write(g_fd, (const uchar *)cipher, enc_len + MAX_CIPHER_STORE_LEN, MYF(MY_WME))) != (enc_len + MAX_CIPHER_STORE_LEN)) goto error; } verbose_msg("Successfully written encrypted data to the login file.\n"); /* Update file_size */ file_size= bytes_read; DBUG_RETURN(0); error: my_perror("couldn't encrypt the file"); DBUG_RETURN(-1); } /** Read the login file, unscramble its contents and store them into the file buffer. @param file_buf [in] Buffer for storing the unscrambled login file contents. @return -1 Error 0 Success */ static int read_and_decrypt_file(DYNAMIC_STRING *file_buf) { DBUG_ENTER("read_and_decrypt_file"); char cipher[MY_LINE_MAX], plain[MY_LINE_MAX]; uchar len_buf[MAX_CIPHER_STORE_LEN]; int cipher_len= 0, dec_len= 0; /* Move past key first. */ if (my_seek(g_fd, MY_LOGIN_HEADER_LEN, SEEK_SET, MYF(MY_WME)) != (MY_LOGIN_HEADER_LEN)) goto error; /* Error while seeking. */ /* First read the length of the cipher. */ while (my_read(g_fd, len_buf, MAX_CIPHER_STORE_LEN, MYF(MY_WME)) == MAX_CIPHER_STORE_LEN) { cipher_len= sint4korr(len_buf); if (cipher_len > MY_LINE_MAX) goto error; /* Now read 'cipher_len' bytes from the file. */ if ((int) my_read(g_fd, (uchar *) cipher, cipher_len, MYF(MY_WME)) == cipher_len) { if ((dec_len= decrypt_buffer(cipher, cipher_len, plain)) < 0) goto error; plain[dec_len]= 0; dynstr_append(file_buf, plain); } } verbose_msg("Successfully decrypted the login file.\n"); DBUG_RETURN(0); error: my_perror("couldn't decrypt the file"); DBUG_RETURN(-1); } /** Encrypt the given plain text. @param plain [in] Plain text to be encrypted. @param plain_len [in] Length of the plain text. @param cipher [out] Encrypted cipher text. @return -1 if error encountered, length encrypted, otherwise. */ static int encrypt_buffer(const char *plain, int plain_len, char cipher[], const int aes_len) { DBUG_ENTER("encrypt_buffer"); if (my_aes_encrypt((const unsigned char *) plain, plain_len, (unsigned char *) cipher, (const unsigned char *) my_key, LOGIN_KEY_LEN, my_aes_128_ecb, NULL) == aes_len) DBUG_RETURN(aes_len); verbose_msg("Error! Couldn't encrypt the buffer.\n"); DBUG_RETURN(-1); /* Error */ } /** Decrypt the given cipher text. @param cipher [in] Cipher text to be decrypted. @param cipher_len [in] Length of the cipher text. @param plain [out] Decrypted plain text. @return -1 if error encountered, length decrypted, otherwise. */ static int decrypt_buffer(const char *cipher, int cipher_len, char plain[]) { DBUG_ENTER("decrypt_buffer"); int aes_length; if ((aes_length= my_aes_decrypt((const unsigned char *) cipher, cipher_len, (unsigned char *) plain, (const unsigned char *) my_key, LOGIN_KEY_LEN, my_aes_128_ecb, NULL)) > 0) DBUG_RETURN(aes_length); verbose_msg("Error! Couldn't decrypt the buffer.\n"); DBUG_RETURN(-1); /* Error */ } /** Add unused bytes alongwith the to the login key to the login file. @return -1 if error encountered, length written, otherwise. */ static int add_header(void) { DBUG_ENTER("add_header"); /* Reserved for future use. */ const char unused[]= {'\0','\0','\0','\0'}; /* Write 'unused' bytes first. */ if ((my_write(g_fd, (const uchar *) unused, 4, MYF(MY_WME))) != 4) goto error; /* Write the login key. */ if ((my_write(g_fd, (const uchar *)my_key, LOGIN_KEY_LEN, MYF(MY_WME))) != LOGIN_KEY_LEN) goto error; verbose_msg("Key successfully written to the file.\n"); DBUG_RETURN(MY_LOGIN_HEADER_LEN); error: my_perror("file write operation failed"); DBUG_RETURN(-1); } /** Algorithm to generate key. */ void generate_login_key() { DBUG_ENTER("generate_login_key"); struct rand_struct rnd; verbose_msg("Generating a new key.\n"); /* Get a sequence of random non-printable ASCII */ for (uint i= 0; i < LOGIN_KEY_LEN; i++) my_key[i]= (char)((int)(my_rnd_ssl(&rnd) * 100000) % 32); DBUG_VOID_RETURN; } /** Read the stored login key. @return -1 Error 0 Success */ static int read_login_key(void) { DBUG_ENTER("read_login_key"); verbose_msg("Reading the login key.\n"); /* Move past the unused buffer. */ if (my_seek(g_fd, 4, SEEK_SET, MYF(MY_WME)) != 4) goto error; /* Error while seeking. */ if (my_read(g_fd, (uchar *)my_key, LOGIN_KEY_LEN, MYF(MY_WME)) != LOGIN_KEY_LEN) goto error; /* Error while reading. */ verbose_msg("Login key read successfully.\n"); DBUG_RETURN(0); error: my_perror("file read operation failed"); DBUG_RETURN(-1); } static void verbose_msg(const char *fmt, ...) { DBUG_ENTER("verbose_msg"); va_list args; if (!opt_verbose) DBUG_VOID_RETURN; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fflush(stderr); DBUG_VOID_RETURN; } static void my_perror(const char *msg) { char errbuf[MYSYS_STRERROR_SIZE]; if (errno == 0) fprintf(stderr, "%s\n", (msg) ? msg : ""); else fprintf(stderr, "%s : %s\n", (msg) ? msg : "", my_strerror(errbuf, sizeof(errbuf), errno)); // reset errno errno= 0; } static void usage_command(int command) { print_version(); puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2012")); puts("MySQL Configuration Utility."); printf("\nDescription: %s\n", command_data[command].description); printf("Usage: %s [program options] [%s [command options]]\n", my_progname, command_data[command].name); my_print_help(command_data[command].options); my_print_variables(command_data[command].options); } static void usage_program(void) { print_version(); puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2012")); puts("MySQL Configuration Utility."); printf("Usage: %s [program options] [command [command options]]\n", my_progname); my_print_help(my_program_long_options); my_print_variables(my_program_long_options); puts("\nWhere command can be any one of the following :\n\ set [command options] Sets user name/password/host name/socket/port\n\ for a given login path (section).\n\ remove [command options] Remove a login path from the login file.\n\ print [command options] Print all the options for a specified\n\ login path.\n\ reset [command options] Deletes the contents of the login file.\n\ help Display this usage/help information.\n"); } static void print_version(void) { printf ("%s Ver %s Distrib %s, for %s on %s\n", my_progname, MYSQL_CONFIG_EDITOR_VERSION, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE); }