aboutsummaryrefslogtreecommitdiff
path: root/src/interfaces/libpq-oauth/oauth-utils.c
blob: 45fdc7579f2852c9cb1ef4af3f1160e4533d50ee (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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/*-------------------------------------------------------------------------
 *
 * oauth-utils.c
 *
 *	  "Glue" helpers providing a copy of some internal APIs from libpq. At
 *	  some point in the future, we might be able to deduplicate.
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/interfaces/libpq-oauth/oauth-utils.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include <signal.h>

#include "oauth-utils.h"

#ifndef USE_DYNAMIC_OAUTH
#error oauth-utils.c is not supported in static builds
#endif

#ifdef LIBPQ_INT_H
#error do not rely on libpq-int.h in dynamic builds of libpq-oauth
#endif

/*
 * Function pointers set by libpq_oauth_init().
 */

pgthreadlock_t pg_g_threadlock;
static libpq_gettext_func libpq_gettext_impl;

conn_errorMessage_func conn_errorMessage;
conn_oauth_client_id_func conn_oauth_client_id;
conn_oauth_client_secret_func conn_oauth_client_secret;
conn_oauth_discovery_uri_func conn_oauth_discovery_uri;
conn_oauth_issuer_id_func conn_oauth_issuer_id;
conn_oauth_scope_func conn_oauth_scope;
conn_sasl_state_func conn_sasl_state;

set_conn_altsock_func set_conn_altsock;
set_conn_oauth_token_func set_conn_oauth_token;

/*-
 * Initializes libpq-oauth by setting necessary callbacks.
 *
 * The current implementation relies on the following private implementation
 * details of libpq:
 *
 * - pg_g_threadlock: protects libcurl initialization if the underlying Curl
 *   installation is not threadsafe
 *
 * - libpq_gettext: translates error messages using libpq's message domain
 *
 * The implementation also needs access to several members of the PGconn struct,
 * which are not guaranteed to stay in place across minor versions. Accessors
 * (named conn_*) and mutators (named set_conn_*) are injected here.
 */
void
libpq_oauth_init(pgthreadlock_t threadlock_impl,
				 libpq_gettext_func gettext_impl,
				 conn_errorMessage_func errmsg_impl,
				 conn_oauth_client_id_func clientid_impl,
				 conn_oauth_client_secret_func clientsecret_impl,
				 conn_oauth_discovery_uri_func discoveryuri_impl,
				 conn_oauth_issuer_id_func issuerid_impl,
				 conn_oauth_scope_func scope_impl,
				 conn_sasl_state_func saslstate_impl,
				 set_conn_altsock_func setaltsock_impl,
				 set_conn_oauth_token_func settoken_impl)
{
	pg_g_threadlock = threadlock_impl;
	libpq_gettext_impl = gettext_impl;
	conn_errorMessage = errmsg_impl;
	conn_oauth_client_id = clientid_impl;
	conn_oauth_client_secret = clientsecret_impl;
	conn_oauth_discovery_uri = discoveryuri_impl;
	conn_oauth_issuer_id = issuerid_impl;
	conn_oauth_scope = scope_impl;
	conn_sasl_state = saslstate_impl;
	set_conn_altsock = setaltsock_impl;
	set_conn_oauth_token = settoken_impl;
}

/*
 * Append a formatted string to the error message buffer of the given
 * connection, after translating it.  This is a copy of libpq's internal API.
 */
void
libpq_append_conn_error(PGconn *conn, const char *fmt,...)
{
	int			save_errno = errno;
	bool		done;
	va_list		args;
	PQExpBuffer errorMessage = conn_errorMessage(conn);

	Assert(fmt[strlen(fmt) - 1] != '\n');

	if (PQExpBufferBroken(errorMessage))
		return;					/* already failed */

	/* Loop in case we have to retry after enlarging the buffer. */
	do
	{
		errno = save_errno;
		va_start(args, fmt);
		done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
		va_end(args);
	} while (!done);

	appendPQExpBufferChar(errorMessage, '\n');
}

#ifdef ENABLE_NLS

/*
 * A shim that defers to the actual libpq_gettext().
 */
char *
libpq_gettext(const char *msgid)
{
	if (!libpq_gettext_impl)
	{
		/*
		 * Possible if the libpq build didn't enable NLS but the libpq-oauth
		 * build did. That's an odd mismatch, but we can handle it.
		 *
		 * Note that callers of libpq_gettext() have to treat the return value
		 * as if it were const, because builds without NLS simply pass through
		 * their argument.
		 */
		return unconstify(char *, msgid);
	}

	return libpq_gettext_impl(msgid);
}

#endif							/* ENABLE_NLS */

/*
 * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
 */
bool
oauth_unsafe_debugging_enabled(void)
{
	const char *env = getenv("PGOAUTHDEBUG");

	return (env && strcmp(env, "UNSAFE") == 0);
}

/*
 * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
 * pq_block/reset_sigpipe().
 */
#ifdef WIN32
#define SOCK_ERRNO (WSAGetLastError())
#define SOCK_ERRNO_SET(e) WSASetLastError(e)
#else
#define SOCK_ERRNO errno
#define SOCK_ERRNO_SET(e) (errno = (e))
#endif

/*
 *	Block SIGPIPE for this thread. This is a copy of libpq's internal API.
 */
int
pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
{
	sigset_t	sigpipe_sigset;
	sigset_t	sigset;

	sigemptyset(&sigpipe_sigset);
	sigaddset(&sigpipe_sigset, SIGPIPE);

	/* Block SIGPIPE and save previous mask for later reset */
	SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
	if (SOCK_ERRNO)
		return -1;

	/* We can have a pending SIGPIPE only if it was blocked before */
	if (sigismember(osigset, SIGPIPE))
	{
		/* Is there a pending SIGPIPE? */
		if (sigpending(&sigset) != 0)
			return -1;

		if (sigismember(&sigset, SIGPIPE))
			*sigpipe_pending = true;
		else
			*sigpipe_pending = false;
	}
	else
		*sigpipe_pending = false;

	return 0;
}

/*
 *	Discard any pending SIGPIPE and reset the signal mask. This is a copy of
 *	libpq's internal API.
 */
void
pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
{
	int			save_errno = SOCK_ERRNO;
	int			signo;
	sigset_t	sigset;

	/* Clear SIGPIPE only if none was pending */
	if (got_epipe && !sigpipe_pending)
	{
		if (sigpending(&sigset) == 0 &&
			sigismember(&sigset, SIGPIPE))
		{
			sigset_t	sigpipe_sigset;

			sigemptyset(&sigpipe_sigset);
			sigaddset(&sigpipe_sigset, SIGPIPE);

			sigwait(&sigpipe_sigset, &signo);
		}
	}

	/* Restore saved block mask */
	pthread_sigmask(SIG_SETMASK, osigset, NULL);

	SOCK_ERRNO_SET(save_errno);
}