Chinaunix首页 | 论坛 | 博客
  • 博客访问: 38883
  • 博文数量: 3
  • 博客积分: 30
  • 博客等级: 民兵
  • 技术积分: 35
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-01 23:28
文章分类

全部博文(3)

文章存档

2014年(2)

2012年(1)

我的朋友

分类: Mysql/postgreSQL

2014-10-19 22:40:27

    最近接到用户反馈,一直使用的mysql帐号却连接不上了。具体错误如下:

点击(此处)折叠或打开

  1. E:\mysql-5.6.16\client\Debug>mysql.exe -h127.0.0.1 -P3306 -uedgeyang -p
  2. Enter password: ********
  3. ERROR 1045 (28000): Access denied for user 'edgeyang'@'127.0.0.1' (using password
  4. : NO)

    首先,登录上去查看用户权限。发现show grants确实没有用户,但mysql.user表内是有该用户的权限记录的。初步有两个猜测:

  1. 权限表没有加载到内存,但flush privileges刷新权限后同样不能登录;
  2. 表损坏,但check table显示表正常。

点击(此处)折叠或打开

  1. mysql> show grants for edgeyang@'127.0.0.1';
  2. ERROR 1141 (42000): There is no such grant defined for user 'edgeyang' on host '
  3. 127.0.0.1'

    然后,发现mysql.user表内的权限密码是明文的。密码一般都是加密存储的,明文存储mysql不能解析?

点击(此处)折叠或打开

  1. mysql> select host,user,password from user where user='edgeyang' \G
  2. *************************** 1. row ***************************
  3.     host: 127.0.0.1
  4.     user: edgeyang
  5. password: edgeyang
  6. 1 row in set (0.00 sec)

    查看mysql error log,发现如下信息。看样子是密码非法,导致mysql不能识别该用户。

点击(此处)折叠或打开

  1. [Warning] Found invalid password for user: 'edgeyang@127.0.0.1'; Ignoring user

    那mysql用户密码是用什么加密的,并且加密后密码有怎样的格式(方便以后判断是否非法密码)?
    查看mysql 5.6.16源码,追踪flush privileges命令的执行情况,看看mysql是如何加载权限表的。
    第一步,调用函数reload_acl_and_cache重载权限表,并根据需要刷新日志文件(例如错误日志,慢查询日志)。其中权限表分三个部分:

  1. acl_reload函数加载的权限表,mysql.user,mysql.db,mysql.proxies_priv;
  2. grant_reload函数加载的权限表,mysql.tables_priv,mysql.columns_priv;
  3. servers_reload函数加载的权限表,mysql.servers。

点击(此处)折叠或打开

  1. bool reload_acl_and_cache(THD *thd, unsigned long options,
  2.                           TABLE_LIST *tables, int *write_to_binlog)
  3. {
  4.     if (thd)
  5.     {
  6.       bool reload_acl_failed= acl_reload(thd);
  7.       bool reload_grants_failed= grant_reload(thd);
  8.       bool reload_servers_failed= servers_reload(thd);
  9.     }
  10.     
  11.     if (options & REFRESH_ERROR_LOG)
  12.         if (flush_error_log())
  13.         {
  14.          /*
  15.             When flush_error_log() failed, my_error() has not been called.
  16.             So, we have to do it here to keep the protocol.
  17.          */
  18.          my_error(ER_UNKNOWN_ERROR, MYF(0));
  19.          result= 1;
  20.         }

  21.   if ((options & REFRESH_SLOW_LOG) && opt_slow_log)
  22.     logger.flush_slow_log();

    第二步,查看acl_reload函数。函数的功能如下:

  1. 打开权限表,清空权限的缓存;
  2. 调用函数acl_load加载权限;
  3. 关闭权限表。

点击(此处)折叠或打开

  1. /*
  2. Forget current user/db-level privileges and read new privileges
  3.   from the privilege tables.
  4. */
  5. my_bool acl_reload(THD *thd)
  6. {
  7.   TABLE_LIST tables[3];
  8.   /*
  9.     To avoid deadlocks we should obtain table locks before
  10.     obtaining acl_cache->lock mutex.
  11.   */
  12.   tables[0].init_one_table(C_STRING_WITH_LEN("mysql"),
  13.                            C_STRING_WITH_LEN("user"), "user", TL_READ);
  14.   tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
  15.                            C_STRING_WITH_LEN("db"), "db", TL_READ);
  16.   tables[2].init_one_table(C_STRING_WITH_LEN("mysql"),
  17.                            C_STRING_WITH_LEN("proxies_priv"),
  18.                            "proxies_priv", TL_READ);
  19.   if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
  20.   {
  21.     goto end;
  22.   }

  23.   delete_dynamic(&acl_wild_hosts);
  24.   my_hash_free(&acl_check_hosts);

  25.   if ((return_val= acl_load(thd, tables)))
  26.   {                    // Error. Revert to old list
  27.     init_check_host();
  28.   }
  29.   else
  30.   {
  31.     //delete something
  32.   }
  33.   if (old_initialized)
  34.     mysql_mutex_unlock(&acl_cache->lock);
  35. end:
  36.   close_acl_tables(thd);
  37.   DBUG_RETURN(return_val);
  38. }

    第三步,查看acl_load函数。函数功能如下:

  1. 逐行读取mysql.user表记录;
  2. 逐字段读取,并检查字段值在mysql各版本是否合法;
  3. 读取其他表,如mysql.db。

其中第二个小步骤里面,函数set_user_salt会检查password字段合法性。


点击(此处)折叠或打开

  1. /*
  2.   Initialize structures responsible for user/db-level privilege checking
  3.   and load information about grants from open privilege tables.
  4. */
  5. static my_bool acl_load(THD *thd, TABLE_LIST *tables)
  6. {
  7.   acl_cache->clear(1);                // Clear locked hostname cache

  8.   init_sql_alloc(&global_acl_memory, ACL_ALLOC_BLOCK_SIZE, 0);
  9.   /*
  10.     Prepare reading from the mysql.user table
  11.   */
  12.   if (init_read_record(&read_record_info, thd, table=tables[0].table,
  13.                        NULL, 1, 1, FALSE))
  14.     goto end;
  15.   while (!(read_record_info.read_record(&read_record_info)))
  16.   {
  17.     /* Read legacy password */
  18.     password= get_field(&global_acl_memory,
  19.                         table->field[MYSQL_USER_FIELD_PASSWORD]);
  20.     password_len= password ? strlen(password) : 0;
  21.     user.auth_string.str= password ? password : const_cast<char*>("");
  22.     user.auth_string.length= password_len;

  23.     {
  24.     /*省略此处代码,为了兼容mysql其他版本的权限信息*/
  25.      /*
  26.          Transform hex to octets and adjust the format.
  27.        */
  28.       if (set_user_salt(&user, password, password_len))
  29.       {
  30.         sql_print_warning("Found invalid password for user: '%s@%s'; "
  31.                           "Ignoring user", user.user ? user.user : "",
  32.                           user.host.get_host() ? user.host.get_host() : "");
  33.         continue;
  34.       }
  35.     }
  36.   } // END while reading records from the mysql.user table
  37. }

    第四步,set_user_salt判断密码是否合法。由此可知,mysq.user存储的加密后l密码的合法格式是:

  1. 为空;
  2. 新格式密码为41个字符,并且以字符*开头;
  3. 旧格式密码为16个字符。

点击(此处)折叠或打开

  1. #define SCRAMBLE_LENGTH 20
  2. #define SCRAMBLE_LENGTH_323 8
  3. /* length of password stored in the db: new passwords are preceeded with '*' */
  4. #define SCRAMBLED_PASSWORD_CHAR_LENGTH (SCRAMBLE_LENGTH*2+1)
  5. #define SCRAMBLED_PASSWORD_CHAR_LENGTH_323 (SCRAMBLE_LENGTH_323*2)
  6. /**
  7.   Convert scrambled password to binary form, according to scramble type,
  8.   Binary form is stored in user.salt.
  9. */

  10. static
  11. bool
  12. set_user_salt(ACL_USER *acl_user, const char *password, uint password_len)
  13. {
  14.   bool result= false;
  15.   /* Using old password protocol */
  16.   if (password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH)
  17.   {
  18.     get_salt_from_password(acl_user->salt, password);
  19.     acl_user->salt_len= SCRAMBLE_LENGTH;
  20.   }
  21.   else if (password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH_323)
  22.   {
  23.     get_salt_from_password_323((ulong *) acl_user->salt, password);
  24.     acl_user->salt_len= SCRAMBLE_LENGTH_323;
  25.   }
  26.   else if (password_len == 0 || password == NULL)
  27.   {
  28.     /* This account doesn't use a password */
  29.     acl_user->salt_len= 0;
  30.   }
  31.   else if (acl_user->plugin.str == native_password_plugin_name.str ||
  32.            acl_user->plugin.str == old_password_plugin_name.str)
  33.   {
  34.     /* Unexpected format of the hash; login will probably be impossible */
  35.     result= true;
  36.   }

  37.   /*
  38.     Since we're changing the password for the user we need to reset the
  39.     expiration flag.
  40.   */
  41.   acl_user->password_expired= false;
  42.   
  43.   return result;
  44. }

    第五步,通过password()函数加密密码后,重新flush privileges就可以登录了。

点击(此处)折叠或打开

  1. mysql> update user set password=password('edgeyang') where user='edgeyang';
  2. Query OK, 1 row affected (0.00 sec)
  3. Rows matched: 1 Changed: 1 Warnings: 0

  4. mysql> flush privileges;
  5. Query OK, 0 rows affected (0.01 sec)

  6. mysql> show grants for edgeyang@'127.0.0.1';
  7. +-------------------------------------------------------------------------------
  8. -------------------------------------------+
  9. | Grants for edgeyang@127.0.0.1
  10.                                            |
  11. +-------------------------------------------------------------------------------
  12. -------------------------------------------+
  13. | GRANT ALL PRIVILEGES ON *.* TO 'edgeyang'@'127.0.0.1' IDENTIFIED BY PASSWORD '
  14. *3C316168860E5204C4A2AEDDF36D13E99D80A981' |
  15. +-------------------------------------------------------------------------------
  16. -------------------------------------------+
  17. 1 row in set (0.00 sec)

    最后,看看password函数的实现和mysql文档,可以发现password采用了两次sha1来加密密码。
    mysql password函数文档描述:http://dev.mysql.com/doc/refman/5.5/en/encryption-functions.html#function_password

点击(此处)折叠或打开

  1. inline static
  2. void compute_two_stage_sha1_hash(const char *password, size_t pass_len,
  3.                                  uint8 *hash_stage1, uint8 *hash_stage2)
  4. {
  5.   /* Stage 1: hash password */
  6.   compute_sha1_hash(hash_stage1, password, pass_len);

  7.   /* Stage 2 : hash first stage's output. */
  8.   compute_sha1_hash(hash_stage2, (const char *) hash_stage1, SHA1_HASH_SIZE);
  9. }
阅读(1344) | 评论(0) | 转发(0) |
0

上一篇:浅析libcurl多线程安全问题

下一篇:没有了

给主人留下些什么吧!~~