MySQLPlugin之如何编写Auth Plugin

转载请署名:印风

---------------------------------------------------------------------

1.什么是Auth Plugin

我们先介绍一下传统的认证方式。在MySQL服务器上的mysql.user表中存储了所有的用户信息,客户端将用户名和密码传递过来根据user表相应的行进行认证匹配,也就是说,传统的方法是客户端需要明确的给出用户名和密码。

但从5.5.7开始不再采用这种方式,而是使用Plugin来实现。通过如下方式来进行授权:

在user表里有一列为plugin,如果没有plugin时,服务器使用password列来认证,与传统方式不同的是,会使用两个内建的plugin来完成认证。

如果plugin列有值,则调用相应的plugin进行认证,如果在plugin列表里找不到,就会报错;

 

2. auth plugin能做什么

通过auth plugin可以做到:

1)外置式认证,例如,我们可以使用usb盘来进行认证(实现很简单),或者系统用户登录信息、LDAP等外部方式。

2)代理用户(proxy users),当一个用户被允许连接时,auth plugin可以返回给服务器一个与连接的用户不同的用户名,表明连接用户是另外一个用户的代理,从而获得其特殊的权限。关于Proxy User机制,见后记。

 

3. 如何编写auth plugin

1)相应的SQL:

CREATE USER 'empl_external'@'localhost'
  IDENTIFIED WITH auth_plugin AS 'auth_string';

IDENTIFIED WITH 插件名 AS ‘认证字符串’

 

2)客户端API

从MySQL5.5开始实现了客户端PLUGIN,由于这是个全新的plugin类型,这里进行一下详细的介绍。API在文件client_plugin.h中进行了定义,主要包括如下几个部分:

 

(1)客户端插件的声明:

宏定义

#definemysql_declare_client_plugin(X)          \
     MYSQL_PLUGIN_EXPORT structst_mysql_client_plugin_ ## X        \
        _mysql_client_plugin_declaration_ ={   \
          MYSQL_CLIENT_ ## X ## _PLUGIN,        \
          MYSQL_CLIENT_ ## X ## _PLUGIN_INTERFACE_VERSION,
#definemysql_end_client_plugin             }


跟服务器端的Plugin类似,我们在mysql_declare_client_plugin和definemysql_end_client_plugin之间填入相应的内容

 

而从如下:

structst_mysql_client_plugin_AUTHENTICATION
{
  MYSQL_CLIENT_PLUGIN_HEADER
  int (*authenticate_user)(MYSQL_PLUGIN_VIO*vio, struct st_mysql *mysql);
};

可知auth plugin的声明信息中应该包含MYSQL_CLIENT_PLUGIN_HEADER,也就是st_mysql_client_plugin的信息和auth主函数

 

先看通用的插件声明结构体的头部信息填充的内容使用结构体st_mysql_client_plugin来决定,结构体描述如下:

字段

类型

描述

type

int

插件类型,目前仅有一种:

MYSQL_CLIENT_AUTHENTICATION_PLUGIN

interface_version

unsigned long

插件的接口版本号,这两个值都会由宏自动生成,因此无需在声明插件时填写

name

const char*

插件名,当在客户端使用MYSQL_DEFAULT_AUTH选项名来调用mysql_options()函数时会指定该插件名,或者通过--default-auth指定的插件名

author

const char*

作者名

desc

const char*

插件的描述信息

version[3]

unsigned int

该插件的版本号

license;

const char *

插件许可证书

mysql_api;

void *

内部使用,设置为NULL

init
int (*init)(char *, size_t, int, va_list);

 

第一个参数存储错误消息

第二个参数表示错误消息的长度

第三个及以后的参数被传递给mysql_load_plugin().第一个表示参数的个数

deinit
int (*deinit)();

无参数,当客户端unload 插件的时候被调用

options
int (*options)(const char *option, const void *);

用于处理传递给plugin的选项,第一个参数执行选项名,第二个参数指向选项值

 

 

 

主函数定义如下:

int(*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql);

第二个参数无需多言,相信大家都很熟悉,重点介绍一下第一个参数MYSQL_PLUGIN_VIO,其对应的结构体为st_plugin_vio,如下:

字段

类型

描述

read_packet

  int (*read_packet)(struct st_plugin_vio *vio,

                     unsigned char **buf);

从vio里读一个记录,以’\0’结尾的字符串

write_packet

int (*write_packet)(struct st_plugin_vio *vio,

                      const unsigned char *packet,

                      int packet_len);

向vio中写一个字符串,需要提供字符串指针地址和长度

info

void (*info)(struct st_plugin_vio *vio, struct st_plugin_vio_info *info);

函数指针,用于向st_plugin_vio_info结构体中填充连接信息

 

st_plugin_vio_info指定了客户端和服务器端通信的协议和socket

字段

类型

描述

protocol

enum { MYSQL_VIO_INVALID, MYSQL_VIO_TCP, MYSQL_VIO_SOCKET,

         MYSQL_VIO_PIPE, MYSQL_VIO_MEMORY }

枚举类型,指定通信的协议

socket

int

套接字

 

对于客户端authplugin,我们一般需要调用write_packet将需要认证的字符串传递给服务器。

 

函数返回值:

CR_ERROR   0

认证失败,表明发生了一个错误

CR_OK      -1

客户端认证成功,这不表明服务器端接受其认证,通常表明发送用户名和认证信息成功

CR_OK_HANDSHAKE_COMPLETE -2

当无法确定服务器与客户端的交互次数时,需要读更多的包,使用该返回值表明交互结束

 

3)服务器端API

在文件plugin_auth.h中定义了服务的auth plugin的API,

插件描述结构体:st_mysql_auth

字段

类型

描述

interface_version

int

接口版本号:

MYSQL_AUTHENTICATION_INTERFACE_VERSION

client_auth_plugin

const char *

客户端的插件名

authenticate_user

int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info);

主要认证函数,用于处理客户端的请求,返回0表示认证成功,

 

认证函数:

int(*authenticate_user)(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO*info);

MYSQL_PLUGIN_VIO结构体前面已经介绍了,这里不再赘述,主要介绍一下MYSQL_SERVER_AUTH_INFO,也就是st_mysql_server_auth_info,如下所示:

 

字段

类型

描述

user_name

char *

客户端发送的用户名,使用show user()显示,如果为NULL表示还没接受到包含用户名的包

user_name_length

unsigned int

用户名长度

auth_string

const char*

在mysql.user表中记录的相应账户的

authentication_string

auth_string_length

unsigned long

长度

authenticated_as

char authenticated_as[MYSQL_USERNAME_LENGTH+1]

代理用户名,

show current_user(),初始时server将其设置为user_name,但在plugin里可以对其进行修改

external_user

char external_user[512]

系统变量external_user显示的值

password_used

int

当认证失败时使用该字段,用于显示错误信息:

Authentication fails. Password used: %s

%s为:

0:NO

1:YES

2:没有%s

host_or_ip

const char*

host/ip

host_or_ip_lenght

unsigned int

host/ip的长度

 

 

4例子:在认证阶段两次提问,通过验证(代码摘录自mysql5.5.16),先看看效果,如下图所示:


代码如下:

/*必要的头文件*/
#include<my_global.h>
#include<mysql.h>
#include<mysql/plugin_auth.h>
#include<mysql/client_plugin.h>
#include<string.h>
#include <stdio.h>
#include<stdlib.h>
 
#if !defined(_GNU_SOURCE)
# define _GNU_SOURCE/* for RTLD_DEFAULT */
#endif
 
/*用于标示发送的包的类型,以便通信双方进行辨别*/
#defineORDINARY_QUESTION       "\2"
#defineLAST_QUESTION           "\3"
#definePASSWORD_QUESTION       "\4"
#define LAST_PASSWORD           "\5"
 
/*********************SERVER SIDE ****************************************/
 
static inttwo_questions(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
{
  unsigned char *pkt;
  int pkt_len;
 
  /* 发送一个问题,提示客户端输入密码*/
  if (vio->write_packet(vio, (const unsignedchar *) PASSWORD_QUESTION "Password, please:", 18))
    return CR_ERROR;
 
  /* 阻塞读密码 */
  if ((pkt_len= vio->read_packet(vio,&pkt)) < 0)
    return CR_ERROR;
 
  info->password_used= PASSWORD_USED_YES;
 
  /* 如果密码为错误时,返回ERROR */
  if (strcmp((const char *) pkt,info->auth_string))
    return CR_ERROR;
 
  /* 确认密码吗? */
  if (vio->write_packet(vio, (const unsignedchar *) LAST_QUESTION "Are you sure ?", 15))
    return CR_ERROR;
 
  /*读取客户端输入 */
  if ((pkt_len= vio->read_packet(vio,&pkt)) < 0)
    return CR_ERROR;
 
  /* 检查客户端输入是否为yes, of course */
  return strcmp((const char *) pkt, "yes,of course") ? CR_ERROR : CR_OK;
}
 
static structst_mysql_auth two_handler=
{
  MYSQL_AUTHENTICATION_INTERFACE_VERSION,
  "dialog", /* requires dialog clientplugin */
  two_questions
};
 
mysql_declare_plugin(dialog)
{
  MYSQL_AUTHENTICATION_PLUGIN,
  &two_handler,
  "two_questions",
  "Sergei Golubchik",
  "Dialog plugin demo 1",
  PLUGIN_LICENSE_GPL,
  NULL,
  NULL,
  0x0100,
  NULL,
  NULL,
  NULL
},
mysql_declare_plugin_end;
 


/*********************CLIENT SIDE ***************************************/

 

/*客户端向服务器端发送认证信息的方式有多种,比如说通过终端、GUI或者其他一些设备,使用函数指针来*/
typedef char*(*mysql_authentication_dialog_ask_t)(struct st_mysql *mysql,
                      int type, const char*prompt, char *buf, int buf_len);
 
staticmysql_authentication_dialog_ask_t ask;
 
/*获取终端的输入*/
static char*builtin_ask(MYSQL *mysql __attribute__((unused)),
                         int type__attribute__((unused)),
                         const char *prompt,
                         char *buf, intbuf_len)
{
  char *ptr;
/*打印服务器端传递的提示信息*/
  fputs(prompt, stdout);
  fputc(' ', stdout);
  if (fgets(buf, buf_len, stdin) == NULL)
    return NULL;
  if ((ptr= strchr(buf, '\n')))
    *ptr= 0;
 
  return buf;
}
 
static intperform_dialog(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
{
  unsigned char *pkt, cmd= 0;
  int pkt_len, res;
  char reply_buf[1024], *reply;
 
  do
  {
    /* read the prompt */
    pkt_len= vio->read_packet(vio,&pkt);
    if (pkt_len < 0)
      return CR_ERROR;
 
    if (pkt == 0)
    {
      /*
        in mysql_change_user()the client sends the first packet, so
        the first vio->read_packet() doesnothing (pkt == 0).
 
        We send the "password",assuming the client knows what it's doing.
        (in other words, the dialog pluginshould be only set as a default
        authentication plugin on the client ifthe first question
        asks for a password - which will besent in clear text, by the way)
      */
      reply= mysql->passwd;
    }
    else
    {
      cmd= *pkt++;
 
      /* is it MySQL protocol packet ? */
      if (cmd == 0 || cmd == 254)
        return CR_OK_HANDSHAKE_COMPLETE; /*yes. we're done */
 
      /*
        asking for a password with an emptyprompt means mysql->password
        otherwise we ask the user and read thereply
      */
      if ((cmd >> 1) == 2 && *pkt== 0)
        reply= mysql->passwd;
      else
        reply= ask(mysql, cmd >> 1, (constchar *) pkt,
                   reply_buf, sizeof(reply_buf));
      if (!reply)
        return CR_ERROR;
    }
    /* send the reply to the server */
    res= vio->write_packet(vio, (constunsigned char *) reply,
                           strlen(reply)+1);
 
    if (reply != mysql->passwd &&reply != reply_buf)
      free(reply);
 
    if (res)
      return CR_ERROR;
 
    /* repeat unless it was the last question*/
  } while ((cmd & 1) != 1);
 
  /* the job of reading the ok/error packet isleft to the server */
  return CR_OK;
}
 
/**
  initialization function of the dialog plugin
 
  Pick up the client'sauthentication_dialog_ask() function, if exists,
  or fall back to the default implementation.
*/
 
static intinit_dialog(char *unused1  __attribute__((unused)),
                       size_t unused2  __attribute__((unused)),
                       int unused3     __attribute__((unused)),
                       va_list unused4__attribute__((unused)))
{
  void *sym= dlsym(RTLD_DEFAULT,"mysql_authentication_dialog_ask");
  ask= sym ?(mysql_authentication_dialog_ask_t) sym : builtin_ask;
  return 0;
}
 
mysql_declare_client_plugin(AUTHENTICATION)
  "dialog",
  "Sergei Golubchik",
  "Dialog Client AuthenticationPlugin",
  {0,1,0},
  "GPL",
  NULL,
  init_dialog,
  NULL,
  NULL,
  perform_dialog
mysql_end_client_plugin;
 

后记:

(翻译、整理自官方文档)

在mysql5.5中,什么是proxy user呢?

客户端请求的用户名(externaluser)可以通过代理另一个用户来获得其权限,external user成为了proxy user,而另一个被代理的用户名成为proxied user,

为了实现该机制,需要满足如下条件:

1).当一个客户端连接被视为代理人时,auth plugin需要返回一个不同的用户名;

2).代理人账户必须设置为通过auth plugin来认证,通过CREATE USER/GRANT来分配账户;

3).代理人账户必须有PROXY权限,举例如下:

CREATE USER 'empl_external'@'localhost'
  IDENTIFIED WITH auth_plugin AS 'auth_string';
CREATE USER 'employee'@'localhost'
  IDENTIFIED BY 'employee_pass';
GRANT PROXY
  ON 'employee'@'localhost'
  TO 'empl_external'@'localhost';

当客户端以用户名empl_external 发起连接时,auth_plugin 通过一些信息(auth_string或其他一些信息)来确认是否接受该用户,并返回用户名employee给服务器端;服务器端会检查empl_external用户是否有在employee上的PROXY权限

 

 

Client plugin API:

http://dev.mysql.com/doc/refman/5.5/en/c-api-plugin-functions.html

 

 

 

smtp auth

03-13

我的email(C语言)程序对smtp.263.net 的验证通过了,但对smtp.sina.com.cn不行。rnrn以下是Outlook Express的log,我看不出有何神秘。rnrnsina的user、pswd的BASE64编码肯定没有错,当我调试到***DEBUG*****处时,等到的rn回复是超时(421);通过telnet 至smtp.sina.com.cn 25时,对方server的响应时间rn非常快,我总是超时(421),rn1、请问专家这问题如何解决?rn2、请问Outlook Express 又是如何解决这个问题的?rnrnrnOutlook Express 5.00.2314.1300rnSMTP Log started at 03/10/2001 16:26:21rnSMTP: 16:26:32 [rx] 220 smtp.263.net ESMTPrnSMTP: 16:26:32 [tx] EHLO HHFrnSMTP: 16:26:32 [rx] 250-smtp.263.netrnSMTP: 16:26:32 [rx] 250-PIPELININGrnSMTP: 16:26:32 [rx] 250-SIZE 10240000rnSMTP: 16:26:32 [rx] 250-ETRNrnSMTP: 16:26:32 [rx] 250-AUTH LOGINrnSMTP: 16:26:32 [rx] 250 8BITMIMErnSMTP: 16:26:32 [tx] AUTH LOGINrnSMTP: 16:26:32 [rx] 334 VXNlcm5hbWU6rnSMTP: 16:26:32 [tx] cXh6ZkrnSMTP: 16:26:33 [rx] 334 UGFzc3dvcmQ6rnSMTP: 16:26:33 [tx] DgDg4O=rnSMTP: 16:26:33 [rx] 235 Authentication successfulrnrnrnOutlook Express 5.00.2314.1300rnSMTP Log started at 03/10/2001 16:33:22rnSMTP: 16:33:23 [rx] 220 sina.com ESMTPrnSMTP: 16:34:28 [tx] EHLO HHFrnSMTP: 16:34:28 [rx] 250-sina.comrnSMTP: 16:34:28 [rx] 250-AUTH=LOGINrnSMTP: 16:34:28 [rx] 250-AUTH LOGINrnSMTP: 16:34:28 [rx] 250-PIPELININGrnSMTP: 16:34:28 [rx] 250 8BITMIMErnSMTP: 16:34:29 [tx] AUTH LOGINrnSMTP: 16:34:29 [rx] 334 VXNlcm5hbWU6rnSMTP: 16:34:29 [tx] d2l6YXJ3I=   ***DEBUG*****rnSMTP: 16:34:29 [rx] 334 UGFzc3dvcmQ6rnSMTP: 16:34:29 [tx] OD4g4ODg=rnSMTP: 16:34:30 [rx] 235 验证通过 - authentication successfullyrn

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试