Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * oauth-utils.c
4 : *
5 : * "Glue" helpers providing a copy of some internal APIs from libpq. At
6 : * some point in the future, we might be able to deduplicate.
7 : *
8 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
9 : * Portions Copyright (c) 1994, Regents of the University of California
10 : *
11 : * IDENTIFICATION
12 : * src/interfaces/libpq-oauth/oauth-utils.c
13 : *
14 : *-------------------------------------------------------------------------
15 : */
16 :
17 : #include "postgres_fe.h"
18 :
19 : #include <signal.h>
20 :
21 : #include "oauth-utils.h"
22 :
23 : #ifndef USE_DYNAMIC_OAUTH
24 : #error oauth-utils.c is not supported in static builds
25 : #endif
26 :
27 : #ifdef LIBPQ_INT_H
28 : #error do not rely on libpq-int.h in dynamic builds of libpq-oauth
29 : #endif
30 :
31 : /*
32 : * Function pointers set by libpq_oauth_init().
33 : */
34 :
35 : pgthreadlock_t pg_g_threadlock;
36 : static libpq_gettext_func libpq_gettext_impl;
37 :
38 : conn_errorMessage_func conn_errorMessage;
39 : conn_oauth_client_id_func conn_oauth_client_id;
40 : conn_oauth_client_secret_func conn_oauth_client_secret;
41 : conn_oauth_discovery_uri_func conn_oauth_discovery_uri;
42 : conn_oauth_issuer_id_func conn_oauth_issuer_id;
43 : conn_oauth_scope_func conn_oauth_scope;
44 : conn_sasl_state_func conn_sasl_state;
45 :
46 : set_conn_altsock_func set_conn_altsock;
47 : set_conn_oauth_token_func set_conn_oauth_token;
48 :
49 : /*-
50 : * Initializes libpq-oauth by setting necessary callbacks.
51 : *
52 : * The current implementation relies on the following private implementation
53 : * details of libpq:
54 : *
55 : * - pg_g_threadlock: protects libcurl initialization if the underlying Curl
56 : * installation is not threadsafe
57 : *
58 : * - libpq_gettext: translates error messages using libpq's message domain
59 : *
60 : * The implementation also needs access to several members of the PGconn struct,
61 : * which are not guaranteed to stay in place across minor versions. Accessors
62 : * (named conn_*) and mutators (named set_conn_*) are injected here.
63 : */
64 : void
65 0 : libpq_oauth_init(pgthreadlock_t threadlock_impl,
66 : libpq_gettext_func gettext_impl,
67 : conn_errorMessage_func errmsg_impl,
68 : conn_oauth_client_id_func clientid_impl,
69 : conn_oauth_client_secret_func clientsecret_impl,
70 : conn_oauth_discovery_uri_func discoveryuri_impl,
71 : conn_oauth_issuer_id_func issuerid_impl,
72 : conn_oauth_scope_func scope_impl,
73 : conn_sasl_state_func saslstate_impl,
74 : set_conn_altsock_func setaltsock_impl,
75 : set_conn_oauth_token_func settoken_impl)
76 : {
77 0 : pg_g_threadlock = threadlock_impl;
78 0 : libpq_gettext_impl = gettext_impl;
79 0 : conn_errorMessage = errmsg_impl;
80 0 : conn_oauth_client_id = clientid_impl;
81 0 : conn_oauth_client_secret = clientsecret_impl;
82 0 : conn_oauth_discovery_uri = discoveryuri_impl;
83 0 : conn_oauth_issuer_id = issuerid_impl;
84 0 : conn_oauth_scope = scope_impl;
85 0 : conn_sasl_state = saslstate_impl;
86 0 : set_conn_altsock = setaltsock_impl;
87 0 : set_conn_oauth_token = settoken_impl;
88 0 : }
89 :
90 : /*
91 : * Append a formatted string to the error message buffer of the given
92 : * connection, after translating it. This is a copy of libpq's internal API.
93 : */
94 : void
95 0 : libpq_append_conn_error(PGconn *conn, const char *fmt,...)
96 : {
97 0 : int save_errno = errno;
98 0 : bool done;
99 0 : va_list args;
100 0 : PQExpBuffer errorMessage = conn_errorMessage(conn);
101 :
102 0 : Assert(fmt[strlen(fmt) - 1] != '\n');
103 :
104 0 : if (PQExpBufferBroken(errorMessage))
105 0 : return; /* already failed */
106 :
107 : /* Loop in case we have to retry after enlarging the buffer. */
108 0 : do
109 : {
110 0 : errno = save_errno;
111 0 : va_start(args, fmt);
112 0 : done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
113 0 : va_end(args);
114 0 : } while (!done);
115 :
116 0 : appendPQExpBufferChar(errorMessage, '\n');
117 0 : }
118 :
119 : #ifdef ENABLE_NLS
120 :
121 : /*
122 : * A shim that defers to the actual libpq_gettext().
123 : */
124 : char *
125 0 : libpq_gettext(const char *msgid)
126 : {
127 0 : if (!libpq_gettext_impl)
128 : {
129 : /*
130 : * Possible if the libpq build didn't enable NLS but the libpq-oauth
131 : * build did. That's an odd mismatch, but we can handle it.
132 : *
133 : * Note that callers of libpq_gettext() have to treat the return value
134 : * as if it were const, because builds without NLS simply pass through
135 : * their argument.
136 : */
137 0 : return unconstify(char *, msgid);
138 : }
139 :
140 0 : return libpq_gettext_impl(msgid);
141 0 : }
142 :
143 : #endif /* ENABLE_NLS */
144 :
145 : /*
146 : * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
147 : */
148 : bool
149 0 : oauth_unsafe_debugging_enabled(void)
150 : {
151 0 : const char *env = getenv("PGOAUTHDEBUG");
152 :
153 0 : return (env && strcmp(env, "UNSAFE") == 0);
154 0 : }
155 :
156 : /*
157 : * Duplicate SOCK_ERRNO* definitions from libpq-int.h, for use by
158 : * pq_block/reset_sigpipe().
159 : */
160 : #ifdef WIN32
161 : #define SOCK_ERRNO (WSAGetLastError())
162 : #define SOCK_ERRNO_SET(e) WSASetLastError(e)
163 : #else
164 : #define SOCK_ERRNO errno
165 : #define SOCK_ERRNO_SET(e) (errno = (e))
166 : #endif
167 :
168 : /*
169 : * Block SIGPIPE for this thread. This is a copy of libpq's internal API.
170 : */
171 : int
172 0 : pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
173 : {
174 0 : sigset_t sigpipe_sigset;
175 0 : sigset_t sigset;
176 :
177 0 : sigemptyset(&sigpipe_sigset);
178 0 : sigaddset(&sigpipe_sigset, SIGPIPE);
179 :
180 : /* Block SIGPIPE and save previous mask for later reset */
181 0 : SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
182 0 : if (SOCK_ERRNO)
183 0 : return -1;
184 :
185 : /* We can have a pending SIGPIPE only if it was blocked before */
186 0 : if (sigismember(osigset, SIGPIPE))
187 : {
188 : /* Is there a pending SIGPIPE? */
189 0 : if (sigpending(&sigset) != 0)
190 0 : return -1;
191 :
192 0 : if (sigismember(&sigset, SIGPIPE))
193 0 : *sigpipe_pending = true;
194 : else
195 0 : *sigpipe_pending = false;
196 0 : }
197 : else
198 0 : *sigpipe_pending = false;
199 :
200 0 : return 0;
201 0 : }
202 :
203 : /*
204 : * Discard any pending SIGPIPE and reset the signal mask. This is a copy of
205 : * libpq's internal API.
206 : */
207 : void
208 0 : pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
209 : {
210 0 : int save_errno = SOCK_ERRNO;
211 0 : int signo;
212 0 : sigset_t sigset;
213 :
214 : /* Clear SIGPIPE only if none was pending */
215 0 : if (got_epipe && !sigpipe_pending)
216 : {
217 0 : if (sigpending(&sigset) == 0 &&
218 0 : sigismember(&sigset, SIGPIPE))
219 : {
220 0 : sigset_t sigpipe_sigset;
221 :
222 0 : sigemptyset(&sigpipe_sigset);
223 0 : sigaddset(&sigpipe_sigset, SIGPIPE);
224 :
225 0 : sigwait(&sigpipe_sigset, &signo);
226 0 : }
227 0 : }
228 :
229 : /* Restore saved block mask */
230 0 : pthread_sigmask(SIG_SETMASK, osigset, NULL);
231 :
232 0 : SOCK_ERRNO_SET(save_errno);
233 0 : }
|