Postgres登陆认证源码

--//客户端认证函数入口

ClientAuthentication(Port *port)

void
ClientAuthentication(Port *port)
{
        int                     status = STATUS_ERROR;
        char       *logdetail = NULL;

        /*
         * Get the authentication method to use for this frontend/database
         * combination.  Note: we do not parse the file at this point; this has
         * already been done elsewhere.  hba.c dropped an error message into the
         * server logfile if parsing the hba config file failed.
         */
        hba_getauthmethod(port);     --//调用check_hba ①

        CHECK_FOR_INTERRUPTS();

        /*
         * This is the first point where we have access to the hba record for the
         * current connection, so perform any verifications based on the hba
         * options field that should be done *before* the authentication here.
         */
        if (port->hba->clientcert)
        {
                /* If we haven't loaded a root certificate store, fail */
                if (!secure_loaded_verify_locations())
                        ereport(FATAL,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("client certificates can only be checked if a root certificate store is available")));

                /*
                 * If we loaded a root certificate store, and if a certificate is
                 * present on the client, then it has been verified against our root
                 * certificate store, and the connection would have been aborted
                 * already if it didn't verify ok.
                 */
                if (!port->peer_cert_valid)
                        ereport(FATAL,
                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                         errmsg("connection requires a valid client certificate")));
        }

        /*
         * Now proceed to do the actual authentication check
         */
        switch (port->hba->auth_method)
        {
                case uaReject:

                        /*
                         * An explicit "reject" entry in pg_hba.conf.  This report exposes
                         * the fact that there's an explicit reject entry, which is
                         * perhaps not so desirable from a security standpoint; but the
                         * message for an implicit reject could confuse the DBA a lot when
                         * the true situation is a match to an explicit reject.  And we
                         * don't want to change the message for an implicit reject.  As
                         * noted below, the additional information shown here doesn't
                         * expose anything not known to an attacker.
                         */
                        {
                                char            hostinfo[NI_MAXHOST];

                                pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
                                                                   hostinfo, sizeof(hostinfo),
                                                                   NULL, 0,
                                                                   NI_NUMERICHOST);

                                if (am_walsender)
                                {
#ifdef USE_SSL
                                        ereport(FATAL,
                                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                                         errmsg("pg_hba.conf rejects replication connection for host \"%s\", user \"%s\", %s",
                                                                        hostinfo, port->user_name,
                                                                        port->ssl_in_use ? _("SSL on") : _("SSL off"))));
#else
                                        ereport(FATAL,
                                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                                         errmsg("pg_hba.conf rejects replication connection for host \"%s\", user \"%s\"",
                                                                        hostinfo, port->user_name)));
#endif
                                }
                                else
                                {
#ifdef USE_SSL
                                        ereport(FATAL,
                                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                                         errmsg("pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\", %s",
                                                                        hostinfo, port->user_name,
                                                                        port->database_name,
                                                                        port->ssl_in_use ? _("SSL on") : _("SSL off"))));
#else
                                        ereport(FATAL,
                                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                                         errmsg("pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\"",
                                                                        hostinfo, port->user_name,
                                                                        port->database_name)));
#endif
                                }
                                break;
                        }

                case uaImplicitReject:

                        /*
                         * No matching entry, so tell the user we fell through.
                         *
                         * NOTE: the extra info reported here is not a security breach,
                         * because all that info is known at the frontend and must be
                         * assumed known to bad guys.  We're merely helping out the less
                         * clueful good guys.
                         */
                        {
                                char            hostinfo[NI_MAXHOST];

                                pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
                                                                   hostinfo, sizeof(hostinfo),
                                                                   NULL, 0,
                                                                   NI_NUMERICHOST);

#define HOSTNAME_LOOKUP_DETAIL(port) \
                                (port->remote_hostname ? \
                                 (port->remote_hostname_resolv == +1 ? \
                                  errdetail_log("Client IP address resolved to \"%s\", forward lookup matches.", \
                                                                port->remote_hostname) : \
                                  port->remote_hostname_resolv == 0 ? \
                                  errdetail_log("Client IP address resolved to \"%s\", forward lookup not checked.", \
                                                                port->remote_hostname) : \
                                  port->remote_hostname_resolv == -1 ? \
                                  errdetail_log("Client IP address resolved to \"%s\", forward lookup does not match.", \
                                                                port->remote_hostname) : \
                                  port->remote_hostname_resolv == -2 ? \
                                  errdetail_log("Could not translate client host name \"%s\" to IP address: %s.", \
                                                                port->remote_hostname, \
                                                                gai_strerror(port->remote_hostname_errcode)) : \
                                  0) \
                                 : (port->remote_hostname_resolv == -2 ? \
                                        errdetail_log("Could not resolve client IP address to a host name: %s.", \
                                                                  gai_strerror(port->remote_hostname_errcode)) : \
                                        0))

                                if (am_walsender)
                                {
#ifdef USE_SSL
                                        ereport(FATAL,
                                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                                         errmsg("no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\", %s",
                                                                        hostinfo, port->user_name,
                                                                        port->ssl_in_use ? _("SSL on") : _("SSL off")),
                                                         HOSTNAME_LOOKUP_DETAIL(port)));
#else
                                        ereport(FATAL,
                                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                                         errmsg("no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\"",
                                                                        hostinfo, port->user_name),
                                                         HOSTNAME_LOOKUP_DETAIL(port)));
#endif
                                }
                                else
                                {
#ifdef USE_SSL
                                        ereport(FATAL,
                                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                                         errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s",
                                                                        hostinfo, port->user_name,
                                                                        port->database_name,
                                                                        port->ssl_in_use ? _("SSL on") : _("SSL off")),
                                                         HOSTNAME_LOOKUP_DETAIL(port)));
#else
                                        ereport(FATAL,
                                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                                         errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\"",
                                                                        hostinfo, port->user_name,
                                                                        port->database_name),
                                                         HOSTNAME_LOOKUP_DETAIL(port)));
#endif
                                }
                                break;
                        }

                case uaGSS:
#ifdef ENABLE_GSS
                        sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
                        status = pg_GSS_recvauth(port);
#else
                        Assert(false);
#endif
                        break;

                case uaSSPI:
#ifdef ENABLE_SSPI
                        sendAuthRequest(port, AUTH_REQ_SSPI, NULL, 0);
                        status = pg_SSPI_recvauth(port);
#else
                        Assert(false);
#endif
                        break;

                case uaPeer:
#ifdef HAVE_UNIX_SOCKETS
                        status = auth_peer(port);
#else
                        Assert(false);
#endif
                        break;

                case uaIdent:
                        status = ident_inet(port);
                        break;

                case uaMD5:
                case uaSCRAM:
                        status = CheckPWChallengeAuth(port, &logdetail);
                        break;

                case uaPassword:
                        status = CheckPasswordAuth(port, &logdetail);
                        break;

                case uaPAM:
#ifdef USE_PAM
                        status = CheckPAMAuth(port, port->user_name, "");
#else
                        Assert(false);
#endif                                                  /* USE_PAM */
                        break;

                case uaBSD:
#ifdef USE_BSD_AUTH
                        status = CheckBSDAuth(port, port->user_name);
#else
                        Assert(false);
#endif                                                  /* USE_BSD_AUTH */
                        break;

                case uaLDAP:
#ifdef USE_LDAP
                        status = CheckLDAPAuth(port);
#else
                        Assert(false);
#endif
                        break;

                case uaCert:
#ifdef USE_SSL
                        status = CheckCertAuth(port);
#else
                        Assert(false);
#endif
                        break;
                case uaRADIUS:
                        status = CheckRADIUSAuth(port);
                        break;
                case uaTrust:
                        status = STATUS_OK;
                        break;
        }

        if (ClientAuthentication_hook)
                (*ClientAuthentication_hook) (port, status);

        if (status == STATUS_OK)
                sendAuthRequest(port, AUTH_REQ_OK, NULL, 0);
        else
                auth_failed(port, status, logdetail);
}

--//函数参数类型为Port结构体

--//文件名字libpq-be.h

typedef struct Port
{
        pgsocket        sock;                   /* File descriptor */
        bool            noblock;                /* is the socket in non-blocking mode? */
        ProtocolVersion proto;          /* FE/BE protocol version */
        SockAddr        laddr;                  /* local addr (postmaster) */
        SockAddr        raddr;                  /* remote addr (client) */
        char       *remote_host;        /* name (or ip addr) of remote host */
        char       *remote_hostname;    /* name (not ip addr) of remote host, if
                                                                         * available */
        int                     remote_hostname_resolv; /* see above */
        int                     remote_hostname_errcode;        /* see above */
        char       *remote_port;        /* text rep of remote port */
        CAC_state       canAcceptConnections;   /* postmaster connection status */

        /*
         * Information that needs to be saved from the startup packet and passed
         * into backend execution.  "char *" fields are NULL if not set.
         * guc_options points to a List of alternating option names and values.
         */
        char       *database_name;
        char       *user_name;
        char       *cmdline_options;
        List       *guc_options;

        /*
         * Information that needs to be held during the authentication cycle.
         */
        HbaLine    *hba;    --//结构体指针

        /*
         * Information that really has no business at all being in struct Port,
         * but since it gets used by elog.c in the same way as database_name and
         * other members of this struct, we may as well keep it here.
         */
        TimestampTz SessionStartTime;   /* backend start time */

        /*
         * TCP keepalive settings.
         *
         * default values are 0 if AF_UNIX or not yet known; current values are 0
         * if AF_UNIX or using the default. Also, -1 in a default value means we
         * were unable to find out the default (getsockopt failed).
         */
        int                     default_keepalives_idle;
        int                     default_keepalives_interval;
        int                     default_keepalives_count;
        int                     keepalives_idle;
        int                     keepalives_interval;
        int                     keepalives_count;

#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)

        /*
         * If GSSAPI is supported, store GSSAPI information. Otherwise, store a
         * NULL pointer to make sure offsets in the struct remain the same.
         */
        pg_gssinfo *gss;
#else
        void       *gss;
#endif

        /*
         * SSL structures.
         */
        bool            ssl_in_use;
        char       *peer_cn;
        bool            peer_cert_valid;

        /*
         * OpenSSL structures. (Keep these last so that the locations of other
         * fields are the same whether or not you build with OpenSSL.)
         */
#ifdef USE_OPENSSL
        SSL                *ssl;
        X509       *peer;
#endif
} Port;

--//HbaLine结构体

--//HbaLine  头文件hba.h
typedef struct HbaLine
{
        int                     linenumber;
        char       *rawline;
        ConnType        conntype;
        List       *databases;
        List       *roles;
        struct sockaddr_storage addr;
        struct sockaddr_storage mask;
        IPCompareMethod ip_cmp_method;
        char       *hostname;
        UserAuth        auth_method;   --//这个认证方式为一个enum

        char       *usermap;
        char       *pamservice;
        bool            pam_use_hostname;
        bool            ldaptls;
        char       *ldapscheme;
        char       *ldapserver;
        int                     ldapport;
        char       *ldapbinddn;
        char       *ldapbindpasswd;
        char       *ldapsearchattribute;
        char       *ldapsearchfilter;
        char       *ldapbasedn;
        int                     ldapscope;
        char       *ldapprefix;
        char       *ldapsuffix;
        bool            clientcert;
        char       *krb_realm;
        bool            include_realm;
        bool            compat_realm;
        bool            upn_username;
        List       *radiusservers;
        char       *radiusservers_s;
        List       *radiussecrets;
        char       *radiussecrets_s;
        List       *radiusidentifiers;
        char       *radiusidentifiers_s;
        List       *radiusports;
        char       *radiusports_s;
} HbaLine;

--//UserAuth

[root@postgres src]# grep -nir 'UserAuth' ./
./include/libpq/hba.h:23: * Note: keep this in sync with the UserAuthName array in hba.c.
./include/libpq/hba.h:25:typedef enum UserAuth
./include/libpq/hba.h:43:} UserAuth;
./include/libpq/hba.h:72:       UserAuth        auth_method;

typedef enum UserAuth
{
        uaReject,
        uaImplicitReject,                       /* Not a user-visible option */
        uaTrust,
        uaIdent,
        uaPassword,
        uaMD5,
        uaSCRAM,
        uaGSS,
        uaSSPI,
        uaPAM,
        uaBSD,
        uaLDAP,
        uaCert,
        uaRADIUS,
        uaPeer
#define USER_AUTH_LAST uaPeer   /* Must be last value of this enum */
} UserAuth;

--//查看ClientAuthentication中调用的hba_getauthmethod函数

--//hba.c
void
hba_getauthmethod(hbaPort *port)
{
        check_hba(port);
}

--//查看check_hba函数  文件名字hba.c

static void
check_hba(hbaPort *port)     --//hbaPort的定义在头文件hba.h typedef struct Port hbaPort;
{
        Oid                     roleid;
        ListCell   *line;          --//  ./nodes/pg_list.h:53:struct ListCell
        HbaLine    *hba;           --// [root@postgres libpq]# grep -ni 'HbaLine' *.h
																															 hba.h:61:typedef struct HbaLine
																															 hba.h:102:} HbaLine;

        /* Get the target role's OID.  Note we do not error out for bad role. */
        roleid = get_role_oid(port->user_name, true);
         //一行行匹配pg_hba.conf的内容
        foreach(line, parsed_hba_lines)
        {
                hba = (HbaLine *) lfirst(line);

                /* Check connection type */
                //local unix连接方式
                if (hba->conntype == ctLocal)
                {
                        if (!IS_AF_UNIX(port->raddr.addr.ss_family))
                                continue;
                }
                else
                {
                        if (IS_AF_UNIX(port->raddr.addr.ss_family))
                                continue;

                        /* Check SSL state */
                        if (port->ssl_in_use)
                        {
                                /* Connection is SSL, match both "host" and "hostssl" */
                                if (hba->conntype == ctHostNoSSL)
                                        continue;
                        }
                        else
                        {
                                /* Connection is not SSL, match both "host" and "hostnossl" */
                                if (hba->conntype == ctHostSSL)
                                        continue;
                        }
                        //使用ip方式连接,ip方式连接包括如下:
                        //1、完整的IP地址(eg:192.168.40.130/32)2、小子网掩码(eg:192.168.40.0/24)3、大子网掩码(eg:192.168.0.0/16)
                        /* Check IP address */
                        switch (hba->ip_cmp_method)
                        {
                                case ipCmpMask:
                                        if (hba->hostname)
                                        {
                                                if (!check_hostname(port,
                                                                                        hba->hostname))
                                                        continue;
                                        }
                                        else
                                        {
                                                if (!check_ip(&port->raddr,
                                                                          (struct sockaddr *) &hba->addr,
                                                                          (struct sockaddr *) &hba->mask))
                                                        continue;
                                        }
                                        break;
                                case ipCmpAll:
                                        break;
                                case ipCmpSameHost:
                                case ipCmpSameNet:
                                        if (!check_same_host_or_net(&port->raddr,
                                                                                                hba->ip_cmp_method))
                                                continue;
                                        break;
                                default:
                                        /* shouldn't get here, but deem it no-match if so */
                                        continue;
                        }
                }                                               /* != ctLocal */

                /* Check database and role */
                if (!check_db(port->database_name, port->user_name, roleid,
                                          hba->databases))
                        continue;

                if (!check_role(port->user_name, roleid, hba->roles))
                        continue;

                /* Found a record that matched! */
                port->hba = hba;
                return;
        }

        //如果不是在for循环之内return,那么会执行下面3个步骤,hba的认证方式为uaImplicitReject(拒绝连接)
        /* If no matching entry was found, then implicitly reject. */
        //申请HbaLine结构体大小的空间
        hba = palloc0(sizeof(HbaLine));
        //认证方式uaImplicitReject
        hba->auth_method = uaImplicitReject;
        //把hba赋值给port结构体中的hba结构体指针
        port->hba = hba;
}

--//ClientAuthentication函数中switch (port->hba->auth_method)的结果

case uaReject:

//就是check_hba函数中没有在for循环之内结束的返回类型,结果是拒绝连接的

case uaImplicitReject:

--//打印拒绝连接的一些日志原因是什么等等。  
 case uaImplicitReject:

                        /*
                         * No matching entry, so tell the user we fell through.
                         *
                         * NOTE: the extra info reported here is not a security breach,
                         * because all that info is known at the frontend and must be
                         * assumed known to bad guys.  We're merely helping out the less
                         * clueful good guys.
                         */
                        {
                                char            hostinfo[NI_MAXHOST];

                                pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
                                                                   hostinfo, sizeof(hostinfo),
                                                                   NULL, 0,
                                                                   NI_NUMERICHOST);

#define HOSTNAME_LOOKUP_DETAIL(port) \
                                (port->remote_hostname ? \
                                 (port->remote_hostname_resolv == +1 ? \
                                  errdetail_log("Client IP address resolved to \"%s\", forward lookup matches.", \
                                                                port->remote_hostname) : \
                                  port->remote_hostname_resolv == 0 ? \
                                  errdetail_log("Client IP address resolved to \"%s\", forward lookup not checked.", \
                                                                port->remote_hostname) : \
                                  port->remote_hostname_resolv == -1 ? \
                                  errdetail_log("Client IP address resolved to \"%s\", forward lookup does not match.", \
                                                                port->remote_hostname) : \
                                  port->remote_hostname_resolv == -2 ? \
                                  errdetail_log("Could not translate client host name \"%s\" to IP address: %s.", \
                                                                port->remote_hostname, \
                                                                gai_strerror(port->remote_hostname_errcode)) : \
                                  0) \
                                 : (port->remote_hostname_resolv == -2 ? \
                                        errdetail_log("Could not resolve client IP address to a host name: %s.", \
                                                                  gai_strerror(port->remote_hostname_errcode)) : \
                                        0))

                                if (am_walsender)
                                {
#ifdef USE_SSL
                                        ereport(FATAL,
                                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                                         errmsg("no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\", %s",
                                                                        hostinfo, port->user_name,
                                                                        port->ssl_in_use ? _("SSL on") : _("SSL off")),
                                                         HOSTNAME_LOOKUP_DETAIL(port)));
#else
                                        ereport(FATAL,
                                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                                         errmsg("no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\"",
                                                                        hostinfo, port->user_name),
                                                         HOSTNAME_LOOKUP_DETAIL(port)));
#endif
                                }
                                else
                                {
#ifdef USE_SSL
                                        ereport(FATAL,
                                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                                         errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s",
                                                                        hostinfo, port->user_name,
                                                                        port->database_name,
                                                                        port->ssl_in_use ? _("SSL on") : _("SSL off")),
                                                         HOSTNAME_LOOKUP_DETAIL(port)));
#else
                                        ereport(FATAL,
                                                        (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                                         errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\"",
                                                                        hostinfo, port->user_name,
                                                                        port->database_name),
                                                         HOSTNAME_LOOKUP_DETAIL(port)));
#endif
                                }
                                break;
                        }

 case uaGSS:
#ifdef ENABLE_GSS
                        sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
                        status = pg_GSS_recvauth(port);
#else
                        Assert(false);
#endif
                        break;

case uaSSPI:
#ifdef ENABLE_SSPI
                        sendAuthRequest(port, AUTH_REQ_SSPI, NULL, 0);
                        status = pg_SSPI_recvauth(port);
#else
                        Assert(false);
#endif
                        break;

 case uaMD5:
 case uaSCRAM:

                        status = CheckPWChallengeAuth(port, &logdetail);
                        break;

上面的case都是一些认证方式,比如uaMD5、uaSCRAM认证方式会调用CheckPWChallengeAuth函数

/*
 * MD5 and SCRAM authentication.
 */
static int
CheckPWChallengeAuth(Port *port, char **logdetail)
{
        int                     auth_result;
        char       *shadow_pass;
        PasswordType pwtype;

        Assert(port->hba->auth_method == uaSCRAM ||
                   port->hba->auth_method == uaMD5);

        /* First look up the user's password. */
        //检查用户密码信息(用户是否存在,密码是否存在,密码是否过期等)
        shadow_pass = get_role_password(port->user_name, logdetail);

        /*
         * If the user does not exist, or has no password or it's expired, we
         * still go through the motions of authentication, to avoid revealing to
         * the client that the user didn't exist.  If 'md5' is allowed, we choose
         * whether to use 'md5' or 'scram-sha-256' authentication based on current
         * password_encryption setting.  The idea is that most genuine users
         * probably have a password of that type, and if we pretend that this user
         * had a password of that type, too, it "blends in" best.
         */
        if (!shadow_pass)
                pwtype = Password_encryption;
        else
                pwtype = get_password_type(shadow_pass);

        /*
         * If 'md5' authentication is allowed, decide whether to perform 'md5' or
         * 'scram-sha-256' authentication based on the type of password the user
         * has.  If it's an MD5 hash, we must do MD5 authentication, and if it's a
         * SCRAM verifier, we must do SCRAM authentication.
         *
         * If MD5 authentication is not allowed, always use SCRAM.  If the user
         * had an MD5 password, CheckSCRAMAuth() will fail.
         */
        //判断认证方式
        if (port->hba->auth_method == uaMD5 && pwtype == PASSWORD_TYPE_MD5)
                auth_result = CheckMD5Auth(port, shadow_pass, logdetail);
        else
                auth_result = CheckSCRAMAuth(port, shadow_pass, logdetail);

        if (shadow_pass)
                pfree(shadow_pass);

        /*
         * If get_role_password() returned error, return error, even if the
         * authentication succeeded.
         */
        if (!shadow_pass)
        {
                Assert(auth_result != STATUS_OK);
                return STATUS_ERROR;
        }
        return auth_result;
}

--//get_role_password

/*
 * Fetch stored password for a user, for authentication.
 *
 * On error, returns NULL, and stores a palloc'd string describing the reason,
 * for the postmaster log, in *logdetail.  The error reason should *not* be
 * sent to the client, to avoid giving away user information!
 */
char *
get_role_password(const char *role, char **logdetail)
{
        TimestampTz vuntil = 0;
        HeapTuple       roleTup;
        Datum           datum;
        bool            isnull;
        char       *shadow_pass;

        /* Get role info from pg_authid */
        roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
        if (!HeapTupleIsValid(roleTup))
        {
                *logdetail = psprintf(_("Role \"%s\" does not exist."),
                                                          role);
                return NULL;                    /* no such user */
        }

        datum = SysCacheGetAttr(AUTHNAME, roleTup,
                                                        Anum_pg_authid_rolpassword, &isnull);
        if (isnull)
        {
                ReleaseSysCache(roleTup);
                *logdetail = psprintf(_("User \"%s\" has no password assigned."),
                                                          role);
                return NULL;                    /* user has no password */
        }
        shadow_pass = TextDatumGetCString(datum);

        datum = SysCacheGetAttr(AUTHNAME, roleTup,
                                                        Anum_pg_authid_rolvaliduntil, &isnull);
        if (!isnull)
                vuntil = DatumGetTimestampTz(datum);

        ReleaseSysCache(roleTup);

        /*
         * Password OK, but check to be sure we are not past rolvaliduntil
         */
        if (!isnull && vuntil < GetCurrentTimestamp())
        {
                *logdetail = psprintf(_("User \"%s\" has an expired password."),
                                                          role);
                return NULL;
        }

        return shadow_pass;
}

--//CheckMD5Auth

static int
CheckMD5Auth(Port *port, char *shadow_pass, char **logdetail)
{
        char            md5Salt[4];             /* Password salt */
        char       *passwd;
        int                     result;

        if (Db_user_namespace)
                ereport(FATAL,
                                (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                                 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));

        /* include the salt to use for computing the response */
        if (!pg_backend_random(md5Salt, 4))
        {
                ereport(LOG,
                                (errmsg("could not generate random MD5 salt")));
                return STATUS_ERROR;
        }

        //发送客户端 让客户端输入密码
        sendAuthRequest(port, AUTH_REQ_MD5, md5Salt, 4);
        //接收客户端输入的密码
        passwd = recv_password_packet(port);
        if (passwd == NULL)
                return STATUS_EOF;              /* client wouldn't send password */

        if (shadow_pass)
                //调用md5_crypt_verify把客户端输入的密码加密之后和数据库中的对比
                result = md5_crypt_verify(port->user_name, shadow_pass, passwd,
                                                                  md5Salt, 4, logdetail);
        else
                result = STATUS_ERROR;

        pfree(passwd);

        return result;
}

--//sendAuthRequest

/*
 * Send an authentication request packet to the frontend.
 */
static void
sendAuthRequest(Port *port, AuthRequest areq, const char *extradata, int extralen)
{
        StringInfoData buf;

        CHECK_FOR_INTERRUPTS();

        pq_beginmessage(&buf, 'R');
        pq_sendint32(&buf, (int32) areq);
        if (extralen > 0)
                pq_sendbytes(&buf, extradata, extralen);

        pq_endmessage(&buf);

        /*
         * Flush message so client will see it, except for AUTH_REQ_OK and
         * AUTH_REQ_SASL_FIN, which need not be sent until we are ready for
         * queries.
         */
        if (areq != AUTH_REQ_OK && areq != AUTH_REQ_SASL_FIN)
                pq_flush();

        CHECK_FOR_INTERRUPTS();
}

--//recv_password_packet

[root@postgres src]# grep -nir 'recv_password_packet' ./
Binary file ./backend/libpq/auth.o matches
./backend/libpq/auth.c:50:static char *recv_password_packet(Port *port);
./backend/libpq/auth.c:647:recv_password_packet(Port *port)


/*
 * Collect password response packet from frontend.
 *
 * Returns NULL if couldn't get password, else palloc'd string.
 */
static char *
recv_password_packet(Port *port)
{
        StringInfoData buf;

        pq_startmsgread();
        if (PG_PROTOCOL_MAJOR(port->proto) >= 3)
        {
                /* Expect 'p' message type */
                int                     mtype;

                mtype = pq_getbyte();
                if (mtype != 'p')
                {
                        /*
                         * If the client just disconnects without offering a password,
                         * don't make a log entry.  This is legal per protocol spec and in
                         * fact commonly done by psql, so complaining just clutters the
                         * log.
                         */
                        if (mtype != EOF)
                                ereport(ERROR,
                                                (errcode(ERRCODE_PROTOCOL_VIOLATION),
                                                 errmsg("expected password response, got message type %d",
                                                                mtype)));
                        return NULL;            /* EOF or bad message type */
                }
        }
        else
        {
                /* For pre-3.0 clients, avoid log entry if they just disconnect */
                if (pq_peekbyte() == EOF)
                        return NULL;            /* EOF */
        }

        initStringInfo(&buf);
        if (pq_getmessage(&buf, 1000))  /* receive password */
        {
                /* EOF - pq_getmessage already logged a suitable message */
                pfree(buf.data);
                return NULL;
        }

        /*
         * Apply sanity check: password packet length should agree with length of
         * contained string.  Note it is safe to use strlen here because
         * StringInfo is guaranteed to have an appended '\0'.
         */
        if (strlen(buf.data) + 1 != buf.len)
                ereport(ERROR,
                                (errcode(ERRCODE_PROTOCOL_VIOLATION),
                                 errmsg("invalid password packet size")));

        /*
         * Don't allow an empty password. Libpq treats an empty password the same
         * as no password at all, and won't even try to authenticate. But other
         * clients might, so allowing it would be confusing.
         *
         * Note that this only catches an empty password sent by the client in
         * plaintext. There's also a check in CREATE/ALTER USER that prevents an
         * empty string from being stored as a user's password in the first place.
         * We rely on that for MD5 and SCRAM authentication, but we still need
         * this check here, to prevent an empty password from being used with
         * authentication methods that check the password against an external
         * system, like PAM, LDAP and RADIUS.
         */
        if (buf.len == 1)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PASSWORD),
                                 errmsg("empty password returned by client")));

        /* Do not echo password to logs, for security. */
        elog(DEBUG5, "received password packet");

        /*
         * Return the received string.  Note we do not attempt to do any
         * character-set conversion on it; since we don't yet know the client's
         * encoding, there wouldn't be much point.
         */
        return buf.data;
}

--//md5_crypt_verify

[root@postgres src]# grep -nir 'md5_crypt_verify' ./
./include/libpq/crypt.h:40:extern int md5_crypt_verify(const char *role, const char *shadow_pass,
Binary file ./backend/libpq/auth.o matches
Binary file ./backend/libpq/crypt.o matches
./backend/libpq/crypt.c:160:md5_crypt_verify(const char *role, const char *shadow_pass,


/*
 * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
 *
 * 'shadow_pass' is the user's correct password or password hash, as stored
 * in pg_authid.rolpassword.
 * 'client_pass' is the response given by the remote user to the MD5 challenge.
 * 'md5_salt' is the salt used in the MD5 authentication challenge.
 *
 * In the error case, optionally store a palloc'd string at *logdetail
 * that will be sent to the postmaster log (but not the client).
 */
int
md5_crypt_verify(const char *role, const char *shadow_pass,
                                 const char *client_pass,
                                 const char *md5_salt, int md5_salt_len,
                                 char **logdetail)
{
        int                     retval;
        char            crypt_pwd[MD5_PASSWD_LEN + 1];

        Assert(md5_salt_len > 0);

        if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
        {
                /* incompatible password hash format. */
                *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
                                                          role);
                return STATUS_ERROR;
        }

        /*
         * Compute the correct answer for the MD5 challenge.
         *
         * We do not bother setting logdetail for any pg_md5_encrypt failure
         * below: the only possible error is out-of-memory, which is unlikely, and
         * if it did happen adding a psprintf call would only make things worse.
         */
        /* stored password already encrypted, only do salt */
        if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
                                                md5_salt, md5_salt_len,
                                                crypt_pwd))
        {
                return STATUS_ERROR;
        }

        if (strcmp(client_pass, crypt_pwd) == 0)
                retval = STATUS_OK;
        else
        {
                *logdetail = psprintf(_("Password does not match for user \"%s\"."),
                                                          role);
                retval = STATUS_ERROR;
        }

        return retval;
}

函数的过程大致如下:

ClientAuthentication->hba_getauthmethod->check_hba(判断发起的连接是属于unix、ip连接方式或者是在pg_hba.conf中不存在)->sendAuthRequest(向客户端要密码)->pq_endmessage(发送buffer数据到客户端)->recv_password_packet(接收客户端输入的密码)->md5_crypt_verify(对输入的密码加密和数据库中的对比是否一致)->sendAuthRequest(如果认证通过再次调用该函数,这次传参是AUTH_REQ_OK)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值