/* * Send initial GSS authentication token */ static int pg_GSS_startup(PGconn *conn, int payloadlen) { OM_uint32 maj_stat, min_stat; int maxlen; gss_buffer_desc temp_gbuf; char *host = PQhost(conn); if (!(host && host[0] != '\0')) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("host name must be specified\n")); return STATUS_ERROR; } if (conn->gctx) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("duplicate GSS authentication request\n")); return STATUS_ERROR; } /* * Import service principal name so the proper ticket can be acquired by * the GSSAPI system. */ maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; temp_gbuf.value = (char *) malloc(maxlen); if (!temp_gbuf.value) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n")); return STATUS_ERROR; } snprintf(temp_gbuf.value, maxlen, "%s@%s", conn->krbsrvname, host); temp_gbuf.length = strlen(temp_gbuf.value); maj_stat = gss_import_name(&min_stat, &temp_gbuf, GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); free(temp_gbuf.value); if (maj_stat != GSS_S_COMPLETE) { pg_GSS_error(libpq_gettext("GSSAPI name import error"), conn, maj_stat, min_stat); return STATUS_ERROR; } /* * Initial packet is the same as a continuation packet with no initial * context. */ conn->gctx = GSS_C_NO_CONTEXT; return pg_GSS_continue(conn, payloadlen); }
/* * 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; }
static int pg_GSS_recvauth(Port *port) { OM_uint32 maj_stat, min_stat, lmin_s, gflags; int mtype; int ret; StringInfoData buf; gss_buffer_desc gbuf; /* * GSS auth is not supported for protocol versions before 3, because it * relies on the overall message length word to determine the GSS payload * size in AuthenticationGSSContinue and PasswordMessage messages. * (This is, in fact, a design error in our GSS support, because protocol * messages are supposed to be parsable without relying on the length * word; but it's not worth changing it now.) */ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) ereport(FATAL, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("GSSAPI is not supported in protocol version 2"))); if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0) { /* * Set default Kerberos keytab file for the Krb5 mechanism. * * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); except setenv() * not always available. */ if (getenv("KRB5_KTNAME") == NULL) { size_t kt_len = strlen(pg_krb_server_keyfile) + 14; char *kt_path = malloc(kt_len); if (!kt_path) { ereport(LOG, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); return STATUS_ERROR; } snprintf(kt_path, kt_len, "KRB5_KTNAME=%s", pg_krb_server_keyfile); putenv(kt_path); } } /* * We accept any service principal that's present in our keytab. This * increases interoperability between kerberos implementations that see * for example case sensitivity differently, while not really opening up * any vector of attack. */ port->gss->cred = GSS_C_NO_CREDENTIAL; /* * Initialize sequence with an empty context */ port->gss->ctx = GSS_C_NO_CONTEXT; /* * Loop through GSSAPI message exchange. This exchange can consist of * multiple messags sent in both directions. First message is always from * the client. All messages from client to server are password packets * (type 'p'). */ do { mtype = pq_getbyte(); if (mtype != 'p') { /* Only log error if client didn't disconnect. */ if (mtype != EOF) ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("expected GSS response, got message type %d", mtype))); return STATUS_ERROR; } /* Get the actual GSS token */ initStringInfo(&buf); if (pq_getmessage(&buf, 2000)) { /* EOF - pq_getmessage already logged error */ pfree(buf.data); return STATUS_ERROR; } /* Map to GSSAPI style buffer */ gbuf.length = buf.len; gbuf.value = buf.data; elog(DEBUG4, "Processing received GSS token of length %u", (unsigned int) gbuf.length); maj_stat = gss_accept_sec_context( &min_stat, &port->gss->ctx, port->gss->cred, &gbuf, GSS_C_NO_CHANNEL_BINDINGS, &port->gss->name, NULL, &port->gss->outbuf, &gflags, NULL, NULL); /* gbuf no longer used */ pfree(buf.data); elog(DEBUG5, "gss_accept_sec_context major: %d, " "minor: %d, outlen: %u, outflags: %x", maj_stat, min_stat, (unsigned int) port->gss->outbuf.length, gflags); if (port->gss->outbuf.length != 0) { /* * Negotiation generated data to be sent to the client. */ OM_uint32 lmin_s; elog(DEBUG4, "sending GSS response token of length %u", (unsigned int) port->gss->outbuf.length); sendAuthRequest(port, AUTH_REQ_GSS_CONT); gss_release_buffer(&lmin_s, &port->gss->outbuf); } if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { OM_uint32 lmin_s; gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER); pg_GSS_error(ERROR, gettext_noop("accepting GSS security context failed"), maj_stat, min_stat); } if (maj_stat == GSS_S_CONTINUE_NEEDED) elog(DEBUG4, "GSS continue needed"); } while (maj_stat == GSS_S_CONTINUE_NEEDED); if (port->gss->cred != GSS_C_NO_CREDENTIAL) { /* * Release service principal credentials */ gss_release_cred(&min_stat, &port->gss->cred); } /* * GSS_S_COMPLETE indicates that authentication is now complete. * * Get the name of the user that authenticated, and compare it to the pg * username that was specified for the connection. */ maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL); if (maj_stat != GSS_S_COMPLETE) pg_GSS_error(ERROR, gettext_noop("retrieving GSS user name failed"), maj_stat, min_stat); /* * Split the username at the realm separator */ if (strchr(gbuf.value, '@')) { char *cp = strchr(gbuf.value, '@'); *cp = '\0'; cp++; if (pg_krb_realm != NULL && strlen(pg_krb_realm)) { /* * Match the realm part of the name first */ if (pg_krb_caseins_users) ret = pg_strcasecmp(pg_krb_realm, cp); else ret = strcmp(pg_krb_realm, cp); if (ret) { /* GSS realm does not match */ elog(DEBUG2, "GSSAPI realm (%s) and configured realm (%s) don't match", cp, pg_krb_realm); gss_release_buffer(&lmin_s, &gbuf); return STATUS_ERROR; } } } else if (pg_krb_realm && strlen(pg_krb_realm)) { elog(DEBUG2, "GSSAPI did not return realm but realm matching was requested"); gss_release_buffer(&lmin_s, &gbuf); return STATUS_ERROR; } if (pg_krb_caseins_users) ret = pg_strcasecmp(port->user_name, gbuf.value); else ret = strcmp(port->user_name, gbuf.value); if (ret) { /* GSS name and PGUSER are not equivalent */ elog(DEBUG2, "provided username (%s) and GSSAPI username (%s) don't match", port->user_name, (char *) gbuf.value); gss_release_buffer(&lmin_s, &gbuf); return STATUS_ERROR; } gss_release_buffer(&lmin_s, &gbuf); return STATUS_OK; }
/* * 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; }