summaryrefslogtreecommitdiffstats
path: root/src/backend/libpq/be-gssapi-common.c
blob: 64d41e529150fa10d2e1775f2afd78eea198455d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*-------------------------------------------------------------------------
 *
 * be-gssapi-common.c
 *     Common code for GSSAPI authentication and encryption
 *
 * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *       src/backend/libpq/be-gssapi-common.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "libpq/be-gssapi-common.h"

/*
 * Fetch all errors of a specific type and append to "s" (buffer of size len).
 * If we obtain more than one string, separate them with spaces.
 * Call once for GSS_CODE and once for MECH_CODE.
 */
static void
pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type)
{
	gss_buffer_desc gmsg;
	size_t		i = 0;
	OM_uint32	lmin_s,
				msg_ctx = 0;

	do
	{
		if (gss_display_status(&lmin_s, stat, type, GSS_C_NO_OID,
							   &msg_ctx, &gmsg) != GSS_S_COMPLETE)
			break;
		if (i > 0)
		{
			if (i < len)
				s[i] = ' ';
			i++;
		}
		if (i < len)
			memcpy(s + i, gmsg.value, Min(len - i, gmsg.length));
		i += gmsg.length;
		gss_release_buffer(&lmin_s, &gmsg);
	}
	while (msg_ctx);

	/* add nul termination */
	if (i < len)
		s[i] = '\0';
	else
	{
		elog(COMMERROR, "incomplete GSS error report");
		s[len - 1] = '\0';
	}
}

/*
 * Report the GSSAPI error described by maj_stat/min_stat.
 *
 * errmsg should be an already-translated primary error message.
 * The GSSAPI info is appended as errdetail.
 *
 * The error is always reported with elevel COMMERROR; we daren't try to
 * send it to the client, as that'd likely lead to infinite recursion
 * when elog.c tries to write to the client.
 *
 * To avoid memory allocation, total error size is capped (at 128 bytes for
 * each of major and minor).  No known mechanisms will produce error messages
 * beyond this cap.
 */
void
pg_GSS_error(const char *errmsg,
			 OM_uint32 maj_stat, OM_uint32 min_stat)
{
	char		msg_major[128],
				msg_minor[128];

	/* Fetch major status message */
	pg_GSS_error_int(msg_major, sizeof(msg_major), maj_stat, GSS_C_GSS_CODE);

	/* Fetch mechanism minor status message */
	pg_GSS_error_int(msg_minor, sizeof(msg_minor), min_stat, GSS_C_MECH_CODE);

	/*
	 * errmsg_internal, since translation of the first part must be done
	 * before calling this function anyway.
	 */
	ereport(COMMERROR,
			(errmsg_internal("%s", errmsg),
			 errdetail_internal("%s: %s", msg_major, msg_minor)));
}

/*
 * Store the credentials passed in into the memory cache for later usage.
 *
 * This allows credentials to be delegated to us for us to use to connect
 * to other systems with, using, e.g. postgres_fdw or dblink.
 */
#define GSS_MEMORY_CACHE "MEMORY:"
void
pg_store_delegated_credential(gss_cred_id_t cred)
{
	OM_uint32	major,
				minor;
	gss_OID_set mech;
	gss_cred_usage_t usage;
	gss_key_value_element_desc cc;
	gss_key_value_set_desc ccset;

	cc.key = "ccache";
	cc.value = GSS_MEMORY_CACHE;
	ccset.count = 1;
	ccset.elements = &cc;

	/* Make the delegated credential only available to current process */
	major = gss_store_cred_into(&minor,
								cred,
								GSS_C_INITIATE, /* credential only used for
												 * starting libpq connection */
								GSS_C_NULL_OID, /* store all */
								true,	/* overwrite */
								true,	/* make default */
								&ccset,
								&mech,
								&usage);

	if (major != GSS_S_COMPLETE)
	{
		pg_GSS_error("gss_store_cred", major, minor);
	}

	/* Credential stored, so we can release our credential handle. */
	major = gss_release_cred(&minor, &cred);
	if (major != GSS_S_COMPLETE)
	{
		pg_GSS_error("gss_release_cred", major, minor);
	}

	/*
	 * Set KRB5CCNAME for this backend, so that later calls to
	 * gss_acquire_cred will find the delegated credentials we stored.
	 */
	setenv("KRB5CCNAME", GSS_MEMORY_CACHE, 1);
}