static int pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq) { int ret; char *crypt_pwd = NULL; const char *pwd_to_send; /* Encrypt the password if needed. */ switch (areq) { case AUTH_REQ_MD5: { char *crypt_pwd2; /* Allocate enough space for two MD5 hashes */ crypt_pwd = malloc(2 * (MD5_PASSWD_LEN + 1)); if (!crypt_pwd) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n")); return STATUS_ERROR; } crypt_pwd2 = crypt_pwd + MD5_PASSWD_LEN + 1; if (!pg_md5_encrypt(password, conn->pguser, strlen(conn->pguser), crypt_pwd2)) { free(crypt_pwd); return STATUS_ERROR; } if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"), conn->md5Salt, sizeof(conn->md5Salt), crypt_pwd)) { free(crypt_pwd); return STATUS_ERROR; } pwd_to_send = crypt_pwd; break; } case AUTH_REQ_PASSWORD: pwd_to_send = password; break; default: return STATUS_ERROR; } /* Packet has a message type as of protocol 3.0 */ if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) ret = pqPacketSend(conn, 'p', pwd_to_send, strlen(pwd_to_send) + 1); else ret = pqPacketSend(conn, 0, pwd_to_send, strlen(pwd_to_send) + 1); if (crypt_pwd) free(crypt_pwd); return ret; }
/* * Continue SSPI authentication with next token as needed. */ static int pg_SSPI_continue(PGconn *conn) { SECURITY_STATUS r; CtxtHandle newContext; ULONG contextAttr; SecBufferDesc inbuf; SecBufferDesc outbuf; SecBuffer OutBuffers[1]; SecBuffer InBuffers[1]; if (conn->sspictx != NULL) { /* * On runs other than the first we have some data to send. Put this * data in a SecBuffer type structure. */ inbuf.ulVersion = SECBUFFER_VERSION; inbuf.cBuffers = 1; inbuf.pBuffers = InBuffers; InBuffers[0].pvBuffer = conn->ginbuf.value; InBuffers[0].cbBuffer = conn->ginbuf.length; InBuffers[0].BufferType = SECBUFFER_TOKEN; } OutBuffers[0].pvBuffer = NULL; OutBuffers[0].BufferType = SECBUFFER_TOKEN; OutBuffers[0].cbBuffer = 0; outbuf.cBuffers = 1; outbuf.pBuffers = OutBuffers; outbuf.ulVersion = SECBUFFER_VERSION; r = InitializeSecurityContext(conn->sspicred, conn->sspictx, conn->sspitarget, ISC_REQ_ALLOCATE_MEMORY, 0, SECURITY_NETWORK_DREP, (conn->sspictx == NULL) ? NULL : &inbuf, 0, &newContext, &outbuf, &contextAttr, NULL); if (r != SEC_E_OK && r != SEC_I_CONTINUE_NEEDED) { pg_SSPI_error(conn, libpq_gettext("SSPI continuation error"), r); return STATUS_ERROR; } if (conn->sspictx == NULL) { /* On first run, transfer retreived context handle */ conn->sspictx = malloc(sizeof(CtxtHandle)); if (conn->sspictx == NULL) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n")); return STATUS_ERROR; } memcpy(conn->sspictx, &newContext, sizeof(CtxtHandle)); } else { /* * On subsequent runs when we had data to send, free buffers that * contained this data. */ free(conn->ginbuf.value); conn->ginbuf.value = NULL; conn->ginbuf.length = 0; } /* * If SSPI returned any data to be sent to the server (as it normally * would), send this data as a password packet. */ if (outbuf.cBuffers > 0) { if (outbuf.cBuffers != 1) { /* * This should never happen, at least not for Kerberos * authentication. Keep check in case it shows up with other * authentication methods later. */ printfPQExpBuffer(&conn->errorMessage, "SSPI returned invalid number of output buffers\n"); return STATUS_ERROR; } /* * If the negotiation is complete, there may be zero bytes to send. * The server is at this point not expecting any more data, so don't * send it. */ if (outbuf.pBuffers[0].cbBuffer > 0) { if (pqPacketSend(conn, 'p', outbuf.pBuffers[0].pvBuffer, outbuf.pBuffers[0].cbBuffer)) { FreeContextBuffer(outbuf.pBuffers[0].pvBuffer); return STATUS_ERROR; } } FreeContextBuffer(outbuf.pBuffers[0].pvBuffer); } /* Cleanup is handled by the code in freePGconn() */ return STATUS_OK; }
/* * Continue GSS authentication with next token as needed. */ static int pg_GSS_continue(PGconn *conn) { OM_uint32 maj_stat, min_stat, lmin_s; maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &conn->gctx, conn->gtarg_nam, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG, 0, GSS_C_NO_CHANNEL_BINDINGS, (conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &conn->ginbuf, NULL, &conn->goutbuf, NULL, NULL); if (conn->gctx != GSS_C_NO_CONTEXT) { free(conn->ginbuf.value); conn->ginbuf.value = NULL; conn->ginbuf.length = 0; } if (conn->goutbuf.length != 0) { /* * GSS generated data to send to the server. We don't care if it's the * first or subsequent packet, just send the same kind of password * packet. */ if (pqPacketSend(conn, 'p', conn->goutbuf.value, conn->goutbuf.length) != STATUS_OK) { gss_release_buffer(&lmin_s, &conn->goutbuf); return STATUS_ERROR; } } gss_release_buffer(&lmin_s, &conn->goutbuf); if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { pg_GSS_error(libpq_gettext("GSSAPI continuation error"), conn, maj_stat, min_stat); gss_release_name(&lmin_s, &conn->gtarg_nam); if (conn->gctx) gss_delete_sec_context(&lmin_s, &conn->gctx, GSS_C_NO_BUFFER); return STATUS_ERROR; } if (maj_stat == GSS_S_COMPLETE) gss_release_name(&lmin_s, &conn->gtarg_nam); return STATUS_OK; }
/* ---------------- * GTMPQconnectPoll * * Poll an asynchronous connection. * * Returns a GTMClientPollingStatusType. * Before calling this function, use select(2) to determine when data * has arrived.. * * You must call GTMPQfinish whether or not this fails. */ GTMClientPollingStatusType GTMPQconnectPoll(GTM_Conn *conn) { if (conn == NULL) return PGRES_POLLING_FAILED; /* Get the new data */ switch (conn->status) { /* * We really shouldn't have been polled in these two cases, but we * can handle it. */ case CONNECTION_BAD: return PGRES_POLLING_FAILED; case CONNECTION_OK: return PGRES_POLLING_OK; /* These are reading states */ case CONNECTION_AWAITING_RESPONSE: case CONNECTION_AUTH_OK: { /* Load waiting data */ int n = gtmpqReadData(conn); if (n < 0) goto error_return; if (n == 0) return PGRES_POLLING_READING; break; } /* These are writing states, so we just proceed. */ case CONNECTION_STARTED: case CONNECTION_MADE: break; case CONNECTION_NEEDED: break; default: appendGTMPQExpBuffer(&conn->errorMessage, "invalid connection state, " "probably indicative of memory corruption\n" ); goto error_return; } keep_going: /* We will come back to here until there is * nothing left to do. */ switch (conn->status) { case CONNECTION_NEEDED: { /* * Try to initiate a connection to one of the addresses * returned by gtm_getaddrinfo_all(). conn->addr_cur is the * next one to try. We fail when we run out of addresses * (reporting the error returned for the *last* alternative, * which may not be what users expect :-(). */ while (conn->addr_cur != NULL) { struct addrinfo *addr_cur = conn->addr_cur; /* Remember current address for possible error msg */ memcpy(&conn->raddr.addr, addr_cur->ai_addr, addr_cur->ai_addrlen); conn->raddr.salen = addr_cur->ai_addrlen; /* Open a socket */ conn->sock = socket(addr_cur->ai_family, SOCK_STREAM, 0); if (conn->sock < 0) { /* * ignore socket() failure if we have more addresses * to try */ if (addr_cur->ai_next != NULL) { conn->addr_cur = addr_cur->ai_next; continue; } appendGTMPQExpBuffer(&conn->errorMessage, "could not create socket: \n"); break; } /* * Select socket options: no delay of outgoing data for * TCP sockets, nonblock mode, close-on-exec. Fail if any * of this fails. */ if (!IS_AF_UNIX(addr_cur->ai_family)) { if (!connectNoDelay(conn)) { close(conn->sock); conn->sock = -1; conn->addr_cur = addr_cur->ai_next; continue; } } /* * Start/make connection. This should not block, since we * are in nonblock mode. If it does, well, too bad. */ if (connect(conn->sock, addr_cur->ai_addr, addr_cur->ai_addrlen) < 0) { if (SOCK_ERRNO == EINPROGRESS || SOCK_ERRNO == EWOULDBLOCK || SOCK_ERRNO == EINTR || SOCK_ERRNO == 0) { /* * This is fine - we're in non-blocking mode, and * the connection is in progress. Tell caller to * wait for write-ready on socket. */ conn->status = CONNECTION_STARTED; return PGRES_POLLING_WRITING; } /* otherwise, trouble */ } else { /* * Hm, we're connected already --- seems the "nonblock * connection" wasn't. Advance the state machine and * go do the next stuff. */ conn->status = CONNECTION_STARTED; goto keep_going; } /* * This connection failed --- set up error report, then * close socket (do it this way in case close() affects * the value of errno...). We will ignore the connect() * failure and keep going if there are more addresses. */ connectFailureMessage(conn, SOCK_ERRNO); if (conn->sock >= 0) { close(conn->sock); conn->sock = -1; } /* * Try the next address, if any. */ conn->addr_cur = addr_cur->ai_next; } /* loop over addresses */ /* * Ooops, no more addresses. An appropriate error message is * already set up, so just set the right status. */ goto error_return; } case CONNECTION_STARTED: { int optval; size_t optlen = sizeof(optval); /* * Write ready, since we've made it here, so the connection * has been made ... or has failed. */ /* * Now check (using getsockopt) that there is not an error * state waiting for us on the socket. */ if (getsockopt(conn->sock, SOL_SOCKET, SO_ERROR, (char *) &optval, (socklen_t *)&optlen) == -1) { appendGTMPQExpBuffer(&conn->errorMessage, libpq_gettext("could not get socket error status: \n")); goto error_return; } else if (optval != 0) { /* * When using a nonblocking connect, we will typically see * connect failures at this point, so provide a friendly * error message. */ connectFailureMessage(conn, optval); /* * If more addresses remain, keep trying, just as in the * case where connect() returned failure immediately. */ if (conn->addr_cur->ai_next != NULL) { if (conn->sock >= 0) { close(conn->sock); conn->sock = -1; } conn->addr_cur = conn->addr_cur->ai_next; conn->status = CONNECTION_NEEDED; goto keep_going; } goto error_return; } /* Fill in the client address */ conn->laddr.salen = sizeof(conn->laddr.addr); if (getsockname(conn->sock, (struct sockaddr *) & conn->laddr.addr, (socklen_t *)&conn->laddr.salen) < 0) { appendGTMPQExpBuffer(&conn->errorMessage, "could not get client address from socket:\n"); goto error_return; } /* * Make sure we can write before advancing to next step. */ conn->status = CONNECTION_MADE; return PGRES_POLLING_WRITING; } case CONNECTION_MADE: { GTM_StartupPacket sp; /* * Build a startup packet. We tell the GTM server/proxy our * PGXC Node name and whether we are a proxy or not. * * When the connection is made from the proxy, we let the GTM * server know about it so that some special headers are * handled correctly by the server. */ strcpy(sp.sp_node_name, conn->gc_node_name); sp.sp_remotetype = conn->remote_type; sp.sp_ispostmaster = conn->is_postmaster; /* * Send the startup packet. * * Theoretically, this could block, but it really shouldn't * since we only got here if the socket is write-ready. */ if (pqPacketSend(conn, 'A', &sp, sizeof (GTM_StartupPacket)) != STATUS_OK) { appendGTMPQExpBuffer(&conn->errorMessage, "could not send startup packet: \n"); goto error_return; } conn->status = CONNECTION_AWAITING_RESPONSE; return PGRES_POLLING_READING; } /* * Handle authentication exchange: wait for postmaster messages * and respond as necessary. */ case CONNECTION_AWAITING_RESPONSE: { char beresp; /* * Scan the message from current point (note that if we find * the message is incomplete, we will return without advancing * inStart, and resume here next time). */ conn->inCursor = conn->inStart; /* Read type byte */ if (gtmpqGetc(&beresp, conn)) { /* We'll come back when there is more data */ return PGRES_POLLING_READING; } /* * Validate message type: we expect only an authentication * request or an error here. Anything else probably means * it's not GTM on the other end at all. */ if (!(beresp == 'R' || beresp == 'E')) { appendGTMPQExpBuffer(&conn->errorMessage, "expected authentication request from " "server, but received %c\n", beresp); goto error_return; } /* Handle errors. */ if (beresp == 'E') { if (gtmpqGets_append(&conn->errorMessage, conn)) { /* We'll come back when there is more data */ return PGRES_POLLING_READING; } /* OK, we read the message; mark data consumed */ conn->inStart = conn->inCursor; goto error_return; } { /* * Server sends a dummy message body of size 4 bytes */ int tmp_int; gtmpqGetInt(&tmp_int, 4, conn); } /* * OK, we successfully read the message; mark data consumed */ conn->inStart = conn->inCursor; /* We are done with authentication exchange */ conn->status = CONNECTION_AUTH_OK; /* Look to see if we have more data yet. */ goto keep_going; } case CONNECTION_AUTH_OK: { /* We can release the address list now. */ gtm_freeaddrinfo_all(conn->addrlist_family, conn->addrlist); conn->addrlist = NULL; conn->addr_cur = NULL; /* Otherwise, we are open for business! */ conn->status = CONNECTION_OK; return PGRES_POLLING_OK; } default: appendGTMPQExpBuffer(&conn->errorMessage, "invalid connection state %c, " "probably indicative of memory corruption\n" , conn->status); goto error_return; } /* Unreachable */ error_return: /* * We used to close the socket at this point, but that makes it awkward * for those above us if they wish to remove this socket from their own * records (an fd_set for example). We'll just have this socket closed * when GTMPQfinish is called (which is compulsory even after an error, since * the connection structure must be freed). */ conn->status = CONNECTION_BAD; return PGRES_POLLING_FAILED; }
static int pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq) { int ret; char *crypt_pwd; /* Encrypt the password if needed. */ switch (areq) { case AUTH_REQ_MD5: { char *crypt_pwd2; /* Allocate enough space for two MD5 hashes */ crypt_pwd = malloc(2 * (MD5_PASSWD_LEN + 1)); if (!crypt_pwd) { fprintf(stderr, libpq_gettext("out of memory\n")); return STATUS_ERROR; } crypt_pwd2 = crypt_pwd + MD5_PASSWD_LEN + 1; if (!pg_md5_encrypt(password, conn->pguser, strlen(conn->pguser), crypt_pwd2)) { free(crypt_pwd); return STATUS_ERROR; } if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"), conn->md5Salt, sizeof(conn->md5Salt), crypt_pwd)) { free(crypt_pwd); return STATUS_ERROR; } break; } case AUTH_REQ_CRYPT: { char salt[3]; StrNCpy(salt, conn->cryptSalt, 3); crypt_pwd = crypt(password, salt); break; } case AUTH_REQ_PASSWORD: /* discard const so we can assign it */ crypt_pwd = (char *) password; break; default: return STATUS_ERROR; } /* Packet has a message type as of protocol 3.0 */ if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) ret = pqPacketSend(conn, 'p', crypt_pwd, strlen(crypt_pwd) + 1); else ret = pqPacketSend(conn, 0, crypt_pwd, strlen(crypt_pwd) + 1); if (areq == AUTH_REQ_MD5) free(crypt_pwd); return ret; }
/* * Continue GSS authentication with next token as needed. */ static int pg_GSS_continue(PGconn *conn, int payloadlen) { OM_uint32 maj_stat, min_stat, lmin_s; gss_buffer_desc ginbuf; gss_buffer_desc goutbuf; /* * On first call, there's no input token. On subsequent calls, read the * input token into a GSS buffer. */ if (conn->gctx != GSS_C_NO_CONTEXT) { ginbuf.length = payloadlen; ginbuf.value = malloc(payloadlen); if (!ginbuf.value) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory allocating GSSAPI buffer (%d)\n"), payloadlen); return STATUS_ERROR; } if (pqGetnchar(ginbuf.value, payloadlen, conn)) { /* * Shouldn't happen, because the caller should've ensured that the * whole message is already in the input buffer. */ free(ginbuf.value); return STATUS_ERROR; } } else { ginbuf.length = 0; ginbuf.value = NULL; } maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &conn->gctx, conn->gtarg_nam, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG, 0, GSS_C_NO_CHANNEL_BINDINGS, (ginbuf.value == NULL) ? GSS_C_NO_BUFFER : &ginbuf, NULL, &goutbuf, NULL, NULL); if (ginbuf.value) free(ginbuf.value); if (goutbuf.length != 0) { /* * GSS generated data to send to the server. We don't care if it's the * first or subsequent packet, just send the same kind of password * packet. */ if (pqPacketSend(conn, 'p', goutbuf.value, goutbuf.length) != STATUS_OK) { gss_release_buffer(&lmin_s, &goutbuf); return STATUS_ERROR; } } gss_release_buffer(&lmin_s, &goutbuf); if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { pg_GSS_error(libpq_gettext("GSSAPI continuation error"), conn, maj_stat, min_stat); gss_release_name(&lmin_s, &conn->gtarg_nam); if (conn->gctx) gss_delete_sec_context(&lmin_s, &conn->gctx, GSS_C_NO_BUFFER); return STATUS_ERROR; } if (maj_stat == GSS_S_COMPLETE) gss_release_name(&lmin_s, &conn->gtarg_nam); return STATUS_OK; }